| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Inventory;
- using Xunit;
- namespace Admin.NET.Plugin.AiDOP.Tests.S5.MaterialWarehouse;
- /// <summary>
- /// C5 9 道前置守卫正反测试(Phase 5A;纯校验,无 DB)。
- /// LIVE 实核为 9 道;旧方案文档「8 道」为不完整总结(遗漏 G8 iss-tr-ins 质检过账≤质检库存)。
- /// 实施以 pr_SFM_InventoryTransactionProcessing2 为最终契约。
- /// </summary>
- public class InventoryGuardValidatorTests
- {
- private static InventoryTransactionCommand Cmd(string tt, decimal qty, string item = "IT-1", string loc = "L1", string lot = "", string refs = "")
- => new InventoryTransactionCommand { TransType = tt, ChangeQty = qty, ItemNum = item, Location = loc, LotSerial = lot, Refs = refs };
- private static InventoryContext Ctx(
- bool lotControl = false, bool dup = false, decimal avail = 1000m, decimal freeze = 1000m, decimal assay = 1000m,
- bool withItem = true, bool withLoc = true, bool withInvMaster = true, bool withDetail = true,
- string item = "IT-1", string loc = "L1", string lot = "", string refs = "")
- {
- var ctx = new InventoryContext { IsLotControl = lotControl, HasDuplicateBaseData = dup };
- if (withItem) ctx.Items.Add(item);
- if (withLoc) ctx.Locations.Add(loc);
- if (withInvMaster) ctx.InvMasters[InventoryContext.InvKey(item, loc)] = new InventoryBalance { QtyOnHand = 1000m, AvailStatusQty = avail, Assay = assay, FreezeQty = freeze };
- if (withDetail)
- {
- var k = lotControl ? InventoryContext.DetailKey(item, loc, lot, refs) : InventoryContext.DetailKey(item, loc, "", "");
- ctx.LocationDetails[k] = new InventoryBalance { QtyOnHand = 1000m, AvailStatusQty = avail, Assay = assay, FreezeQty = freeze };
- }
- return ctx;
- }
- private static List<InventoryTransactionCommand> L(params InventoryTransactionCommand[] c) => c.ToList();
- [Fact]
- public void G1_DuplicateBaseData_Fails()
- {
- var e = InventoryGuardValidator.Validate(L(Cmd("rct-po-ins", 10)), Ctx(dup: true));
- Assert.Single(e);
- Assert.Equal(InventoryGuardCode.DuplicateBaseData, e[0].Code);
- }
- [Fact]
- public void G2_ItemMasterMissing_Fails_Present_Passes()
- {
- var bad = InventoryGuardValidator.Validate(L(Cmd("rct-po-ins", 10)), Ctx(withItem: false));
- Assert.Contains(bad, x => x.Code == InventoryGuardCode.ItemMasterMissing);
- var ok = InventoryGuardValidator.Validate(L(Cmd("rct-po-ins", 10)), Ctx());
- Assert.Empty(ok);
- }
- [Fact]
- public void G3_LocationMasterMissing_Fails()
- {
- var e = InventoryGuardValidator.Validate(L(Cmd("rct-po-ins", 10)), Ctx(withLoc: false));
- Assert.Contains(e, x => x.Code == InventoryGuardCode.LocationMasterMissing);
- }
- [Fact]
- public void G4_InvMasterMissing_OnNetOutbound_Fails()
- {
- // 净出库 且 无 InvMaster
- var e = InventoryGuardValidator.Validate(L(Cmd("iss-x", -10)), Ctx(withInvMaster: false, withDetail: false));
- Assert.Contains(e, x => x.Code == InventoryGuardCode.InvMasterMissing);
- // 入库则不触发 G4
- var ok = InventoryGuardValidator.Validate(L(Cmd("rct-po-ins", 10)), Ctx(withInvMaster: false, withDetail: false));
- Assert.DoesNotContain(ok, x => x.Code == InventoryGuardCode.InvMasterMissing);
- }
- [Fact]
- public void G5_LocationDetailMissing_OnNetOutbound_Fails()
- {
- var e = InventoryGuardValidator.Validate(L(Cmd("iss-x", -10)), Ctx(withDetail: false));
- Assert.Contains(e, x => x.Code == InventoryGuardCode.LocationDetailMissing);
- }
- [Fact]
- public void G6_UnfreezeExceedsFreeze_Fails_Sufficient_Passes()
- {
- var bad = InventoryGuardValidator.Validate(L(Cmd("inv-unfrz", -100)), Ctx(freeze: 50m));
- Assert.Contains(bad, x => x.Code == InventoryGuardCode.UnfreezeExceedsFreeze);
- var ok = InventoryGuardValidator.Validate(L(Cmd("inv-unfrz", -100)), Ctx(freeze: 200m));
- Assert.Empty(ok);
- }
- [Fact]
- public void G7_FreezeExceedsAvail_Fails()
- {
- var e = InventoryGuardValidator.Validate(L(Cmd("inv-frz", -100)), Ctx(avail: 50m));
- Assert.Contains(e, x => x.Code == InventoryGuardCode.FreezeExceedsAvail);
- }
- [Fact]
- public void G8_AssayIssueExceedsAssay_Fails()
- {
- // 旧总结遗漏的一道
- var e = InventoryGuardValidator.Validate(L(Cmd("iss-tr-ins", -100)), Ctx(assay: 50m));
- Assert.Contains(e, x => x.Code == InventoryGuardCode.AssayIssueExceedsAssay);
- }
- [Fact]
- public void G9_IssueExceedsAvail_Fails()
- {
- var e = InventoryGuardValidator.Validate(L(Cmd("iss-x", -100)), Ctx(avail: 50m));
- Assert.Contains(e, x => x.Code == InventoryGuardCode.IssueExceedsAvail);
- }
- [Fact]
- public void AllGuardsPass_RctPoInsInbound_NoErrors()
- {
- var e = InventoryGuardValidator.Validate(L(Cmd("rct-po-ins", 100)), Ctx());
- Assert.Empty(e);
- }
- [Fact]
- public void LotControl_G5_MissingBatch_IncludesLot()
- {
- var e = InventoryGuardValidator.Validate(
- L(Cmd("iss-x", -10, lot: "LOT-A")),
- Ctx(lotControl: true, withDetail: false, lot: "LOT-A"));
- var err = Assert.Single(e, x => x.Code == InventoryGuardCode.LocationDetailMissing);
- Assert.Equal("LOT-A", err.LotSerial);
- }
- [Fact]
- public void LotControl_DifferentLot_IsolatedStock()
- {
- // LOT-A 有库存、LOT-B 无 → 出 LOT-B 触发 G5,不受 LOT-A 影响
- var ctx = Ctx(lotControl: true, withDetail: true, lot: "LOT-A");
- var e = InventoryGuardValidator.Validate(L(Cmd("iss-x", -10, lot: "LOT-B")), ctx);
- Assert.Contains(e, x => x.Code == InventoryGuardCode.LocationDetailMissing && x.LotSerial == "LOT-B");
- }
- }
|