InventorySqlWriter.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. using Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Entity;
  2. using SqlSugar;
  3. using Yitter.IdGenerator;
  4. namespace Admin.NET.Plugin.AiDOP.MaterialWarehouse.InventoryPosting.Inventory;
  5. /// <summary>
  6. /// DOP-L3 库存加锁加载 + 写入的原始 SQL 工具(**不管理事务**;由调用方在既有事务内使用)。
  7. /// 供独立 C5 UoW 与 IQC 收货外层 UoW 复用,避免重复 SQL。**Phase 5B 未实库验证**(目标库 aidopdev)。
  8. /// 并发策略:既有行 FOR UPDATE 锁定 + 相对 UPDATE(set x=x+delta) 防 lost-update;首建用 INSERT…ON DUPLICATE KEY(noop)。
  9. /// </summary>
  10. public static class InventorySqlWriter
  11. {
  12. public static async Task<InventoryContext> LoadLockedAsync(ISqlSugarClient db, IReadOnlyList<InventoryTransactionCommand> cmds)
  13. {
  14. var ctx = new InventoryContext();
  15. if (cmds == null || cmds.Count == 0) return ctx;
  16. var tenantId = cmds[0].TenantId;
  17. var domain = cmds[0].DomainCode ?? string.Empty;
  18. var items = cmds.Select(c => c.ItemNum).Distinct(StringComparer.Ordinal).ToList();
  19. var locations = cmds.Select(c => c.Location).Distinct(StringComparer.Ordinal).ToList();
  20. foreach (var it in await db.Ado.SqlQueryAsync<string>(
  21. "SELECT ItemNum FROM ItemMaster WHERE Domain=@d AND ItemNum IN (@items)",
  22. new SugarParameter("@d", domain), new SugarParameter("@items", items)))
  23. ctx.Items.Add(it);
  24. foreach (var lo in await db.Ado.SqlQueryAsync<string>(
  25. "SELECT Location FROM LocationMaster WHERE Domain=@d AND Location IN (@locs)",
  26. new SugarParameter("@d", domain), new SugarParameter("@locs", locations)))
  27. ctx.Locations.Add(lo);
  28. var invRows = await db.Ado.SqlQueryAsync<InvRow>(
  29. """
  30. SELECT item_num AS ItemNum, location AS Location, qty_on_hand AS QtyOnHand,
  31. avail_status_qty AS AvailStatusQty, assay_qty AS AssayQty, freeze_qty AS FreezeQty
  32. FROM ado_inventory_master
  33. WHERE tenant_id=@t AND domain_code=@d AND item_num IN (@items) AND location IN (@locs)
  34. FOR UPDATE
  35. """,
  36. new SugarParameter("@t", tenantId), new SugarParameter("@d", domain),
  37. new SugarParameter("@items", items), new SugarParameter("@locs", locations));
  38. foreach (var r in invRows)
  39. ctx.InvMasters[InventoryContext.InvKey(r.ItemNum, r.Location)] =
  40. new InventoryBalance { QtyOnHand = r.QtyOnHand, AvailStatusQty = r.AvailStatusQty, Assay = r.AssayQty, FreezeQty = r.FreezeQty };
  41. var detRows = await db.Ado.SqlQueryAsync<DetailRow>(
  42. """
  43. SELECT item_num AS ItemNum, location AS Location, lot_serial AS LotSerial, refs AS Refs,
  44. qty_on_hand AS QtyOnHand, avail_status_qty AS AvailStatusQty, assay_qty AS AssayQty, freeze_qty AS FreezeQty
  45. FROM ado_inventory_location_detail
  46. WHERE tenant_id=@t AND domain_code=@d AND item_num IN (@items) AND location IN (@locs)
  47. FOR UPDATE
  48. """,
  49. new SugarParameter("@t", tenantId), new SugarParameter("@d", domain),
  50. new SugarParameter("@items", items), new SugarParameter("@locs", locations));
  51. foreach (var r in detRows)
  52. ctx.LocationDetails[InventoryContext.DetailKey(r.ItemNum, r.Location, r.LotSerial, r.Refs)] =
  53. new InventoryBalance { QtyOnHand = r.QtyOnHand, AvailStatusQty = r.AvailStatusQty, Assay = r.AssayQty, FreezeQty = r.FreezeQty };
  54. foreach (var s in await db.Ado.SqlQueryAsync<StateRow>(
  55. "SELECT item_num AS ItemNum, default_location AS DefaultLocation FROM ado_item_location_state WHERE tenant_id=@t AND domain_code=@d AND item_num IN (@items) FOR UPDATE",
  56. new SugarParameter("@t", tenantId), new SugarParameter("@d", domain), new SugarParameter("@items", items)))
  57. ctx.ItemDefaultLocation[s.ItemNum] = s.DefaultLocation ?? string.Empty;
  58. return ctx;
  59. }
  60. public static async Task WriteAsync(ISqlSugarClient db, InventoryWriteSet ws)
  61. {
  62. if (ws == null) throw new ArgumentNullException(nameof(ws));
  63. var now = DateTime.Now;
  64. foreach (var w in ws.InvMasterWrites)
  65. {
  66. await db.Ado.ExecuteCommandAsync(
  67. """
  68. INSERT INTO ado_inventory_master (id,tenant_id,domain_code,item_num,location,qty_on_hand,avail_status_qty,assay_qty,freeze_qty,is_active,create_time,create_user)
  69. VALUES (@id,@t,@d,@i,@l,0,0,0,0,1,@now,@u)
  70. ON DUPLICATE KEY UPDATE update_time=@now
  71. """,
  72. new SugarParameter("@id", YitIdHelper.NextId()), new SugarParameter("@t", w.TenantId), new SugarParameter("@d", w.DomainCode ?? ""),
  73. new SugarParameter("@i", w.ItemNum), new SugarParameter("@l", w.Location), new SugarParameter("@now", now), new SugarParameter("@u", w.User ?? ""));
  74. await db.Ado.ExecuteCommandAsync(
  75. """
  76. UPDATE ado_inventory_master
  77. SET qty_on_hand=qty_on_hand+@dq, avail_status_qty=avail_status_qty+@da, assay_qty=assay_qty+@das, freeze_qty=freeze_qty+@df,
  78. last_rct_time=@now, is_active=1, update_time=@now, update_user=@u
  79. WHERE tenant_id=@t AND domain_code=@d AND item_num=@i AND location=@l
  80. """,
  81. new SugarParameter("@dq", w.Delta.DeltaQtyOnHand), new SugarParameter("@da", w.Delta.DeltaAvailStatusQty),
  82. new SugarParameter("@das", w.Delta.DeltaAssay), new SugarParameter("@df", w.Delta.DeltaFreezeQty),
  83. new SugarParameter("@now", now), new SugarParameter("@u", w.User ?? ""),
  84. new SugarParameter("@t", w.TenantId), new SugarParameter("@d", w.DomainCode ?? ""),
  85. new SugarParameter("@i", w.ItemNum), new SugarParameter("@l", w.Location));
  86. }
  87. foreach (var w in ws.ItemLocationWrites)
  88. await db.Ado.ExecuteCommandAsync(
  89. """
  90. INSERT INTO ado_item_location_state (id,tenant_id,domain_code,item_num,default_location,create_time,create_user)
  91. VALUES (@id,@t,@d,@i,@loc,@now,@u)
  92. ON DUPLICATE KEY UPDATE default_location=IF(IFNULL(default_location,'')='',@loc,default_location), update_time=@now, update_user=@u
  93. """,
  94. new SugarParameter("@id", YitIdHelper.NextId()), new SugarParameter("@t", w.TenantId), new SugarParameter("@d", w.DomainCode ?? ""),
  95. new SugarParameter("@i", w.ItemNum), new SugarParameter("@loc", w.DefaultLocation), new SugarParameter("@now", now), new SugarParameter("@u", w.User ?? ""));
  96. foreach (var w in ws.LocationDetailWrites)
  97. {
  98. await db.Ado.ExecuteCommandAsync(
  99. """
  100. INSERT INTO ado_inventory_location_detail (id,tenant_id,domain_code,item_num,location,lot_serial,refs,qty_on_hand,avail_status_qty,assay_qty,freeze_qty,create_time,create_user)
  101. VALUES (@id,@t,@d,@i,@l,@lot,@refs,0,0,0,0,@now,@u)
  102. ON DUPLICATE KEY UPDATE update_time=@now
  103. """,
  104. new SugarParameter("@id", YitIdHelper.NextId()), new SugarParameter("@t", w.TenantId), new SugarParameter("@d", w.DomainCode ?? ""),
  105. new SugarParameter("@i", w.ItemNum), new SugarParameter("@l", w.Location), new SugarParameter("@lot", w.LotSerial ?? ""), new SugarParameter("@refs", w.Refs ?? ""),
  106. new SugarParameter("@now", now), new SugarParameter("@u", w.User ?? ""));
  107. await db.Ado.ExecuteCommandAsync(
  108. """
  109. UPDATE ado_inventory_location_detail
  110. SET qty_on_hand=qty_on_hand+@dq, avail_status_qty=avail_status_qty+@da, assay_qty=assay_qty+@das, freeze_qty=freeze_qty+@df, update_time=@now, update_user=@u
  111. WHERE tenant_id=@t AND domain_code=@d AND item_num=@i AND location=@l AND lot_serial=@lot AND refs=@refs
  112. """,
  113. new SugarParameter("@dq", w.Delta.DeltaQtyOnHand), new SugarParameter("@da", w.Delta.DeltaAvailStatusQty),
  114. new SugarParameter("@das", w.Delta.DeltaAssay), new SugarParameter("@df", w.Delta.DeltaFreezeQty),
  115. new SugarParameter("@now", now), new SugarParameter("@u", w.User ?? ""),
  116. new SugarParameter("@t", w.TenantId), new SugarParameter("@d", w.DomainCode ?? ""),
  117. new SugarParameter("@i", w.ItemNum), new SugarParameter("@l", w.Location),
  118. new SugarParameter("@lot", w.LotSerial ?? ""), new SugarParameter("@refs", w.Refs ?? ""));
  119. }
  120. foreach (var r in ws.TransactionRows)
  121. await db.Insertable(ToEntity(r)).ExecuteCommandAsync();
  122. }
  123. private static AdoInventoryTransaction ToEntity(InventoryTransactionRow r)
  124. => new AdoInventoryTransaction
  125. {
  126. Id = YitIdHelper.NextId(), TenantId = r.TenantId, DomainCode = r.DomainCode ?? "",
  127. ItemNum = r.ItemNum, Location = r.Location, LotSerial = r.LotSerial ?? "",
  128. TransType = r.TransType ?? "", QtyChange = r.QtyChange, BeginBalance = r.BeginBalance,
  129. WorkOrd = r.WorkOrd, ShipperNum = r.ShipperNum, Remark = r.Remark,
  130. FbillNo = r.Fbillno, Receiver = r.Receiver, RctQcNbr = r.RctQcNbr,
  131. PostingId = r.PostingId == 0 ? (long?)null : r.PostingId,
  132. TransactionGroupId = r.TransactionGroupId, Seq = r.Seq,
  133. CreateTime = DateTime.Now, CreateUser = r.User,
  134. };
  135. private sealed class InvRow { public string ItemNum { get; set; } public string Location { get; set; } public decimal QtyOnHand { get; set; } public decimal AvailStatusQty { get; set; } public decimal AssayQty { get; set; } public decimal FreezeQty { get; set; } }
  136. private sealed class DetailRow { public string ItemNum { get; set; } public string Location { get; set; } public string LotSerial { get; set; } public string Refs { get; set; } public decimal QtyOnHand { get; set; } public decimal AvailStatusQty { get; set; } public decimal AssayQty { get; set; } public decimal FreezeQty { get; set; } }
  137. private sealed class StateRow { public string ItemNum { get; set; } public string DefaultLocation { get; set; } }
  138. }