WorkOrderMaterialDetailSyncService.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. namespace Admin.NET.Plugin.AiDOP.WorkOrder;
  2. /// <summary>从资源检查结果同步工单物料明细(WorkOrdDetail)。</summary>
  3. public class WorkOrderMaterialDetailSyncService : ITransient
  4. {
  5. private readonly ISqlSugarClient _db;
  6. public WorkOrderMaterialDetailSyncService(ISqlSugarClient db)
  7. {
  8. _db = db;
  9. }
  10. public async Task<int> EnsureFromResourceCheckAsync(long tenantId, string workOrd, string account)
  11. {
  12. var wo = await LoadWorkOrderAsync(tenantId, workOrd);
  13. if (wo is null)
  14. return 0;
  15. var components = await LoadResourceCheckComponentsAsync(tenantId, workOrd);
  16. if (components.Count == 0)
  17. return 0;
  18. var now = DateTime.Now;
  19. // 加载已有明细,按 (ItemNum) 匹配进行 upsert
  20. var existingDetails = await _db.Ado.SqlQueryAsync<ExistingDetailRow>(
  21. """
  22. SELECT RecID, ItemNum, QtyRequired
  23. FROM WorkOrdDetail
  24. WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd AND IFNULL(IsActive, 0) = 1
  25. """,
  26. new SugarParameter("@TenantId", tenantId),
  27. new SugarParameter("@WorkOrd", workOrd));
  28. var existingMap = existingDetails.ToDictionary(x => x.ItemNum, x => x);
  29. var inserted = 0;
  30. var line = existingDetails.Count + 1;
  31. foreach (var c in components)
  32. {
  33. if (existingMap.TryGetValue(c.ItemNumber, out var ex))
  34. {
  35. // 已有明细:更新 QtyRequired
  36. if (ex.QtyRequired != c.QtyRequired)
  37. {
  38. await _db.Ado.ExecuteCommandAsync(
  39. """
  40. UPDATE WorkOrdDetail
  41. SET QtyRequired = @QtyRequired, UpdateUser = @User, UpdateTime = @Now
  42. WHERE RecID = @RecID AND tenant_id = @TenantId
  43. """,
  44. new SugarParameter("@QtyRequired", c.QtyRequired),
  45. new SugarParameter("@User", account),
  46. new SugarParameter("@Now", now),
  47. new SugarParameter("@RecID", ex.RecID),
  48. new SugarParameter("@TenantId", tenantId));
  49. }
  50. existingMap.Remove(c.ItemNumber);
  51. }
  52. else
  53. {
  54. // 新增明细
  55. await _db.Ado.ExecuteCommandAsync(
  56. """
  57. INSERT INTO WorkOrdDetail (
  58. WorkOrdMasterRecID, `Domain`, WorkOrd, LineNum, ItemNum, Op,
  59. Location, QtyRequired, QtyPosted, UM, IsActive, IsConfirm,
  60. CreateUser, CreateTime, UpdateUser, UpdateTime, tenant_id
  61. ) VALUES (
  62. @MasterRecId, @Domain, @WorkOrd, @LineNum, @ItemNum, @Op,
  63. @Location, @QtyRequired, 0, @UM, 1, 0,
  64. @User, @Now, @User, @Now, @TenantId
  65. )
  66. """,
  67. new SugarParameter("@MasterRecId", wo.RecId),
  68. new SugarParameter("@Domain", wo.Domain ?? "8010"),
  69. new SugarParameter("@WorkOrd", workOrd),
  70. new SugarParameter("@LineNum", line),
  71. new SugarParameter("@ItemNum", c.ItemNumber),
  72. new SugarParameter("@Op", c.Op),
  73. new SugarParameter("@Location", c.Location ?? (object)DBNull.Value),
  74. new SugarParameter("@QtyRequired", c.QtyRequired),
  75. new SugarParameter("@UM", c.Unit ?? (object)DBNull.Value),
  76. new SugarParameter("@User", account),
  77. new SugarParameter("@Now", now),
  78. new SugarParameter("@TenantId", tenantId));
  79. inserted++;
  80. line++;
  81. }
  82. }
  83. // 删除资源检查中没有的子物料
  84. foreach (var stale in existingMap.Values)
  85. {
  86. await _db.Ado.ExecuteCommandAsync(
  87. "DELETE FROM WorkOrdDetail WHERE RecID = @RecID AND tenant_id = @TenantId",
  88. new SugarParameter("@RecID", stale.RecID),
  89. new SugarParameter("@TenantId", tenantId));
  90. }
  91. return components.Count;
  92. }
  93. private async Task<WorkOrderRow?> LoadWorkOrderAsync(long tenantId, string workOrd)
  94. {
  95. var rows = await _db.Ado.SqlQueryAsync<WorkOrderRow>(
  96. """
  97. SELECT RecID AS RecId, `Domain`, ItemNum
  98. FROM WorkOrdMaster
  99. WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd
  100. LIMIT 1
  101. """,
  102. new SugarParameter("@TenantId", tenantId),
  103. new SugarParameter("@WorkOrd", workOrd));
  104. return rows.FirstOrDefault();
  105. }
  106. private async Task<List<ComponentRow>> LoadResourceCheckComponentsAsync(long tenantId, string workOrd)
  107. {
  108. return await _db.Ado.SqlQueryAsync<ComponentRow>(
  109. """
  110. SELECT
  111. bce.item_number AS ItemNumber,
  112. SUM(IFNULL(bce.needCount, 0)) AS QtyRequired,
  113. IFNULL(MAX(im.Um), '') AS Unit,
  114. IFNULL(MAX(im.Location), '') AS Location,
  115. IFNULL(MAX(bce.Op), 0) AS Op
  116. FROM b_examine_result ber
  117. INNER JOIN b_bom_child_examine bce ON ber.Id = bce.examine_id AND bce.is_use = 1
  118. LEFT JOIN ItemMaster im ON bce.item_number = im.ItemNum AND (ber.tenant_id = im.tenant_id OR ber.tenant_id IS NULL)
  119. WHERE ber.tenant_id = @TenantId
  120. AND ber.IsDeleted = 0
  121. AND ber.morder_no = @WorkOrd
  122. AND ber.Id = (
  123. SELECT br.Id FROM b_examine_result br
  124. WHERE br.tenant_id = @TenantId AND br.morder_no = @WorkOrd AND br.IsDeleted = 0
  125. ORDER BY br.create_time DESC LIMIT 1
  126. )
  127. AND IFNULL(bce.num, '') <> '1'
  128. AND IFNULL(bce.backflush, 0) = 0
  129. AND IFNULL(bce.erp_cls, 3) <> 4
  130. GROUP BY bce.item_number
  131. HAVING SUM(IFNULL(bce.needCount, 0)) > 0
  132. ORDER BY MIN(bce.id)
  133. """,
  134. new SugarParameter("@TenantId", tenantId),
  135. new SugarParameter("@WorkOrd", workOrd));
  136. }
  137. private sealed class WorkOrderRow
  138. {
  139. public long RecId { get; set; }
  140. public string? Domain { get; set; }
  141. public string? ItemNum { get; set; }
  142. }
  143. private sealed class ComponentRow
  144. {
  145. public string ItemNumber { get; set; } = string.Empty;
  146. public decimal QtyRequired { get; set; }
  147. public string? Unit { get; set; }
  148. public string? Location { get; set; }
  149. public int Op { get; set; }
  150. }
  151. private sealed class ExistingDetailRow
  152. {
  153. public long RecID { get; set; }
  154. public string ItemNum { get; set; } = string.Empty;
  155. public decimal QtyRequired { get; set; }
  156. }
  157. }