S2MdpSyncTransformService.cs 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923
  1. using Admin.NET.Plugin.AiDOP.SmartOps;
  2. namespace Admin.NET.Plugin.AiDOP.Production;
  3. /// <summary>
  4. /// S2 生产排程 MDP 同步、标准化、DWD 与 KPI 计算服务。
  5. /// </summary>
  6. public class S2MdpSyncTransformService : ITransient
  7. {
  8. private const string JobCode = "S2_MDP_SYNC_TRANSFORM";
  9. private readonly ISqlSugarClient _db;
  10. private readonly SmartOpsKpiAtomicBuildService _atomicBuild;
  11. public S2MdpSyncTransformService(ISqlSugarClient db, SmartOpsKpiAtomicBuildService atomicBuild)
  12. {
  13. _db = db;
  14. _atomicBuild = atomicBuild;
  15. }
  16. public async Task<S2MdpSyncTransformResult> RunFullAsync(CancellationToken cancellationToken = default, string triggerType = "AUTO")
  17. {
  18. cancellationToken.ThrowIfCancellationRequested();
  19. var now = DateTime.Now;
  20. var batchId = $"S2_MDP_FULL_{now:yyyyMMddHHmmss}";
  21. var runLogId = await InsertTransformRunLogAsync(batchId, now, triggerType);
  22. var result = new S2MdpSyncTransformResult { BatchId = batchId, RunLogId = runLogId };
  23. try
  24. {
  25. await EnsureS2RuntimeObjectsAsync();
  26. result.StageRows = await SyncStagingAsync(batchId, now, cancellationToken);
  27. result.StandardRows = await TransformStandardAsync(batchId, now, cancellationToken);
  28. result.DwdRows = await BuildDwdAsync(batchId, now, cancellationToken);
  29. result.KpiRows = await BuildS2KpiValuesAsync(batchId, now, cancellationToken);
  30. result.AtomicRows = await _atomicBuild.BuildWorkScheduleDomainForAllDatesAsync(batchId, cancellationToken: cancellationToken);
  31. await MarkTransformRunSuccessAsync(runLogId, now, result);
  32. return result;
  33. }
  34. catch (Exception ex)
  35. {
  36. await MarkTransformRunFailedAsync(runLogId, now, ex.Message);
  37. throw;
  38. }
  39. }
  40. private async Task EnsureS2RuntimeObjectsAsync()
  41. {
  42. foreach (var sql in S2MdpDdl.SqlBlocks)
  43. {
  44. await _db.Ado.ExecuteCommandAsync(sql);
  45. }
  46. }
  47. private async Task<int> SyncStagingAsync(string batchId, DateTime now, CancellationToken cancellationToken)
  48. {
  49. var total = 0;
  50. foreach (var entity in S2MdpEntityConfig.All)
  51. {
  52. cancellationToken.ThrowIfCancellationRequested();
  53. total += await SyncOneEntityAsync(entity, batchId, now);
  54. }
  55. return total;
  56. }
  57. private async Task<int> SyncOneEntityAsync(S2MdpEntityConfig entity, string batchId, DateTime now)
  58. {
  59. var entityRow = await _db.Ado.SqlQuerySingleAsync<S2MdpEntityRow>(
  60. "SELECT id AS Id, entity_name AS EntityName FROM mdp_entity WHERE tenant_id=0 AND entity_code=@EntityCode LIMIT 1",
  61. new SugarParameter("@EntityCode", entity.EntityCode));
  62. if (entityRow == null) throw Oops.Oh($"未找到 MDP 实体配置:{entity.EntityCode}");
  63. var columns = await _db.Ado.SqlQueryAsync<S2ColumnRow>(
  64. """
  65. SELECT COLUMN_NAME AS ColumnName
  66. FROM information_schema.COLUMNS
  67. WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=@TableName
  68. ORDER BY ORDINAL_POSITION
  69. """,
  70. new SugarParameter("@TableName", entity.SourceTable));
  71. if (columns.Count == 0) throw Oops.Oh($"未找到源表:{entity.SourceTable}");
  72. var names = columns.Select(u => u.ColumnName).ToList();
  73. var tenantExpr = names.Any(u => string.Equals(u, "tenant_id", StringComparison.OrdinalIgnoreCase))
  74. ? $"IFNULL(s.`{FindColumn(names, "tenant_id")}`,0)"
  75. : "0";
  76. var factoryExpr = names.Any(u => string.Equals(u, "factory_id", StringComparison.OrdinalIgnoreCase))
  77. ? $"s.`{FindColumn(names, "factory_id")}`"
  78. : "NULL";
  79. var domainExpr = names.Any(u => string.Equals(u, "Domain", StringComparison.OrdinalIgnoreCase))
  80. ? $"s.`{FindColumn(names, "Domain")}`"
  81. : "NULL";
  82. var sourceRowExpr = names.Any(u => string.Equals(u, entity.SourceRowIdExpression, StringComparison.OrdinalIgnoreCase))
  83. ? $"s.`{FindColumn(names, entity.SourceRowIdExpression)}`"
  84. : entity.SourceRowIdExpression;
  85. var rawDataExpr = BuildJsonObjectExpression(names);
  86. var rowsRead = await _db.Ado.GetIntAsync($"SELECT COUNT(1) FROM `{entity.SourceTable}`");
  87. var logId = await InsertSyncLogAsync(entityRow.Id, entityRow.EntityName, batchId, rowsRead);
  88. var started = DateTime.Now;
  89. try
  90. {
  91. var affected = await _db.Ado.ExecuteCommandAsync(
  92. $"""
  93. INSERT INTO `{entity.TargetTable}`
  94. (tenant_id, factory_id, source_system, source_table, source_row_id, source_biz_key, sync_batch_id, sync_time, process_status, raw_data)
  95. SELECT
  96. {tenantExpr},
  97. COALESCE({factoryExpr}, {domainExpr}),
  98. 'AIDOP',
  99. @SourceTable,
  100. CAST({sourceRowExpr} AS CHAR),
  101. CAST(COALESCE({entity.SourceBizKeyExpression}, CAST({sourceRowExpr} AS CHAR)) AS CHAR),
  102. @BatchId,
  103. @Now,
  104. 'PENDING',
  105. {rawDataExpr}
  106. FROM `{entity.SourceTable}` s
  107. ON DUPLICATE KEY UPDATE
  108. tenant_id=VALUES(tenant_id),
  109. factory_id=VALUES(factory_id),
  110. sync_batch_id=VALUES(sync_batch_id),
  111. sync_time=VALUES(sync_time),
  112. process_status=VALUES(process_status),
  113. raw_data=VALUES(raw_data),
  114. update_time=CURRENT_TIMESTAMP
  115. """,
  116. new SugarParameter("@SourceTable", entity.SourceTable),
  117. new SugarParameter("@BatchId", batchId),
  118. new SugarParameter("@Now", now));
  119. await MarkSyncLogSuccessAsync(logId, started, affected);
  120. return rowsRead;
  121. }
  122. catch (Exception ex)
  123. {
  124. await MarkSyncLogFailedAsync(logId, started, ex.Message);
  125. throw;
  126. }
  127. }
  128. private async Task<int> TransformStandardAsync(string batchId, DateTime now, CancellationToken cancellationToken)
  129. {
  130. var total = 0;
  131. foreach (var command in BuildStandardCommands(batchId, now))
  132. {
  133. cancellationToken.ThrowIfCancellationRequested();
  134. total += await _db.Ado.ExecuteCommandAsync(command.Sql, command.Parameters);
  135. }
  136. return total;
  137. }
  138. private async Task<int> BuildDwdAsync(string batchId, DateTime now, CancellationToken cancellationToken)
  139. {
  140. var total = 0;
  141. foreach (var command in BuildDwdCommands(batchId, now))
  142. {
  143. cancellationToken.ThrowIfCancellationRequested();
  144. total += await _db.Ado.ExecuteCommandAsync(command.Sql, command.Parameters);
  145. }
  146. return total;
  147. }
  148. private async Task<int> BuildS2KpiValuesAsync(string batchId, DateTime now, CancellationToken cancellationToken)
  149. {
  150. var rows = await CalculateS2KpiValuesAsync(batchId, now.Date);
  151. var affected = 0;
  152. foreach (var row in rows)
  153. {
  154. cancellationToken.ThrowIfCancellationRequested();
  155. affected += await UpsertS2KpiValueAsync(row, now.Date, now);
  156. }
  157. return affected;
  158. }
  159. private IEnumerable<S2MdpSqlCommand> BuildStandardCommands(string batchId, DateTime now)
  160. {
  161. yield return Cmd(
  162. """
  163. INSERT INTO mdp_std_work_order_schedule
  164. (tenant_id, factory_id, source_system, work_order, sales_order_no, item_code, item_name, site_code, status,
  165. priority, urgent_flag, qty_ordered, qty_completed, order_date, due_date, release_date, prod_line,
  166. source_biz_key, sync_batch_id, sync_time)
  167. SELECT tenant_id,
  168. CASE WHEN factory_id REGEXP '^[0-9]+$' THEN CAST(factory_id AS UNSIGNED) ELSE 1 END,
  169. 'AIDOP',
  170. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.WorkOrd')),
  171. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.SalesJob')),
  172. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.ItemNum')),
  173. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.ItemName')),
  174. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.Site')),
  175. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.Status')),
  176. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.Priority')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.Priority')) AS DECIMAL(18,6)) END,
  177. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.Urgent')) IN ('1','true','True') THEN 1 ELSE 0 END,
  178. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.QtyOrded')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.QtyOrded')) AS DECIMAL(18,6)) ELSE 0 END,
  179. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.QtyCompleted')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.QtyCompleted')) AS DECIMAL(18,6)) ELSE 0 END,
  180. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.OrdDate')), 'null'), ''),
  181. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.DueDate')), 'null'), ''),
  182. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.ReleaseDate')), 'null'), ''),
  183. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.ProdLine')),
  184. source_biz_key, @BatchId, @Now
  185. FROM mdp_stg_schedule
  186. WHERE source_table='WorkOrdMaster' AND IFNULL(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.WorkOrd')), '') <> ''
  187. ON DUPLICATE KEY UPDATE
  188. sales_order_no=VALUES(sales_order_no), item_code=VALUES(item_code), item_name=VALUES(item_name),
  189. site_code=VALUES(site_code), status=VALUES(status), priority=VALUES(priority), urgent_flag=VALUES(urgent_flag),
  190. qty_ordered=VALUES(qty_ordered), qty_completed=VALUES(qty_completed), order_date=VALUES(order_date),
  191. due_date=VALUES(due_date), release_date=VALUES(release_date), prod_line=VALUES(prod_line),
  192. sync_batch_id=VALUES(sync_batch_id), sync_time=VALUES(sync_time), update_time=CURRENT_TIMESTAMP
  193. """, batchId, now);
  194. yield return Cmd(
  195. """
  196. INSERT INTO mdp_std_operation_schedule
  197. (tenant_id, factory_id, source_system, work_order, op_no, work_center, line_code, item_code,
  198. plan_date, prod_date, start_time, end_time, ord_qty, comp_qty, run_crew, employee,
  199. source_biz_key, sync_batch_id, sync_time)
  200. SELECT tenant_id,
  201. CASE WHEN factory_id REGEXP '^[0-9]+$' THEN CAST(factory_id AS UNSIGNED) ELSE 1 END,
  202. 'AIDOP',
  203. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.WorkOrds')),
  204. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.Op')),
  205. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.WorkCtr')),
  206. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.Line')),
  207. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.ItemNum')),
  208. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.PlanDate')), 'null'), ''),
  209. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.ProdDate')), 'null'), ''),
  210. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.StartTime')), 'null'), ''),
  211. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.EndTime')), 'null'), ''),
  212. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.OrdQty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.OrdQty')) AS DECIMAL(18,6)) ELSE 0 END,
  213. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.CompQty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.CompQty')) AS DECIMAL(18,6)) ELSE 0 END,
  214. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.RunCrew')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.RunCrew')) AS DECIMAL(18,6)) END,
  215. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.Employee')),
  216. source_biz_key, @BatchId, @Now
  217. FROM mdp_stg_schedule
  218. WHERE source_table='PeriodSequenceDet' AND IFNULL(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.WorkOrds')), '') <> ''
  219. ON DUPLICATE KEY UPDATE
  220. work_center=VALUES(work_center), line_code=VALUES(line_code), item_code=VALUES(item_code),
  221. plan_date=VALUES(plan_date), prod_date=VALUES(prod_date), start_time=VALUES(start_time), end_time=VALUES(end_time),
  222. ord_qty=VALUES(ord_qty), comp_qty=VALUES(comp_qty), run_crew=VALUES(run_crew), employee=VALUES(employee),
  223. sync_batch_id=VALUES(sync_batch_id), sync_time=VALUES(sync_time), update_time=CURRENT_TIMESTAMP
  224. """, batchId, now);
  225. yield return Cmd(
  226. """
  227. INSERT INTO mdp_std_operation_schedule
  228. (tenant_id, factory_id, source_system, work_order, op_no, work_center, line_code, item_code,
  229. plan_date, prod_date, start_time, end_time, ord_qty, comp_qty, run_crew, employee,
  230. source_biz_key, sync_batch_id, sync_time)
  231. SELECT tenant_id,
  232. CASE WHEN factory_id REGEXP '^[0-9]+$' THEN CAST(factory_id AS UNSIGNED) ELSE 1 END,
  233. 'AIDOP',
  234. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.WorkOrd')),
  235. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.Op')),
  236. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.WorkCtr')),
  237. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.Line')),
  238. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.ItemNum')),
  239. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.WorkDate')), 'null'), ''),
  240. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.WorkDate')), 'null'), ''),
  241. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.WorkStartTime')), 'null'), ''),
  242. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.WorkEndTime')), 'null'), ''),
  243. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.WorkQty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.WorkQty')) AS DECIMAL(18,6)) ELSE 0 END,
  244. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.WorkQty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.WorkQty')) AS DECIMAL(18,6)) ELSE 0 END,
  245. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.AssignedPersonnelCount')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.AssignedPersonnelCount')) AS DECIMAL(18,6)) END,
  246. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.AssignedEmployeeID')),
  247. source_biz_key, @BatchId, @Now
  248. FROM mdp_stg_schedule
  249. WHERE source_table='ScheduleResultOpMaster' AND IFNULL(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.WorkOrd')), '') <> ''
  250. ON DUPLICATE KEY UPDATE
  251. work_center=VALUES(work_center), line_code=VALUES(line_code), item_code=VALUES(item_code),
  252. plan_date=VALUES(plan_date), prod_date=VALUES(prod_date), start_time=VALUES(start_time), end_time=VALUES(end_time),
  253. ord_qty=VALUES(ord_qty), comp_qty=VALUES(comp_qty), run_crew=VALUES(run_crew), employee=VALUES(employee),
  254. sync_batch_id=VALUES(sync_batch_id), sync_time=VALUES(sync_time), update_time=CURRENT_TIMESTAMP
  255. """, batchId, now);
  256. }
  257. private IEnumerable<S2MdpSqlCommand> BuildDwdCommands(string batchId, DateTime now)
  258. {
  259. yield return Cmd(
  260. """
  261. INSERT INTO dwd_order_schedule_trans
  262. (tenant_id, factory_id, stat_date, work_order, sales_order_no, item_code, item_name, site_code, prod_line,
  263. status, urgent_flag, qty_ordered, qty_completed, order_date, due_date, release_date, first_plan_date,
  264. last_plan_date, first_start_time, last_end_time, operation_count, scheduled_qty, completed_op_qty,
  265. schedule_cycle_days, schedule_satisfaction_flag, wip_qty, resource_person_count, calc_batch_id, calc_time)
  266. SELECT w.tenant_id, COALESCE(w.factory_id, 1), @StatDate,
  267. w.work_order, w.sales_order_no, w.item_code, w.item_name, w.site_code, w.prod_line,
  268. w.status, w.urgent_flag, w.qty_ordered, w.qty_completed, w.order_date, w.due_date, w.release_date,
  269. MIN(o.plan_date), MAX(o.plan_date), MIN(o.start_time), MAX(o.end_time),
  270. COUNT(o.id), SUM(IFNULL(o.ord_qty, 0)), SUM(IFNULL(o.comp_qty, 0)),
  271. CASE
  272. WHEN COALESCE(MIN(o.plan_date), MAX(o.plan_date)) IS NOT NULL
  273. AND COALESCE(w.release_date, w.order_date) IS NOT NULL
  274. THEN TIMESTAMPDIFF(HOUR, COALESCE(w.release_date, w.order_date), COALESCE(MAX(o.plan_date), MIN(o.plan_date))) / 24
  275. END,
  276. CASE
  277. WHEN w.due_date IS NOT NULL AND COALESCE(MAX(o.plan_date), MIN(o.plan_date)) IS NOT NULL
  278. AND DATE(COALESCE(MAX(o.plan_date), MIN(o.plan_date))) <= DATE(w.due_date) THEN 1
  279. ELSE 0
  280. END,
  281. GREATEST(IFNULL(w.qty_ordered, 0) - IFNULL(w.qty_completed, 0), 0),
  282. SUM(IFNULL(o.run_crew, 0)),
  283. @BatchId, @Now
  284. FROM mdp_std_work_order_schedule w
  285. LEFT JOIN mdp_std_operation_schedule o
  286. ON o.tenant_id=w.tenant_id AND IFNULL(o.work_order,'')=IFNULL(w.work_order,'')
  287. WHERE IFNULL(w.work_order, '') <> ''
  288. GROUP BY w.tenant_id, COALESCE(w.factory_id, 1), w.work_order, w.sales_order_no, w.item_code, w.item_name,
  289. w.site_code, w.prod_line, w.status, w.urgent_flag, w.qty_ordered, w.qty_completed,
  290. w.order_date, w.due_date, w.release_date
  291. ON DUPLICATE KEY UPDATE
  292. sales_order_no=VALUES(sales_order_no), item_code=VALUES(item_code), item_name=VALUES(item_name),
  293. site_code=VALUES(site_code), prod_line=VALUES(prod_line), status=VALUES(status), urgent_flag=VALUES(urgent_flag),
  294. qty_ordered=VALUES(qty_ordered), qty_completed=VALUES(qty_completed), order_date=VALUES(order_date),
  295. due_date=VALUES(due_date), release_date=VALUES(release_date), first_plan_date=VALUES(first_plan_date),
  296. last_plan_date=VALUES(last_plan_date), first_start_time=VALUES(first_start_time), last_end_time=VALUES(last_end_time),
  297. operation_count=VALUES(operation_count), scheduled_qty=VALUES(scheduled_qty), completed_op_qty=VALUES(completed_op_qty),
  298. schedule_cycle_days=VALUES(schedule_cycle_days), schedule_satisfaction_flag=VALUES(schedule_satisfaction_flag),
  299. wip_qty=VALUES(wip_qty), resource_person_count=VALUES(resource_person_count), calc_time=VALUES(calc_time),
  300. update_time=CURRENT_TIMESTAMP
  301. """, batchId, now);
  302. }
  303. private async Task<List<S2KpiCalcRow>> CalculateS2KpiValuesAsync(string batchId, DateTime statDate)
  304. {
  305. return await _db.Ado.SqlQueryAsync<S2KpiCalcRow>(
  306. """
  307. SELECT tenant_id AS TenantId, factory_id AS FactoryId, 'S2_L1_001' AS MetricCode,
  308. ROUND(AVG(schedule_cycle_days), 4) AS MetricValue
  309. FROM dwd_order_schedule_trans
  310. WHERE calc_batch_id=@BatchId AND schedule_cycle_days IS NOT NULL AND schedule_cycle_days >= 0
  311. GROUP BY tenant_id, factory_id
  312. UNION ALL
  313. SELECT tenant_id AS TenantId, factory_id AS FactoryId, 'S2_L1_002' AS MetricCode,
  314. ROUND(100 * SUM(schedule_satisfaction_flag) / NULLIF(COUNT(1), 0), 4) AS MetricValue
  315. FROM dwd_order_schedule_trans
  316. WHERE calc_batch_id=@BatchId
  317. GROUP BY tenant_id, factory_id
  318. UNION ALL
  319. SELECT tenant_id AS TenantId, factory_id AS FactoryId, 'S2_L1_003' AS MetricCode,
  320. ROUND(COUNT(1) / NULLIF(SUM(CASE WHEN IFNULL(resource_person_count, 0) > 0 THEN resource_person_count ELSE 1 END), 0), 4) AS MetricValue
  321. FROM dwd_order_schedule_trans
  322. WHERE calc_batch_id=@BatchId
  323. GROUP BY tenant_id, factory_id
  324. UNION ALL
  325. SELECT tenant_id AS TenantId, factory_id AS FactoryId, 'S2_L1_004' AS MetricCode,
  326. ROUND(SUM(wip_qty) / NULLIF(SUM(CASE WHEN qty_completed > 0 THEN qty_completed ELSE completed_op_qty END), 0) * 30, 4) AS MetricValue
  327. FROM dwd_order_schedule_trans
  328. WHERE calc_batch_id=@BatchId
  329. GROUP BY tenant_id, factory_id
  330. UNION ALL
  331. SELECT tenant_id AS TenantId, factory_id AS FactoryId, 'S2_L2_001' AS MetricCode,
  332. ROUND(AVG(schedule_cycle_days), 4) AS MetricValue
  333. FROM dwd_order_schedule_trans
  334. WHERE calc_batch_id=@BatchId AND schedule_cycle_days IS NOT NULL AND schedule_cycle_days >= 0
  335. GROUP BY tenant_id, factory_id
  336. UNION ALL
  337. SELECT tenant_id AS TenantId, factory_id AS FactoryId, 'S2_L2_002' AS MetricCode,
  338. ROUND(100 * SUM(schedule_satisfaction_flag) / NULLIF(COUNT(1), 0), 4) AS MetricValue
  339. FROM dwd_order_schedule_trans
  340. WHERE calc_batch_id=@BatchId
  341. GROUP BY tenant_id, factory_id
  342. UNION ALL
  343. SELECT tenant_id AS TenantId, factory_id AS FactoryId, 'S2_L2_003' AS MetricCode,
  344. ROUND(COUNT(1) / NULLIF(SUM(CASE WHEN IFNULL(resource_person_count, 0) > 0 THEN resource_person_count ELSE 1 END), 0), 4) AS MetricValue
  345. FROM dwd_order_schedule_trans
  346. WHERE calc_batch_id=@BatchId
  347. GROUP BY tenant_id, factory_id
  348. UNION ALL
  349. SELECT tenant_id AS TenantId, factory_id AS FactoryId, 'S2_L3_001' AS MetricCode,
  350. ROUND(AVG(
  351. CASE
  352. WHEN start_time IS NOT NULL AND end_time IS NOT NULL AND end_time >= start_time
  353. THEN TIMESTAMPDIFF(HOUR, start_time, end_time) / 24
  354. WHEN plan_date IS NOT NULL AND prod_date IS NOT NULL
  355. THEN ABS(TIMESTAMPDIFF(DAY, plan_date, prod_date))
  356. END
  357. ), 4) AS MetricValue
  358. FROM mdp_std_operation_schedule
  359. WHERE IFNULL(work_order, '') <> ''
  360. GROUP BY tenant_id, factory_id
  361. UNION ALL
  362. SELECT tenant_id AS TenantId, factory_id AS FactoryId, 'S2_L3_002' AS MetricCode,
  363. ROUND(100 * SUM(CASE WHEN plan_date IS NOT NULL AND (prod_date IS NULL OR prod_date >= plan_date) THEN 1 ELSE 0 END) / NULLIF(COUNT(1), 0), 4) AS MetricValue
  364. FROM mdp_std_operation_schedule
  365. WHERE IFNULL(work_order, '') <> ''
  366. GROUP BY tenant_id, factory_id
  367. UNION ALL
  368. SELECT tenant_id AS TenantId, factory_id AS FactoryId, 'S2_L3_003' AS MetricCode,
  369. ROUND(COUNT(1) / NULLIF(SUM(CASE WHEN IFNULL(run_crew, 0) > 0 THEN run_crew ELSE 1 END), 0), 4) AS MetricValue
  370. FROM mdp_std_operation_schedule
  371. WHERE IFNULL(work_order, '') <> ''
  372. GROUP BY tenant_id, factory_id
  373. UNION ALL
  374. SELECT tenant_id AS TenantId, factory_id AS FactoryId, 'S2_L3_004' AS MetricCode,
  375. ROUND(AVG(
  376. CASE
  377. WHEN start_time IS NOT NULL AND end_time IS NOT NULL AND end_time >= start_time
  378. THEN TIMESTAMPDIFF(HOUR, start_time, end_time) / 24
  379. WHEN plan_date IS NOT NULL AND prod_date IS NOT NULL
  380. THEN ABS(TIMESTAMPDIFF(DAY, plan_date, prod_date))
  381. END
  382. ), 4) AS MetricValue
  383. FROM mdp_std_operation_schedule
  384. WHERE IFNULL(COALESCE(NULLIF(work_center,''), NULLIF(line_code,'')), '') <> ''
  385. GROUP BY tenant_id, factory_id
  386. UNION ALL
  387. SELECT tenant_id AS TenantId, factory_id AS FactoryId, 'S2_L3_005' AS MetricCode,
  388. ROUND(100 * SUM(CASE WHEN plan_date IS NOT NULL AND (prod_date IS NULL OR prod_date >= plan_date) THEN 1 ELSE 0 END) / NULLIF(COUNT(1), 0), 4) AS MetricValue
  389. FROM mdp_std_operation_schedule
  390. WHERE IFNULL(COALESCE(NULLIF(work_center,''), NULLIF(line_code,'')), '') <> ''
  391. GROUP BY tenant_id, factory_id
  392. UNION ALL
  393. SELECT tenant_id AS TenantId, factory_id AS FactoryId, 'S2_L3_006' AS MetricCode,
  394. ROUND(COUNT(DISTINCT COALESCE(NULLIF(work_center,''), NULLIF(line_code,''), CONCAT(work_order, ':', op_no)))
  395. / NULLIF(SUM(CASE WHEN IFNULL(run_crew, 0) > 0 THEN run_crew ELSE 1 END), 0), 4) AS MetricValue
  396. FROM mdp_std_operation_schedule
  397. WHERE IFNULL(COALESCE(NULLIF(work_center,''), NULLIF(line_code,'')), '') <> ''
  398. GROUP BY tenant_id, factory_id
  399. """,
  400. new SugarParameter("@BatchId", batchId),
  401. new SugarParameter("@StatDate", statDate));
  402. }
  403. private async Task<int> UpsertS2KpiValueAsync(S2KpiCalcRow row, DateTime statDate, DateTime now)
  404. {
  405. if (row.MetricValue == null) return 0;
  406. var meta = await _db.Ado.SqlQuerySingleAsync<S2KpiMetaRow>(
  407. """
  408. SELECT MetricLevel, Direction, YellowThreshold, RedThreshold
  409. FROM ado_smart_ops_kpi_master
  410. WHERE TenantId=@TenantId AND ModuleCode='S2' AND MetricCode=@MetricCode AND IsEnabled=1
  411. LIMIT 1
  412. """,
  413. new SugarParameter("@TenantId", row.TenantId),
  414. new SugarParameter("@MetricCode", row.MetricCode));
  415. if (meta == null) return 0;
  416. var table = ResolveKpiValueTable(meta.MetricLevel);
  417. var current = await _db.Ado.SqlQuerySingleAsync<S2KpiValueRow>(
  418. $"""
  419. SELECT id AS Id, metric_value AS MetricValue, target_value AS TargetValue
  420. FROM {table}
  421. WHERE tenant_id=@TenantId AND factory_id=@FactoryId AND module_code='S2'
  422. AND metric_code=@MetricCode AND biz_date=@BizDate AND is_deleted=0
  423. ORDER BY id
  424. LIMIT 1
  425. """,
  426. new SugarParameter("@TenantId", row.TenantId),
  427. new SugarParameter("@FactoryId", row.FactoryId),
  428. new SugarParameter("@MetricCode", row.MetricCode),
  429. new SugarParameter("@BizDate", statDate));
  430. var prior = await _db.Ado.SqlQuerySingleAsync<S2KpiValueRow>(
  431. $"""
  432. SELECT id AS Id, metric_value AS MetricValue, target_value AS TargetValue
  433. FROM {table}
  434. WHERE tenant_id=@TenantId AND factory_id=@FactoryId AND module_code='S2'
  435. AND metric_code=@MetricCode AND biz_date<@BizDate AND is_deleted=0
  436. ORDER BY biz_date DESC, id DESC
  437. LIMIT 1
  438. """,
  439. new SugarParameter("@TenantId", row.TenantId),
  440. new SugarParameter("@FactoryId", row.FactoryId),
  441. new SugarParameter("@MetricCode", row.MetricCode),
  442. new SugarParameter("@BizDate", statDate));
  443. var actual = Math.Round(row.MetricValue.Value, 4);
  444. var target = current?.TargetValue ?? prior?.TargetValue ?? DefaultS2Target(row.MetricCode);
  445. var status = ResolveKpiStatus(actual, target, meta.Direction, meta.YellowThreshold, meta.RedThreshold);
  446. var trend = ResolveTrendFlag(actual, prior?.MetricValue);
  447. if (current != null)
  448. {
  449. return await _db.Ado.ExecuteCommandAsync(
  450. $"""
  451. UPDATE {table}
  452. SET metric_value=@MetricValue, target_value=@TargetValue, status_color=@StatusColor, trend_flag=@TrendFlag,
  453. is_active=1, status='ACTIVE', calc_time=@CalcTime, update_time=@CalcTime
  454. WHERE tenant_id=@TenantId AND factory_id=@FactoryId AND module_code='S2'
  455. AND metric_code=@MetricCode AND biz_date=@BizDate AND is_deleted=0
  456. """,
  457. new SugarParameter("@MetricValue", actual),
  458. new SugarParameter("@TargetValue", target),
  459. new SugarParameter("@StatusColor", status),
  460. new SugarParameter("@TrendFlag", trend),
  461. new SugarParameter("@CalcTime", now),
  462. new SugarParameter("@TenantId", row.TenantId),
  463. new SugarParameter("@FactoryId", row.FactoryId),
  464. new SugarParameter("@MetricCode", row.MetricCode),
  465. new SugarParameter("@BizDate", statDate));
  466. }
  467. var nextId = await _db.Ado.GetLongAsync($"SELECT COALESCE(MAX(id), 0) + 1 FROM {table}");
  468. return await _db.Ado.ExecuteCommandAsync(
  469. $"""
  470. INSERT INTO {table}
  471. (id, tenant_id, factory_id, status, biz_date, create_time, update_time, is_deleted, is_active,
  472. module_code, metric_code, metric_value, target_value, status_color, trend_flag, calc_time)
  473. VALUES
  474. (@Id, @TenantId, @FactoryId, 'ACTIVE', @BizDate, @CalcTime, @CalcTime, 0, 1,
  475. 'S2', @MetricCode, @MetricValue, @TargetValue, @StatusColor, @TrendFlag, @CalcTime)
  476. """,
  477. new SugarParameter("@Id", nextId),
  478. new SugarParameter("@TenantId", row.TenantId),
  479. new SugarParameter("@FactoryId", row.FactoryId),
  480. new SugarParameter("@BizDate", statDate),
  481. new SugarParameter("@CalcTime", now),
  482. new SugarParameter("@MetricCode", row.MetricCode),
  483. new SugarParameter("@MetricValue", actual),
  484. new SugarParameter("@TargetValue", target),
  485. new SugarParameter("@StatusColor", status),
  486. new SugarParameter("@TrendFlag", trend));
  487. }
  488. private async Task<long> InsertSyncLogAsync(long entityId, string entityName, string batchId, int rowsRead)
  489. {
  490. await _db.Ado.ExecuteCommandAsync(
  491. """
  492. INSERT INTO mdp_sync_log
  493. (tenant_id, entity_id, source_code, entity_name, sync_batch_id, sync_type, trigger_type, sync_start, rows_read, status)
  494. VALUES (0, @EntityId, 'AIDOPDEV_MYSQL', @EntityName, @BatchId, 'FULL', 'AUTO', NOW(), @RowsRead, 'RUNNING')
  495. """,
  496. new SugarParameter("@EntityId", entityId),
  497. new SugarParameter("@EntityName", entityName),
  498. new SugarParameter("@BatchId", batchId),
  499. new SugarParameter("@RowsRead", rowsRead));
  500. return await _db.Ado.GetLongAsync(
  501. "SELECT id FROM mdp_sync_log WHERE sync_batch_id=@BatchId AND entity_id=@EntityId ORDER BY id DESC LIMIT 1",
  502. new List<SugarParameter> { new("@BatchId", batchId), new("@EntityId", entityId) });
  503. }
  504. private async Task MarkSyncLogSuccessAsync(long logId, DateTime started, int affected)
  505. {
  506. await _db.Ado.ExecuteCommandAsync(
  507. """
  508. UPDATE mdp_sync_log
  509. SET sync_end=NOW(), duration_ms=@DurationMs, rows_insert=@RowsInsert, rows_update=0, rows_skip=0, rows_error=0, status='SUCCESS'
  510. WHERE id=@Id
  511. """,
  512. new SugarParameter("@DurationMs", (int)(DateTime.Now - started).TotalMilliseconds),
  513. new SugarParameter("@RowsInsert", affected),
  514. new SugarParameter("@Id", logId));
  515. }
  516. private async Task MarkSyncLogFailedAsync(long logId, DateTime started, string message)
  517. {
  518. try
  519. {
  520. await _db.Ado.ExecuteCommandAsync(
  521. """
  522. UPDATE mdp_sync_log
  523. SET sync_end=NOW(), duration_ms=@DurationMs, rows_error=1, status='FAILED', error_msg=@ErrorMsg
  524. WHERE id=@Id
  525. """,
  526. new SugarParameter("@DurationMs", (int)(DateTime.Now - started).TotalMilliseconds),
  527. new SugarParameter("@ErrorMsg", Truncate(message, 1000)),
  528. new SugarParameter("@Id", logId));
  529. }
  530. catch (Exception ex)
  531. {
  532. Console.Error.WriteLine($"[S2MdpSyncTransform] MarkSyncLogFailed write failed (syncLogId={logId}): {ex.Message}");
  533. }
  534. }
  535. private async Task<long> InsertTransformRunLogAsync(string batchId, DateTime startedAt, string triggerType)
  536. {
  537. await _db.Ado.ExecuteCommandAsync(
  538. """
  539. INSERT INTO mdp_transform_run_log
  540. (tenant_id, job_code, job_name, trigger_type, batch_id, status, start_time)
  541. VALUES (0, 'S2_MDP_SYNC_TRANSFORM', 'S2 MDP同步与KPI计算', @TriggerType, @BatchId, 'RUNNING', @StartTime)
  542. """,
  543. new SugarParameter("@TriggerType", NormalizeTriggerType(triggerType)),
  544. new SugarParameter("@BatchId", batchId),
  545. new SugarParameter("@StartTime", startedAt));
  546. return await _db.Ado.GetLongAsync(
  547. "SELECT id FROM mdp_transform_run_log WHERE batch_id=@BatchId ORDER BY id DESC LIMIT 1",
  548. new List<SugarParameter> { new("@BatchId", batchId) });
  549. }
  550. private async Task MarkTransformRunSuccessAsync(long runLogId, DateTime startedAt, S2MdpSyncTransformResult result)
  551. {
  552. var finishedAt = DateTime.Now;
  553. await _db.Ado.ExecuteCommandAsync(
  554. """
  555. UPDATE mdp_transform_run_log
  556. SET status='SUCCESS', end_time=@EndTime, duration_ms=@DurationMs,
  557. stage_rows=@StageRows, standard_rows=@StandardRows, dwd_rows=@DwdRows,
  558. summary_json=@SummaryJson, update_time=CURRENT_TIMESTAMP
  559. WHERE id=@Id
  560. """,
  561. new SugarParameter("@EndTime", finishedAt),
  562. new SugarParameter("@DurationMs", (int)(finishedAt - startedAt).TotalMilliseconds),
  563. new SugarParameter("@StageRows", result.StageRows),
  564. new SugarParameter("@StandardRows", result.StandardRows),
  565. new SugarParameter("@DwdRows", result.DwdRows),
  566. new SugarParameter("@SummaryJson", BuildRunSummaryJson(result)),
  567. new SugarParameter("@Id", runLogId));
  568. }
  569. private async Task MarkTransformRunFailedAsync(long runLogId, DateTime startedAt, string message)
  570. {
  571. try
  572. {
  573. var finishedAt = DateTime.Now;
  574. await _db.Ado.ExecuteCommandAsync(
  575. """
  576. UPDATE mdp_transform_run_log
  577. SET status='FAILED', end_time=@EndTime, duration_ms=@DurationMs,
  578. error_message=@ErrorMessage, update_time=CURRENT_TIMESTAMP
  579. WHERE id=@Id
  580. """,
  581. new SugarParameter("@EndTime", finishedAt),
  582. new SugarParameter("@DurationMs", (int)(finishedAt - startedAt).TotalMilliseconds),
  583. new SugarParameter("@ErrorMessage", Truncate(message, 2000)),
  584. new SugarParameter("@Id", runLogId));
  585. }
  586. catch (Exception ex)
  587. {
  588. Console.Error.WriteLine($"[S2MdpSyncTransform] MarkTransformRunFailed write failed (runLogId={runLogId}): {ex.Message}");
  589. }
  590. }
  591. private static S2MdpSqlCommand Cmd(string sql, string batchId, DateTime now)
  592. {
  593. return new S2MdpSqlCommand(sql, new[]
  594. {
  595. new SugarParameter("@BatchId", batchId),
  596. new SugarParameter("@Now", now),
  597. new SugarParameter("@StatDate", now.Date)
  598. });
  599. }
  600. private static string BuildJsonObjectExpression(IEnumerable<string> columns)
  601. {
  602. var parts = columns.SelectMany(c => new[] { $"'{c.Replace("'", "''")}'", $"s.`{c}`" });
  603. return $"JSON_OBJECT({string.Join(",", parts)})";
  604. }
  605. private static string FindColumn(IEnumerable<string> columns, string expected)
  606. {
  607. return columns.First(u => string.Equals(u, expected, StringComparison.OrdinalIgnoreCase));
  608. }
  609. private static string NormalizeTriggerType(string? triggerType)
  610. {
  611. return string.IsNullOrWhiteSpace(triggerType) ? "AUTO" : triggerType.Trim().ToUpperInvariant();
  612. }
  613. private static string BuildRunSummaryJson(S2MdpSyncTransformResult result)
  614. {
  615. return $$"""{"batchId":"{{result.BatchId}}","stageRows":{{result.StageRows}},"standardRows":{{result.StandardRows}},"dwdRows":{{result.DwdRows}},"kpiRows":{{result.KpiRows}}}""";
  616. }
  617. private static string ResolveKpiValueTable(int metricLevel)
  618. {
  619. return metricLevel switch
  620. {
  621. 1 => "ado_s9_kpi_value_l1_day",
  622. 2 => "ado_s9_kpi_value_l2_day",
  623. 3 => "ado_s9_kpi_value_l3_day",
  624. 4 => "ado_s9_kpi_value_l4_day",
  625. _ => "ado_s9_kpi_value_l2_day"
  626. };
  627. }
  628. private static decimal DefaultS2Target(string metricCode)
  629. {
  630. return metricCode switch
  631. {
  632. "S2_L1_001" => 20m,
  633. "S2_L1_002" => 99m,
  634. "S2_L1_003" => 20m,
  635. "S2_L1_004" => 18m,
  636. "S2_L2_001" => 1.2m,
  637. "S2_L2_002" => 95m,
  638. "S2_L2_003" => 20m,
  639. "S2_L3_001" => 3m,
  640. "S2_L3_002" => 95m,
  641. "S2_L3_003" => 60m,
  642. "S2_L3_004" => 3m,
  643. "S2_L3_005" => 95m,
  644. "S2_L3_006" => 60m,
  645. _ => 0m
  646. };
  647. }
  648. private static string ResolveKpiStatus(decimal actual, decimal target, string? direction, decimal? yellowThreshold, decimal? redThreshold)
  649. {
  650. if (target <= 0) return "gray";
  651. var ratio = actual / target * 100m;
  652. if (string.Equals(direction, "lower_is_better", StringComparison.OrdinalIgnoreCase))
  653. {
  654. if (actual <= target) return "green";
  655. if (ratio <= (yellowThreshold ?? 110m)) return "yellow";
  656. return ratio >= (redThreshold ?? 120m) ? "red" : "yellow";
  657. }
  658. if (actual >= target) return "green";
  659. if (ratio >= (yellowThreshold ?? 95m)) return "yellow";
  660. return ratio <= (redThreshold ?? 80m) ? "red" : "yellow";
  661. }
  662. private static string ResolveTrendFlag(decimal actual, decimal? previous)
  663. {
  664. if (previous == null) return "flat";
  665. if (actual > previous.Value) return "up";
  666. if (actual < previous.Value) return "down";
  667. return "flat";
  668. }
  669. private static string Truncate(string? raw, int maxLength)
  670. {
  671. if (string.IsNullOrEmpty(raw)) return string.Empty;
  672. return raw.Length <= maxLength ? raw : raw[..maxLength];
  673. }
  674. private sealed class S2ColumnRow
  675. {
  676. public string ColumnName { get; set; } = string.Empty;
  677. }
  678. private sealed class S2MdpEntityRow
  679. {
  680. public long Id { get; set; }
  681. public string EntityName { get; set; } = string.Empty;
  682. }
  683. private sealed class S2KpiCalcRow
  684. {
  685. public long TenantId { get; set; }
  686. public long FactoryId { get; set; }
  687. public string MetricCode { get; set; } = string.Empty;
  688. public decimal? MetricValue { get; set; }
  689. }
  690. private sealed class S2KpiMetaRow
  691. {
  692. public int MetricLevel { get; set; }
  693. public string Direction { get; set; } = "higher_is_better";
  694. public decimal? YellowThreshold { get; set; }
  695. public decimal? RedThreshold { get; set; }
  696. }
  697. private sealed class S2KpiValueRow
  698. {
  699. public long Id { get; set; }
  700. public decimal? MetricValue { get; set; }
  701. public decimal? TargetValue { get; set; }
  702. }
  703. }
  704. public sealed class S2MdpSyncTransformResult
  705. {
  706. public long RunLogId { get; set; }
  707. public string BatchId { get; set; } = string.Empty;
  708. public int StageRows { get; set; }
  709. public int StandardRows { get; set; }
  710. public int DwdRows { get; set; }
  711. public int KpiRows { get; set; }
  712. public int AtomicRows { get; set; }
  713. }
  714. internal sealed record S2MdpSqlCommand(string Sql, SugarParameter[] Parameters);
  715. internal sealed record S2MdpEntityConfig(
  716. string EntityCode,
  717. string SourceTable,
  718. string TargetTable,
  719. string SourceRowIdExpression,
  720. string SourceBizKeyExpression)
  721. {
  722. public static readonly IReadOnlyList<S2MdpEntityConfig> All = new List<S2MdpEntityConfig>
  723. {
  724. new("S2_WORK_ORDER_MASTER", "WorkOrdMaster", "mdp_stg_schedule", "RecID", "CONCAT(IFNULL(s.`Domain`,''), ':', IFNULL(s.`WorkOrd`,''))"),
  725. new("S2_WORK_ORDER_ROUTING", "WorkOrdRouting", "mdp_stg_schedule", "RecID", "CONCAT(IFNULL(s.`Domain`,''), ':', IFNULL(s.`WorkOrd`,''), ':', IFNULL(s.`OP`,''))"),
  726. new("S2_WORK_ORDER_DETAIL", "WorkOrdDetail", "mdp_stg_schedule", "RecID", "CONCAT(IFNULL(s.`Domain`,''), ':', IFNULL(s.`WorkOrd`,''), ':', IFNULL(s.`Op`,''), ':', IFNULL(s.`ItemNum`,''))"),
  727. new("S2_PERIOD_SEQUENCE_DET", "PeriodSequenceDet", "mdp_stg_schedule", "RecID", "CONCAT(IFNULL(s.`Domain`,''), ':', IFNULL(s.`WorkOrds`,''), ':', IFNULL(s.`Op`,''), ':', IFNULL(s.`Sequence`,''))"),
  728. new("S2_SCHEDULE_RESULT_OP", "ScheduleResultOpMaster", "mdp_stg_schedule", "RecID", "CONCAT(IFNULL(s.`Domain`,''), ':', IFNULL(s.`WorkOrd`,''), ':', IFNULL(s.`Op`,''), ':', IFNULL(s.`WorkDate`,''))")
  729. };
  730. }
  731. internal static class S2MdpDdl
  732. {
  733. public static readonly IReadOnlyList<string> SqlBlocks = new[]
  734. {
  735. """
  736. CREATE TABLE IF NOT EXISTS mdp_stg_schedule (
  737. id BIGINT AUTO_INCREMENT PRIMARY KEY,
  738. tenant_id BIGINT NOT NULL DEFAULT 0,
  739. factory_id VARCHAR(64) NULL,
  740. source_system VARCHAR(50) NOT NULL DEFAULT 'AIDOP',
  741. source_table VARCHAR(100) NOT NULL,
  742. source_row_id VARCHAR(100) NOT NULL,
  743. source_biz_key VARCHAR(200) NULL,
  744. sync_batch_id VARCHAR(100) NOT NULL,
  745. sync_time DATETIME NOT NULL,
  746. process_status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
  747. raw_data JSON NOT NULL,
  748. update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  749. UNIQUE KEY uk_mdp_stg_schedule (tenant_id, source_table, source_row_id),
  750. KEY idx_mdp_stg_schedule_batch (sync_batch_id),
  751. KEY idx_mdp_stg_schedule_biz (source_biz_key)
  752. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='S2生产排程贴源层';
  753. """,
  754. """
  755. CREATE TABLE IF NOT EXISTS mdp_std_work_order_schedule (
  756. id BIGINT AUTO_INCREMENT PRIMARY KEY,
  757. tenant_id BIGINT NOT NULL DEFAULT 0,
  758. factory_id BIGINT NULL,
  759. source_system VARCHAR(50) NOT NULL DEFAULT 'AIDOP',
  760. work_order VARCHAR(100) NOT NULL,
  761. sales_order_no VARCHAR(100) NULL,
  762. item_code VARCHAR(100) NULL,
  763. item_name VARCHAR(200) NULL,
  764. site_code VARCHAR(50) NULL,
  765. status VARCHAR(50) NULL,
  766. priority DECIMAL(18,6) NULL,
  767. urgent_flag TINYINT NOT NULL DEFAULT 0,
  768. qty_ordered DECIMAL(18,6) NULL,
  769. qty_completed DECIMAL(18,6) NULL,
  770. order_date DATETIME NULL,
  771. due_date DATETIME NULL,
  772. release_date DATETIME NULL,
  773. prod_line VARCHAR(100) NULL,
  774. source_biz_key VARCHAR(200) NULL,
  775. sync_batch_id VARCHAR(100) NOT NULL,
  776. sync_time DATETIME NOT NULL,
  777. update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  778. UNIQUE KEY uk_std_work_order_schedule (tenant_id, work_order),
  779. KEY idx_std_work_order_schedule_batch (sync_batch_id),
  780. KEY idx_std_work_order_schedule_due (tenant_id, due_date)
  781. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='S2标准工单排程';
  782. """,
  783. """
  784. CREATE TABLE IF NOT EXISTS mdp_std_operation_schedule (
  785. id BIGINT AUTO_INCREMENT PRIMARY KEY,
  786. tenant_id BIGINT NOT NULL DEFAULT 0,
  787. factory_id BIGINT NULL,
  788. source_system VARCHAR(50) NOT NULL DEFAULT 'AIDOP',
  789. work_order VARCHAR(100) NOT NULL,
  790. op_no VARCHAR(50) NULL,
  791. work_center VARCHAR(100) NULL,
  792. line_code VARCHAR(100) NULL,
  793. item_code VARCHAR(100) NULL,
  794. plan_date DATETIME NULL,
  795. prod_date DATETIME NULL,
  796. start_time DATETIME NULL,
  797. end_time DATETIME NULL,
  798. ord_qty DECIMAL(18,6) NULL,
  799. comp_qty DECIMAL(18,6) NULL,
  800. run_crew DECIMAL(18,6) NULL,
  801. employee VARCHAR(200) NULL,
  802. source_biz_key VARCHAR(200) NULL,
  803. sync_batch_id VARCHAR(100) NOT NULL,
  804. sync_time DATETIME NOT NULL,
  805. update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  806. UNIQUE KEY uk_std_operation_schedule (tenant_id, source_biz_key),
  807. KEY idx_std_operation_schedule_work_order (tenant_id, work_order),
  808. KEY idx_std_operation_schedule_batch (sync_batch_id)
  809. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='S2标准工序排程';
  810. """,
  811. """
  812. CREATE TABLE IF NOT EXISTS dwd_order_schedule_trans (
  813. id BIGINT AUTO_INCREMENT PRIMARY KEY,
  814. tenant_id BIGINT NOT NULL DEFAULT 0,
  815. factory_id BIGINT NOT NULL DEFAULT 1,
  816. stat_date DATE NOT NULL,
  817. work_order VARCHAR(100) NOT NULL,
  818. sales_order_no VARCHAR(100) NULL,
  819. item_code VARCHAR(100) NULL,
  820. item_name VARCHAR(200) NULL,
  821. site_code VARCHAR(50) NULL,
  822. prod_line VARCHAR(100) NULL,
  823. status VARCHAR(50) NULL,
  824. urgent_flag TINYINT NOT NULL DEFAULT 0,
  825. qty_ordered DECIMAL(18,6) NULL,
  826. qty_completed DECIMAL(18,6) NULL,
  827. order_date DATETIME NULL,
  828. due_date DATETIME NULL,
  829. release_date DATETIME NULL,
  830. first_plan_date DATETIME NULL,
  831. last_plan_date DATETIME NULL,
  832. first_start_time DATETIME NULL,
  833. last_end_time DATETIME NULL,
  834. operation_count INT NOT NULL DEFAULT 0,
  835. scheduled_qty DECIMAL(18,6) NULL,
  836. completed_op_qty DECIMAL(18,6) NULL,
  837. schedule_cycle_days DECIMAL(18,6) NULL,
  838. schedule_satisfaction_flag TINYINT NOT NULL DEFAULT 0,
  839. wip_qty DECIMAL(18,6) NULL,
  840. resource_person_count DECIMAL(18,6) NULL,
  841. calc_batch_id VARCHAR(100) NOT NULL,
  842. calc_time DATETIME NOT NULL,
  843. update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  844. UNIQUE KEY uk_dwd_order_schedule_trans (tenant_id, work_order, calc_batch_id),
  845. KEY idx_dwd_order_schedule_trans_batch (calc_batch_id),
  846. KEY idx_dwd_order_schedule_trans_stat (tenant_id, stat_date),
  847. KEY idx_dwd_order_schedule_trans_order (tenant_id, sales_order_no)
  848. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='S2订单工单排程DWD';
  849. """,
  850. """
  851. INSERT INTO mdp_entity
  852. (tenant_id, source_id, entity_code, entity_name, entity_type, source_table_name, target_table_name, sync_mode, batch_size, status, remark)
  853. SELECT 0, s.id, v.entity_code, v.entity_name, 'TABLE', v.source_table_name, 'mdp_stg_schedule', 'FULL', 5000, 1, v.remark
  854. FROM mdp_source s
  855. JOIN (
  856. SELECT 'S2_WORK_ORDER_MASTER' AS entity_code, 'S2工单主数据' AS entity_name, 'WorkOrdMaster' AS source_table_name, '工单主数据进入 S2 贴源层' AS remark
  857. UNION ALL SELECT 'S2_WORK_ORDER_ROUTING', 'S2工单工艺路线', 'WorkOrdRouting', '工单工艺路线进入 S2 贴源层'
  858. UNION ALL SELECT 'S2_WORK_ORDER_DETAIL', 'S2工单物料明细', 'WorkOrdDetail', '工单物料需求进入 S2 贴源层'
  859. UNION ALL SELECT 'S2_PERIOD_SEQUENCE_DET', 'S2工序排程计划', 'PeriodSequenceDet', '工序间衔接与排程计划进入 S2 贴源层'
  860. UNION ALL SELECT 'S2_SCHEDULE_RESULT_OP', 'S2工序排产结果', 'ScheduleResultOpMaster', '工序排产结果进入 S2 贴源层'
  861. ) v
  862. WHERE s.tenant_id=0 AND s.source_code='AIDOPDEV_MYSQL'
  863. ON DUPLICATE KEY UPDATE
  864. source_id=VALUES(source_id), entity_name=VALUES(entity_name), entity_type=VALUES(entity_type),
  865. source_table_name=VALUES(source_table_name), target_table_name=VALUES(target_table_name),
  866. sync_mode=VALUES(sync_mode), batch_size=VALUES(batch_size), status=VALUES(status),
  867. remark=VALUES(remark), update_time=CURRENT_TIMESTAMP;
  868. """
  869. };
  870. }