IqcC7AndMrbRoutingTests.cs 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting;
  2. using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Inventory;
  3. using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Receipt;
  4. using Xunit;
  5. namespace Admin.NET.Plugin.AiDOP.Tests.S5.MaterialWarehouse;
  6. /// <summary>
  7. /// Phase 7-1:C7 移动任务(InvUpShelf / PurOrdRctByMRBChoose)+ MRB 五分支最终路由 + C3 排除。
  8. /// 据 Phase 6A/7 LIVE:①④ 收货→C5+InvUpShelf;②⑤ 挑选→PurOrdRctByMRBChoose 任务(无 C5);③ 退货→无任务无 C5。
  9. /// </summary>
  10. public class IqcC7AndMrbRoutingTests
  11. {
  12. private const long T1 = 797403760988229L;
  13. private const long T2 = 824585161322565L;
  14. private static IqcAcceptedReceipt Receipt(long tenant = T1, string receiver = "RCV-1")
  15. => new IqcAcceptedReceipt
  16. {
  17. TenantId = tenant, DomainCode = "8010", PostingId = 1, TransactionGroupId = 100, RctNbr = "RCT-1",
  18. ItemNum = "IT-1", Location = "L1", AcceptedQty = 100m, UmConversion = 1m,
  19. PurOrd = "PO-1", PurLine = 1, Potype = "po", OrderedQty = 100m, BeforeReceivedQty = 0m,
  20. Receiver = receiver, RctQcNbr = "QC-1", Fbillno = "FB-1", User = "t",
  21. };
  22. private static PurchaseReceiptGateInput GateOk()
  23. => new PurchaseReceiptGateInput { HasPendingReceipt = true, AcceptedQty = 100m, HasReceivableDetails = true };
  24. private static IqcMrbSelectionCommand MrbCmd(long tenant = T1, string qcNbr = "QC-9")
  25. => new IqcMrbSelectionCommand { TenantId = tenant, DomainCode = "8010", PostingId = 2, QcNbr = qcNbr, ItemNum = "IT-1", Fbillno = "FB-2", Receiver = "RCV-2", User = "t" };
  26. private static FakeInventoryDatabase DbWithMaster()
  27. {
  28. var db = new FakeInventoryDatabase();
  29. db.SeedMaster(T1, "8010", "IT-1", "L1");
  30. db.SeedMaster(T2, "8010", "IT-1", "L1");
  31. return db;
  32. }
  33. // ── C7 InvUpShelf 经编排创建 ──
  34. [Fact]
  35. public async Task C7_InvUpShelf_CreatedViaOrchestrator()
  36. {
  37. var db = DbWithMaster();
  38. var orch = new IqcReceiptOrchestrator(new FakeIqcReceiptUnitOfWorkFactory(db));
  39. var r = await orch.PostAsync(Receipt(), GateOk());
  40. Assert.Equal(IqcReceiptOutcome.Success, r.Outcome);
  41. var task = db.MobileTasks[$"{T1}|8010|RCV-1"];
  42. Assert.Equal("InvUpShelf", task.Name);
  43. Assert.Equal("RCV-1", task.TaskId); // TaskID=Receiver
  44. }
  45. // ── T7:C7 失败 → C6/C5/C7 全回滚 ──
  46. [Fact]
  47. public async Task T7_C7Fail_AllRollback()
  48. {
  49. var db = DbWithMaster();
  50. var orch = new IqcReceiptOrchestrator(new FakeIqcReceiptUnitOfWorkFactory(db, FakeReceiptFailPhase.C7));
  51. var r = await orch.PostAsync(Receipt(), GateOk());
  52. Assert.Equal(IqcReceiptOutcome.Error, r.Outcome);
  53. Assert.Equal(0, db.TotalPersisted); // C6+C5+C7 全滚
  54. Assert.Equal(new[] { "C6", "C5", "C7" }, db.CallOrder.ToArray()); // C7 抛错→完成锚未达
  55. }
  56. // ── TaskID 重复 upsert(同 QCNbr 两次 MRB 选择 → 一个任务)──
  57. [Fact]
  58. public async Task T9_SameTaskId_UpsertsToOne()
  59. {
  60. var db = DbWithMaster();
  61. var svc = new IqcMrbSelectionTaskService(new FakeIqcReceiptUnitOfWorkFactory(db));
  62. await svc.PostAsync(MrbCmd(qcNbr: "QC-DUP"));
  63. await svc.PostAsync(MrbCmd(qcNbr: "QC-DUP"));
  64. Assert.Equal(1, db.MobileTasks.Count); // 幂等 upsert → 1
  65. }
  66. // ── T10:同 TaskID 不同 tenant → 各自独立任务 ──
  67. [Fact]
  68. public async Task T10_SameTaskId_DifferentTenant_Isolated()
  69. {
  70. var db = DbWithMaster();
  71. var svc = new IqcMrbSelectionTaskService(new FakeIqcReceiptUnitOfWorkFactory(db));
  72. await svc.PostAsync(MrbCmd(tenant: T1, qcNbr: "QC-X"));
  73. await svc.PostAsync(MrbCmd(tenant: T2, qcNbr: "QC-X"));
  74. Assert.Equal(2, db.MobileTasks.Count); // tenant 隔离
  75. }
  76. // ── MRB ①④:C5 + InvUpShelf(经编排)──
  77. [Theory]
  78. [InlineData(0, "")] // ① 合格无处理
  79. [InlineData(1, "2")] // ④ 让步接收
  80. public async Task Mrb1And4_Orchestrator_C5PlusInvUpShelf(int pd, string clfs)
  81. {
  82. var m = IqcResultMapping.Map(new IqcResultMappingInput { Pd = pd, Clfs = clfs, Dhsl = 100m, Bhgsl = 0m });
  83. Assert.True(m.EntersInventoryPosting); // 路由=进 C5
  84. var db = DbWithMaster();
  85. var r = await new IqcReceiptOrchestrator(new FakeIqcReceiptUnitOfWorkFactory(db)).PostAsync(Receipt(), GateOk());
  86. Assert.Equal(IqcReceiptOutcome.Success, r.Outcome);
  87. Assert.True(db.Transactions.Count > 0); // C5 过账
  88. Assert.Equal(1, db.MobileTasks.Count); // InvUpShelf
  89. Assert.Equal("InvUpShelf", db.MobileTasks[$"{T1}|8010|RCV-1"].Name);
  90. }
  91. // ── MRB ②⑤:PurOrdRctByMRBChoose 任务,无 C5 ──
  92. [Theory]
  93. [InlineData(0, "3")] // ② 挑选
  94. [InlineData(1, "3")] // ⑤ 挑选
  95. public async Task Mrb2And5_MrbSelection_ChooseTask_NoC5(int pd, string clfs)
  96. {
  97. var m = IqcResultMapping.Map(new IqcResultMappingInput { Pd = pd, Clfs = clfs, Dhsl = 100m, Bhgsl = 3m });
  98. Assert.False(m.EntersInventoryPosting); // 路由=不进 C5
  99. Assert.Equal(IqcRoutingKind.MrbChooseMobileTask, m.RoutingKind);
  100. var db = DbWithMaster();
  101. var r = await new IqcMrbSelectionTaskService(new FakeIqcReceiptUnitOfWorkFactory(db)).PostAsync(MrbCmd());
  102. Assert.Equal(IqcReceiptOutcome.Success, r.Outcome);
  103. Assert.Equal(1, db.MobileTasks.Count);
  104. Assert.Equal("PurOrdRctByMRBChoose", db.MobileTasks[$"{T1}|8010|QC-9"].Name);
  105. Assert.Equal(0, db.Transactions.Count); // 无 C5 过账
  106. Assert.Equal(0, db.InvMaster.Count);
  107. }
  108. // ── MRB ③ 退货:无 C5、无任务 ──
  109. [Fact]
  110. public void Mrb3_ReturnOnly_NoC5_NoTask()
  111. {
  112. var m = IqcResultMapping.Map(new IqcResultMappingInput { Pd = 1, Clfs = "0", Dhsl = 10m, Bhgsl = 3m });
  113. Assert.False(m.EntersInventoryPosting);
  114. Assert.Equal(IqcRoutingKind.ReturnOnly, m.RoutingKind); // 既不进 C5 也不建挑选任务
  115. }
  116. // ── C3 仍为 0(接线后不复现)──
  117. [Fact]
  118. public async Task C3_StaysZero_AfterC7Wiring()
  119. {
  120. var db = DbWithMaster();
  121. await new IqcReceiptOrchestrator(new FakeIqcReceiptUnitOfWorkFactory(db)).PostAsync(Receipt(), GateOk());
  122. Assert.Equal(0, db.BarcodeCardWrites);
  123. }
  124. }