WorkOrderMaterialReadinessService.cs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. using Admin.NET.Plugin.AiDOP.DataPlatform;
  2. namespace Admin.NET.Plugin.AiDOP.Supply;
  3. /// <summary>
  4. /// 工单物料齐套上线看板服务
  5. /// </summary>
  6. [ApiDescriptionSettings(Order = 312, Description = "工单物料齐套上线看板")]
  7. [Route("api/Supply")]
  8. [AllowAnonymous]
  9. [NonUnify]
  10. public class WorkOrderMaterialReadinessService : IDynamicApiController, ITransient
  11. {
  12. private const string SqlServerSourceCode = "DOPDEMORQ_SQLSERVER";
  13. private readonly ISqlSugarClient _db;
  14. private readonly UserManager _userManager;
  15. private readonly MdpSourceScopeFactory _scopeFactory;
  16. private readonly SourceDomainTenantResolver _domainTenant;
  17. public WorkOrderMaterialReadinessService(
  18. ISqlSugarClient db,
  19. UserManager userManager,
  20. MdpSourceScopeFactory scopeFactory,
  21. SourceDomainTenantResolver domainTenant)
  22. {
  23. _db = db;
  24. _userManager = userManager;
  25. _scopeFactory = scopeFactory;
  26. _domainTenant = domainTenant;
  27. }
  28. [DisplayName("工单物料齐套上线看板列表")]
  29. [HttpGet("work-order-material-readiness/list")]
  30. public async Task<object> GetList([FromQuery] WorkOrderMaterialReadinessListInput input)
  31. {
  32. var page = input.Page <= 0 ? 1 : input.Page;
  33. var pageSize = input.PageSize <= 0 ? 10 : input.PageSize;
  34. var offset = (page - 1) * pageSize;
  35. var where = new List<string> { "1=1" };
  36. var pars = new List<SugarParameter>();
  37. // 租户隔离:只查当前租户的 WorkOrdTemp 数据
  38. var tenantId = Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  39. where.Add("a.tenant_id = @TenantId");
  40. pars.Add(new SugarParameter("@TenantId", tenantId));
  41. if (!string.IsNullOrWhiteSpace(input.SeOrder))
  42. {
  43. where.Add("a.SeOrder LIKE @SeOrder");
  44. pars.Add(new SugarParameter("@SeOrder", $"%{input.SeOrder.Trim()}%"));
  45. }
  46. if (!string.IsNullOrWhiteSpace(input.Workord))
  47. {
  48. where.Add("a.workord LIKE @Workord");
  49. pars.Add(new SugarParameter("@Workord", $"%{input.Workord.Trim()}%"));
  50. }
  51. if (!string.IsNullOrWhiteSpace(input.BomNumber))
  52. {
  53. where.Add("a.bom_number = @BomNumber");
  54. pars.Add(new SugarParameter("@BomNumber", input.BomNumber.Trim()));
  55. }
  56. if (!string.IsNullOrWhiteSpace(input.ItemNum))
  57. {
  58. where.Add("a.ItemNum = @ItemNum");
  59. pars.Add(new SugarParameter("@ItemNum", input.ItemNum.Trim()));
  60. }
  61. if (!string.IsNullOrWhiteSpace(input.SupplierName))
  62. {
  63. where.Add("a.supplier_name LIKE @SupplierName");
  64. pars.Add(new SugarParameter("@SupplierName", $"%{input.SupplierName.Trim()}%"));
  65. }
  66. // 始终使用 WorkOrdTemp 路径(字段完整);dwd_material_readiness 缺少 SeOrder/Priority/MorderType 等列,
  67. // 走 DWD 路径会导致订单号、工单优先级、工单类型等列全部为 NULL。
  68. // if (await ShouldUseDwdAsync(input))
  69. // {
  70. // return await GetDwdListAsync(input, page, pageSize, offset);
  71. // }
  72. var fromSql = $"FROM WorkOrdTemp a WHERE {string.Join(" AND ", where)}";
  73. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) FROM (SELECT DISTINCT a.RowNum, a.SeOrder, a.workord, a.ItemNum, a.bom_number, a.supplier_number {fromSql}) t", pars);
  74. var orderBy = BuildOrderBy(input.SortField, input.SortOrder);
  75. var list = await _db.Ado.SqlQueryAsync<WorkOrderMaterialReadinessRow>(
  76. $"""
  77. SELECT DISTINCT
  78. a.RowNum AS RowNum,
  79. a.SeOrder AS SeOrder,
  80. a.SePriority AS SePriority,
  81. a.workord AS Workord,
  82. a.Priority AS Priority,
  83. a.morder_type AS MorderType,
  84. a.LotSerial AS LotSerial,
  85. a.bom_number AS BomNumber,
  86. a.Descr AS Descr,
  87. a.Descr1 AS Descr1,
  88. a.production_number AS ProductionNumber,
  89. a.checktime AS Checktime,
  90. a.PMBOM AS PMBOM,
  91. a.ItemType AS ItemType,
  92. a.OwnerApplication AS OwnerApplication,
  93. a.ItemNum AS ItemNum,
  94. a.ComponentItemDescr AS ComponentItemDescr,
  95. a.ComponentItemDrawing AS ComponentItemDrawing,
  96. a.QtyRequired AS QtyRequired,
  97. a.ComponentItemUM AS ComponentItemUM,
  98. a.ComponentItemKittingTime AS ComponentItemKittingTime,
  99. a.QtyRec AS QtyRec,
  100. a.RecDate AS RecDate,
  101. a.shortage AS Shortage,
  102. a.QtyFrom AS QtyFrom,
  103. a.FromDate AS FromDate,
  104. a.zjsl AS Zjsl,
  105. a.jykssj AS Jykssj,
  106. a.ztsl AS Ztsl,
  107. a.jhshrq AS Jhshrq,
  108. a.jqsl AS Jqsl,
  109. a.hfjq AS Hfjq,
  110. a.PurOrdQty AS PurOrdQty,
  111. a.PurOrdorddate AS PurOrdorddate,
  112. a.PurOrds AS PurOrds,
  113. a.ContractDate AS ContractDate,
  114. a.ContractNO AS ContractNO,
  115. a.supplier_number AS SupplierNumber,
  116. a.supplier_name AS SupplierName,
  117. a.Buyer AS Buyer,
  118. a.BuyerUser AS BuyerUser
  119. {fromSql}
  120. ORDER BY {orderBy}
  121. LIMIT {pageSize} OFFSET {offset}
  122. """,
  123. pars);
  124. return new { total, page, pageSize, list };
  125. }
  126. [DisplayName("刷新工单物料齐套上线看板")]
  127. [HttpPost("work-order-material-readiness/refresh")]
  128. public async Task<object> Refresh()
  129. {
  130. var sw = System.Diagnostics.Stopwatch.StartNew();
  131. try
  132. {
  133. var tenantId = Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  134. var now = DateTime.Now;
  135. var batchId = $"workordtemp-{now:yyyyMMdd-HHmmss}";
  136. var tenantPar = new SugarParameter("@TenantId", tenantId);
  137. var domain = await _domainTenant.ResolveDomainAsync(SqlServerSourceCode, tenantId);
  138. // Step 0: 从 SQL Server 读取 InvMaster,写入 MySQL 辅助表
  139. await PrepareInvMasterTempAsync(domain, tenantId);
  140. // Step 1: 清空本租户 WorkOrdTemp
  141. await _db.Ado.ExecuteCommandAsync(
  142. "DELETE FROM WorkOrdTemp WHERE tenant_id = @TenantId", tenantPar);
  143. // Step 2: INSERT(原存储过程核心查询,所有源表加租户过滤)
  144. await _db.Ado.ExecuteCommandAsync("""
  145. INSERT INTO WorkOrdTemp(
  146. RowNum, Status, workord, Priority, OrdDate, morder_type, bom_number,
  147. Descr, Descr1, production_number, QtyRequired, supplier_name, ItemNum,
  148. supplier_number, ljsl, SeOrder, SePriority, LotSerial,
  149. checktime, PMBOM, ItemType, OwnerApplication, ComponentItemDescr,
  150. ComponentItemDrawing, ComponentItemUM, ComponentItemKittingTime,
  151. QtyRec, RecDate, QtyFrom, FromDate, jhshrq, PurOrdQty, PurOrdorddate,
  152. Buyer, BuyerUser, tenant_id
  153. )
  154. SELECT
  155. viewql.RowNum, viewql.Status, viewql.workord, viewql.Priority, viewql.OrdDate,
  156. viewql.morder_type, viewql.bom_number, viewql.Descr, viewql.Descr1,
  157. viewql.morder_production_number, viewql.QtyRequired, viewql.supplier_name,
  158. viewql.ItemNum, viewql.supplier_number,
  159. SUM(viewql.QtyRequired) OVER (
  160. PARTITION BY viewql.ItemNum
  161. ORDER BY viewql.Priority, viewql.workord
  162. ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  163. ) AS ljsl,
  164. viewql.fbill_no, viewql.Priority AS SePriority,
  165. viewql.LotSerial, viewql.checktime,
  166. viewql.PMBOM, viewql.ItemType, viewql.OwnerApplication,
  167. viewql.ComponentItemDescr, viewql.ComponentItemDrawing,
  168. viewql.ComponentItemUM, viewql.ComponentItemKittingTime,
  169. viewql.QtyRec, viewql.RecDate, viewql.QtyFrom, viewql.FromDate,
  170. viewql.jhshrq, viewql.PurOrdQty, viewql.PurOrdorddate,
  171. viewql.Buyer, '' AS BuyerUser,
  172. @TenantId AS tenant_id
  173. FROM (
  174. SELECT
  175. a.RowNum, a.Status, a.workord, a.Priority, a.OrdDate, a.LotSerial,
  176. b.morder_type, c.ItemNum AS bom_number, c.Descr, c.Descr1,
  177. b.morder_production_number, d.QtyRequired, e.supplier_name,
  178. d.ItemNum, e.supplier_number, f.fbill_no,
  179. c.PMBOM,
  180. Sonitem.ItemType, Sonitem.OwnerApplication,
  181. Sonitem.Descr AS ComponentItemDescr,
  182. Sonitem.Drawing AS ComponentItemDrawing,
  183. Sonitem.UM AS ComponentItemUM,
  184. exm.checktime,
  185. exd.kitting_time AS ComponentItemKittingTime,
  186. j.QtyRec, j.updatetime AS RecDate,
  187. j.QtyFrom, j.updatetime AS FromDate,
  188. sh.jhshrq,
  189. pm.QtyOrded AS PurOrdQty, pm.orddate AS PurOrdorddate,
  190. pm.Buyer
  191. FROM (
  192. SELECT RowNum, Status, workord, Priority, OrdDate, LotSerial
  193. FROM (
  194. SELECT CAST(ROW_NUMBER() OVER (PARTITION BY Status ORDER BY Priority ASC) AS CHAR(50)) AS RowNum,
  195. Status, workord, Priority, OrdDate, LotSerial
  196. FROM WorkOrdMaster
  197. WHERE Status IN ('W','S','r','p') AND tenant_id = @TenantId
  198. ) AS workview
  199. WHERE workord IN (
  200. SELECT WorkOrd FROM NbrMaster
  201. WHERE Type = 'SM' AND Status = ''
  202. GROUP BY WorkOrd
  203. ) OR Status = 'p'
  204. ) a
  205. LEFT JOIN mes_morder b ON a.workord = b.morder_no AND b.tenant_id = @TenantId
  206. LEFT JOIN ItemMaster c ON b.product_code = c.ItemNum AND c.tenant_id = @TenantId
  207. LEFT JOIN WorkOrdDetail d ON a.WorkOrd = d.WorkOrd AND d.tenant_id = @TenantId
  208. LEFT JOIN ItemMaster Sonitem ON d.itemnum = Sonitem.itemnum AND Sonitem.tenant_id = @TenantId
  209. LEFT JOIN (
  210. SELECT moentry_mono, GROUP_CONCAT(DISTINCT fbill_no SEPARATOR ',') AS fbill_no
  211. FROM mes_moentry WHERE tenant_id = @TenantId
  212. GROUP BY moentry_mono
  213. ) f ON b.morder_no = f.moentry_mono
  214. LEFT JOIN (
  215. SELECT workord, itemnum,
  216. SUM(QtyRec) AS QtyRec, MAX(updatetime) AS updatetime,
  217. SUM(QtyFrom) AS QtyFrom
  218. FROM NbrDetail
  219. WHERE tenant_id = @TenantId
  220. GROUP BY workord, itemnum
  221. ) j ON a.workord = j.workord AND d.itemnum = j.itemnum
  222. LEFT JOIN (
  223. SELECT a0.number,
  224. GROUP_CONCAT(a0.supplier_name SEPARATOR ',') AS supplier_name,
  225. GROUP_CONCAT(a0.supplier_number SEPARATOR ',') AS supplier_number
  226. FROM srm_purchase a0
  227. WHERE a0.quota_rate > 0 AND a0.tenant_id = @TenantId
  228. GROUP BY a0.number
  229. ) e ON d.ItemNum = e.number
  230. LEFT JOIN LATERAL (
  231. SELECT create_time AS checktime, id
  232. FROM b_examine_result
  233. WHERE morder_no = a.WorkOrd AND tenant_id = @TenantId
  234. ORDER BY create_time DESC LIMIT 1
  235. ) exm ON TRUE
  236. LEFT JOIN (
  237. SELECT examine_id, item_number, MIN(kitting_time) AS kitting_time
  238. FROM b_bom_child_examine
  239. GROUP BY examine_id, item_number
  240. ) exd ON exm.id = exd.examine_id AND exd.item_number != b.product_code and exd.item_number=d.ItemNum
  241. LEFT JOIN (
  242. SELECT MIN(shd.jhshrq) AS jhshrq,
  243. SUM(shzb.sh_delivery_quantity) AS sh_delivery_quantity,
  244. shzb.sh_material_code
  245. FROM scm_shd shd
  246. LEFT JOIN scm_shdzb shzb ON shd.id = shzb.glid AND shzb.tenant_id = shd.tenant_id
  247. WHERE shd.shzt != '完成' AND shd.tenant_id = @TenantId
  248. GROUP BY shzb.sh_material_code
  249. ) sh ON d.itemnum = sh.sh_material_code
  250. LEFT JOIN (
  251. SELECT SUM(pd.QtyOrded) AS QtyOrded, pd.itemnum,
  252. MIN(pm.orddate) AS orddate,
  253. GROUP_CONCAT(pm.Buyer SEPARATOR ',') AS Buyer
  254. FROM PurOrdMaster pm
  255. LEFT JOIN PurOrdDetail pd ON pm.purord = pd.purord AND pd.tenant_id = pm.tenant_id
  256. WHERE pm.status != 'C' AND pd.status != 'C' AND pm.tenant_id = @TenantId
  257. GROUP BY pd.itemnum
  258. ) pm ON d.itemnum = pm.itemnum
  259. ) viewql
  260. """, new List<SugarParameter> { tenantPar });
  261. // 兜底:确保所有行都有 tenant_id(防止参数绑定异常导致写入 NULL)
  262. await _db.Ado.ExecuteCommandAsync(
  263. "UPDATE WorkOrdTemp SET tenant_id = @TenantId WHERE tenant_id IS NULL",
  264. new List<SugarParameter> { tenantPar });
  265. var workOrdTempCount = await _db.Ado.GetIntAsync(
  266. "SELECT COUNT(1) FROM WorkOrdTemp WHERE IFNULL(workord,'') <> '' AND tenant_id = @TenantId",
  267. tenantPar);
  268. // Step 3: UPDATE locStock(从 _tmp_inv_master 辅助表 JOIN)
  269. await _db.Ado.ExecuteCommandAsync("""
  270. UPDATE WorkOrdTemp a
  271. LEFT JOIN _tmp_inv_master t ON a.ItemNum = t.ItemNum AND t.tenant_id = @TenantId
  272. SET a.locStock = IFNULL(t.locStock, 0)
  273. WHERE a.tenant_id = @TenantId
  274. """, tenantPar);
  275. // Step 4: UPDATE ztsl / zjsl / jykssj
  276. await _db.Ado.ExecuteCommandAsync("""
  277. UPDATE WorkOrdTemp a
  278. LEFT JOIN (
  279. SELECT ItemNum, SUM(ztsl) AS ztsl, SUM(zsl1) AS zjsl, MIN(jykssj) AS jykssj
  280. FROM (
  281. SELECT p.ItemNum,
  282. CASE WHEN IFNULL(p.zfhl, 0) <= IFNULL(p.zshl, 0) THEN 0
  283. ELSE IFNULL(p.zfhl, 0) - IFNULL(p.zshl, 0) END AS ztsl,
  284. IFNULL(p.zsl, 0) AS zsl1, p.jykssj
  285. FROM (
  286. SELECT p0.PurOrd, p0.Line, p0.ItemNum,
  287. IFNULL(s0.fhsl, 0) - IFNULL(Do0.thsl, 0) AS zfhl,
  288. p0.ReceiptQty + p0.RctQty AS zshl,
  289. IFNULL(i0.zjsl, 0) AS zsl, i0.jykssj
  290. FROM PurOrdDetail p0
  291. LEFT JOIN LATERAL (
  292. SELECT SUM(sh_delivery_quantity) AS fhsl
  293. FROM scm_shdzb WHERE po_bill = p0.PurOrd AND po_billline = p0.Line
  294. AND tenant_id = p0.tenant_id
  295. ) s0 ON TRUE
  296. LEFT JOIN LATERAL (
  297. SELECT SUM(m.Qty) AS zjsl, MIN(qms.FAPPLYTIME) AS jykssj
  298. FROM MissedPrint m
  299. LEFT JOIN qms_qcp_inspecapplyn qms ON m.checkno = qms.FBILLNO
  300. WHERE m.Status = 'I' AND m.PurOrd = p0.PurOrd AND m.PurLine = p0.Line
  301. AND m.tenant_id = p0.tenant_id
  302. ) i0 ON TRUE
  303. LEFT JOIN LATERAL (
  304. SELECT SUM(t.QtyReturn) AS thsl
  305. FROM PurOrdRctDetail t
  306. WHERE t.OrdNbr = p0.PurOrd AND t.OrdLine = p0.Line
  307. AND t.rcttype IN ('pt','temp') AND t.tenant_id = p0.tenant_id
  308. ) Do0 ON TRUE
  309. WHERE p0.Status <> 'C' AND p0.tenant_id = @TenantId
  310. ) p
  311. ) viewpur
  312. GROUP BY ItemNum
  313. ) b ON a.ItemNum = b.ItemNum
  314. SET a.ztsl = IFNULL(b.ztsl, 0),
  315. a.zjsl = IFNULL(b.zjsl, 0),
  316. a.jykssj = b.jykssj
  317. WHERE a.tenant_id = @TenantId
  318. """, tenantPar);
  319. // Step 5: UPDATE jqsl / hfjq
  320. await _db.Ado.ExecuteCommandAsync("""
  321. UPDATE WorkOrdTemp a
  322. LEFT JOIN (
  323. SELECT IF(jqsl > schedqty, schedqty, jqsl) AS jqsl, ItemNum, jqhf
  324. FROM (
  325. SELECT SUM(CAST(IFNULL(jq.jhdsl, 0) AS DECIMAL(18,6))) AS jqsl,
  326. MAX(ds.schedqty) AS schedqty,
  327. ds.ItemNum, DATE(jq.jqhf) AS jqhf
  328. FROM scm_jhjh_jq jq
  329. INNER JOIN srm_polist_ds ds ON jq.glid = ds.id AND ds.tenant_id = jq.tenant_id
  330. LEFT JOIN (
  331. SELECT SUM(sh_delivery_quantity) AS sh_delivery_quantity, jhdbh
  332. FROM scm_shdzb WHERE tenant_id = @TenantId
  333. GROUP BY jhdbh
  334. ) c ON ds.dsnum = c.jhdbh
  335. WHERE jq.flag = 0 AND jq.tenant_id = @TenantId
  336. AND IFNULL(c.sh_delivery_quantity, 0) < ds.schedqty
  337. AND ds.status = 'P' AND ds.schedqty - ds.sentqty > 0
  338. AND DATE(jq.jqhf) > CURDATE()
  339. GROUP BY ds.ItemNum, DATE(jq.jqhf)
  340. ) jqview
  341. ) b ON a.ItemNum = b.ItemNum AND DATE(a.OrdDate) = b.jqhf
  342. SET a.jqsl = CAST(IFNULL(b.jqsl, 0) AS DECIMAL(18,6)),
  343. a.hfjq = b.jqhf
  344. WHERE a.tenant_id = @TenantId
  345. """, tenantPar);
  346. // Step 6: UPDATE available / shortage(按日期分段累加)
  347. await _db.Ado.ExecuteCommandAsync("""
  348. UPDATE WorkOrdTemp
  349. SET available = IFNULL(locStock, 0),
  350. shortage = IFNULL(locStock, 0) - IFNULL(ljsl, 0)
  351. WHERE tenant_id = @TenantId AND DATE(OrdDate) <= CURDATE() + INTERVAL 1 DAY
  352. """, tenantPar);
  353. await _db.Ado.ExecuteCommandAsync("""
  354. UPDATE WorkOrdTemp
  355. SET available = IFNULL(locStock, 0) + IFNULL(zjsl, 0),
  356. shortage = IFNULL(locStock, 0) + IFNULL(zjsl, 0) - IFNULL(ljsl, 0)
  357. WHERE tenant_id = @TenantId AND DATE(OrdDate) = CURDATE() + INTERVAL 2 DAY
  358. """, tenantPar);
  359. await _db.Ado.ExecuteCommandAsync("""
  360. UPDATE WorkOrdTemp
  361. SET available = IFNULL(locStock, 0) + IFNULL(zjsl, 0) + IFNULL(ztsl, 0),
  362. shortage = IFNULL(locStock, 0) + IFNULL(zjsl, 0) + IFNULL(ztsl, 0) - IFNULL(ljsl, 0)
  363. WHERE tenant_id = @TenantId AND DATE(OrdDate) = CURDATE() + INTERVAL 3 DAY
  364. """, tenantPar);
  365. await _db.Ado.ExecuteCommandAsync("""
  366. UPDATE WorkOrdTemp
  367. SET available = IFNULL(locStock, 0) + IFNULL(zjsl, 0) + IFNULL(ztsl, 0) + IFNULL(jqsl, 0),
  368. shortage = IFNULL(locStock, 0) + IFNULL(zjsl, 0) + IFNULL(ztsl, 0) + IFNULL(jqsl, 0) - IFNULL(ljsl, 0)
  369. WHERE tenant_id = @TenantId
  370. AND DATE(OrdDate) >= CURDATE() + INTERVAL 4 DAY
  371. AND DATE(OrdDate) <= CURDATE() + INTERVAL 14 DAY
  372. """, tenantPar);
  373. // 清理辅助表(仅本租户)
  374. await _db.Ado.ExecuteCommandAsync(
  375. "DELETE FROM _tmp_inv_master WHERE tenant_id = @TenantId", tenantPar);
  376. // ── DWD 写入 ──
  377. await _db.Ado.BeginTranAsync();
  378. try
  379. {
  380. await _db.Ado.ExecuteCommandAsync(
  381. """
  382. DELETE FROM dwd_material_readiness
  383. WHERE tenant_id = @TenantId
  384. AND work_order NOT IN (
  385. SELECT DISTINCT workord FROM WorkOrdTemp WHERE IFNULL(workord, '') <> '' AND tenant_id = @TenantId
  386. )
  387. """, tenantPar);
  388. var dwdCount = await _db.Ado.ExecuteCommandAsync(
  389. """
  390. INSERT INTO dwd_material_readiness
  391. (tenant_id, stat_date, work_order, op_code, parent_item_code, component_item_code, component_item_name, required_qty, cumulative_required_qty, stock_available_qty, qc_pending_qty, in_transit_qty, delivery_reply_qty, available_qty, shortage_qty, ready_status, supplier_code, supplier_name, need_date, calc_batch_id, calc_time)
  392. SELECT
  393. @TenantId, @StatDate,
  394. IFNULL(workord, ''), CAST(IFNULL(Op, 0) AS CHAR), IFNULL(bom_number, ''),
  395. COALESCE(NULLIF(ItemNum, ''), NULLIF(PMBOM, ''), CONCAT('WORKORDTEMP-', IFNULL(RowNum, 0))),
  396. IFNULL(ComponentItemDescr, ''),
  397. IFNULL(QtyRequired, 0), IFNULL(ljsl, 0), IFNULL(locStock, 0),
  398. IFNULL(jqsl, 0), IFNULL(ztsl, 0), IFNULL(PurOrdQty, 0), IFNULL(available, 0),
  399. GREATEST(IFNULL(shortage, 0), IFNULL(QtyRequired, 0) - IFNULL(locStock, 0) - IFNULL(jqsl, 0) - IFNULL(ztsl, 0) - IFNULL(PurOrdQty, 0)),
  400. CASE WHEN GREATEST(IFNULL(shortage, 0), IFNULL(QtyRequired, 0) - IFNULL(locStock, 0) - IFNULL(jqsl, 0) - IFNULL(ztsl, 0) - IFNULL(PurOrdQty, 0)) > 0 THEN 'SHORTAGE' ELSE 'READY' END,
  401. supplier_number, supplier_name, COALESCE(jhshrq, hfjq, RecDate),
  402. @BatchId, @SyncTime
  403. FROM WorkOrdTemp
  404. WHERE IFNULL(workord, '') <> '' AND tenant_id = @TenantId
  405. ON DUPLICATE KEY UPDATE
  406. component_item_name = VALUES(component_item_name),
  407. required_qty = VALUES(required_qty),
  408. cumulative_required_qty = VALUES(cumulative_required_qty),
  409. stock_available_qty = VALUES(stock_available_qty),
  410. qc_pending_qty = VALUES(qc_pending_qty),
  411. in_transit_qty = VALUES(in_transit_qty),
  412. delivery_reply_qty = VALUES(delivery_reply_qty),
  413. available_qty = VALUES(available_qty),
  414. shortage_qty = VALUES(shortage_qty),
  415. ready_status = VALUES(ready_status),
  416. supplier_code = VALUES(supplier_code),
  417. supplier_name = VALUES(supplier_name),
  418. need_date = VALUES(need_date),
  419. calc_time = VALUES(calc_time),
  420. update_time = CURRENT_TIMESTAMP
  421. """,
  422. new SugarParameter("@StatDate", now.Date),
  423. new SugarParameter("@BatchId", batchId),
  424. new SugarParameter("@SyncTime", now),
  425. tenantPar);
  426. await _db.Ado.ExecuteCommandAsync(
  427. "DELETE FROM dwd_material_shortage WHERE stat_date = @StatDate AND tenant_id = @TenantId",
  428. new SugarParameter("@StatDate", now.Date), tenantPar);
  429. var shortageCount = await _db.Ado.ExecuteCommandAsync(
  430. """
  431. INSERT INTO dwd_material_shortage
  432. (tenant_id, stat_date, work_order, op_code, component_item_code, shortage_qty, shortage_reason, expected_supply_date, supplier_code, related_po_no, risk_level, calc_batch_id, calc_time)
  433. SELECT
  434. @TenantId, @StatDate,
  435. IFNULL(workord, ''), CAST(IFNULL(Op, 0) AS CHAR),
  436. COALESCE(NULLIF(ItemNum, ''), NULLIF(PMBOM, ''), CONCAT('WORKORDTEMP-', IFNULL(RowNum, 0))),
  437. GREATEST(IFNULL(shortage, 0), IFNULL(QtyRequired, 0) - IFNULL(locStock, 0) - IFNULL(jqsl, 0) - IFNULL(ztsl, 0) - IFNULL(PurOrdQty, 0)),
  438. '当前可用量不足', COALESCE(jhshrq, hfjq, RecDate),
  439. supplier_number, PurOrds,
  440. CASE
  441. WHEN GREATEST(IFNULL(shortage, 0), IFNULL(QtyRequired, 0) - IFNULL(locStock, 0) - IFNULL(jqsl, 0) - IFNULL(ztsl, 0) - IFNULL(PurOrdQty, 0)) > IFNULL(QtyRequired, 0) THEN 'HIGH'
  442. WHEN GREATEST(IFNULL(shortage, 0), IFNULL(QtyRequired, 0) - IFNULL(locStock, 0) - IFNULL(jqsl, 0) - IFNULL(ztsl, 0) - IFNULL(PurOrdQty, 0)) > 0 THEN 'MEDIUM'
  443. ELSE 'LOW'
  444. END,
  445. @BatchId, @SyncTime
  446. FROM WorkOrdTemp
  447. WHERE IFNULL(workord, '') <> '' AND tenant_id = @TenantId
  448. AND GREATEST(IFNULL(shortage, 0), IFNULL(QtyRequired, 0) - IFNULL(locStock, 0) - IFNULL(jqsl, 0) - IFNULL(ztsl, 0) - IFNULL(PurOrdQty, 0)) > 0
  449. """,
  450. new SugarParameter("@StatDate", now.Date),
  451. new SugarParameter("@BatchId", batchId),
  452. new SugarParameter("@SyncTime", now),
  453. tenantPar);
  454. await _db.Ado.CommitTranAsync();
  455. return new
  456. {
  457. enabled = true, batchId, workOrdTempCount, dwdCount, shortageCount,
  458. elapsedMs = sw.ElapsedMilliseconds,
  459. message = $"齐套/缺料刷新完成:获取 {workOrdTempCount} 条 WorkOrdTemp,写入 DWD 齐套 {dwdCount} 条、缺料 {shortageCount} 条。"
  460. };
  461. }
  462. catch
  463. {
  464. await _db.Ado.RollbackTranAsync();
  465. throw;
  466. }
  467. }
  468. catch (Exception ex)
  469. {
  470. return new { enabled = false, elapsedMs = sw.ElapsedMilliseconds, message = ex.Message };
  471. }
  472. }
  473. /// <summary>
  474. /// 从 SQL Server 读取 InvMaster 在库库存,写入 MySQL 辅助表 _tmp_inv_master。
  475. /// </summary>
  476. private async Task PrepareInvMasterTempAsync(string domain, long tenantId)
  477. {
  478. await _db.Ado.ExecuteCommandAsync("""
  479. CREATE TABLE IF NOT EXISTS _tmp_inv_master (
  480. tenant_id BIGINT NOT NULL,
  481. ItemNum VARCHAR(50) NOT NULL,
  482. locStock DECIMAL(18,6),
  483. PRIMARY KEY (tenant_id, ItemNum)
  484. )
  485. """);
  486. await _db.Ado.ExecuteCommandAsync(
  487. "DELETE FROM _tmp_inv_master WHERE tenant_id = @TenantId",
  488. new List<SugarParameter> { new("@TenantId", tenantId) });
  489. var locRange = await _db.Ado.GetStringAsync(
  490. "SELECT Val FROM ConfigurationItem WHERE FldName='MRPLocationRange' AND tenant_id = @TenantId LIMIT 1",
  491. new List<SugarParameter> { new("@TenantId", tenantId) });
  492. if (string.IsNullOrWhiteSpace(locRange)) return;
  493. var locations = locRange.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
  494. if (locations.Length == 0) return;
  495. var remote = await _scopeFactory.GetScopeAsync(SqlServerSourceCode);
  496. var locParams = new List<string>();
  497. var pars = new List<SugarParameter> { new("@Domain", domain) };
  498. for (var i = 0; i < locations.Length; i++)
  499. {
  500. locParams.Add($"@loc{i}");
  501. pars.Add(new SugarParameter($"@loc{i}", locations[i]));
  502. }
  503. var invSql = $"""
  504. SELECT ItemNum, SUM(ISNULL(AvailStatusQty, 0) + ISNULL(Assay, 0)) AS locStock
  505. FROM InvMaster
  506. WHERE Domain = @Domain AND Location IN ({string.Join(",", locParams)})
  507. GROUP BY ItemNum
  508. """;
  509. var rows = await remote.Ado.SqlQueryAsync<LocStockRow>(invSql, pars);
  510. if (rows.Count == 0) return;
  511. const int batchSize = 500;
  512. for (var offset = 0; offset < rows.Count; offset += batchSize)
  513. {
  514. var batch = rows.Skip(offset).Take(batchSize).ToList();
  515. var batchPars = new List<SugarParameter> { new("@TenantId", tenantId) };
  516. var values = new List<string>();
  517. for (var i = 0; i < batch.Count; i++)
  518. {
  519. if (string.IsNullOrWhiteSpace(batch[i].ItemNum)) continue;
  520. values.Add($"(@TenantId, @item{i}, @stock{i})");
  521. batchPars.Add(new SugarParameter($"@item{i}", batch[i].ItemNum));
  522. batchPars.Add(new SugarParameter($"@stock{i}", batch[i].LocStock));
  523. }
  524. if (values.Count > 0)
  525. {
  526. await _db.Ado.ExecuteCommandAsync(
  527. $"INSERT INTO _tmp_inv_master (tenant_id, ItemNum, locStock) VALUES {string.Join(",", values)}",
  528. batchPars);
  529. }
  530. }
  531. }
  532. private sealed class LocStockRow
  533. {
  534. public string? ItemNum { get; set; }
  535. public decimal LocStock { get; set; }
  536. }
  537. private async Task<bool> ShouldUseDwdAsync(WorkOrderMaterialReadinessListInput input)
  538. {
  539. if (!string.IsNullOrWhiteSpace(input.SeOrder))
  540. return false;
  541. var tenantId = Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  542. var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
  543. return await _db.Ado.GetIntAsync("SELECT COUNT(1) FROM dwd_material_readiness WHERE tenant_id=@TenantId LIMIT 1", pars) > 0;
  544. }
  545. private async Task<object> GetDwdListAsync(WorkOrderMaterialReadinessListInput input, int page, int pageSize, int offset)
  546. {
  547. var where = new List<string> { "1=1" };
  548. var pars = new List<SugarParameter>();
  549. var tenantId = Infrastructure.AidopTenantScope.ResolveOrThrow(_userManager);
  550. where.Add("a.tenant_id=@TenantId");
  551. pars.Add(new SugarParameter("@TenantId", tenantId));
  552. if (!string.IsNullOrWhiteSpace(input.Workord))
  553. {
  554. where.Add("a.work_order LIKE @Workord");
  555. pars.Add(new SugarParameter("@Workord", $"%{input.Workord.Trim()}%"));
  556. }
  557. if (!string.IsNullOrWhiteSpace(input.BomNumber))
  558. {
  559. where.Add("a.parent_item_code = @BomNumber");
  560. pars.Add(new SugarParameter("@BomNumber", input.BomNumber.Trim()));
  561. }
  562. if (!string.IsNullOrWhiteSpace(input.ItemNum))
  563. {
  564. where.Add("a.component_item_code = @ItemNum");
  565. pars.Add(new SugarParameter("@ItemNum", input.ItemNum.Trim()));
  566. }
  567. if (!string.IsNullOrWhiteSpace(input.SupplierName))
  568. {
  569. where.Add("a.supplier_name LIKE @SupplierName");
  570. pars.Add(new SugarParameter("@SupplierName", $"%{input.SupplierName.Trim()}%"));
  571. }
  572. var fromSql = $"FROM dwd_material_readiness a WHERE {string.Join(" AND ", where)}";
  573. var total = await _db.Ado.GetIntAsync($"SELECT COUNT(1) {fromSql}", pars);
  574. var orderBy = BuildDwdOrderBy(input.SortField, input.SortOrder);
  575. var list = await _db.Ado.SqlQueryAsync<WorkOrderMaterialReadinessRow>(
  576. $"""
  577. SELECT
  578. CAST(a.id AS CHAR) AS RowNum,
  579. NULL AS SeOrder,
  580. NULL AS SePriority,
  581. a.work_order AS Workord,
  582. NULL AS Priority,
  583. NULL AS MorderType,
  584. NULL AS LotSerial,
  585. a.op_code AS Op,
  586. a.need_date AS OpOrdDate,
  587. a.parent_item_code AS BomNumber,
  588. a.parent_item_code AS Descr,
  589. NULL AS Descr1,
  590. NULL AS ProductionNumber,
  591. a.calc_time AS Checktime,
  592. a.parent_item_code AS PMBOM,
  593. NULL AS ItemType,
  594. 'DWD' AS OwnerApplication,
  595. a.component_item_code AS ItemNum,
  596. a.component_item_name AS ComponentItemDescr,
  597. NULL AS ComponentItemDrawing,
  598. a.required_qty AS QtyRequired,
  599. NULL AS ComponentItemUM,
  600. NULL AS ComponentItemKittingTime,
  601. NULL AS QtyRec,
  602. NULL AS RecDate,
  603. a.shortage_qty AS Shortage,
  604. NULL AS QtyFrom,
  605. NULL AS FromDate,
  606. a.available_qty AS Zjsl,
  607. NULL AS Jykssj,
  608. a.in_transit_qty AS Ztsl,
  609. a.need_date AS Jhshrq,
  610. a.qc_pending_qty AS Jqsl,
  611. a.need_date AS Hfjq,
  612. a.delivery_reply_qty AS PurOrdQty,
  613. NULL AS PurOrdorddate,
  614. NULL AS PurOrds,
  615. NULL AS ContractDate,
  616. NULL AS ContractNO,
  617. a.supplier_code AS SupplierNumber,
  618. a.supplier_name AS SupplierName,
  619. NULL AS Buyer,
  620. NULL AS BuyerUser
  621. {fromSql}
  622. ORDER BY {orderBy}
  623. LIMIT {pageSize} OFFSET {offset}
  624. """,
  625. pars);
  626. return new { total, page, pageSize, list, source = "dwd_material_readiness" };
  627. }
  628. private static string BuildDwdOrderBy(string? sortField, string? sortOrder)
  629. {
  630. var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  631. return sortField?.ToLowerInvariant() switch
  632. {
  633. "rownum" => $"a.id {dir}",
  634. "workord" => $"a.work_order {dir}",
  635. "op" => $"a.op_code {dir}",
  636. "bomnumber" => $"a.parent_item_code {dir}",
  637. "itemnum" => $"a.component_item_code {dir}",
  638. "componentitemdescr" => $"a.component_item_name {dir}",
  639. "qtyrequired" => $"a.required_qty {dir}",
  640. "shortage" => $"a.shortage_qty {dir}",
  641. "zjsl" => $"a.available_qty {dir}",
  642. "ztsl" => $"a.in_transit_qty {dir}",
  643. "jhshrq" => $"a.need_date {dir}",
  644. "jqsl" => $"a.qc_pending_qty {dir}",
  645. "hfjq" => $"a.need_date {dir}",
  646. "purordqty" => $"a.delivery_reply_qty {dir}",
  647. "suppliernumber" => $"a.supplier_code {dir}",
  648. "suppliername" => $"a.supplier_name {dir}",
  649. _ => "a.stat_date DESC, a.work_order ASC, a.component_item_code ASC"
  650. };
  651. }
  652. private static string BuildOrderBy(string? sortField, string? sortOrder)
  653. {
  654. var dir = string.Equals(sortOrder, "asc", StringComparison.OrdinalIgnoreCase) ? "ASC" : "DESC";
  655. return sortField?.ToLowerInvariant() switch
  656. {
  657. "rownum" => $"a.RowNum {dir}",
  658. "seorder" => $"a.SeOrder {dir}",
  659. "sepriority" => $"a.SePriority {dir}",
  660. "workord" => $"a.workord {dir}",
  661. "priority" => $"a.Priority {dir}",
  662. "mordertype" => $"a.morder_type {dir}",
  663. "lotserial" => $"a.LotSerial {dir}",
  664. "op" => $"a.Op {dir}",
  665. "oporddate" => $"a.OpOrdDate {dir}",
  666. "bomnumber" => $"a.bom_number {dir}",
  667. "descr" => $"a.Descr {dir}",
  668. "descr1" => $"a.Descr1 {dir}",
  669. "productionnumber" => $"a.production_number {dir}",
  670. "checktime" => $"a.checktime {dir}",
  671. "pmbom" => $"a.PMBOM {dir}",
  672. "itemtype" => $"a.ItemType {dir}",
  673. "ownerapplication" => $"a.OwnerApplication {dir}",
  674. "itemnum" => $"a.ItemNum {dir}",
  675. "componentitemdescr" => $"a.ComponentItemDescr {dir}",
  676. "componentitemdrawing" => $"a.ComponentItemDrawing {dir}",
  677. "qtyrequired" => $"a.QtyRequired {dir}",
  678. "componentitemum" => $"a.ComponentItemUM {dir}",
  679. "componentitemkittingtime" => $"a.ComponentItemKittingTime {dir}",
  680. "qtyrec" => $"a.QtyRec {dir}",
  681. "recdate" => $"a.RecDate {dir}",
  682. "shortage" => $"a.shortage {dir}",
  683. "qtyfrom" => $"a.QtyFrom {dir}",
  684. "fromdate" => $"a.FromDate {dir}",
  685. "zjsl" => $"a.zjsl {dir}",
  686. "jykssj" => $"a.jykssj {dir}",
  687. "ztsl" => $"a.ztsl {dir}",
  688. "jhshrq" => $"a.jhshrq {dir}",
  689. "jqsl" => $"a.jqsl {dir}",
  690. "hfjq" => $"a.hfjq {dir}",
  691. "purordqty" => $"a.PurOrdQty {dir}",
  692. "purordorddate" => $"a.PurOrdorddate {dir}",
  693. "purords" => $"a.PurOrds {dir}",
  694. "contractdate" => $"a.ContractDate {dir}",
  695. "contractno" => $"a.ContractNO {dir}",
  696. "suppliernumber" => $"a.supplier_number {dir}",
  697. "suppliername" => $"a.supplier_name {dir}",
  698. "buyer" => $"a.Buyer {dir}",
  699. "buyeruser" => $"a.BuyerUser {dir}",
  700. _ => "a.Priority ASC, a.workord ASC"
  701. };
  702. }
  703. }