WorkOrderPickBillService.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. using Admin.NET.Plugin.AiDOP.Supply;
  2. using Yitter.IdGenerator;
  3. namespace Admin.NET.Plugin.AiDOP.WorkOrder;
  4. /// <summary>工单下达领料单生成(NbrMaster / NbrDetail,Type=SM)。</summary>
  5. public class WorkOrderPickBillService : ITransient
  6. {
  7. private readonly ISqlSugarClient _db;
  8. private readonly NumberRuleService _numberRule;
  9. private readonly WorkOrderMaterialDetailSyncService _detailSync;
  10. public WorkOrderPickBillService(
  11. ISqlSugarClient db,
  12. NumberRuleService numberRule,
  13. WorkOrderMaterialDetailSyncService detailSync)
  14. {
  15. _db = db;
  16. _numberRule = numberRule;
  17. _detailSync = detailSync;
  18. }
  19. public async Task<PickBillResult> CreateForWorkOrderAsync(long tenantId, string workOrd, string account)
  20. {
  21. workOrd = workOrd.Trim();
  22. var existing = await FindExistingPickBillAsync(tenantId, workOrd);
  23. if (!string.IsNullOrWhiteSpace(existing))
  24. return new PickBillResult { Nbr = existing, Created = false, Message = "领料单已存在" };
  25. var wo = await LoadWorkOrderAsync(tenantId, workOrd)
  26. ?? throw Oops.Oh($"工单 {workOrd} 不存在");
  27. await _detailSync.EnsureFromResourceCheckAsync(tenantId, workOrd, account);
  28. var details = await LoadPickDetailsAsync(tenantId, workOrd);
  29. if (details.Count == 0)
  30. throw Oops.Oh($"工单 {workOrd} 无物料明细,请先完成订单评审资源检查");
  31. var domain = string.IsNullOrWhiteSpace(wo.Domain) ? tenantId.ToString() : wo.Domain.Trim();
  32. var lineTo = await ResolveLineSideLocationAsync(tenantId, wo.ItemNum);
  33. var now = DateTime.Now;
  34. var effDate = wo.OrdDate?.Date ?? now.Date;
  35. var pickDate = effDate < now.Date ? now.Date : effDate;
  36. string nbrNo;
  37. try
  38. {
  39. var numbers = await _numberRule.NextBatchInCurrentTransactionAsync("SM", domain, 1, account);
  40. nbrNo = numbers.FirstOrDefault() ?? throw Oops.Oh("领料单号生成失败");
  41. }
  42. catch
  43. {
  44. nbrNo = "SM" + now.ToString("yyyyMMddHHmmss") + YitIdHelper.NextId().ToString()[^5..];
  45. }
  46. var masterRecId = await NextRecIdAsync("NbrMaster");
  47. await _db.Ado.ExecuteCommandAsync(
  48. """
  49. INSERT INTO NbrMaster (
  50. RecID, `Domain`, Type, Nbr, Status, Remark, Date, EffDate,
  51. WorkOrd, QtyOrd, QtyRec, ProdLine, Department, Name,
  52. TransType, IsActive, IsChanged, IsConfirm, BusinessID,
  53. CreateUser, CreateTime, UpdateUser, UpdateTime, tenant_id
  54. ) VALUES (
  55. @RecId, @Domain, 'SM', @Nbr, '', @Remark, @PickDate, @EffDate,
  56. @WorkOrd, @QtyOrd, 0, @ProdLine, '101', @Name,
  57. '', 1, 1, 0, 0,
  58. @User, @Now, @User, @Now, @TenantId
  59. )
  60. """,
  61. new SugarParameter("@RecId", masterRecId),
  62. new SugarParameter("@Domain", domain.Length > 8 ? domain[..8] : domain),
  63. new SugarParameter("@Nbr", nbrNo),
  64. new SugarParameter("@Remark", "下达自动领料"),
  65. new SugarParameter("@PickDate", pickDate),
  66. new SugarParameter("@EffDate", effDate),
  67. new SugarParameter("@WorkOrd", workOrd),
  68. new SugarParameter("@QtyOrd", wo.QtyOrded ?? 0),
  69. new SugarParameter("@ProdLine", wo.ProdLine ?? (object)DBNull.Value),
  70. new SugarParameter("@Name", account.Length > 12 ? account[..12] : account),
  71. new SugarParameter("@User", account.Length > 24 ? account[..24] : account),
  72. new SugarParameter("@Now", now),
  73. new SugarParameter("@TenantId", tenantId));
  74. short line = 1;
  75. foreach (var d in details)
  76. {
  77. var detailRecId = await NextRecIdAsync("NbrDetail");
  78. await _db.Ado.ExecuteCommandAsync(
  79. """
  80. INSERT INTO NbrDetail (
  81. RecID, `Domain`, Type, Nbr, Line, ItemNum, Dimension1, Dimension2,
  82. LocationFrom, LocationTo, QtyFrom, QtyTo, QtyOrd, QtyRec,
  83. CurrQtyOpened, UM, WorkOrd, ItemName, Status,
  84. IsActive, IsConfirm, IsChanged, BusinessID, NbrRecID,
  85. CreateUser, CreateTime, UpdateUser, UpdateTime, tenant_id
  86. ) VALUES (
  87. @RecId, @Domain, 'SM', @Nbr, @Line, @ItemNum, '', '',
  88. @LocationFrom, @LocationTo, 0, 0, @QtyOrd, 0,
  89. @CurrQtyOpened, @UM, @WorkOrd, @ItemName, '',
  90. 1, 0, 1, 0, @NbrRecId,
  91. @User, @Now, @User, @Now, @TenantId
  92. )
  93. """,
  94. new SugarParameter("@RecId", detailRecId),
  95. new SugarParameter("@Domain", domain.Length > 8 ? domain[..8] : domain),
  96. new SugarParameter("@Nbr", nbrNo),
  97. new SugarParameter("@Line", line),
  98. new SugarParameter("@ItemNum", d.ItemNum),
  99. new SugarParameter("@LocationFrom", d.LocationFrom ?? (object)DBNull.Value),
  100. new SugarParameter("@LocationTo", lineTo ?? (object)DBNull.Value),
  101. new SugarParameter("@QtyOrd", d.QtyRequired),
  102. new SugarParameter("@CurrQtyOpened", d.QtyRequired),
  103. new SugarParameter("@UM", d.Unit ?? (object)DBNull.Value),
  104. new SugarParameter("@WorkOrd", workOrd),
  105. new SugarParameter("@ItemName", d.ItemName ?? (object)DBNull.Value),
  106. new SugarParameter("@NbrRecId", masterRecId),
  107. new SugarParameter("@User", account.Length > 24 ? account[..24] : account),
  108. new SugarParameter("@Now", now),
  109. new SugarParameter("@TenantId", tenantId));
  110. line++;
  111. }
  112. await _db.Ado.ExecuteCommandAsync(
  113. """
  114. UPDATE mes_morder
  115. SET morder_state = '下达',
  116. MaterialSituation = CASE
  117. WHEN MaterialSituation = '缺料' THEN '未备料'
  118. ELSE IFNULL(MaterialSituation, '齐套')
  119. END,
  120. update_by_name = @User,
  121. update_time = @Now
  122. WHERE tenant_id = @TenantId AND morder_no = @WorkOrd AND IsDeleted = 0
  123. """,
  124. new SugarParameter("@User", account),
  125. new SugarParameter("@Now", now),
  126. new SugarParameter("@TenantId", tenantId),
  127. new SugarParameter("@WorkOrd", workOrd));
  128. return new PickBillResult
  129. {
  130. Nbr = nbrNo,
  131. Created = true,
  132. LineCount = details.Count,
  133. Message = "领料单已生成"
  134. };
  135. }
  136. private async Task<string?> FindExistingPickBillAsync(long tenantId, string workOrd)
  137. {
  138. return await _db.Ado.GetStringAsync(
  139. """
  140. SELECT Nbr FROM NbrMaster
  141. WHERE tenant_id = @TenantId AND WorkOrd = @WorkOrd AND Type = 'SM'
  142. AND IFNULL(TransType, '') = ''
  143. ORDER BY RecID DESC LIMIT 1
  144. """,
  145. new List<SugarParameter>
  146. {
  147. new("@TenantId", tenantId),
  148. new("@WorkOrd", workOrd)
  149. });
  150. }
  151. private async Task<WorkOrderPickRow?> LoadWorkOrderAsync(long tenantId, string workOrd)
  152. {
  153. var rows = await _db.Ado.SqlQueryAsync<WorkOrderPickRow>(
  154. """
  155. SELECT
  156. w.RecID AS RecId,
  157. w.`Domain`,
  158. w.ItemNum,
  159. w.QtyOrded,
  160. w.OrdDate,
  161. (
  162. SELECT LEFT(IFNULL(r.ProdLine, ''), 8)
  163. FROM WorkOrdRouting r
  164. WHERE r.WorkOrd = w.WorkOrd AND r.tenant_id = w.tenant_id AND IFNULL(r.IsActive, 0) = 1
  165. ORDER BY (r.OP + 0) LIMIT 1
  166. ) AS ProdLine
  167. FROM WorkOrdMaster w
  168. WHERE w.tenant_id = @TenantId AND w.WorkOrd = @WorkOrd
  169. LIMIT 1
  170. """,
  171. new SugarParameter("@TenantId", tenantId),
  172. new SugarParameter("@WorkOrd", workOrd));
  173. return rows.FirstOrDefault();
  174. }
  175. private async Task<List<PickDetailRow>> LoadPickDetailsAsync(long tenantId, string workOrd)
  176. {
  177. return await _db.Ado.SqlQueryAsync<PickDetailRow>(
  178. """
  179. SELECT
  180. d.ItemNum,
  181. SUM(d.QtyRequired) AS QtyRequired,
  182. MAX(IFNULL(d.Location, im.Location)) AS LocationFrom,
  183. MAX(IFNULL(d.UM, im.Um)) AS Unit,
  184. MAX(im.Descr) AS ItemName
  185. FROM WorkOrdDetail d
  186. LEFT JOIN ItemMaster im ON d.ItemNum = im.ItemNum
  187. WHERE d.tenant_id = @TenantId AND d.WorkOrd = @WorkOrd AND IFNULL(d.IsActive, 0) = 1
  188. GROUP BY d.ItemNum
  189. HAVING SUM(d.QtyRequired) > 0
  190. ORDER BY d.ItemNum
  191. """,
  192. new SugarParameter("@TenantId", tenantId),
  193. new SugarParameter("@WorkOrd", workOrd));
  194. }
  195. private async Task<string?> ResolveLineSideLocationAsync(long tenantId, string? itemNum)
  196. {
  197. if (string.IsNullOrWhiteSpace(itemNum))
  198. return null;
  199. var loc = await _db.Ado.GetStringAsync(
  200. """
  201. SELECT LEFT(IFNULL(lm.Location, lm.PickingLocation), 8)
  202. FROM ProdLineDetail pld
  203. INNER JOIN LineMaster lm ON pld.Line = lm.Line AND lm.tenant_id = pld.tenant_id
  204. WHERE pld.Part = @ItemNum AND pld.tenant_id = @TenantId AND IFNULL(pld.IsActive, 1) = 1
  205. ORDER BY IFNULL(pld.Sequence, 0)
  206. LIMIT 1
  207. """,
  208. new List<SugarParameter>
  209. {
  210. new("@ItemNum", itemNum.Trim()),
  211. new("@TenantId", tenantId)
  212. });
  213. return string.IsNullOrWhiteSpace(loc) ? null : loc;
  214. }
  215. private async Task<int> NextRecIdAsync(string table)
  216. {
  217. return await _db.Ado.GetIntAsync($"SELECT IFNULL(MAX(RecID), 0) + 1 FROM {table}");
  218. }
  219. public sealed class PickBillResult
  220. {
  221. public string Nbr { get; set; } = string.Empty;
  222. public bool Created { get; set; }
  223. public int LineCount { get; set; }
  224. public string Message { get; set; } = string.Empty;
  225. }
  226. private sealed class WorkOrderPickRow
  227. {
  228. public long RecId { get; set; }
  229. public string? Domain { get; set; }
  230. public string? ItemNum { get; set; }
  231. public decimal? QtyOrded { get; set; }
  232. public DateTime? OrdDate { get; set; }
  233. public string? ProdLine { get; set; }
  234. }
  235. private sealed class PickDetailRow
  236. {
  237. public string ItemNum { get; set; } = string.Empty;
  238. public decimal QtyRequired { get; set; }
  239. public string? LocationFrom { get; set; }
  240. public string? Unit { get; set; }
  241. public string? ItemName { get; set; }
  242. }
  243. }