WorkOrderMaterialReadinessService.cs 37 KB

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