using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Idempotency; using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.L1; using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Numbering; using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Receipt; using Xunit; namespace Admin.NET.Plugin.AiDOP.Tests.S5.MaterialWarehouse; /// Phase 5B-3 接线单测:IqcDomainResolver(无 8010 fallback)+ orchestrator 事务内 RctNbr 取号。 public class IqcWiringTests { private const long T = 950000000000001L; [Fact] public void DomainResolver_NonEmpty_Trims() => Assert.Equal("2400", new IqcDomainResolver().Resolve(T, " 2400 ")); [Fact] public void DomainResolver_Empty_Throws_NoFallback() { var r = new IqcDomainResolver(); Assert.Throws(() => r.Resolve(T, "")); Assert.Throws(() => r.Resolve(T, null)); Assert.Throws(() => r.Resolve(T, " ")); } [Fact] public async Task Orchestrator_GeneratesRctNbr_InTx_StampsC6AndTrans() { var db = new FakeInventoryDatabase(); db.SeedMaster(T, "D5B3", "IT", "L"); var prov = new InMemoryBusinessNumberProvider(new[] { new BusinessNumberRule { RuleCode = "P5B3_RCT", Prefix1 = "RC", IniValue = 1 }, }); var orch = new IqcReceiptOrchestrator(new FakeIqcReceiptUnitOfWorkFactory(db), prov, "P5B3_RCT"); var receipt = new IqcAcceptedReceipt { TenantId = T, DomainCode = "D5B3", PostingId = 1, TransactionGroupId = 1, RctNbr = "OLD-PLACEHOLDER", ItemNum = "IT", Location = "L", AcceptedQty = 100m, UmConversion = 1m, PurOrd = "PO", PurLine = 1, Potype = "po", OrderedQty = 100m, Fbillno = "FB", User = "u", }; var gate = new PurchaseReceiptGateInput { HasPendingReceipt = true, AcceptedQty = 100m, HasReceivableDetails = true }; var r = await orch.PostAsync(receipt, gate); Assert.Equal(IqcReceiptOutcome.Success, r.Outcome); var c6 = db.ReceiptWriteback.Values.Single(); Assert.StartsWith("RC", c6.RctNbr); // 用取号结果(新收货号落 C6.rct_nbr) Assert.NotEqual("OLD-PLACEHOLDER", c6.RctNbr); // 非入参占位 Assert.NotEmpty(db.Transactions); // C5 流水已生成(trans 经 posting_id/group 关联 C6,DOP shadow 无 ord_nbr 列) } [Fact] public async Task Consumer_EmptyDomain_WithResolver_Retryable() { var db = new FakeInventoryDatabase(); var store = new InMemoryIqcPostingStore(); var idem = new IqcPostingIdempotencyService(store); var factory = new FakeIqcReceiptUnitOfWorkFactory(db); var consumer = new IqcInventoryEventConsumer( idem, new InMemoryBusinessCompletionStore(db), new FakeIqcReceiptStateLoader { State = new IqcReceiptState { Found = true } }, new IqcReceiptOrchestrator(factory), new IqcMrbSelectionTaskService(factory), null, new IqcDomainResolver()); var e = new IqcInventoryEvent { TenantId = T, DomainCode = "", Fbillno = "FB-X", Pd = 0, Clfs = "", Dhsl = 100m, EventTime = new DateTime(2026, 8, 9) }; var r = await consumer.ConsumeAsync(e); Assert.Equal(IqcConsumeOutcome.Retryable, r.Outcome); Assert.Equal(0, db.TotalPersisted); // domain 未解析 → 未认领未过账 } }