LinkagePlanService.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. using Admin.NET.Plugin.AiDOP.DataPlatform;
  2. using Admin.NET.Plugin.AiDOP.DataPlatform.S1Refresh;
  3. using Admin.NET.Plugin.AiDOP.Infrastructure;
  4. namespace Admin.NET.Plugin.AiDOP.Order;
  5. /// <summary>
  6. /// 计划联动看板服务
  7. /// 路由前缀:/api/Order/linkageplan/...
  8. /// </summary>
  9. [ApiDescriptionSettings(Order = 250, Description = "计划联动看板")]
  10. [Route("api/Order")]
  11. [AllowAnonymous]
  12. [NonUnify]
  13. public class LinkagePlanService : IDynamicApiController, ITransient
  14. {
  15. private const string SqlServerSourceCode = "DOPDEMORQ_SQLSERVER";
  16. private readonly ISqlSugarClient _db;
  17. private readonly SqlSugarRepository<LinkagePlan> _linkagePlanRep;
  18. private readonly UserManager _userManager;
  19. private readonly S1MdpSyncTransformService _s1MdpSyncTransformService;
  20. private readonly MdpSourceScopeFactory _scopeFactory;
  21. public LinkagePlanService(
  22. ISqlSugarClient db,
  23. SqlSugarRepository<LinkagePlan> linkagePlanRep,
  24. UserManager userManager,
  25. S1MdpSyncTransformService s1MdpSyncTransformService,
  26. MdpSourceScopeFactory scopeFactory)
  27. {
  28. _db = db;
  29. _linkagePlanRep = linkagePlanRep;
  30. _userManager = userManager;
  31. _s1MdpSyncTransformService = s1MdpSyncTransformService;
  32. _scopeFactory = scopeFactory;
  33. }
  34. [DisplayName("获取计划联动看板列表")]
  35. [HttpGet("linkageplan/list")]
  36. public async Task<object> GetLinkagePlanList([FromQuery] LinkagePlanListInput input)
  37. {
  38. var tenantId = _userManager.TenantId;
  39. var q = _db.Queryable<LinkagePlan>()
  40. .Where(u => u.TenantId == tenantId)
  41. .WhereIF(!string.IsNullOrWhiteSpace(input.BillNo), u => u.BillNo != null && u.BillNo.Contains(input.BillNo!.Trim()))
  42. .WhereIF(!string.IsNullOrWhiteSpace(input.CustomNo), u => u.CustomNo != null && u.CustomNo.Contains(input.CustomNo!.Trim()))
  43. .WhereIF(!string.IsNullOrWhiteSpace(input.OrderType), u => u.OrderType == input.OrderType!.Trim())
  44. .WhereIF(!string.IsNullOrWhiteSpace(input.ItemNumber), u => u.ItemNumber == input.ItemNumber!.Trim());
  45. var paged = await q
  46. .OrderBy(u => u.BillNo)
  47. .OrderBy(u => u.ItemNumber)
  48. .OrderBy(u => u.LevelNum)
  49. .OrderBy("CASE WHEN type='计划' THEN 1 WHEN type='实际' THEN 2 WHEN type='偏差' THEN 3 ELSE 4 END")
  50. .ToPagedListAsync(input.Page, input.PageSize);
  51. var list = paged.Items.Select(u => new
  52. {
  53. id = u.Id,
  54. billNo = u.BillNo,
  55. customNo = u.CustomNo,
  56. orderType = u.OrderType,
  57. itemNumber = u.ItemNumber,
  58. descr = u.Descr,
  59. descr1 = u.Descr1,
  60. itemType = u.ItemType,
  61. qty = u.Qty,
  62. updateTime = u.UpdateTime?.ToString("yyyy-MM-dd"),
  63. sysCapacityDate = u.SysCapacityDate?.ToString("yyyy-MM-dd"),
  64. type = u.Type,
  65. bomStart = u.BomStart,
  66. bomEnd = u.BomEnd,
  67. lineStart = u.LineStart,
  68. lineEnd = u.LineEnd,
  69. productStart = u.ProductStart,
  70. productEnd = u.ProductEnd,
  71. needTime = u.NeedTime,
  72. sysMaterialDate = u.SysMaterialDate,
  73. cgNeedTime = u.CgNeedTime,
  74. cgEnd = u.CgEnd,
  75. blStart = u.BlStart,
  76. blEnd = u.BlEnd,
  77. startTime = u.StartTime,
  78. endTime = u.EndTime,
  79. ipqcJyStart = u.IpqcJyStart,
  80. ipqcJyEnd = u.IpqcJyEnd,
  81. fqcJyStart = u.FqcJyStart,
  82. fqcJyEnd = u.FqcJyEnd,
  83. rkStart = u.RkStart,
  84. rkEnd = u.RkEnd,
  85. fyStartTime = u.FyStartTime,
  86. fyEndTime = u.FyEndTime,
  87. levelNum = u.LevelNum,
  88. background = u.Background,
  89. businessId = u.BusinessId,
  90. }).ToList();
  91. return new { total = paged.Total, page = input.Page, pageSize = input.PageSize, list };
  92. }
  93. [DisplayName("获取计划联动看板详情")]
  94. [HttpGet("linkageplan/{id:long}")]
  95. public async Task<object> GetLinkagePlanDetail(long id)
  96. {
  97. var tenantId = _userManager.TenantId;
  98. var m = await _linkagePlanRep.GetFirstAsync(u => u.Id == id && u.TenantId == tenantId)
  99. ?? throw Oops.Oh("计划联动记录不存在");
  100. return new
  101. {
  102. id = m.Id,
  103. billNo = m.BillNo,
  104. customNo = m.CustomNo,
  105. orderType = m.OrderType,
  106. itemNumber = m.ItemNumber,
  107. descr = m.Descr,
  108. descr1 = m.Descr1,
  109. itemType = m.ItemType,
  110. qty = m.Qty,
  111. updateTime = m.UpdateTime?.ToString("yyyy-MM-dd HH:mm:ss"),
  112. sysCapacityDate = m.SysCapacityDate?.ToString("yyyy-MM-dd HH:mm:ss"),
  113. type = m.Type,
  114. bomStart = m.BomStart,
  115. bomEnd = m.BomEnd,
  116. lineStart = m.LineStart,
  117. lineEnd = m.LineEnd,
  118. productStart = m.ProductStart,
  119. productEnd = m.ProductEnd,
  120. needTime = m.NeedTime,
  121. sysMaterialDate = m.SysMaterialDate,
  122. cgNeedTime = m.CgNeedTime,
  123. cgEnd = m.CgEnd,
  124. blStart = m.BlStart,
  125. blEnd = m.BlEnd,
  126. startTime = m.StartTime,
  127. endTime = m.EndTime,
  128. ipqcJyStart = m.IpqcJyStart,
  129. ipqcJyEnd = m.IpqcJyEnd,
  130. fqcJyStart = m.FqcJyStart,
  131. fqcJyEnd = m.FqcJyEnd,
  132. rkStart = m.RkStart,
  133. rkEnd = m.RkEnd,
  134. fyStartTime = m.FyStartTime,
  135. fyEndTime = m.FyEndTime,
  136. levelNum = m.LevelNum,
  137. background = m.Background,
  138. };
  139. }
  140. [DisplayName("刷新计划联动数据")]
  141. [HttpPost("linkageplan/refresh")]
  142. public async Task<object> RefreshLinkagePlanData()
  143. {
  144. try
  145. {
  146. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  147. var result = await _s1MdpSyncTransformService.RunFullAsync(S1MdpRunScope.Create(tenantId, 1), triggerType: "MANUAL");
  148. return new
  149. {
  150. message = "数据刷新成功",
  151. batchId = result.BatchId,
  152. result.StageRows,
  153. result.StandardRows,
  154. result.DwdRows
  155. };
  156. }
  157. catch (Exception ex)
  158. {
  159. throw Oops.Oh($"刷新数据失败: {ex.Message}");
  160. }
  161. }
  162. [DisplayName("执行计划联动刷新")]
  163. [HttpPost("linkageplan/run")]
  164. public async Task<object> RunLinkagePlanProcedure()
  165. {
  166. try
  167. {
  168. var tenantId = AidopTenantScope.ResolveOrThrow(_userManager);
  169. var pars = new List<SugarParameter> { new("@TenantId", tenantId) };
  170. // ── Step 0: 从 SQL Server 读取 InvTransHist / NbrMaster / NbrDetail,写入 MySQL 辅助表 ──
  171. await PrepareInvTransHistTempAsync();
  172. await PrepareNbrMasterTempAsync();
  173. await PrepareNbrDetailTempAsync();
  174. // ── Step 1: 清空全部数据 ──
  175. await _db.Ado.ExecuteCommandAsync("DELETE FROM LinkagePlan");
  176. // ── Step 2: 插入计划 + 实际数据(IGNORE 跳过重复主键) ──
  177. await _db.Ado.ExecuteCommandAsync("""
  178. INSERT IGNORE INTO LinkagePlan(
  179. id, bill_no, custom_no, order_type, item_number, Descr, Descr1, ItemType,
  180. qty, update_time, sys_capacity_date, bomstart, bomend, linestart, lineend,
  181. productstart, productend, needtime, sys_material_date, cgneedtime, cgend,
  182. blstart, blend, starttime, endtime, ipqcjystart, ipqcjyend, fqcjystart,
  183. fqcjyend, rkstart, rkend, fystarttime, fyendtime, `type`, levelnum, isuse, tenant_id
  184. )
  185. SELECT
  186. id, bill_no, custom_no, order_type, item_number, Descr, Descr1, ItemType,
  187. qty, update_time, sys_capacity_date, bomstart, bomend, linestart, lineend,
  188. productstart, productend, needtime, sys_material_date, cgneedtime, cgend,
  189. blstart, blend, starttime, endtime, ipqcjystart, ipqcjyend, fqcjystart,
  190. fqcjyend, rkstart, rkend, fystarttime, fyendtime, `type`, levelnum, isuse, @TenantId
  191. FROM (
  192. -- 计划数据
  193. SELECT
  194. b.id, a.bill_no, a.custom_no,
  195. IF(a.order_type='1', '销售', '计划') AS order_type,
  196. b.item_number, c.Descr, c.Descr1, c.ownerapplication AS ItemType,
  197. b.qty, h.create_time AS update_time, b.sys_capacity_date,
  198. h.create_time AS bomstart,
  199. DATE_ADD(h.create_time, INTERVAL IFNULL(c.BOMDesignTime, 0) DAY) AS bomend,
  200. DATE_ADD(h.create_time, INTERVAL IFNULL(c.BOMDesignTime, 0) DAY) AS linestart,
  201. DATE_ADD(h.create_time, INTERVAL (IFNULL(c.BOMDesignTime, 0) + IFNULL(c.RoutingDesignTime, 0)) DAY) AS lineend,
  202. DATE_ADD(h.create_time, INTERVAL (IFNULL(c.BOMDesignTime, 0) + IFNULL(c.RoutingDesignTime, 0)) DAY) AS productstart,
  203. DATE_ADD(h.create_time, INTERVAL (IFNULL(c.BOMDesignTime, 0) + IFNULL(c.RoutingDesignTime, 0) +
  204. CEILING(IFNULL(((b.qty-1)*d.maxruntime+d.runtime)/8, 0))) DAY) AS productend,
  205. h.create_time AS needtime, b.sys_material_date,
  206. h.create_time AS cgneedtime, e.kitting_time AS cgend,
  207. e.kitting_time AS blstart,
  208. DATE_ADD(e.kitting_time, INTERVAL IFNULL(c.mfgmttr, 0) DAY) AS blend,
  209. g.starttime, g.endtime,
  210. g.endtime AS ipqcjystart,
  211. DATE_ADD(g.endtime, INTERVAL IFNULL(c.InsLT, 0) DAY) AS ipqcjyend,
  212. g.endtime AS fqcjystart,
  213. DATE_ADD(g.endtime, INTERVAL IFNULL(c.InsLT, 0) DAY) AS fqcjyend,
  214. DATE_ADD(g.endtime, INTERVAL IFNULL(c.InsLT, 0) DAY) AS rkstart,
  215. DATE_ADD(g.endtime, INTERVAL (IFNULL(c.InsLT, 0) + 1) DAY) AS rkend,
  216. DATE_ADD(g.endtime, INTERVAL (IFNULL(c.InsLT, 0) + 1) DAY) AS fystarttime,
  217. DATE_ADD(g.endtime, INTERVAL (IFNULL(c.InsLT, 0) + 2) DAY) AS fyendtime,
  218. '计划' AS `type`, 2 AS levelnum, 0 AS isuse
  219. FROM crm_seorder a
  220. LEFT JOIN crm_seorderentry b ON a.bill_no = b.bill_no AND b.tenant_id = a.tenant_id
  221. LEFT JOIN ItemMaster c ON b.item_number = c.ItemNum AND (c.tenant_id = a.tenant_id OR a.tenant_id IS NULL)
  222. LEFT JOIN (
  223. SELECT RoutingCode, SUM(runtime) AS runtime, MAX(runtime) AS maxruntime
  224. FROM RoutingOpDetail GROUP BY RoutingCode
  225. ) d ON c.ItemNum = d.RoutingCode
  226. LEFT JOIN (
  227. SELECT sentry_id, MAX(kitting_time) AS kitting_time
  228. FROM (
  229. SELECT ar.sentry_id, bce.kitting_time,
  230. ROW_NUMBER() OVER (PARTITION BY ar.sentry_id ORDER BY ar.create_time DESC) AS rn
  231. FROM b_examine_result ar
  232. JOIN b_bom_child_examine bce ON ar.id = bce.examine_id
  233. WHERE ar.sorderid IS NOT NULL AND bce.erp_cls IN ('2','3')
  234. AND ar.tenant_id = @TenantId
  235. ) ex WHERE rn = 1
  236. GROUP BY sentry_id
  237. ) e ON b.id = e.sentry_id
  238. LEFT JOIN mes_moentry f ON b.id = f.soentry_id AND f.tenant_id = a.tenant_id
  239. LEFT JOIN mes_morder h ON f.moentry_mono = h.morder_no AND h.parent_id IS NULL AND h.tenant_id = a.tenant_id
  240. LEFT JOIN (
  241. SELECT workords, MIN(plandate) AS starttime, MAX(plandate) AS endtime
  242. FROM PeriodSequenceDet GROUP BY workords
  243. ) g ON f.moentry_mono = g.workords
  244. WHERE a.IsDeleted = 0 AND h.create_time IS NOT NULL
  245. AND a.tenant_id = @TenantId
  246. UNION ALL
  247. -- 实际数据(InvTransHist 从 SQL Server 临时表读取)
  248. SELECT
  249. b.id, a.bill_no, a.custom_no,
  250. IF(a.order_type='1', '销售', '计划') AS order_type,
  251. b.item_number, c.Descr, c.Descr1, c.ownerapplication AS ItemType,
  252. b.qty, h.create_time AS update_time, b.sys_capacity_date,
  253. h.create_time AS bomstart, nb.CreateTime AS bomend,
  254. h.create_time AS linestart, nb.CreateTime AS lineend,
  255. d.starttime AS productstart, d.endtime AS productend,
  256. h.create_time AS needtime, b.sys_material_date,
  257. e.cgneedtime, e.kitting_time AS cgend,
  258. nb.CreateTime AS blstart, nd.blend,
  259. g.starttime, g.endtime,
  260. e.FAPPLYTIME AS ipqcjystart, e.FINSPESTARTDATE AS ipqcjyend,
  261. g.endtime AS fqcjystart, fqc.fqcDate AS fqcjyend,
  262. i.starttime AS rkstart, i.endtime AS rkend,
  263. asn.starttime AS fystarttime, asn.endtime AS fyendtime,
  264. '实际' AS `type`, 1 AS levelnum, 0 AS isuse
  265. FROM crm_seorder a
  266. LEFT JOIN crm_seorderentry b ON a.bill_no = b.bill_no AND b.tenant_id = a.tenant_id
  267. LEFT JOIN ItemMaster c ON b.item_number = c.ItemNum AND (c.tenant_id = a.tenant_id OR a.tenant_id IS NULL)
  268. LEFT JOIN (
  269. SELECT pr.sentry_id,
  270. MAX(sh.createtime) AS kitting_time,
  271. MIN(jq.jqhf) AS cgneedtime,
  272. MIN(iqcjy.FINSPESTARTDATE) AS FINSPESTARTDATE,
  273. MAX(iqcjy.FAPPLYTIME) AS FAPPLYTIME
  274. FROM srm_pr_main pr
  275. LEFT JOIN PurOrdDetail pod ON pr.pr_billno = pod.Req AND pod.tenant_id = pr.tenant_id
  276. LEFT JOIN vscm_cgshrk sh ON pod.PurOrd = sh.OrdNbr AND pod.Line = sh.OrdLine
  277. LEFT JOIN scm_jhjh_jq jq ON pod.PurOrd = jq.cgdd AND pod.Line = jq.ddhh
  278. LEFT JOIN (
  279. SELECT f.po_bill, f.po_billline,
  280. MIN(a2.FINSPESTARTDATE) AS FINSPESTARTDATE,
  281. MAX(b2.FAPPLYTIME) AS FAPPLYTIME
  282. FROM qms_qcp_inspbill a2
  283. LEFT JOIN qms_qcp_inspecapplyn b2 ON a2.lydjbh = b2.FBILLNO
  284. LEFT JOIN qms_qcp_insappnentry c2 ON b2.id = c2.glid
  285. JOIN scm_shd cd ON c2.shdh = cd.shddh
  286. LEFT JOIN scm_shdzb f ON f.shpc = c2.FLOTNUMBER
  287. AND f.sh_material_code = c2.FMATERIALCFG
  288. AND f.glid = cd.id
  289. GROUP BY f.po_bill, f.po_billline
  290. ) iqcjy ON pod.PurOrd = iqcjy.po_bill AND pod.Line = iqcjy.po_billline
  291. WHERE jq.flag = '1' AND pr.sentry_id IS NOT NULL
  292. AND pr.tenant_id = @TenantId
  293. GROUP BY pr.sentry_id
  294. ) e ON e.sentry_id = b.id
  295. LEFT JOIN mes_moentry f ON b.id = f.soentry_id AND f.tenant_id = a.tenant_id
  296. LEFT JOIN mes_morder h ON f.moentry_mono = h.morder_no AND h.parent_id IS NULL AND h.tenant_id = a.tenant_id
  297. LEFT JOIN (
  298. SELECT workords, MIN(plandate) AS starttime, MAX(plandate) AS endtime
  299. FROM PeriodSequenceDet GROUP BY workords
  300. ) d ON h.morder_no = d.workords
  301. LEFT JOIN _tmp_nbr_master nb ON h.morder_no = nb.WorkOrd
  302. LEFT JOIN _tmp_nbr_detail nd ON h.morder_no = nd.WorkOrd
  303. LEFT JOIN (
  304. SELECT workords, MIN(plandate) AS starttime, MAX(plandate) AS endtime
  305. FROM PeriodSequenceDet WHERE CompQty > 0 GROUP BY workords
  306. ) g ON f.moentry_mono = g.workords
  307. LEFT JOIN _tmp_inv_trans_hist i ON h.morder_no = i.WorkOrd
  308. LEFT JOIN (
  309. SELECT ad.OrdNbr, ad.ContainerItem, ad.OrdLine,
  310. MIN(ad.CreateTime) AS starttime, MAX(ad.UpdateTime) AS endtime
  311. FROM ASNBOLShipperDetail ad
  312. INNER JOIN ASNBOLShipperMaster am ON ad.ASNBOLShipperRecID = am.RecID
  313. WHERE am.shtype = 'SH' AND am.typed <> 'S'
  314. GROUP BY ad.OrdNbr, ad.ContainerItem, ad.OrdLine
  315. ) asn ON b.bill_no = asn.OrdNbr AND b.item_number = asn.ContainerItem
  316. LEFT JOIN WorkOrdMaster wm ON h.morder_no = wm.WorkOrd AND wm.tenant_id = a.tenant_id
  317. LEFT JOIN (
  318. SELECT sczld, MAX(FINSPEENDDATE) AS fqcDate
  319. FROM qms_qcpp_inspbill GROUP BY sczld
  320. ) fqc ON wm.Batch = fqc.sczld
  321. WHERE a.IsDeleted = 0 AND h.create_time IS NOT NULL
  322. AND a.tenant_id = @TenantId
  323. ) AS viewall
  324. """, pars);
  325. // ── Step 3: 插入偏差数据 ──
  326. await _db.Ado.ExecuteCommandAsync("""
  327. INSERT IGNORE INTO LinkagePlan(
  328. id, bill_no, custom_no, order_type, item_number, Descr, Descr1, ItemType,
  329. qty, update_time, sys_capacity_date, bomstart, bomend, linestart, lineend,
  330. productstart, productend, needtime, sys_material_date, cgneedtime, cgend,
  331. blstart, blend, starttime, endtime, ipqcjystart, ipqcjyend, fqcjystart,
  332. fqcjyend, rkstart, rkend, fystarttime, fyendtime, `type`, levelnum, isuse, tenant_id
  333. )
  334. SELECT
  335. a.id, a.bill_no, a.custom_no, a.order_type, b.item_number,
  336. a.Descr, a.Descr1, a.ItemType, b.qty, a.update_time, b.sys_capacity_date,
  337. DATEDIFF(b.bomstart, a.bomstart), DATEDIFF(b.bomend, a.bomend),
  338. DATEDIFF(b.linestart, a.linestart), DATEDIFF(b.lineend, a.lineend),
  339. DATEDIFF(b.productstart, a.productstart), DATEDIFF(b.productend, a.productend),
  340. DATEDIFF(b.needtime, a.needtime), DATEDIFF(b.sys_material_date, a.sys_material_date),
  341. DATEDIFF(b.cgneedtime, a.cgneedtime), DATEDIFF(b.cgend, a.cgend),
  342. DATEDIFF(b.blstart, a.blstart), DATEDIFF(b.blend, a.blend),
  343. DATEDIFF(b.starttime, a.starttime), DATEDIFF(b.endtime, a.endtime),
  344. DATEDIFF(b.ipqcjystart, a.ipqcjystart), DATEDIFF(b.ipqcjyend, a.ipqcjyend),
  345. DATEDIFF(b.fqcjystart, a.fqcjystart), DATEDIFF(b.fqcjyend, a.fqcjyend),
  346. DATEDIFF(b.rkstart, a.rkstart), DATEDIFF(b.rkend, a.rkend),
  347. DATEDIFF(b.fystarttime, a.fystarttime), DATEDIFF(b.fyendtime, a.fyendtime),
  348. '偏差', 0, 0, @TenantId
  349. FROM (SELECT * FROM LinkagePlan WHERE `type` = '计划' AND tenant_id = @TenantId) a
  350. LEFT JOIN (SELECT * FROM LinkagePlan WHERE `type` = '实际' AND tenant_id = @TenantId) b ON a.id = b.id
  351. """, pars);
  352. // ── Step 4: 更新背景色标记 ──
  353. await _db.Ado.ExecuteCommandAsync("""
  354. UPDATE LinkagePlan a
  355. LEFT JOIN (
  356. SELECT a2.id, a2.`type`,
  357. CONCAT(
  358. CASE WHEN a2.`type` = '计划' THEN
  359. CASE WHEN a2.bomstart IS NOT NULL AND b2.bomstart IS NULL AND DATEDIFF(NOW(), a2.bomstart) > 7 THEN 'bomstart:red;'
  360. WHEN a2.bomstart IS NOT NULL AND b2.bomstart IS NULL AND DATEDIFF(NOW(), a2.bomstart) > 0 THEN 'bomstart:yellow;'
  361. ELSE '' END
  362. WHEN a2.`type` = '偏差' THEN
  363. CASE WHEN IFNULL(CAST(a2.bomstart AS SIGNED), 0) > 7 THEN 'bomstart:red;'
  364. WHEN IFNULL(CAST(a2.bomstart AS SIGNED), 0) > 0 THEN 'bomstart:yellow;'
  365. ELSE '' END
  366. ELSE '' END,
  367. CASE WHEN a2.`type` = '计划' THEN
  368. CASE WHEN a2.bomend IS NOT NULL AND b2.bomend IS NULL AND DATEDIFF(NOW(), a2.bomend) > 7 THEN 'bomend:red;'
  369. WHEN a2.bomend IS NOT NULL AND b2.bomend IS NULL AND DATEDIFF(NOW(), a2.bomend) > 0 THEN 'bomend:yellow;'
  370. ELSE '' END
  371. WHEN a2.`type` = '偏差' THEN
  372. CASE WHEN IFNULL(CAST(a2.bomend AS SIGNED), 0) > 7 THEN 'bomend:red;'
  373. WHEN IFNULL(CAST(a2.bomend AS SIGNED), 0) > 0 THEN 'bomend:yellow;'
  374. ELSE '' END
  375. ELSE '' END,
  376. CASE WHEN a2.`type` = '计划' THEN
  377. CASE WHEN a2.starttime IS NOT NULL AND b2.starttime IS NULL AND DATEDIFF(NOW(), a2.starttime) > 7 THEN 'starttime:red;'
  378. WHEN a2.starttime IS NOT NULL AND b2.starttime IS NULL AND DATEDIFF(NOW(), a2.starttime) > 0 THEN 'starttime:yellow;'
  379. ELSE '' END
  380. WHEN a2.`type` = '偏差' THEN
  381. CASE WHEN IFNULL(CAST(a2.starttime AS SIGNED), 0) > 7 THEN 'starttime:red;'
  382. WHEN IFNULL(CAST(a2.starttime AS SIGNED), 0) > 0 THEN 'starttime:yellow;'
  383. ELSE '' END
  384. ELSE '' END,
  385. CASE WHEN a2.`type` = '计划' THEN
  386. CASE WHEN a2.endtime IS NOT NULL AND b2.endtime IS NULL AND DATEDIFF(NOW(), a2.endtime) > 7 THEN 'endtime:red;'
  387. WHEN a2.endtime IS NOT NULL AND b2.endtime IS NULL AND DATEDIFF(NOW(), a2.endtime) > 0 THEN 'endtime:yellow;'
  388. ELSE '' END
  389. WHEN a2.`type` = '偏差' THEN
  390. CASE WHEN IFNULL(CAST(a2.endtime AS SIGNED), 0) > 7 THEN 'endtime:red;'
  391. WHEN IFNULL(CAST(a2.endtime AS SIGNED), 0) > 0 THEN 'endtime:yellow;'
  392. ELSE '' END
  393. ELSE '' END
  394. ) AS background
  395. FROM LinkagePlan a2
  396. LEFT JOIN (SELECT * FROM LinkagePlan WHERE `type` = '实际' AND tenant_id = @TenantId) b2 ON a2.id = b2.id
  397. LEFT JOIN (SELECT * FROM LinkagePlan WHERE `type` = '偏差' AND tenant_id = @TenantId) c2 ON a2.id = c2.id
  398. WHERE a2.tenant_id = @TenantId
  399. ) AS bg ON a.id = bg.id AND a.`type` = bg.`type`
  400. SET a.background = bg.background
  401. WHERE a.tenant_id = @TenantId
  402. """, pars);
  403. // ── 清理辅助表 ──
  404. await _db.Ado.ExecuteCommandAsync("DROP TABLE IF EXISTS _tmp_inv_trans_hist");
  405. await _db.Ado.ExecuteCommandAsync("DROP TABLE IF EXISTS _tmp_nbr_master");
  406. await _db.Ado.ExecuteCommandAsync("DROP TABLE IF EXISTS _tmp_nbr_detail");
  407. return new { message = "计划联动刷新成功" };
  408. }
  409. catch (Exception ex)
  410. {
  411. throw Oops.Oh($"执行计划联动刷新失败: {ex.Message}");
  412. }
  413. }
  414. /// <summary>
  415. /// 从 SQL Server 读取 InvTransHist,写入 MySQL 临时表 _tmp_inv_trans_hist。
  416. /// </summary>
  417. private async Task PrepareInvTransHistTempAsync()
  418. {
  419. await _db.Ado.ExecuteCommandAsync("DROP TABLE IF EXISTS _tmp_inv_trans_hist");
  420. await _db.Ado.ExecuteCommandAsync("""
  421. CREATE TABLE IF NOT EXISTS _tmp_inv_trans_hist (
  422. WorkOrd VARCHAR(64) PRIMARY KEY,
  423. starttime DATETIME NULL,
  424. endtime DATETIME NULL
  425. )
  426. """);
  427. var remote = await _scopeFactory.GetScopeAsync(SqlServerSourceCode);
  428. var rows = await remote.Ado.SqlQueryAsync<InvTransHistRow>("""
  429. SELECT WorkOrd,
  430. MIN(CreateTime) AS starttime,
  431. MAX(CreateTime) AS endtime
  432. FROM InvTransHist
  433. WHERE TransType = 'rct-wo' AND QtyChange > 0
  434. GROUP BY WorkOrd
  435. """);
  436. if (rows.Count == 0)
  437. return;
  438. // 分批 INSERT(每批 500 条)
  439. var insertPars = new List<SugarParameter>();
  440. for (var i = 0; i < rows.Count; i++)
  441. {
  442. var r = rows[i];
  443. if (string.IsNullOrWhiteSpace(r.WorkOrd)) continue;
  444. insertPars.Add(new SugarParameter($"@wo{i}", r.WorkOrd));
  445. insertPars.Add(new SugarParameter($"@st{i}", r.StartTime));
  446. insertPars.Add(new SugarParameter($"@et{i}", r.EndTime));
  447. }
  448. const int batchSize = 500;
  449. for (var offset = 0; offset < insertPars.Count; offset += batchSize * 3)
  450. {
  451. var batchEnd = Math.Min(offset + batchSize * 3, insertPars.Count);
  452. var batchPars = insertPars.GetRange(offset, batchEnd - offset);
  453. var batchValues = new List<string>();
  454. for (var j = offset; j < batchEnd; j += 3)
  455. batchValues.Add($"({insertPars[j].ParameterName}, {insertPars[j+1].ParameterName}, {insertPars[j+2].ParameterName})");
  456. await _db.Ado.ExecuteCommandAsync(
  457. $"INSERT INTO _tmp_inv_trans_hist (WorkOrd, starttime, endtime) VALUES {string.Join(",", batchValues)}",
  458. batchPars);
  459. }
  460. }
  461. private sealed class InvTransHistRow
  462. {
  463. public string? WorkOrd { get; set; }
  464. public DateTime? StartTime { get; set; }
  465. public DateTime? EndTime { get; set; }
  466. }
  467. /// <summary>
  468. /// 从 SQL Server 读取 NbrMaster (Type='SM'),写入 MySQL 临时表 _tmp_nbr_master。
  469. /// </summary>
  470. private async Task PrepareNbrMasterTempAsync()
  471. {
  472. await _db.Ado.ExecuteCommandAsync("DROP TABLE IF EXISTS _tmp_nbr_master");
  473. await _db.Ado.ExecuteCommandAsync("""
  474. CREATE TABLE IF NOT EXISTS _tmp_nbr_master (
  475. WorkOrd VARCHAR(64) PRIMARY KEY,
  476. CreateTime DATETIME NULL
  477. )
  478. """);
  479. var remote = await _scopeFactory.GetScopeAsync(SqlServerSourceCode);
  480. var rows = await remote.Ado.SqlQueryAsync<NbrMasterRow>("""
  481. SELECT WorkOrd, MIN(CreateTime) AS CreateTime
  482. FROM NbrMaster WHERE Type = 'SM'
  483. GROUP BY WorkOrd
  484. """);
  485. if (rows.Count == 0)
  486. return;
  487. var insertPars = new List<SugarParameter>();
  488. for (var i = 0; i < rows.Count; i++)
  489. {
  490. var r = rows[i];
  491. if (string.IsNullOrWhiteSpace(r.WorkOrd)) continue;
  492. insertPars.Add(new SugarParameter($"@wo{i}", r.WorkOrd));
  493. insertPars.Add(new SugarParameter($"@ct{i}", r.CreateTime));
  494. }
  495. const int batchSize = 500;
  496. for (var offset = 0; offset < insertPars.Count; offset += batchSize * 2)
  497. {
  498. var batchEnd = Math.Min(offset + batchSize * 2, insertPars.Count);
  499. var batchPars = insertPars.GetRange(offset, batchEnd - offset);
  500. var batchValues = new List<string>();
  501. for (var j = offset; j < batchEnd; j += 2)
  502. batchValues.Add($"({insertPars[j].ParameterName}, {insertPars[j+1].ParameterName})");
  503. await _db.Ado.ExecuteCommandAsync(
  504. $"INSERT INTO _tmp_nbr_master (WorkOrd, CreateTime) VALUES {string.Join(",", batchValues)}",
  505. batchPars);
  506. }
  507. }
  508. /// <summary>
  509. /// 从 SQL Server 读取 NbrDetail,写入 MySQL 临时表 _tmp_nbr_detail。
  510. /// </summary>
  511. private async Task PrepareNbrDetailTempAsync()
  512. {
  513. await _db.Ado.ExecuteCommandAsync("DROP TABLE IF EXISTS _tmp_nbr_detail");
  514. await _db.Ado.ExecuteCommandAsync("""
  515. CREATE TABLE IF NOT EXISTS _tmp_nbr_detail (
  516. WorkOrd VARCHAR(64) PRIMARY KEY,
  517. blend DATETIME NULL
  518. )
  519. """);
  520. var remote = await _scopeFactory.GetScopeAsync(SqlServerSourceCode);
  521. var rows = await remote.Ado.SqlQueryAsync<NbrDetailRow>("""
  522. SELECT WorkOrd, MIN(UpdateTime) AS blend
  523. FROM NbrDetail
  524. GROUP BY WorkOrd
  525. """);
  526. if (rows.Count == 0)
  527. return;
  528. var insertPars = new List<SugarParameter>();
  529. for (var i = 0; i < rows.Count; i++)
  530. {
  531. var r = rows[i];
  532. if (string.IsNullOrWhiteSpace(r.WorkOrd)) continue;
  533. insertPars.Add(new SugarParameter($"@wo{i}", r.WorkOrd));
  534. insertPars.Add(new SugarParameter($"@bl{i}", r.Blend));
  535. }
  536. const int batchSize = 500;
  537. for (var offset = 0; offset < insertPars.Count; offset += batchSize * 2)
  538. {
  539. var batchEnd = Math.Min(offset + batchSize * 2, insertPars.Count);
  540. var batchPars = insertPars.GetRange(offset, batchEnd - offset);
  541. var batchValues = new List<string>();
  542. for (var j = offset; j < batchEnd; j += 2)
  543. batchValues.Add($"({insertPars[j].ParameterName}, {insertPars[j+1].ParameterName})");
  544. await _db.Ado.ExecuteCommandAsync(
  545. $"INSERT INTO _tmp_nbr_detail (WorkOrd, blend) VALUES {string.Join(",", batchValues)}",
  546. batchPars);
  547. }
  548. }
  549. private sealed class NbrMasterRow
  550. {
  551. public string? WorkOrd { get; set; }
  552. public DateTime? CreateTime { get; set; }
  553. }
  554. private sealed class NbrDetailRow
  555. {
  556. public string? WorkOrd { get; set; }
  557. public DateTime? Blend { get; set; }
  558. }
  559. }