IqcWiringTests.cs 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Idempotency;
  2. using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.L1;
  3. using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Numbering;
  4. using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Receipt;
  5. using Xunit;
  6. namespace Admin.NET.Plugin.AiDOP.Tests.S5.MaterialWarehouse;
  7. /// <summary>Phase 5B-3 接线单测:IqcDomainResolver(无 8010 fallback)+ orchestrator 事务内 RctNbr 取号。</summary>
  8. public class IqcWiringTests
  9. {
  10. private const long T = 950000000000001L;
  11. [Fact]
  12. public void DomainResolver_NonEmpty_Trims()
  13. => Assert.Equal("2400", new IqcDomainResolver().Resolve(T, " 2400 "));
  14. [Fact]
  15. public void DomainResolver_Empty_Throws_NoFallback()
  16. {
  17. var r = new IqcDomainResolver();
  18. Assert.Throws<InvalidOperationException>(() => r.Resolve(T, ""));
  19. Assert.Throws<InvalidOperationException>(() => r.Resolve(T, null));
  20. Assert.Throws<InvalidOperationException>(() => r.Resolve(T, " "));
  21. }
  22. [Fact]
  23. public async Task Orchestrator_GeneratesRctNbr_InTx_StampsC6AndTrans()
  24. {
  25. var db = new FakeInventoryDatabase();
  26. db.SeedMaster(T, "D5B3", "IT", "L");
  27. var prov = new InMemoryBusinessNumberProvider(new[]
  28. {
  29. new BusinessNumberRule { RuleCode = "P5B3_RCT", Prefix1 = "RC", IniValue = 1 },
  30. });
  31. var orch = new IqcReceiptOrchestrator(new FakeIqcReceiptUnitOfWorkFactory(db), prov, "P5B3_RCT");
  32. var receipt = new IqcAcceptedReceipt
  33. {
  34. TenantId = T, DomainCode = "D5B3", PostingId = 1, TransactionGroupId = 1, RctNbr = "OLD-PLACEHOLDER",
  35. ItemNum = "IT", Location = "L", AcceptedQty = 100m, UmConversion = 1m,
  36. PurOrd = "PO", PurLine = 1, Potype = "po", OrderedQty = 100m, Fbillno = "FB", User = "u",
  37. };
  38. var gate = new PurchaseReceiptGateInput { HasPendingReceipt = true, AcceptedQty = 100m, HasReceivableDetails = true };
  39. var r = await orch.PostAsync(receipt, gate);
  40. Assert.Equal(IqcReceiptOutcome.Success, r.Outcome);
  41. var c6 = db.ReceiptWriteback.Values.Single();
  42. Assert.StartsWith("RC", c6.RctNbr); // 用取号结果(新收货号落 C6.rct_nbr)
  43. Assert.NotEqual("OLD-PLACEHOLDER", c6.RctNbr); // 非入参占位
  44. Assert.NotEmpty(db.Transactions); // C5 流水已生成(trans 经 posting_id/group 关联 C6,DOP shadow 无 ord_nbr 列)
  45. }
  46. [Fact]
  47. public async Task Consumer_EmptyDomain_WithResolver_Retryable()
  48. {
  49. var db = new FakeInventoryDatabase();
  50. var store = new InMemoryIqcPostingStore();
  51. var idem = new IqcPostingIdempotencyService(store);
  52. var factory = new FakeIqcReceiptUnitOfWorkFactory(db);
  53. var consumer = new IqcInventoryEventConsumer(
  54. idem, new InMemoryBusinessCompletionStore(db), new FakeIqcReceiptStateLoader { State = new IqcReceiptState { Found = true } },
  55. new IqcReceiptOrchestrator(factory), new IqcMrbSelectionTaskService(factory),
  56. null, new IqcDomainResolver());
  57. var e = new IqcInventoryEvent { TenantId = T, DomainCode = "", Fbillno = "FB-X", Pd = 0, Clfs = "", Dhsl = 100m, EventTime = new DateTime(2026, 8, 9) };
  58. var r = await consumer.ConsumeAsync(e);
  59. Assert.Equal(IqcConsumeOutcome.Retryable, r.Outcome);
  60. Assert.Equal(0, db.TotalPersisted); // domain 未解析 → 未认领未过账
  61. }
  62. }