WorkOrderMaterialDetailSyncService.cs 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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
  43. """,
  44. new SugarParameter("@QtyRequired", c.QtyRequired),
  45. new SugarParameter("@User", account),
  46. new SugarParameter("@Now", now),
  47. new SugarParameter("@RecID", ex.RecID));
  48. }
  49. existingMap.Remove(c.ItemNumber);
  50. }
  51. else
  52. {
  53. // 新增明细
  54. await _db.Ado.ExecuteCommandAsync(
  55. """
  56. INSERT INTO WorkOrdDetail (
  57. WorkOrdMasterRecID, `Domain`, WorkOrd, LineNum, ItemNum, Op,
  58. Location, QtyRequired, QtyPosted, UM, IsActive, IsConfirm,
  59. CreateUser, CreateTime, UpdateUser, UpdateTime, tenant_id
  60. ) VALUES (
  61. @MasterRecId, @Domain, @WorkOrd, @LineNum, @ItemNum, @Op,
  62. @Location, @QtyRequired, 0, @UM, 1, 0,
  63. @User, @Now, @User, @Now, @TenantId
  64. )
  65. """,
  66. new SugarParameter("@MasterRecId", wo.RecId),
  67. new SugarParameter("@Domain", wo.Domain ?? tenantId.ToString()),
  68. new SugarParameter("@WorkOrd", workOrd),
  69. new SugarParameter("@LineNum", line),
  70. new SugarParameter("@ItemNum", c.ItemNumber),
  71. new SugarParameter("@Op", c.Op),
  72. new SugarParameter("@Location", c.Location ?? (object)DBNull.Value),
  73. new SugarParameter("@QtyRequired", c.QtyRequired),
  74. new SugarParameter("@UM", c.Unit ?? (object)DBNull.Value),
  75. new SugarParameter("@User", account),
  76. new SugarParameter("@Now", now),
  77. new SugarParameter("@TenantId", tenantId));
  78. inserted++;
  79. line++;
  80. }
  81. }
  82. // 删除不再有缺料的旧明细
  83. foreach (var stale in existingMap.Values)
  84. {
  85. await _db.Ado.ExecuteCommandAsync(
  86. """
  87. UPDATE WorkOrdDetail
  88. SET IsActive = 0, UpdateUser = @User, UpdateTime = @Now
  89. WHERE RecID = @RecID
  90. """,
  91. new SugarParameter("@User", account),
  92. new SugarParameter("@Now", now),
  93. new SugarParameter("@RecID", stale.RecID));
  94. }
  95. return components.Count;
  96. }
  97. private async Task<WorkOrderRow?> LoadWorkOrderAsync(long tenantId, string workOrd)
  98. {
  99. var rows = await _db.Ado.SqlQueryAsync<WorkOrderRow>(
  100. """
  101. SELECT RecID AS RecId, `Domain`, ItemNum
  102. FROM WorkOrdMaster
  103. WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd
  104. LIMIT 1
  105. """,
  106. new SugarParameter("@TenantId", tenantId),
  107. new SugarParameter("@WorkOrd", workOrd));
  108. return rows.FirstOrDefault();
  109. }
  110. private async Task<List<ComponentRow>> LoadResourceCheckComponentsAsync(long tenantId, string workOrd)
  111. {
  112. return await _db.Ado.SqlQueryAsync<ComponentRow>(
  113. """
  114. SELECT
  115. bce.item_number AS ItemNumber,
  116. SUM(IFNULL(bce.needCount, 0)) AS QtyRequired,
  117. IFNULL(MAX(im.Um), '') AS Unit,
  118. IFNULL(MAX(im.Location), '') AS Location,
  119. IFNULL(MAX(bce.Op), 0) AS Op
  120. FROM b_examine_result ber
  121. INNER JOIN b_bom_child_examine bce ON ber.Id = bce.examine_id AND bce.is_use = 1
  122. LEFT JOIN ItemMaster im ON bce.item_number = im.ItemNum
  123. WHERE ber.tenant_id = @TenantId
  124. AND ber.IsDeleted = 0
  125. AND ber.morder_no = @WorkOrd
  126. AND ber.Id = (
  127. SELECT br.Id FROM b_examine_result br
  128. WHERE br.tenant_id = @TenantId AND br.morder_no = @WorkOrd AND br.IsDeleted = 0
  129. ORDER BY br.create_time DESC LIMIT 1
  130. )
  131. AND IFNULL(bce.num, '') <> '1'
  132. AND IFNULL(bce.backflush, 0) = 0
  133. AND IFNULL(bce.erp_cls, 3) <> 4
  134. GROUP BY bce.item_number
  135. HAVING SUM(IFNULL(bce.needCount, 0)) > 0
  136. ORDER BY MIN(bce.id)
  137. """,
  138. new SugarParameter("@TenantId", tenantId),
  139. new SugarParameter("@WorkOrd", workOrd));
  140. }
  141. private sealed class WorkOrderRow
  142. {
  143. public long RecId { get; set; }
  144. public string? Domain { get; set; }
  145. public string? ItemNum { get; set; }
  146. }
  147. private sealed class ComponentRow
  148. {
  149. public string ItemNumber { get; set; } = string.Empty;
  150. public decimal QtyRequired { get; set; }
  151. public string? Unit { get; set; }
  152. public string? Location { get; set; }
  153. public int Op { get; set; }
  154. }
  155. private sealed class ExistingDetailRow
  156. {
  157. public long RecID { get; set; }
  158. public string ItemNum { get; set; } = string.Empty;
  159. public decimal QtyRequired { get; set; }
  160. }
  161. }