| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123 |
- using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Inventory;
- using Xunit;
- namespace Admin.NET.Plugin.AiDOP.Tests.S5.MaterialWarehouse;
- /// <summary>
- /// C5 库存量变化契约测试(Phase 5A;纯计算,无 DB)。
- /// 精确固化 pr_SFM_InventoryTransactionProcessing2 各 TransType 的 ΔQtyOnHand/ΔAvail/ΔAssay/ΔFreeze/ΔQtyOnOrd。
- /// </summary>
- public class InventoryBalanceCalculatorTests
- {
- private static InventoryTransactionCommand Cmd(string transType, decimal changeQty, decimal umc = 1m)
- => new InventoryTransactionCommand
- {
- TransType = transType, ChangeQty = changeQty, UmConversion = umc,
- ItemNum = "IT-1", Location = "L1",
- };
- // TransType | ChangeQty | umc | conv | ΔQtyOnHand | ΔAvail | ΔAssay | ΔFreeze | ΔQtyOnOrd
- [Theory]
- // 采购收货入质检库存(IQC 合格入库核心):在手+、质检+、可用不变
- [InlineData("rct-po-ins", 100, 1, 100, 100, 0, 100, 0, 0)]
- // 质检转出(ChangeQty<0 走 Assay 掩码):在手-、质检-
- [InlineData("iss-tr-ins", -30, 1, -30, -30, 0, -30, 0, 0)]
- // 质检相关入(ChangeQty>0 不走 Assay 掩码):在手+、可用+
- [InlineData("iss-tr-ins", 30, 1, 30, 30, 30, 0, 0, 0)]
- // 冻结:可用-、冻结+(ΔFreeze=-conv)
- [InlineData("inv-frz", -50, 1, -50, 0, -50, 0, 50, 0)]
- // 解冻:按 LIVE 同式(DB_PENDING 符号)
- [InlineData("inv-unfrz", -50, 1, -50, 0, -50, 0, 50, 0)]
- // 销售出库:在手-、可用-、在订-
- [InlineData("iss-so", -20, 1, -20, -20, -20, 0, 0, -20)]
- // 一般出入库(其它 TransType):在手/可用同步
- [InlineData("rct-x-ins", 40, 1, 40, 40, 40, 0, 0, 0)]
- // UMConversion≠1:conv=ChangeQty×umc
- [InlineData("rct-po-ins", 10, 2, 20, 20, 0, 20, 0, 0)]
- public void ComputeRowDeltas_PerTransType_MatchLiveFormula(
- string tt, double changeQty, double umc, double conv,
- double dQoh, double dAvail, double dAssay, double dFreeze, double dOrd)
- {
- var d = InventoryBalanceCalculator.ComputeRowDeltas(Cmd(tt, (decimal)changeQty, (decimal)umc));
- Assert.Equal((decimal)conv, d.Conv);
- Assert.Equal((decimal)dQoh, d.DeltaQtyOnHand);
- Assert.Equal((decimal)dAvail, d.DeltaAvailStatusQty);
- Assert.Equal((decimal)dAssay, d.DeltaAssay);
- Assert.Equal((decimal)dFreeze, d.DeltaFreezeQty);
- Assert.Equal((decimal)dOrd, d.DeltaQtyOnOrd);
- }
- [Fact]
- public void ComputeRowDeltas_UmConversionZero_NormalizedToOne()
- {
- var d = InventoryBalanceCalculator.ComputeRowDeltas(Cmd("rct-po-ins", 7m, 0m));
- Assert.Equal(7m, d.Conv); // umc 0 → 1
- Assert.Equal(7m, d.DeltaAssay);
- }
- [Fact]
- public void Project_BeforeToAfter_RctPoIns_IncreasesOnHandAndAssayOnly()
- {
- var before = new InventoryBalance { QtyOnHand = 100m, AvailStatusQty = 80m, Assay = 20m, FreezeQty = 0m, QtyOnOrd = 0m };
- var (d, after) = InventoryBalanceCalculator.Project(before, Cmd("rct-po-ins", 50m));
- Assert.Equal(50m, d.Conv);
- Assert.Equal(150m, after.QtyOnHand); // 在手 +50
- Assert.Equal(80m, after.AvailStatusQty); // 可用不变(进质检)
- Assert.Equal(70m, after.Assay); // 质检 +50
- Assert.Equal(0m, after.FreezeQty);
- // 不 mutate 入参
- Assert.Equal(100m, before.QtyOnHand);
- }
- [Fact]
- public void Project_Freeze_MovesAvailToFreeze()
- {
- var before = new InventoryBalance { QtyOnHand = 100m, AvailStatusQty = 100m, Assay = 0m, FreezeQty = 0m };
- var (_, after) = InventoryBalanceCalculator.Project(before, Cmd("inv-frz", -40m));
- Assert.Equal(100m, after.QtyOnHand); // 在手不变
- Assert.Equal(60m, after.AvailStatusQty); // 可用 -40
- Assert.Equal(40m, after.FreezeQty); // 冻结 +40
- }
- [Fact]
- public void BuildTransHist_RctPoIns_FieldsMatchLive()
- {
- var cmd = Cmd("rct-po-ins", 100m);
- cmd.Price = 5m;
- var d = InventoryBalanceCalculator.ComputeRowDeltas(cmd);
- var h = InventoryBalanceCalculator.BuildTransHist(cmd, d, beginBalance: 100m);
- Assert.Equal(100m, h.BeginBalance);
- Assert.Equal(100m, h.QtyChange); // 非 frz → conv
- Assert.Equal(0m, h.FreezeQty);
- Assert.Equal(100m, h.Assay);
- Assert.Equal(500m, h.Amt); // conv × Price
- Assert.Equal("rct-po-ins", h.TransType);
- }
- [Fact]
- public void BuildTransHist_FreezeTrans_QtyChangeZero()
- {
- var cmd = Cmd("inv-frz", -50m);
- var d = InventoryBalanceCalculator.ComputeRowDeltas(cmd);
- var h = InventoryBalanceCalculator.BuildTransHist(cmd, d, 0m);
- Assert.Equal(0m, h.QtyChange); // frz/unfrz 流水 QtyChange=0
- Assert.Equal(50m, h.FreezeQty);
- }
- [Fact]
- public void BuildTransHist_QcCheckRemark_UsesReasonAsTransType()
- {
- var cmd = Cmd("rct-po-ins", 10m);
- cmd.Remark = "QcCheck";
- cmd.Reason = "iqc-pass";
- var d = InventoryBalanceCalculator.ComputeRowDeltas(cmd);
- var h = InventoryBalanceCalculator.BuildTransHist(cmd, d, 0m);
- Assert.Equal("iqc-pass", h.TransType); // Remark=='QcCheck' → 取 Reason
- }
- [Fact]
- public void ComputeRowDeltas_NullCommand_Throws()
- {
- Assert.Throws<ArgumentNullException>(() => InventoryBalanceCalculator.ComputeRowDeltas(null));
- }
- }
|