|
|
@@ -1,5 +1,6 @@
|
|
|
using Admin.NET.Plugin.AiDOP.Controllers.S0.Quality;
|
|
|
using Admin.NET.Plugin.AiDOP.Dto.S0.Quality;
|
|
|
+using Admin.NET.Plugin.AiDOP.Entity.S0.Quality;
|
|
|
using Xunit;
|
|
|
|
|
|
namespace Admin.NET.Plugin.AiDOP.Tests.S0.Quality;
|
|
|
@@ -55,7 +56,7 @@ public class AdoS0QmsSpecAggregateGuardTests
|
|
|
{
|
|
|
// 实测 aidopdev 有 72 条历史记录缺必填字段;它们必须仍可编辑其他字段。
|
|
|
var error = AdoS0QmsSpecAggregateGuard.ValidateUpdate(
|
|
|
- [("物料编码", null, null), ("版本", "V1", "V2")], existingItemCount: 3, incomingMeaningfulItemCount: 3);
|
|
|
+ [("物料编码", null, null), ("版本", "V1", "V2")], existingMeaningfulItemCount: 3, incomingMeaningfulItemCount: 3);
|
|
|
Assert.Null(error);
|
|
|
}
|
|
|
|
|
|
@@ -64,7 +65,7 @@ public class AdoS0QmsSpecAggregateGuardTests
|
|
|
{
|
|
|
// 实测 22 条历史记录 0 明细。
|
|
|
var error = AdoS0QmsSpecAggregateGuard.ValidateUpdate(
|
|
|
- [("物料编码", "M-001", "M-001")], existingItemCount: 0, incomingMeaningfulItemCount: 0);
|
|
|
+ [("物料编码", "M-001", "M-001")], existingMeaningfulItemCount: 0, incomingMeaningfulItemCount: 0);
|
|
|
Assert.Null(error);
|
|
|
}
|
|
|
|
|
|
@@ -72,7 +73,7 @@ public class AdoS0QmsSpecAggregateGuardTests
|
|
|
public void Update_WhenHistoricalBlankFieldGetsFilled_Passes()
|
|
|
{
|
|
|
var error = AdoS0QmsSpecAggregateGuard.ValidateUpdate(
|
|
|
- [("物料编码", "", "M-001")], existingItemCount: 1, incomingMeaningfulItemCount: 1);
|
|
|
+ [("物料编码", "", "M-001")], existingMeaningfulItemCount: 1, incomingMeaningfulItemCount: 1);
|
|
|
Assert.Null(error);
|
|
|
}
|
|
|
|
|
|
@@ -82,7 +83,7 @@ public class AdoS0QmsSpecAggregateGuardTests
|
|
|
public void Update_WhenClearingAPopulatedRequiredField_IsRejected()
|
|
|
{
|
|
|
var error = AdoS0QmsSpecAggregateGuard.ValidateUpdate(
|
|
|
- [("物料编码", "M-001", " ")], existingItemCount: 1, incomingMeaningfulItemCount: 1);
|
|
|
+ [("物料编码", "M-001", " ")], existingMeaningfulItemCount: 1, incomingMeaningfulItemCount: 1);
|
|
|
Assert.Equal("物料编码原本已有值,不允许清空", error);
|
|
|
}
|
|
|
|
|
|
@@ -90,7 +91,7 @@ public class AdoS0QmsSpecAggregateGuardTests
|
|
|
public void Update_WhenDeletingAllExistingDetails_IsRejected()
|
|
|
{
|
|
|
var error = AdoS0QmsSpecAggregateGuard.ValidateUpdate(
|
|
|
- [("物料编码", "M-001", "M-001")], existingItemCount: 5, incomingMeaningfulItemCount: 0);
|
|
|
+ [("物料编码", "M-001", "M-001")], existingMeaningfulItemCount: 5, incomingMeaningfulItemCount: 0);
|
|
|
Assert.Equal("该检规原有 5 条检验明细,不允许全部删除", error);
|
|
|
}
|
|
|
|
|
|
@@ -107,7 +108,7 @@ public class AdoS0QmsSpecAggregateGuardTests
|
|
|
("物料编码", "M-001", "M-002"),
|
|
|
("版本", "V1", "V1")
|
|
|
],
|
|
|
- existingItemCount: 2, incomingMeaningfulItemCount: 2);
|
|
|
+ existingMeaningfulItemCount: 2, incomingMeaningfulItemCount: 2);
|
|
|
Assert.Null(error);
|
|
|
}
|
|
|
|
|
|
@@ -116,7 +117,7 @@ public class AdoS0QmsSpecAggregateGuardTests
|
|
|
public void Update_WhenClearingAnExistingFileNumber_IsRejected()
|
|
|
{
|
|
|
var error = AdoS0QmsSpecAggregateGuard.ValidateUpdate(
|
|
|
- [("文件编号", "F-001", "")], existingItemCount: 1, incomingMeaningfulItemCount: 1);
|
|
|
+ [("文件编号", "F-001", "")], existingMeaningfulItemCount: 1, incomingMeaningfulItemCount: 1);
|
|
|
Assert.Equal("文件编号原本已有值,不允许清空", error);
|
|
|
}
|
|
|
|
|
|
@@ -162,11 +163,73 @@ public class AdoS0QmsSpecAggregateGuardTests
|
|
|
[Fact]
|
|
|
public void Update_WhenHistoricalHadNoDetailAndUiAddsBlankPlaceholder_WritesNothing()
|
|
|
{
|
|
|
- var kept = AdoS0QmsSpecAggregateGuard.Meaningful(new List<AdoS0QmsProcessInspectionSpecEntryDto> { new() });
|
|
|
+ // UI 新补的空占位没有 Id → 不进写入集
|
|
|
+ var written = AdoS0QmsSpecAggregateGuard.ForUpdateWrite(new List<AdoS0QmsProcessInspectionSpecEntryDto> { new() });
|
|
|
|
|
|
- Assert.Empty(kept); // 写库用的就是这份 → 不会新增全空明细
|
|
|
+ Assert.Empty(written); // 写库用的就是这份 → 不会新增全空明细
|
|
|
Assert.Null(AdoS0QmsSpecAggregateGuard.ValidateUpdate(
|
|
|
- [("物料编码", "M-001", "M-001")], existingItemCount: 0, incomingMeaningfulItemCount: kept.Count));
|
|
|
+ [("物料编码", "M-001", "M-001")], existingMeaningfulItemCount: 0, incomingMeaningfulItemCount: 0));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 阶段 1.1 · 场景 10:Update 是 delete-all + 全量重建语义,
|
|
|
+ /// 因此写入集必须保留**历史已持久化**的空明细(带 Id),否则用户编辑一次头字段
|
|
|
+ /// 就会静默删掉库里的空行——实测 aidopdev 有 10 条这样的行,分布在 9 张真实主单上。
|
|
|
+ /// </summary>
|
|
|
+ [Fact]
|
|
|
+ public void Update_KeepsPersistedEmptyDetail_ButDropsNewEmptyPlaceholder()
|
|
|
+ {
|
|
|
+ var incoming = new List<AdoS0QmsRawInspectionSpecEntryDto>
|
|
|
+ {
|
|
|
+ new() { Id = 101, InspectionItem = "外观" }, // 历史真实明细
|
|
|
+ new() { Id = 102 }, // 历史已持久化的空明细 → 必须保留
|
|
|
+ new() { InspectionItem = " " }, // 本次 UI 新补的空占位 → 必须丢弃
|
|
|
+ };
|
|
|
+
|
|
|
+ var written = AdoS0QmsSpecAggregateGuard.ForUpdateWrite(incoming);
|
|
|
+
|
|
|
+ Assert.Equal(2, written.Count);
|
|
|
+ Assert.Equal(new long?[] { 101, 102 }, written.Select(x => x.Id).ToArray());
|
|
|
+ // 数量校验仍只认 meaningful:这里只有 1 条有内容
|
|
|
+ Assert.Single(AdoS0QmsSpecAggregateGuard.Meaningful(incoming));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>Create 侧相反:任何空明细都没有 Id,一律不落库。</summary>
|
|
|
+ [Fact]
|
|
|
+ public void Create_NeverPersistsEmptyDetail_EvenIfClientSuppliesAnId()
|
|
|
+ {
|
|
|
+ var incoming = new List<AdoS0QmsRawInspectionSpecEntryDto> { new() { InspectionItem = "外观" }, new() { Id = 999 } };
|
|
|
+
|
|
|
+ // Create 走 Meaningful,不走 ForUpdateWrite → 带 Id 的空行同样不落库
|
|
|
+ Assert.Single(AdoS0QmsSpecAggregateGuard.Meaningful(incoming));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 「原有 ≥1 明细不许删空」必须按 meaningful 计。
|
|
|
+ /// 若按 raw count,一张明细全为空的历史主单会被自己的空占位永久锁死、无法保存。
|
|
|
+ /// </summary>
|
|
|
+ [Fact]
|
|
|
+ public void Update_WhenAllExistingDetailsAreEmpty_IsNotLockedOut()
|
|
|
+ {
|
|
|
+ var existingRows = new List<AdoS0QmsRawInspectionSpecEntry> { new(), new() }; // 2 条空行
|
|
|
+ var existingMeaningful = AdoS0QmsSpecAggregateGuard.CountMeaningful(existingRows);
|
|
|
+
|
|
|
+ Assert.Equal(0, existingMeaningful);
|
|
|
+ Assert.Null(AdoS0QmsSpecAggregateGuard.ValidateUpdate(
|
|
|
+ [("物料编码", "M-001", "M-001")], existingMeaningful, incomingMeaningfulItemCount: 0));
|
|
|
+ }
|
|
|
+
|
|
|
+ [Fact]
|
|
|
+ public void CountMeaningful_OnEntities_MatchesDtoSemantics()
|
|
|
+ {
|
|
|
+ Assert.Equal(1, AdoS0QmsSpecAggregateGuard.CountMeaningful(new List<AdoS0QmsRawInspectionSpecEntry>
|
|
|
+ {
|
|
|
+ new() { InspectionItem = "外观" }, new() { InspectionItem = " " }, new()
|
|
|
+ }));
|
|
|
+ Assert.Equal(2, AdoS0QmsSpecAggregateGuard.CountMeaningful(new List<AdoS0QmsProcessInspectionSpecEntry>
|
|
|
+ {
|
|
|
+ new() { OperationCode = "OP10" }, new() { PeelingForce = 5 }, new()
|
|
|
+ }));
|
|
|
}
|
|
|
|
|
|
/// <summary>阶段 1.1 · 场景 6:原有真实明细被删到只剩空占位 → 按「删到 0」拒绝。</summary>
|
|
|
@@ -178,7 +241,7 @@ public class AdoS0QmsSpecAggregateGuardTests
|
|
|
Assert.Empty(kept);
|
|
|
Assert.Equal("该检规原有 3 条检验明细,不允许全部删除",
|
|
|
AdoS0QmsSpecAggregateGuard.ValidateUpdate(
|
|
|
- [("物料编码", "M-001", "M-001")], existingItemCount: 3, incomingMeaningfulItemCount: kept.Count));
|
|
|
+ [("物料编码", "M-001", "M-001")], existingMeaningfulItemCount: 3, incomingMeaningfulItemCount: kept.Count));
|
|
|
}
|
|
|
|
|
|
/// <summary>阶段 1.1 · 场景 7:导入侧口径与实体侧同源——空明细行不落库,全空 aggregate 拒绝。</summary>
|