137 lines
5.8 KiB
C#
137 lines
5.8 KiB
C#
using DongfangHydro.Dashboard.Api.Data;
|
||
using DongfangHydro.Dashboard.Api.Domain;
|
||
using DongfangHydro.Dashboard.Api.Importing;
|
||
using Microsoft.EntityFrameworkCore;
|
||
using Xunit;
|
||
|
||
namespace DongfangHydro.Dashboard.Tests;
|
||
|
||
public sealed class BomImportServiceTests
|
||
{
|
||
[Fact]
|
||
public void Order_code_index_is_not_unique_because_source_document_is_the_import_identity()
|
||
{
|
||
var options = new DbContextOptionsBuilder<DashboardDbContext>()
|
||
.UseInMemoryDatabase($"bom-model-{Guid.NewGuid():N}")
|
||
.Options;
|
||
using var dbContext = new DashboardDbContext(options);
|
||
|
||
var orderType = dbContext.Model.FindEntityType(typeof(ProductionOrder))!;
|
||
var codeIndex = Assert.Single(orderType.GetIndexes(), index =>
|
||
index.Properties.Count == 1 && index.Properties[0].Name == nameof(ProductionOrder.Code));
|
||
|
||
Assert.False(codeIndex.IsUnique);
|
||
}
|
||
|
||
[Fact]
|
||
public async Task Import_replaces_matching_source_purges_demo_and_is_idempotent()
|
||
{
|
||
var options = new DbContextOptionsBuilder<DashboardDbContext>()
|
||
.UseInMemoryDatabase($"bom-import-{Guid.NewGuid():N}")
|
||
.Options;
|
||
await using var dbContext = new DashboardDbContext(options);
|
||
await dbContext.Database.EnsureCreatedAsync();
|
||
|
||
dbContext.ProductionOrders.AddRange(
|
||
CreateOrder("MO-DEMO", "demo", string.Empty),
|
||
CreateOrder("2437-OLD", "excel", "SG2026-06-033"),
|
||
CreateOrder("KEEP-001", "manual", "OTHER-DOC"));
|
||
await dbContext.SaveChangesAsync();
|
||
|
||
var service = new BomImportService(dbContext);
|
||
var document = CreateDocument();
|
||
var first = await service.ImportAsync(document, purgeDemo: true, CancellationToken.None);
|
||
var progressedNode = await dbContext.ProductionNodes.SingleAsync(node => node.SourceRow == 6);
|
||
progressedNode.CompletedQty = 1;
|
||
progressedNode.Progress = 50;
|
||
progressedNode.Status = "in_progress";
|
||
await dbContext.SaveChangesAsync();
|
||
var second = await service.ImportAsync(document, purgeDemo: true, CancellationToken.None);
|
||
|
||
Assert.Equal(1, first.RemovedDemoOrders);
|
||
Assert.Equal(1, first.ReplacedOrders);
|
||
Assert.Equal(0, second.ReplacedOrders);
|
||
Assert.Equal(2, second.TotalOrders);
|
||
Assert.Equal(2, await dbContext.ProductionOrders.CountAsync());
|
||
Assert.DoesNotContain(await dbContext.ProductionOrders.ToListAsync(), order => order.DataSource == "demo");
|
||
Assert.Contains(await dbContext.ProductionOrders.ToListAsync(), order => order.Code == "KEEP-001");
|
||
|
||
var importedOrder = await dbContext.ProductionOrders.SingleAsync(
|
||
order => order.SourceDocumentCode == "SG2026-06-033");
|
||
Assert.Equal("2437-QM0201", importedOrder.Code);
|
||
Assert.Equal("excel", importedOrder.DataSource);
|
||
Assert.Null(importedOrder.PlannedStart);
|
||
Assert.Null(importedOrder.PlannedEnd);
|
||
|
||
var nodes = await dbContext.ProductionNodes
|
||
.Where(node => node.OrderId == importedOrder.Id)
|
||
.OrderBy(node => node.Level)
|
||
.ThenBy(node => node.SourceRow)
|
||
.ToListAsync();
|
||
Assert.Equal(3, nodes.Count);
|
||
Assert.Equal("order", nodes[0].NodeType);
|
||
Assert.Equal("part", nodes[1].NodeType);
|
||
Assert.Equal("material", nodes[2].NodeType);
|
||
Assert.Equal(nodes[0].Id, nodes[1].ParentNodeId);
|
||
Assert.Equal(nodes[1].Id, nodes[2].ParentNodeId);
|
||
Assert.Equal(1, nodes.Single(node => node.SourceRow == 6).CompletedQty);
|
||
Assert.Equal(50, nodes.Single(node => node.SourceRow == 6).Progress);
|
||
Assert.Equal("in_progress", nodes.Single(node => node.SourceRow == 6).Status);
|
||
|
||
var quota = await dbContext.MaterialQuotas.SingleAsync(item => item.OrderId == importedOrder.Id);
|
||
Assert.Equal("钢板", quota.Category);
|
||
Assert.Equal(33.61m, quota.NetWeight);
|
||
Assert.Equal(9, quota.SourceRow);
|
||
|
||
var forced = await service.ImportAsync(
|
||
document,
|
||
purgeDemo: true,
|
||
CancellationToken.None,
|
||
forceReplace: true);
|
||
Assert.Equal(1, forced.ReplacedOrders);
|
||
Assert.False(forced.SkippedUnchanged);
|
||
Assert.Equal(0, (await dbContext.ProductionNodes.SingleAsync(node => node.SourceRow == 6)).Progress);
|
||
}
|
||
|
||
private static ProductionOrder CreateOrder(string code, string dataSource, string sourceDocumentCode)
|
||
{
|
||
return new ProductionOrder
|
||
{
|
||
Id = Guid.NewGuid(),
|
||
Code = code,
|
||
ProductName = code,
|
||
DataSource = dataSource,
|
||
SourceDocumentCode = sourceDocumentCode,
|
||
BatchQty = 1,
|
||
RequiredQty = 1,
|
||
Status = "waiting",
|
||
RiskLevel = "normal",
|
||
CreatedAt = DateTimeOffset.UtcNow,
|
||
UpdatedAt = DateTimeOffset.UtcNow,
|
||
};
|
||
}
|
||
|
||
private static BomImportDocument CreateDocument()
|
||
{
|
||
return new BomImportDocument(
|
||
new BomImportMetadata(
|
||
"SG2026-06-033",
|
||
"2437-QM0201",
|
||
"青峪口水库工程",
|
||
"青峪口尾水单向门机 2×160/10",
|
||
"电站尾水2×160kN/100kN单向门式启闭机",
|
||
1,
|
||
"bom.xlsx",
|
||
new string('a', 64)),
|
||
[
|
||
new BomImportNode(2, null, "1", "P-01", "主起升机构", "", 1, "部件", 10, 10, "", 1, "part", "self_made"),
|
||
new BomImportNode(6, 2, "1.1", "GB/T700", "钢板", "δ10", 2, "Q235B", 5, 10, "", 2, "material", "purchased"),
|
||
],
|
||
[
|
||
new MaterialQuotaImportRow(9, "钢板", "1", "1010100182", "钢板/Q235B", "δ2-1500", "kg", null, 33.61m, 35.6266m, "无", ""),
|
||
],
|
||
2,
|
||
[]);
|
||
}
|
||
}
|