S1MdpSyncTransformService.cs 81 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354
  1. namespace Admin.NET.Plugin.AiDOP.Order;
  2. /// <summary>
  3. /// S1 首批 MDP 同步和标准化转换服务。
  4. /// </summary>
  5. public class S1MdpSyncTransformService : ITransient
  6. {
  7. private const string JobCode = "S1_MDP_SYNC_TRANSFORM";
  8. private readonly ISqlSugarClient _db;
  9. public S1MdpSyncTransformService(ISqlSugarClient db)
  10. {
  11. _db = db;
  12. }
  13. public async Task<S1MdpSyncTransformResult> RunFullAsync(CancellationToken cancellationToken = default, string triggerType = "AUTO")
  14. {
  15. cancellationToken.ThrowIfCancellationRequested();
  16. var now = DateTime.Now;
  17. var batchId = $"S1_MDP_FULL_{now:yyyyMMddHHmmss}";
  18. var runLogId = await InsertTransformRunLogAsync(batchId, now, triggerType);
  19. var result = new S1MdpSyncTransformResult { BatchId = batchId, RunLogId = runLogId };
  20. try
  21. {
  22. await EnsureS1RuntimeObjectsAsync();
  23. result.StageRows = await SyncStagingAsync(batchId, now, cancellationToken);
  24. result.StandardRows = await TransformStandardAsync(batchId, now, cancellationToken);
  25. result.DwdRows = await BuildDwdAsync(batchId, now, cancellationToken);
  26. result.KpiRows = await BuildS1KpiValuesAsync(batchId, now, cancellationToken);
  27. await MarkTransformRunSuccessAsync(runLogId, now, result);
  28. return result;
  29. }
  30. catch (Exception ex)
  31. {
  32. await MarkTransformRunFailedAsync(runLogId, now, ex.Message);
  33. throw;
  34. }
  35. }
  36. private async Task EnsureS1RuntimeObjectsAsync()
  37. {
  38. await _db.Ado.ExecuteCommandAsync(
  39. """
  40. CREATE TABLE IF NOT EXISTS dwd_requirement_examine_detail (
  41. id BIGINT AUTO_INCREMENT PRIMARY KEY,
  42. tenant_id BIGINT NOT NULL,
  43. factory_id BIGINT NULL,
  44. stat_date DATE NOT NULL,
  45. row_id BIGINT NOT NULL,
  46. parent_row_id BIGINT NULL,
  47. examine_id BIGINT NULL,
  48. order_entry_id BIGINT NULL,
  49. bill_no VARCHAR(100) NULL,
  50. morder_no VARCHAR(100) NULL,
  51. num VARCHAR(100) NULL,
  52. item_number VARCHAR(100) NULL,
  53. item_name VARCHAR(200) NULL,
  54. bom_number VARCHAR(100) NULL,
  55. model VARCHAR(200) NULL,
  56. kitting_time DATE NULL,
  57. item_type VARCHAR(50) NULL,
  58. erp_cls_name VARCHAR(80) NULL,
  59. qty DECIMAL(18,4) NULL,
  60. wastage DECIMAL(18,4) NULL,
  61. need_count DECIMAL(18,4) NULL,
  62. sqty DECIMAL(18,4) NULL,
  63. use_qty DECIMAL(18,4) NULL,
  64. self_lack_qty DECIMAL(18,4) NULL,
  65. lack_qty DECIMAL(18,4) NULL,
  66. mo_qty DECIMAL(18,4) NULL,
  67. make_qty DECIMAL(18,4) NULL,
  68. purchase_qty DECIMAL(18,4) NULL,
  69. purchase_occupy_qty DECIMAL(18,4) NULL,
  70. satisfy_time DATE NULL,
  71. have_ic_subs VARCHAR(10) NULL,
  72. substitute_code VARCHAR(100) NULL,
  73. create_time DATETIME NULL,
  74. source_system VARCHAR(50) NOT NULL DEFAULT 'AIDOP',
  75. sync_batch_id VARCHAR(100) NOT NULL,
  76. calc_batch_id VARCHAR(100) NOT NULL,
  77. calc_time DATETIME NOT NULL,
  78. update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  79. UNIQUE KEY uk_dwd_req_exam_detail (tenant_id, row_id, calc_batch_id),
  80. KEY idx_req_exam_tenant_batch (tenant_id, calc_batch_id),
  81. KEY idx_req_exam_bill (tenant_id, bill_no),
  82. KEY idx_req_exam_item (tenant_id, item_number)
  83. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='S1需求明细核验DWD';
  84. """);
  85. await _db.Ado.ExecuteCommandAsync(
  86. """
  87. INSERT INTO mdp_entity
  88. (tenant_id, source_id, entity_code, entity_name, entity_type, source_table_name, target_table_name, sync_mode, batch_size, status, remark)
  89. SELECT 0, s.id, 'S1_REQUIREMENT_EXAMINE_RESULT', 'S1需求核验结果主表', 'TABLE',
  90. 'b_examine_result', 'mdp_stg_so', 'FULL', 5000, 1, '需求核验结果主表,进入 S1 贴源层'
  91. FROM mdp_source s
  92. WHERE s.tenant_id=0 AND s.source_code='AIDOPDEV_MYSQL'
  93. LIMIT 1
  94. ON DUPLICATE KEY UPDATE
  95. source_id=VALUES(source_id), entity_name=VALUES(entity_name), source_table_name=VALUES(source_table_name),
  96. target_table_name=VALUES(target_table_name), sync_mode=VALUES(sync_mode), status=VALUES(status),
  97. remark=VALUES(remark), update_time=CURRENT_TIMESTAMP;
  98. """);
  99. await _db.Ado.ExecuteCommandAsync(
  100. """
  101. INSERT INTO mdp_entity
  102. (tenant_id, source_id, entity_code, entity_name, entity_type, source_table_name, target_table_name, sync_mode, batch_size, status, remark)
  103. SELECT 0, s.id, 'S1_REQUIREMENT_EXAMINE_DETAIL', 'S1需求核验BOM明细', 'TABLE',
  104. 'b_bom_child_examine', 'mdp_stg_so', 'FULL', 5000, 1, '需求核验BOM明细,进入 S1 贴源层和 DWD'
  105. FROM mdp_source s
  106. WHERE s.tenant_id=0 AND s.source_code='AIDOPDEV_MYSQL'
  107. LIMIT 1
  108. ON DUPLICATE KEY UPDATE
  109. source_id=VALUES(source_id), entity_name=VALUES(entity_name), source_table_name=VALUES(source_table_name),
  110. target_table_name=VALUES(target_table_name), sync_mode=VALUES(sync_mode), status=VALUES(status),
  111. remark=VALUES(remark), update_time=CURRENT_TIMESTAMP;
  112. """);
  113. await _db.Ado.ExecuteCommandAsync(
  114. """
  115. INSERT INTO mdp_entity
  116. (tenant_id, source_id, entity_code, entity_name, entity_type, source_table_name, target_table_name, sync_mode, incr_column, batch_size, status, remark)
  117. SELECT 0, s.id, v.entity_code, v.entity_name, 'TABLE', v.source_table_name, 'mdp_stg_so', 'INCR', 'UpdateTime', 5000, 1, v.remark
  118. FROM mdp_source s
  119. JOIN (
  120. SELECT 'S1_PRODUCT_DESIGN' AS entity_code, 'S1产品设计主表' AS entity_name, 'ado_product_design' AS source_table_name, '产品设计主表,进入订单标准层' AS remark
  121. UNION ALL SELECT 'S1_PRODUCT_DESIGN_BOM', 'S1产品设计BOM', 'ado_product_design_bom', '产品设计BOM,进入订单标准层'
  122. UNION ALL SELECT 'S1_PRODUCT_DESIGN_ROUTING', 'S1产品设计工艺路线', 'ado_product_design_routing', '产品设计工艺路线,进入订单标准层'
  123. ) v
  124. WHERE s.tenant_id=0 AND s.source_code='AIDOPDEV_MYSQL'
  125. ON DUPLICATE KEY UPDATE
  126. source_id=VALUES(source_id), entity_name=VALUES(entity_name), source_table_name=VALUES(source_table_name),
  127. target_table_name=VALUES(target_table_name), sync_mode=VALUES(sync_mode), incr_column=VALUES(incr_column),
  128. status=VALUES(status), remark=VALUES(remark), update_time=CURRENT_TIMESTAMP;
  129. """);
  130. }
  131. private async Task<int> SyncStagingAsync(string batchId, DateTime now, CancellationToken cancellationToken)
  132. {
  133. var total = 0;
  134. foreach (var entity in S1MdpEntityConfig.All)
  135. {
  136. cancellationToken.ThrowIfCancellationRequested();
  137. total += await SyncOneEntityAsync(entity, batchId, now);
  138. }
  139. return total;
  140. }
  141. private async Task<int> SyncOneEntityAsync(S1MdpEntityConfig entity, string batchId, DateTime now)
  142. {
  143. var entityRow = await _db.Ado.SqlQuerySingleAsync<S1MdpEntityRow>(
  144. "SELECT id AS Id, entity_name AS EntityName FROM mdp_entity WHERE tenant_id=0 AND entity_code=@EntityCode LIMIT 1",
  145. new SugarParameter("@EntityCode", entity.EntityCode));
  146. if (entityRow == null) throw Oops.Oh($"未找到 MDP 实体配置:{entity.EntityCode}");
  147. var columns = await _db.Ado.SqlQueryAsync<S1ColumnRow>(
  148. """
  149. SELECT COLUMN_NAME AS ColumnName
  150. FROM information_schema.COLUMNS
  151. WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME=@TableName
  152. ORDER BY ORDINAL_POSITION
  153. """,
  154. new SugarParameter("@TableName", entity.SourceTable));
  155. if (columns.Count == 0) throw Oops.Oh($"未找到源表:{entity.SourceTable}");
  156. var names = columns.Select(u => u.ColumnName).ToList();
  157. var tenantExpr = BuildOptionalColumnExpr(names, "tenant_id", "0");
  158. var factoryExpr = BuildOptionalColumnExpr(names, "factory_id", "NULL");
  159. var companyExpr = BuildOptionalColumnExpr(names, "company_id", "NULL");
  160. var sourceRowExpr = names.Any(u => string.Equals(u, entity.SourceRowIdExpression, StringComparison.OrdinalIgnoreCase))
  161. ? $"s.`{FindColumn(names, entity.SourceRowIdExpression)}`"
  162. : entity.SourceRowIdExpression;
  163. var rawDataExpr = BuildJsonObjectExpression(names);
  164. var rowsRead = await _db.Ado.GetIntAsync($"SELECT COUNT(1) FROM `{entity.SourceTable}`");
  165. var logId = await InsertSyncLogAsync(entityRow.Id, entityRow.EntityName, batchId, rowsRead);
  166. var started = DateTime.Now;
  167. try
  168. {
  169. var affected = await _db.Ado.ExecuteCommandAsync(
  170. $"""
  171. INSERT INTO `{entity.TargetTable}`
  172. (tenant_id, factory_id, company_id, source_system, source_table, source_row_id, source_biz_key, sync_batch_id, sync_time, process_status, raw_data)
  173. SELECT
  174. {tenantExpr},
  175. {factoryExpr},
  176. {companyExpr},
  177. 'AIDOP',
  178. @SourceTable,
  179. CAST({sourceRowExpr} AS CHAR),
  180. CAST(COALESCE({entity.SourceBizKeyExpression}, CAST({sourceRowExpr} AS CHAR)) AS CHAR),
  181. @BatchId,
  182. @Now,
  183. 'PENDING',
  184. {rawDataExpr}
  185. FROM `{entity.SourceTable}` s
  186. ON DUPLICATE KEY UPDATE
  187. tenant_id=VALUES(tenant_id),
  188. factory_id=VALUES(factory_id),
  189. company_id=VALUES(company_id),
  190. source_row_id=VALUES(source_row_id),
  191. sync_batch_id=VALUES(sync_batch_id),
  192. sync_time=VALUES(sync_time),
  193. process_status=VALUES(process_status),
  194. raw_data=VALUES(raw_data),
  195. update_time=CURRENT_TIMESTAMP
  196. """,
  197. new SugarParameter("@SourceTable", entity.SourceTable),
  198. new SugarParameter("@BatchId", batchId),
  199. new SugarParameter("@Now", now));
  200. await MarkSyncLogSuccessAsync(logId, started, affected);
  201. return rowsRead;
  202. }
  203. catch (Exception ex)
  204. {
  205. await MarkSyncLogFailedAsync(logId, started, ex.Message);
  206. throw;
  207. }
  208. }
  209. private async Task<int> TransformStandardAsync(string batchId, DateTime now, CancellationToken cancellationToken)
  210. {
  211. var total = 0;
  212. foreach (var command in BuildStandardCommands(batchId, now))
  213. {
  214. cancellationToken.ThrowIfCancellationRequested();
  215. total += await _db.Ado.ExecuteCommandAsync(command.Sql, command.Parameters);
  216. }
  217. return total;
  218. }
  219. private async Task<int> BuildDwdAsync(string batchId, DateTime now, CancellationToken cancellationToken)
  220. {
  221. var total = 0;
  222. foreach (var command in BuildDwdCommands(batchId, now))
  223. {
  224. cancellationToken.ThrowIfCancellationRequested();
  225. total += await _db.Ado.ExecuteCommandAsync(command.Sql, command.Parameters);
  226. }
  227. return total;
  228. }
  229. private async Task<int> BuildS1KpiValuesAsync(string batchId, DateTime now, CancellationToken cancellationToken)
  230. {
  231. var statDate = now.Date;
  232. var rows = await CalculateS1KpiValuesAsync(batchId, statDate);
  233. var affected = 0;
  234. foreach (var row in rows)
  235. {
  236. cancellationToken.ThrowIfCancellationRequested();
  237. affected += await UpsertS1KpiValueAsync(row, statDate, now);
  238. }
  239. return affected;
  240. }
  241. private async Task<List<S1KpiCalcRow>> CalculateS1KpiValuesAsync(string batchId, DateTime statDate)
  242. {
  243. return await _db.Ado.SqlQueryAsync<S1KpiCalcRow>(
  244. """
  245. SELECT tenant_id AS TenantId, COALESCE(NULLIF(factory_id, 0), 1) AS FactoryId,
  246. 'S1_L1_001' AS MetricCode,
  247. ROUND(AVG(TIMESTAMPDIFF(HOUR, order_date, COALESCE(promised_delivery_date, capacity_date, material_ready_date, plan_delivery_date)) / 24), 4) AS MetricValue
  248. FROM mdp_std_so
  249. WHERE order_date IS NOT NULL
  250. AND COALESCE(promised_delivery_date, capacity_date, material_ready_date, plan_delivery_date) IS NOT NULL
  251. AND COALESCE(promised_delivery_date, capacity_date, material_ready_date, plan_delivery_date) >= order_date
  252. GROUP BY tenant_id, COALESCE(NULLIF(factory_id, 0), 1)
  253. UNION ALL
  254. SELECT tenant_id AS TenantId, COALESCE(NULLIF(factory_id, 0), 1) AS FactoryId,
  255. 'S1_L1_002' AS MetricCode,
  256. ROUND(100 * SUM(CASE WHEN TIMESTAMPDIFF(HOUR, order_date, COALESCE(promised_delivery_date, capacity_date, material_ready_date, plan_delivery_date)) <= 72 THEN 1 ELSE 0 END) / COUNT(1), 4) AS MetricValue
  257. FROM mdp_std_so
  258. WHERE order_date IS NOT NULL
  259. AND COALESCE(promised_delivery_date, capacity_date, material_ready_date, plan_delivery_date) IS NOT NULL
  260. GROUP BY tenant_id, COALESCE(NULLIF(factory_id, 0), 1)
  261. UNION ALL
  262. SELECT tenant_id AS TenantId, COALESCE(NULLIF(factory_id, 0), 1) AS FactoryId,
  263. 'S1_L1_003' AS MetricCode,
  264. ROUND(COUNT(1) / GREATEST(COUNT(DISTINCT NULLIF(planner_no, '')), 1), 4) AS MetricValue
  265. FROM mdp_std_so
  266. WHERE order_date IS NOT NULL AND order_date <= @StatDate
  267. GROUP BY tenant_id, COALESCE(NULLIF(factory_id, 0), 1)
  268. UNION ALL
  269. SELECT tenant_id AS TenantId, 1 AS FactoryId,
  270. 'S1_L1_004' AS MetricCode,
  271. ROUND(AVG(GREATEST(IFNULL(remaining_qty, 0), 0)) / GREATEST(SUM(IFNULL(shipped_qty, 0)), 1) * 30, 4) AS MetricValue
  272. FROM dwd_ship_trans
  273. WHERE calc_batch_id=@BatchId
  274. GROUP BY tenant_id
  275. HAVING SUM(IFNULL(shipped_qty, 0)) > 0
  276. UNION ALL
  277. SELECT tenant_id AS TenantId, COALESCE(NULLIF(factory_id, 0), 1) AS FactoryId,
  278. 'S1_L2_010' AS MetricCode,
  279. ROUND(AVG(TIMESTAMPDIFF(HOUR, order_date, COALESCE(promised_delivery_date, capacity_date, material_ready_date, plan_delivery_date)) / 24), 4) AS MetricValue
  280. FROM mdp_std_so
  281. WHERE order_date IS NOT NULL
  282. AND COALESCE(promised_delivery_date, capacity_date, material_ready_date, plan_delivery_date) IS NOT NULL
  283. AND COALESCE(promised_delivery_date, capacity_date, material_ready_date, plan_delivery_date) >= order_date
  284. GROUP BY tenant_id, COALESCE(NULLIF(factory_id, 0), 1)
  285. UNION ALL
  286. SELECT tenant_id AS TenantId, COALESCE(NULLIF(factory_id, 0), 1) AS FactoryId,
  287. 'S1_L2_011' AS MetricCode,
  288. ROUND(100 * SUM(CASE WHEN TIMESTAMPDIFF(HOUR, order_date, COALESCE(promised_delivery_date, capacity_date, material_ready_date, plan_delivery_date)) <= 72 THEN 1 ELSE 0 END) / COUNT(1), 4) AS MetricValue
  289. FROM mdp_std_so
  290. WHERE order_date IS NOT NULL
  291. AND COALESCE(promised_delivery_date, capacity_date, material_ready_date, plan_delivery_date) IS NOT NULL
  292. GROUP BY tenant_id, COALESCE(NULLIF(factory_id, 0), 1)
  293. UNION ALL
  294. SELECT tenant_id AS TenantId, COALESCE(NULLIF(factory_id, 0), 1) AS FactoryId,
  295. 'S1_L2_012' AS MetricCode,
  296. ROUND(COUNT(1) / GREATEST(COUNT(DISTINCT NULLIF(planner_no, '')), 1), 4) AS MetricValue
  297. FROM mdp_std_so
  298. WHERE order_date IS NOT NULL AND order_date <= @StatDate
  299. GROUP BY tenant_id, COALESCE(NULLIF(factory_id, 0), 1)
  300. UNION ALL
  301. SELECT tenant_id AS TenantId, 1 AS FactoryId,
  302. 'S1_L2_013' AS MetricCode,
  303. ROUND(100 * SUM(CASE WHEN planned_ship_qty > 0 THEN 1 ELSE 0 END) / COUNT(1), 4) AS MetricValue
  304. FROM dwd_ship_trans
  305. WHERE calc_batch_id=@BatchId
  306. GROUP BY tenant_id
  307. UNION ALL
  308. SELECT tenant_id AS TenantId, 1 AS FactoryId,
  309. 'S1_L2_014' AS MetricCode,
  310. ROUND(100 * SUM(CASE WHEN shipped_qty >= planned_ship_qty AND planned_ship_qty > 0 THEN 1 ELSE 0 END)
  311. / GREATEST(SUM(CASE WHEN planned_ship_qty > 0 THEN 1 ELSE 0 END), 1), 4) AS MetricValue
  312. FROM dwd_ship_trans
  313. WHERE calc_batch_id=@BatchId
  314. GROUP BY tenant_id
  315. HAVING SUM(CASE WHEN planned_ship_qty > 0 THEN 1 ELSE 0 END) > 0
  316. UNION ALL
  317. SELECT tenant_id AS TenantId, 1 AS FactoryId,
  318. 'S1_L2_015' AS MetricCode,
  319. ROUND(100 * SUM(CASE WHEN linkage_status = 'LINKED' THEN 1 ELSE 0 END)
  320. / GREATEST(SUM(CASE WHEN planned_ship_qty > 0 THEN 1 ELSE 0 END), 1), 4) AS MetricValue
  321. FROM dwd_ship_trans
  322. WHERE calc_batch_id=@BatchId
  323. GROUP BY tenant_id
  324. HAVING SUM(CASE WHEN planned_ship_qty > 0 THEN 1 ELSE 0 END) > 0
  325. UNION ALL
  326. SELECT tenant_id AS TenantId, 1 AS FactoryId,
  327. 'S1_L2_001' AS MetricCode,
  328. ROUND(AVG(TIMESTAMPDIFF(HOUR, create_time, update_time) / 24), 4) AS MetricValue
  329. FROM (
  330. SELECT tenant_id,
  331. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.CreateTime')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f') AS create_time,
  332. COALESCE(
  333. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.UpdateTime')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f'),
  334. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.CreateTime')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f')
  335. ) AS update_time
  336. FROM mdp_stg_so
  337. WHERE source_table = 'ado_contract_review'
  338. ) t
  339. WHERE create_time IS NOT NULL AND update_time IS NOT NULL AND update_time >= create_time
  340. GROUP BY tenant_id
  341. UNION ALL
  342. SELECT tenant_id AS TenantId, 1 AS FactoryId,
  343. 'S1_L2_002' AS MetricCode,
  344. ROUND(100 * SUM(CASE WHEN TIMESTAMPDIFF(HOUR, create_time, update_time) <= 72 THEN 1 ELSE 0 END) / COUNT(1), 4) AS MetricValue
  345. FROM (
  346. SELECT tenant_id,
  347. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.CreateTime')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f') AS create_time,
  348. COALESCE(
  349. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.UpdateTime')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f'),
  350. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.CreateTime')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f')
  351. ) AS update_time
  352. FROM mdp_stg_so
  353. WHERE source_table = 'ado_contract_review'
  354. ) t
  355. WHERE create_time IS NOT NULL AND update_time IS NOT NULL AND update_time >= create_time
  356. GROUP BY tenant_id
  357. UNION ALL
  358. SELECT tenant_id AS TenantId, 1 AS FactoryId,
  359. 'S1_L2_003' AS MetricCode,
  360. ROUND(COUNT(1) / GREATEST(COUNT(DISTINCT NULLIF(owner_account, '')), 1), 4) AS MetricValue
  361. FROM (
  362. SELECT tenant_id,
  363. COALESCE(
  364. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.ResponsibleAccount')),
  365. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.CreateUser'))
  366. ) AS owner_account
  367. FROM mdp_stg_so
  368. WHERE source_table = 'ado_contract_review'
  369. ) t
  370. GROUP BY tenant_id
  371. UNION ALL
  372. SELECT tenant_id AS TenantId, 1 AS FactoryId,
  373. 'S1_L2_004' AS MetricCode,
  374. ROUND(AVG(cycle_hours / 24), 4) AS MetricValue
  375. FROM (
  376. SELECT tenant_id,
  377. COALESCE(
  378. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.DrawingDesignCycle')) REGEXP '^-?[0-9]+$'
  379. THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.DrawingDesignCycle')) AS DECIMAL(18,6)) END,
  380. TIMESTAMPDIFF(HOUR,
  381. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.DrawingActualStart')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f'),
  382. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.DrawingActualEnd')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f')
  383. )
  384. ) AS cycle_hours
  385. FROM mdp_stg_so
  386. WHERE source_table = 'ado_product_design'
  387. ) t
  388. WHERE cycle_hours IS NOT NULL AND cycle_hours >= 0
  389. GROUP BY tenant_id
  390. UNION ALL
  391. SELECT tenant_id AS TenantId, 1 AS FactoryId,
  392. 'S1_L2_005' AS MetricCode,
  393. ROUND(100 * SUM(CASE WHEN actual_end <= plan_end THEN 1 ELSE 0 END) / COUNT(1), 4) AS MetricValue
  394. FROM (
  395. SELECT tenant_id,
  396. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.DrawingPlanEnd')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f') AS plan_end,
  397. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.DrawingActualEnd')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f') AS actual_end
  398. FROM mdp_stg_so
  399. WHERE source_table = 'ado_product_design'
  400. ) t
  401. WHERE plan_end IS NOT NULL AND actual_end IS NOT NULL
  402. GROUP BY tenant_id
  403. UNION ALL
  404. SELECT tenant_id AS TenantId, 1 AS FactoryId,
  405. 'S1_L2_006' AS MetricCode,
  406. ROUND(COUNT(1) / GREATEST(COUNT(DISTINCT NULLIF(design_lead, '')), 1), 4) AS MetricValue
  407. FROM (
  408. SELECT tenant_id,
  409. COALESCE(
  410. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.DesignLeadAccount')),
  411. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.DesignLeadName')),
  412. JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.CreateUser'))
  413. ) AS design_lead
  414. FROM mdp_stg_so
  415. WHERE source_table = 'ado_product_design'
  416. ) t
  417. GROUP BY tenant_id
  418. UNION ALL
  419. SELECT tenant_id AS TenantId, 1 AS FactoryId,
  420. CASE stage_no
  421. WHEN 1 THEN 'S1_L3_001'
  422. WHEN 2 THEN 'S1_L3_002'
  423. WHEN 3 THEN 'S1_L3_003'
  424. WHEN 4 THEN 'S1_L3_004'
  425. WHEN 5 THEN 'S1_L3_005'
  426. END AS MetricCode,
  427. ROUND(AVG(cycle_hours), 4) AS MetricValue
  428. FROM (
  429. SELECT tenant_id,
  430. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.StageNo')) REGEXP '^-?[0-9]+$'
  431. THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.StageNo')) AS SIGNED) END AS stage_no,
  432. COALESCE(
  433. TIMESTAMPDIFF(HOUR,
  434. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.StartTime')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f'),
  435. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.CompleteTime')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f')
  436. ),
  437. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.ActualDays')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$'
  438. THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.ActualDays')) AS DECIMAL(18,6)) * 24 END
  439. ) AS cycle_hours
  440. FROM mdp_stg_so
  441. WHERE source_table = 'ado_contract_review_flow'
  442. ) t
  443. WHERE stage_no BETWEEN 1 AND 5
  444. AND cycle_hours IS NOT NULL AND cycle_hours >= 0
  445. GROUP BY tenant_id, stage_no
  446. UNION ALL
  447. SELECT tenant_id AS TenantId, 1 AS FactoryId,
  448. CASE stage_no
  449. WHEN 1 THEN 'S1_L3_101'
  450. WHEN 2 THEN 'S1_L3_102'
  451. WHEN 3 THEN 'S1_L3_103'
  452. WHEN 4 THEN 'S1_L3_104'
  453. WHEN 5 THEN 'S1_L3_105'
  454. END AS MetricCode,
  455. ROUND(AVG(cycle_hours), 4) AS MetricValue
  456. FROM (
  457. SELECT tenant_id,
  458. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.StageNo')) REGEXP '^-?[0-9]+$'
  459. THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.StageNo')) AS SIGNED) END AS stage_no,
  460. COALESCE(
  461. TIMESTAMPDIFF(HOUR,
  462. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.StartTime')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f'),
  463. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.CompleteTime')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f')
  464. ),
  465. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.ActualDays')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$'
  466. THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.ActualDays')) AS DECIMAL(18,6)) * 24 END
  467. ) AS cycle_hours
  468. FROM mdp_stg_so
  469. WHERE source_table = 'ado_contract_review_flow'
  470. ) t
  471. WHERE stage_no BETWEEN 1 AND 5
  472. AND cycle_hours IS NOT NULL AND cycle_hours >= 0
  473. GROUP BY tenant_id, stage_no
  474. UNION ALL
  475. SELECT tenant_id AS TenantId, 1 AS FactoryId,
  476. metric_code AS MetricCode,
  477. ROUND(AVG(cycle_hours), 4) AS MetricValue
  478. FROM (
  479. SELECT tenant_id,
  480. CASE COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.DeptNo')), ''), JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.Department')))
  481. WHEN 'LAW' THEN 'S1_L4_001'
  482. WHEN 'PRE_SALES' THEN 'S1_L4_002'
  483. WHEN 'MPS' THEN 'S1_L4_003'
  484. WHEN 'TEST' THEN 'S1_L4_004'
  485. WHEN '法律事务部' THEN 'S1_L4_001'
  486. WHEN '技术售前组' THEN 'S1_L4_002'
  487. WHEN '综合主计划' THEN 'S1_L4_003'
  488. WHEN '试验站' THEN 'S1_L4_004'
  489. END AS metric_code,
  490. COALESCE(
  491. TIMESTAMPDIFF(HOUR,
  492. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.StartTime')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f'),
  493. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.CompleteTime')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f')
  494. ),
  495. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.ActualDays')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$'
  496. THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.ActualDays')) AS DECIMAL(18,6)) * 24 END
  497. ) AS cycle_hours
  498. FROM mdp_stg_so
  499. WHERE source_table = 'ado_contract_review_flow'
  500. AND JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.StageNo')) = '1'
  501. ) t
  502. WHERE metric_code IS NOT NULL
  503. AND cycle_hours IS NOT NULL AND cycle_hours >= 0
  504. GROUP BY tenant_id, metric_code
  505. UNION ALL
  506. SELECT tenant_id AS TenantId, 1 AS FactoryId,
  507. metric_code AS MetricCode,
  508. ROUND(AVG(cycle_hours), 4) AS MetricValue
  509. FROM (
  510. SELECT tenant_id,
  511. CASE COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.DeptNo')), ''), JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.Department')))
  512. WHEN 'LAW' THEN 'S1_L4_101'
  513. WHEN 'PRE_SALES' THEN 'S1_L4_102'
  514. WHEN 'MPS' THEN 'S1_L4_103'
  515. WHEN 'TEST' THEN 'S1_L4_104'
  516. WHEN '法律事务部' THEN 'S1_L4_101'
  517. WHEN '技术售前组' THEN 'S1_L4_102'
  518. WHEN '综合主计划' THEN 'S1_L4_103'
  519. WHEN '试验站' THEN 'S1_L4_104'
  520. END AS metric_code,
  521. COALESCE(
  522. TIMESTAMPDIFF(HOUR,
  523. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.StartTime')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f'),
  524. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.CompleteTime')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f')
  525. ),
  526. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.ActualDays')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$'
  527. THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.ActualDays')) AS DECIMAL(18,6)) * 24 END
  528. ) AS cycle_hours
  529. FROM mdp_stg_so
  530. WHERE source_table = 'ado_contract_review_flow'
  531. AND JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.StageNo')) = '1'
  532. ) t
  533. WHERE metric_code IS NOT NULL
  534. AND cycle_hours IS NOT NULL AND cycle_hours >= 0
  535. GROUP BY tenant_id, metric_code
  536. """,
  537. new SugarParameter("@BatchId", batchId),
  538. new SugarParameter("@StatDate", statDate));
  539. }
  540. private async Task<int> UpsertS1KpiValueAsync(S1KpiCalcRow row, DateTime statDate, DateTime now)
  541. {
  542. var meta = await _db.Ado.SqlQuerySingleAsync<S1KpiMetaRow>(
  543. """
  544. SELECT MetricLevel, Direction, YellowThreshold, RedThreshold
  545. FROM ado_smart_ops_kpi_master
  546. WHERE TenantId=@TenantId AND ModuleCode='S1' AND MetricCode=@MetricCode AND IsEnabled=1
  547. LIMIT 1
  548. """,
  549. new SugarParameter("@TenantId", row.TenantId),
  550. new SugarParameter("@MetricCode", row.MetricCode));
  551. if (meta == null || row.MetricValue == null)
  552. return 0;
  553. var table = ResolveKpiValueTable(meta.MetricLevel);
  554. var current = await _db.Ado.SqlQuerySingleAsync<S1KpiValueRow>(
  555. $"""
  556. SELECT id AS Id, metric_value AS MetricValue, target_value AS TargetValue
  557. FROM {table}
  558. WHERE tenant_id=@TenantId AND factory_id=@FactoryId AND module_code='S1'
  559. AND metric_code=@MetricCode AND biz_date=@BizDate AND is_deleted=0
  560. ORDER BY id
  561. LIMIT 1
  562. """,
  563. new SugarParameter("@TenantId", row.TenantId),
  564. new SugarParameter("@FactoryId", row.FactoryId),
  565. new SugarParameter("@MetricCode", row.MetricCode),
  566. new SugarParameter("@BizDate", statDate));
  567. var prior = await _db.Ado.SqlQuerySingleAsync<S1KpiValueRow>(
  568. $"""
  569. SELECT id AS Id, metric_value AS MetricValue, target_value AS TargetValue
  570. FROM {table}
  571. WHERE tenant_id=@TenantId AND factory_id=@FactoryId AND module_code='S1'
  572. AND metric_code=@MetricCode AND biz_date<@BizDate AND is_deleted=0
  573. ORDER BY biz_date DESC, id DESC
  574. LIMIT 1
  575. """,
  576. new SugarParameter("@TenantId", row.TenantId),
  577. new SugarParameter("@FactoryId", row.FactoryId),
  578. new SugarParameter("@MetricCode", row.MetricCode),
  579. new SugarParameter("@BizDate", statDate));
  580. var actual = Math.Round(row.MetricValue.Value, 4);
  581. var target = current?.TargetValue ?? prior?.TargetValue ?? DefaultS1Target(row.MetricCode);
  582. var status = ResolveKpiStatus(actual, target, meta.Direction, meta.YellowThreshold, meta.RedThreshold);
  583. var trend = ResolveTrendFlag(actual, prior?.MetricValue);
  584. if (current != null)
  585. {
  586. return await _db.Ado.ExecuteCommandAsync(
  587. $"""
  588. UPDATE {table}
  589. SET metric_value=@MetricValue, target_value=@TargetValue, status_color=@StatusColor, trend_flag=@TrendFlag,
  590. is_active=1, status='ACTIVE', calc_time=@CalcTime, update_time=@CalcTime
  591. WHERE tenant_id=@TenantId AND factory_id=@FactoryId AND module_code='S1'
  592. AND metric_code=@MetricCode AND biz_date=@BizDate AND is_deleted=0
  593. """,
  594. new SugarParameter("@MetricValue", actual),
  595. new SugarParameter("@TargetValue", target),
  596. new SugarParameter("@StatusColor", status),
  597. new SugarParameter("@TrendFlag", trend),
  598. new SugarParameter("@CalcTime", now),
  599. new SugarParameter("@TenantId", row.TenantId),
  600. new SugarParameter("@FactoryId", row.FactoryId),
  601. new SugarParameter("@MetricCode", row.MetricCode),
  602. new SugarParameter("@BizDate", statDate));
  603. }
  604. var nextId = await _db.Ado.GetLongAsync($"SELECT COALESCE(MAX(id), 0) + 1 FROM {table}");
  605. return await _db.Ado.ExecuteCommandAsync(
  606. $"""
  607. INSERT INTO {table}
  608. (id, tenant_id, factory_id, status, biz_date, create_time, update_time, is_deleted, is_active,
  609. module_code, metric_code, metric_value, target_value, status_color, trend_flag, calc_time)
  610. VALUES
  611. (@Id, @TenantId, @FactoryId, 'ACTIVE', @BizDate, @CalcTime, @CalcTime, 0, 1,
  612. 'S1', @MetricCode, @MetricValue, @TargetValue, @StatusColor, @TrendFlag, @CalcTime)
  613. """,
  614. new SugarParameter("@Id", nextId),
  615. new SugarParameter("@TenantId", row.TenantId),
  616. new SugarParameter("@FactoryId", row.FactoryId),
  617. new SugarParameter("@BizDate", statDate),
  618. new SugarParameter("@CalcTime", now),
  619. new SugarParameter("@MetricCode", row.MetricCode),
  620. new SugarParameter("@MetricValue", actual),
  621. new SugarParameter("@TargetValue", target),
  622. new SugarParameter("@StatusColor", status),
  623. new SugarParameter("@TrendFlag", trend));
  624. }
  625. private IEnumerable<S1MdpSqlCommand> BuildStandardCommands(string batchId, DateTime now)
  626. {
  627. yield return Cmd(
  628. """
  629. INSERT INTO mdp_std_so
  630. (tenant_id, factory_id, company_id, source_system, order_id, order_entry_id, order_no, order_line, order_type,
  631. customer_id, customer_no, customer_name, customer_order_no, country, item_code, item_name, item_spec,
  632. map_number, map_name, bom_number, unit, order_qty, delivered_notice_qty, delivered_qty, price, tax_price,
  633. amount, total_amount, order_date, customer_request_date, plan_delivery_date, promised_delivery_date,
  634. capacity_date, material_ready_date, planner_no, planner_name, order_status, review_status, review_stage,
  635. flow_state, progress, urgent, closed, deleted_flag, source_table, source_row_id, source_biz_key, sync_batch_id, sync_time)
  636. SELECT
  637. COALESCE(e.tenant_id, h.tenant_id, 0),
  638. COALESCE(e.factory_id, h.factory_id),
  639. e.company_id,
  640. 'AIDOP',
  641. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.Id')) REGEXP '^-?[0-9]+$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.Id')) AS SIGNED) END,
  642. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.Id')) REGEXP '^-?[0-9]+$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.Id')) AS SIGNED) END,
  643. COALESCE(JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.bill_no')), JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.bill_no')), e.source_biz_key),
  644. CAST(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.entry_seq')), JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.Id'))) AS CHAR),
  645. CAST(JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.order_type')) AS CHAR),
  646. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.custom_id')) REGEXP '^-?[0-9]+$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.custom_id')) AS SIGNED) END,
  647. JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.custom_no')),
  648. JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.custom_name')),
  649. COALESCE(JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.custom_order_bill_no')), JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.bill_from'))),
  650. JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.country')),
  651. JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.item_number')),
  652. JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.item_name')),
  653. JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.specification')),
  654. JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.map_number')),
  655. JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.map_name')),
  656. JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.bom_number')),
  657. JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.unit')),
  658. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.qty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.qty')) AS DECIMAL(18,6)) END,
  659. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.deliver_notice_count')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.deliver_notice_count')) AS DECIMAL(18,6)) END,
  660. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.deliver_count')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.deliver_count')) AS DECIMAL(18,6)) END,
  661. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.price')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.price')) AS DECIMAL(18,6)) END,
  662. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.tax_price')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.tax_price')) AS DECIMAL(18,6)) END,
  663. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.amount')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.amount')) AS DECIMAL(18,6)) END,
  664. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.total_amount')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.total_amount')) AS DECIMAL(18,6)) END,
  665. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.date')), 'null'), ''),
  666. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.rdate')), 'null'), ''),
  667. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.plan_date')), 'null'), ''),
  668. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.date')), 'null'), ''),
  669. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.sys_capacity_date')), 'null'), ''),
  670. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.sys_material_date')), 'null'), ''),
  671. JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.planner_no')),
  672. JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.planner_name')),
  673. CASE WHEN JSON_EXTRACT(h.raw_data,'$.closed') IN (1, true) THEN 'CLOSED' ELSE 'OPEN' END,
  674. COALESCE(JSON_UNQUOTE(JSON_EXTRACT(r.raw_data,'$.FlowStatus')), JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.flowstate'))),
  675. COALESCE(JSON_UNQUOTE(JSON_EXTRACT(r.raw_data,'$.CurrentDept')), JSON_UNQUOTE(JSON_EXTRACT(r.raw_data,'$.CurrentStage'))),
  676. JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.flowstate')),
  677. JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.progress')),
  678. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(COALESCE(e.raw_data, h.raw_data),'$.urgent')) REGEXP '^-?[0-9]+$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(COALESCE(e.raw_data, h.raw_data),'$.urgent')) AS SIGNED) END,
  679. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.closed')) REGEXP '^-?[0-9]+$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.closed')) AS SIGNED) END,
  680. COALESCE(CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(COALESCE(e.raw_data, h.raw_data),'$.IsDeleted')) REGEXP '^-?[0-9]+$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(COALESCE(e.raw_data, h.raw_data),'$.IsDeleted')) AS SIGNED) END, 0),
  681. e.source_table,
  682. e.source_row_id,
  683. e.source_biz_key,
  684. @BatchId,
  685. @Now
  686. FROM mdp_stg_so e
  687. LEFT JOIN mdp_stg_so h ON h.source_table='crm_seorder'
  688. AND JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.Id')) = JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.seorder_id'))
  689. LEFT JOIN mdp_stg_so r ON r.source_table='ado_contract_review'
  690. AND JSON_UNQUOTE(JSON_EXTRACT(r.raw_data,'$.BillNo')) = COALESCE(JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.contract_no')), JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.bill_no')), JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.bill_no')))
  691. WHERE e.source_table='crm_seorderentry'
  692. AND IFNULL(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(e.raw_data,'$.bill_no')), JSON_UNQUOTE(JSON_EXTRACT(h.raw_data,'$.bill_no'))), '') <> ''
  693. ON DUPLICATE KEY UPDATE
  694. customer_no=VALUES(customer_no), customer_name=VALUES(customer_name), item_code=VALUES(item_code),
  695. item_name=VALUES(item_name), item_spec=VALUES(item_spec), order_qty=VALUES(order_qty),
  696. delivered_notice_qty=VALUES(delivered_notice_qty), delivered_qty=VALUES(delivered_qty),
  697. plan_delivery_date=VALUES(plan_delivery_date), promised_delivery_date=VALUES(promised_delivery_date),
  698. capacity_date=VALUES(capacity_date), material_ready_date=VALUES(material_ready_date),
  699. order_status=VALUES(order_status), review_status=VALUES(review_status), review_stage=VALUES(review_stage),
  700. flow_state=VALUES(flow_state), progress=VALUES(progress), deleted_flag=VALUES(deleted_flag),
  701. sync_batch_id=VALUES(sync_batch_id), sync_time=VALUES(sync_time), update_time=CURRENT_TIMESTAMP
  702. """, batchId, now);
  703. yield return Cmd(
  704. """
  705. INSERT INTO mdp_std_ship_trans
  706. (tenant_id, factory_id, company_id, source_system, trans_type, plan_id, plan_no, plan_line, order_id, order_entry_id,
  707. order_no, order_line, customer_no, customer_name, country, item_code, item_name, item_spec, qty, plan_qty,
  708. weight, volume, order_date, plan_ship_date, shipping_site, shipping_address, consignee, telephone,
  709. status, confirm_status, source_table, source_row_id, source_biz_key, sync_batch_id, sync_time)
  710. SELECT
  711. IFNULL(d.tenant_id, 0),
  712. d.factory_id,
  713. d.company_id,
  714. 'AIDOP',
  715. 'SHIP_PLAN',
  716. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.plan_id')) REGEXP '^-?[0-9]+$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.plan_id')) AS SIGNED) END,
  717. COALESCE(JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.LotSerial')), CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.plan_id')) AS CHAR)),
  718. CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.RecID')) AS CHAR),
  719. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.seorder_id')) REGEXP '^-?[0-9]+$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.seorder_id')) AS SIGNED) END,
  720. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.sentry_id')) REGEXP '^-?[0-9]+$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.sentry_id')) AS SIGNED) END,
  721. COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.bill_no')), JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.OrdNbr'))),
  722. CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.sentry_id')) AS CHAR),
  723. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.CustomNo')),
  724. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.CustomName')),
  725. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Country')),
  726. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.ItemNum')),
  727. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.ItemName')),
  728. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Specification')),
  729. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Qty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Qty')) AS DECIMAL(18,6)) END,
  730. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Qty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Qty')) AS DECIMAL(18,6)) END,
  731. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Weight')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Weight')) AS DECIMAL(18,6)) END,
  732. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Volume')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Volume')) AS DECIMAL(18,6)) END,
  733. NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.OrdDate')), 'null'), ''),
  734. NULLIF(NULLIF(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.ShippingDate')), JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.CreateTime'))), 'null'), ''),
  735. JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.ShippingSite')),
  736. JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.ShippingAddress')),
  737. JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.Consignee')),
  738. JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.Telephone')),
  739. COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Status')), JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.Status'))),
  740. CAST(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.IsConfirm')), JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.IsConfirm'))) AS CHAR),
  741. d.source_table,
  742. d.source_row_id,
  743. d.source_biz_key,
  744. @BatchId,
  745. @Now
  746. FROM mdp_stg_ship_trans d
  747. LEFT JOIN mdp_stg_ship_trans m ON m.source_table='ShippingPlan'
  748. AND JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.RecID')) = JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.plan_id'))
  749. WHERE d.source_table='ShippingPlanDetail'
  750. AND IFNULL(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.bill_no')), JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.OrdNbr'))), '') <> ''
  751. ON DUPLICATE KEY UPDATE
  752. plan_no=VALUES(plan_no), order_no=VALUES(order_no), customer_no=VALUES(customer_no), customer_name=VALUES(customer_name),
  753. item_code=VALUES(item_code), item_name=VALUES(item_name), qty=VALUES(qty), plan_qty=VALUES(plan_qty),
  754. plan_ship_date=VALUES(plan_ship_date), shipping_site=VALUES(shipping_site), shipping_address=VALUES(shipping_address),
  755. status=VALUES(status), confirm_status=VALUES(confirm_status), sync_batch_id=VALUES(sync_batch_id),
  756. sync_time=VALUES(sync_time), update_time=CURRENT_TIMESTAMP
  757. """, batchId, now);
  758. yield return Cmd(
  759. """
  760. INSERT INTO mdp_std_ship_trans
  761. (tenant_id, factory_id, source_system, trans_type, shipper_rec_id, shipper_no, shipper_line, order_no, order_line,
  762. customer_no, item_code, item_name, qty_to_ship, picking_qty, real_qty, gross_weight, net_weight, volume,
  763. plan_ship_date, actual_ship_date, site, status, confirm_status, source_table, source_row_id, source_biz_key, sync_batch_id, sync_time)
  764. SELECT
  765. IFNULL(d.tenant_id, 0),
  766. d.factory_id,
  767. 'AIDOP',
  768. 'ASN_SHIPPER',
  769. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.ASNBOLShipperRecID')) REGEXP '^-?[0-9]+$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.ASNBOLShipperRecID')) AS SIGNED) END,
  770. COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Id')), JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.Id'))),
  771. CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Line')) AS CHAR),
  772. COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.OrdNbr')), JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.OrdNbr'))),
  773. CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.OrdLine')) AS CHAR),
  774. JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.SoldTo')),
  775. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.ContainerItem')),
  776. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Descr')),
  777. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.QtyToShip')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.QtyToShip')) AS DECIMAL(18,6)) END,
  778. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.PickingQty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.PickingQty')) AS DECIMAL(18,6)) END,
  779. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.RealQty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.RealQty')) AS DECIMAL(18,6)) END,
  780. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.GrossWeight')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.GrossWeight')) AS DECIMAL(18,6)) END,
  781. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.NetWeight')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.NetWeight')) AS DECIMAL(18,6)) END,
  782. CASE WHEN COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Volume')), JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.Volume'))) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Volume')), JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.Volume'))) AS DECIMAL(18,6)) END,
  783. NULLIF(NULLIF(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.ShipDate')), JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.ShipDate'))), 'null'), ''),
  784. NULLIF(NULLIF(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.ShipDate')), JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.ShipDate'))), 'null'), ''),
  785. COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Site')), JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.Site'))),
  786. COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Status')), JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.Status'))),
  787. CAST(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.IsConfirm')), JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.IsConfirm'))) AS CHAR),
  788. d.source_table,
  789. d.source_row_id,
  790. d.source_biz_key,
  791. @BatchId,
  792. @Now
  793. FROM mdp_stg_ship_trans d
  794. LEFT JOIN mdp_stg_ship_trans m ON m.source_table='ASNBOLShipperMaster'
  795. AND JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.RecID')) = JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.ASNBOLShipperRecID'))
  796. WHERE d.source_table='ASNBOLShipperDetail'
  797. AND IFNULL(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Id')), JSON_UNQUOTE(JSON_EXTRACT(m.raw_data,'$.Id'))), '') <> ''
  798. ON DUPLICATE KEY UPDATE
  799. shipper_no=VALUES(shipper_no), order_no=VALUES(order_no), order_line=VALUES(order_line),
  800. customer_no=VALUES(customer_no), item_code=VALUES(item_code), item_name=VALUES(item_name),
  801. qty_to_ship=VALUES(qty_to_ship), picking_qty=VALUES(picking_qty), real_qty=VALUES(real_qty),
  802. actual_ship_date=VALUES(actual_ship_date), site=VALUES(site), status=VALUES(status),
  803. confirm_status=VALUES(confirm_status), sync_batch_id=VALUES(sync_batch_id), sync_time=VALUES(sync_time),
  804. update_time=CURRENT_TIMESTAMP
  805. """, batchId, now);
  806. yield return Cmd(
  807. """
  808. INSERT INTO mdp_std_ship_trans
  809. (tenant_id, factory_id, source_system, trans_type, order_no, customer_no, item_code, item_name, qty,
  810. plan_ship_date, status, linkage_status, source_table, source_row_id, source_biz_key, sync_batch_id, sync_time)
  811. SELECT
  812. IFNULL(d.tenant_id, 0),
  813. d.factory_id,
  814. 'AIDOP',
  815. 'LINKAGE_PLAN',
  816. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.bill_no')),
  817. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.custom_no')),
  818. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.item_number')),
  819. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.Descr')),
  820. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.qty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.qty')) AS DECIMAL(18,6)) END,
  821. NULLIF(NULLIF(COALESCE(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.sys_capacity_date')), JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.fystarttime'))), 'null'), ''),
  822. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.type')),
  823. CASE
  824. WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.isuse')) = '1' THEN 'LINKED'
  825. ELSE 'INACTIVE'
  826. END,
  827. d.source_table,
  828. d.source_row_id,
  829. d.source_biz_key,
  830. @BatchId,
  831. @Now
  832. FROM mdp_stg_ship_trans d
  833. WHERE d.source_table='LinkagePlan'
  834. AND IFNULL(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.bill_no')), '') <> ''
  835. ON DUPLICATE KEY UPDATE
  836. order_no=VALUES(order_no), customer_no=VALUES(customer_no), item_code=VALUES(item_code),
  837. item_name=VALUES(item_name), qty=VALUES(qty), plan_ship_date=VALUES(plan_ship_date),
  838. status=VALUES(status), linkage_status=VALUES(linkage_status), sync_batch_id=VALUES(sync_batch_id),
  839. sync_time=VALUES(sync_time), update_time=CURRENT_TIMESTAMP
  840. """, batchId, now);
  841. }
  842. private IEnumerable<S1MdpSqlCommand> BuildDwdCommands(string batchId, DateTime now)
  843. {
  844. yield return Cmd(
  845. """
  846. INSERT INTO dwd_requirement_examine_detail
  847. (tenant_id, factory_id, stat_date, row_id, parent_row_id, examine_id, order_entry_id, bill_no, morder_no,
  848. num, item_number, item_name, bom_number, model, kitting_time, item_type, erp_cls_name, qty, wastage,
  849. need_count, sqty, use_qty, self_lack_qty, lack_qty, mo_qty, make_qty, purchase_qty, purchase_occupy_qty,
  850. satisfy_time, have_ic_subs, substitute_code, create_time, source_system, sync_batch_id, calc_batch_id, calc_time)
  851. SELECT
  852. COALESCE(d.tenant_id, r.tenant_id, so.tenant_id, 0),
  853. COALESCE(d.factory_id, r.factory_id, so.factory_id),
  854. @StatDate,
  855. CAST(d.source_row_id AS SIGNED),
  856. CASE
  857. WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.num')) = '1' THEN NULL
  858. ELSE p.parent_row_id
  859. END,
  860. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.examine_id')) REGEXP '^-?[0-9]+$'
  861. THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.examine_id')) AS SIGNED) END,
  862. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(r.raw_data,'$.sentry_id')) REGEXP '^-?[0-9]+$'
  863. THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(r.raw_data,'$.sentry_id')) AS SIGNED) END,
  864. COALESCE(so.order_no, JSON_UNQUOTE(JSON_EXTRACT(r.raw_data,'$.bill_no'))),
  865. JSON_UNQUOTE(JSON_EXTRACT(r.raw_data,'$.morder_no')),
  866. CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.num')) AS CHAR),
  867. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.item_number')),
  868. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.item_name')),
  869. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.bom_number')),
  870. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.model')),
  871. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.kitting_time')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f'),
  872. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.type')) = '1' THEN '替代件' ELSE '标准件' END,
  873. COALESCE(
  874. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.erp_cls_name')),
  875. CASE JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.erp_cls'))
  876. WHEN '0' THEN '配置类'
  877. WHEN '1' THEN '自制'
  878. WHEN '2' THEN '委外加工'
  879. WHEN '3' THEN '外购'
  880. WHEN '4' THEN '虚拟件'
  881. END
  882. ),
  883. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.qty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.qty')) AS DECIMAL(18,4)) END,
  884. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.wastage')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.wastage')) AS DECIMAL(18,4)) END,
  885. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.needCount')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.needCount')) AS DECIMAL(18,4)) END,
  886. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.sqty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.sqty')) AS DECIMAL(18,4)) END,
  887. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.use_qty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.use_qty')) AS DECIMAL(18,4)) END,
  888. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.self_lack_qty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.self_lack_qty')) AS DECIMAL(18,4)) END,
  889. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.lack_qty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.lack_qty')) AS DECIMAL(18,4)) END,
  890. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.mo_qty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.mo_qty')) AS DECIMAL(18,4)) END,
  891. CASE
  892. WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.make_qty')) = '0' AND JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.erp_cls')) = '1'
  893. THEN CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.lack_qty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.lack_qty')) AS DECIMAL(18,4)) END
  894. WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.make_qty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$'
  895. THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.make_qty')) AS DECIMAL(18,4))
  896. END,
  897. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.purchase_qty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.purchase_qty')) AS DECIMAL(18,4)) END,
  898. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.purchase_occupy_qty')) REGEXP '^-?[0-9]+(\\.[0-9]+)?$' THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.purchase_occupy_qty')) AS DECIMAL(18,4)) END,
  899. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.satisfy_time')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f'),
  900. CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.haveicsubs')) = '1' THEN '是' ELSE '否' END,
  901. JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.substitute_code')),
  902. STR_TO_DATE(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(r.raw_data,'$.create_time')), 'null'), ''), '%Y-%m-%d %H:%i:%s.%f'),
  903. 'AIDOP',
  904. d.sync_batch_id,
  905. @BatchId,
  906. @Now
  907. FROM mdp_stg_so d
  908. INNER JOIN mdp_stg_so r ON r.source_table='b_examine_result'
  909. AND r.source_row_id = JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.examine_id'))
  910. LEFT JOIN mdp_std_so so ON so.tenant_id = COALESCE(r.tenant_id, d.tenant_id)
  911. AND so.order_entry_id = CASE WHEN JSON_UNQUOTE(JSON_EXTRACT(r.raw_data,'$.sentry_id')) REGEXP '^-?[0-9]+$'
  912. THEN CAST(JSON_UNQUOTE(JSON_EXTRACT(r.raw_data,'$.sentry_id')) AS SIGNED) END
  913. LEFT JOIN (
  914. SELECT examine_id, MIN(source_row_id) AS parent_row_id
  915. FROM (
  916. SELECT JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.examine_id')) AS examine_id, CAST(source_row_id AS SIGNED) AS source_row_id
  917. FROM mdp_stg_so
  918. WHERE source_table='b_bom_child_examine'
  919. AND JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.num')) = '1'
  920. AND JSON_UNQUOTE(JSON_EXTRACT(raw_data,'$.is_use')) IN ('1', 'true', 'True', 'base64:type16:AQ==')
  921. ) x
  922. GROUP BY examine_id
  923. ) p ON p.examine_id = JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.examine_id'))
  924. WHERE d.source_table='b_bom_child_examine'
  925. AND JSON_UNQUOTE(JSON_EXTRACT(d.raw_data,'$.is_use')) IN ('1', 'true', 'True', 'base64:type16:AQ==')
  926. ON DUPLICATE KEY UPDATE
  927. parent_row_id=VALUES(parent_row_id), order_entry_id=VALUES(order_entry_id), bill_no=VALUES(bill_no),
  928. morder_no=VALUES(morder_no), num=VALUES(num), item_number=VALUES(item_number), item_name=VALUES(item_name),
  929. bom_number=VALUES(bom_number), model=VALUES(model), kitting_time=VALUES(kitting_time),
  930. item_type=VALUES(item_type), erp_cls_name=VALUES(erp_cls_name), qty=VALUES(qty), wastage=VALUES(wastage),
  931. need_count=VALUES(need_count), sqty=VALUES(sqty), use_qty=VALUES(use_qty), self_lack_qty=VALUES(self_lack_qty),
  932. lack_qty=VALUES(lack_qty), mo_qty=VALUES(mo_qty), make_qty=VALUES(make_qty), purchase_qty=VALUES(purchase_qty),
  933. purchase_occupy_qty=VALUES(purchase_occupy_qty), satisfy_time=VALUES(satisfy_time), have_ic_subs=VALUES(have_ic_subs),
  934. substitute_code=VALUES(substitute_code), create_time=VALUES(create_time), sync_batch_id=VALUES(sync_batch_id),
  935. calc_time=VALUES(calc_time), update_time=CURRENT_TIMESTAMP
  936. """, batchId, now);
  937. yield return Cmd(
  938. """
  939. INSERT INTO dwd_ship_trans
  940. (tenant_id, factory_id, company_id, stat_date, order_id, order_entry_id, order_no, order_line, customer_no,
  941. customer_name, country, item_code, item_name, item_spec, order_qty, planned_ship_qty, shipped_qty,
  942. remaining_qty, order_date, customer_request_date, plan_delivery_date, promised_delivery_date,
  943. plan_ship_date, actual_ship_date, review_status, order_status, delivery_status, linkage_status, risk_level,
  944. source_system, source_table, source_row_id, source_biz_key, sync_batch_id, calc_batch_id, calc_time)
  945. SELECT
  946. so.tenant_id,
  947. so.factory_id,
  948. so.company_id,
  949. @StatDate,
  950. so.order_id,
  951. so.order_entry_id,
  952. so.order_no,
  953. IFNULL(so.order_line, ''),
  954. so.customer_no,
  955. so.customer_name,
  956. so.country,
  957. IFNULL(so.item_code, ''),
  958. so.item_name,
  959. so.item_spec,
  960. IFNULL(so.order_qty, 0),
  961. IFNULL(p.plan_qty, 0),
  962. IFNULL(a.real_qty, 0),
  963. GREATEST(IFNULL(so.order_qty, 0) - IFNULL(a.real_qty, 0), 0),
  964. so.order_date,
  965. so.customer_request_date,
  966. so.plan_delivery_date,
  967. so.promised_delivery_date,
  968. p.plan_ship_date,
  969. a.actual_ship_date,
  970. so.review_status,
  971. so.order_status,
  972. CASE
  973. WHEN IFNULL(so.order_qty, 0) > 0 AND IFNULL(a.real_qty, 0) >= IFNULL(so.order_qty, 0) THEN 'COMPLETED'
  974. WHEN COALESCE(p.plan_ship_date, so.promised_delivery_date, so.plan_delivery_date) < @Now THEN 'DELAYED'
  975. WHEN IFNULL(p.plan_qty, 0) > 0 THEN 'PLANNED'
  976. ELSE 'OPEN'
  977. END,
  978. l.linkage_status,
  979. CASE
  980. WHEN COALESCE(p.plan_ship_date, so.promised_delivery_date, so.plan_delivery_date) < @Now
  981. AND IFNULL(a.real_qty, 0) < IFNULL(so.order_qty, 0) THEN 'HIGH'
  982. WHEN IFNULL(a.real_qty, 0) < IFNULL(so.order_qty, 0) THEN 'MEDIUM'
  983. ELSE 'LOW'
  984. END,
  985. 'AIDOP',
  986. so.source_table,
  987. so.source_row_id,
  988. so.source_biz_key,
  989. so.sync_batch_id,
  990. @BatchId,
  991. @Now
  992. FROM mdp_std_so so
  993. LEFT JOIN (
  994. SELECT tenant_id, order_no, order_entry_id, IFNULL(order_line, '') AS order_line, IFNULL(item_code, '') AS item_code,
  995. SUM(IFNULL(plan_qty, IFNULL(qty, 0))) AS plan_qty,
  996. MIN(plan_ship_date) AS plan_ship_date
  997. FROM mdp_std_ship_trans
  998. WHERE trans_type='SHIP_PLAN'
  999. GROUP BY tenant_id, order_no, order_entry_id, IFNULL(order_line, ''), IFNULL(item_code, '')
  1000. ) p ON so.tenant_id=p.tenant_id
  1001. AND so.order_no=p.order_no
  1002. AND IFNULL(so.item_code, '')=p.item_code
  1003. AND (
  1004. (p.order_entry_id IS NOT NULL AND so.order_entry_id=p.order_entry_id)
  1005. OR (p.order_entry_id IS NULL AND IFNULL(so.order_line, '')=p.order_line)
  1006. )
  1007. LEFT JOIN (
  1008. SELECT tenant_id, order_no, IFNULL(order_line, '') AS order_line, IFNULL(item_code, '') AS item_code,
  1009. SUM(IFNULL(real_qty, IFNULL(qty_to_ship, 0))) AS real_qty,
  1010. MAX(actual_ship_date) AS actual_ship_date
  1011. FROM mdp_std_ship_trans
  1012. WHERE trans_type='ASN_SHIPPER'
  1013. GROUP BY tenant_id, order_no, IFNULL(order_line, ''), IFNULL(item_code, '')
  1014. ) a ON so.tenant_id=a.tenant_id AND so.order_no=a.order_no AND IFNULL(so.order_line, '')=a.order_line AND IFNULL(so.item_code, '')=a.item_code
  1015. LEFT JOIN (
  1016. SELECT tenant_id, order_no, item_code, MAX(linkage_status) AS linkage_status
  1017. FROM mdp_std_ship_trans
  1018. WHERE IFNULL(linkage_status, '') <> ''
  1019. GROUP BY tenant_id, order_no, item_code
  1020. ) l ON so.tenant_id=l.tenant_id AND so.order_no=l.order_no AND IFNULL(so.item_code, '')=IFNULL(l.item_code, '')
  1021. WHERE IFNULL(so.order_no, '') <> ''
  1022. ON DUPLICATE KEY UPDATE
  1023. customer_no=VALUES(customer_no), customer_name=VALUES(customer_name), item_name=VALUES(item_name),
  1024. order_qty=VALUES(order_qty), planned_ship_qty=VALUES(planned_ship_qty), shipped_qty=VALUES(shipped_qty),
  1025. remaining_qty=VALUES(remaining_qty), plan_ship_date=VALUES(plan_ship_date), actual_ship_date=VALUES(actual_ship_date),
  1026. review_status=VALUES(review_status), order_status=VALUES(order_status), delivery_status=VALUES(delivery_status),
  1027. linkage_status=VALUES(linkage_status), risk_level=VALUES(risk_level), sync_batch_id=VALUES(sync_batch_id),
  1028. calc_batch_id=VALUES(calc_batch_id), calc_time=VALUES(calc_time), update_time=CURRENT_TIMESTAMP
  1029. """, batchId, now);
  1030. }
  1031. private async Task<long> InsertSyncLogAsync(long entityId, string entityName, string batchId, int rowsRead)
  1032. {
  1033. await _db.Ado.ExecuteCommandAsync(
  1034. """
  1035. INSERT INTO mdp_sync_log
  1036. (tenant_id, entity_id, source_code, entity_name, sync_batch_id, sync_type, trigger_type, sync_start, rows_read, status)
  1037. VALUES (0, @EntityId, 'AIDOPDEV_MYSQL', @EntityName, @BatchId, 'FULL', 'AUTO', NOW(), @RowsRead, 'RUNNING')
  1038. """,
  1039. new SugarParameter("@EntityId", entityId),
  1040. new SugarParameter("@EntityName", entityName),
  1041. new SugarParameter("@BatchId", batchId),
  1042. new SugarParameter("@RowsRead", rowsRead));
  1043. return await _db.Ado.GetLongAsync(
  1044. "SELECT id FROM mdp_sync_log WHERE sync_batch_id=@BatchId AND entity_id=@EntityId ORDER BY id DESC LIMIT 1",
  1045. new List<SugarParameter>
  1046. {
  1047. new("@BatchId", batchId),
  1048. new("@EntityId", entityId)
  1049. });
  1050. }
  1051. private async Task MarkSyncLogSuccessAsync(long logId, DateTime started, int affected)
  1052. {
  1053. await _db.Ado.ExecuteCommandAsync(
  1054. """
  1055. UPDATE mdp_sync_log
  1056. SET sync_end=NOW(), duration_ms=@DurationMs, rows_insert=@RowsInsert, rows_update=0, rows_skip=0, rows_error=0, status='SUCCESS'
  1057. WHERE id=@Id
  1058. """,
  1059. new SugarParameter("@DurationMs", (int)(DateTime.Now - started).TotalMilliseconds),
  1060. new SugarParameter("@RowsInsert", affected),
  1061. new SugarParameter("@Id", logId));
  1062. }
  1063. private async Task MarkSyncLogFailedAsync(long logId, DateTime started, string message)
  1064. {
  1065. try
  1066. {
  1067. await _db.Ado.ExecuteCommandAsync(
  1068. """
  1069. UPDATE mdp_sync_log
  1070. SET sync_end=NOW(), duration_ms=@DurationMs, rows_error=1, status='FAILED', error_msg=@ErrorMsg
  1071. WHERE id=@Id
  1072. """,
  1073. new SugarParameter("@DurationMs", (int)(DateTime.Now - started).TotalMilliseconds),
  1074. new SugarParameter("@ErrorMsg", Truncate(message, 1000)),
  1075. new SugarParameter("@Id", logId));
  1076. }
  1077. catch (Exception ex)
  1078. {
  1079. // 写库自身失败兜底:避免再抛掩盖原异常;遗留 RUNNING 行可由运维手动清理
  1080. Console.Error.WriteLine($"[S1MdpSyncTransform] MarkSyncLogFailed write failed (syncLogId={logId}): {ex.Message}");
  1081. }
  1082. }
  1083. private async Task<long> InsertTransformRunLogAsync(string batchId, DateTime startedAt, string triggerType)
  1084. {
  1085. await _db.Ado.ExecuteCommandAsync(
  1086. """
  1087. INSERT INTO mdp_transform_run_log
  1088. (tenant_id, job_code, job_name, trigger_type, batch_id, status, start_time)
  1089. VALUES (0, @JobCode, 'S1 MDP同步与标准化转换', @TriggerType, @BatchId, 'RUNNING', @StartTime)
  1090. """,
  1091. new SugarParameter("@JobCode", JobCode),
  1092. new SugarParameter("@TriggerType", NormalizeTriggerType(triggerType)),
  1093. new SugarParameter("@BatchId", batchId),
  1094. new SugarParameter("@StartTime", startedAt));
  1095. return await _db.Ado.GetLongAsync(
  1096. "SELECT id FROM mdp_transform_run_log WHERE batch_id=@BatchId ORDER BY id DESC LIMIT 1",
  1097. new List<SugarParameter> { new("@BatchId", batchId) });
  1098. }
  1099. private async Task MarkTransformRunSuccessAsync(long runLogId, DateTime startedAt, S1MdpSyncTransformResult result)
  1100. {
  1101. var finishedAt = DateTime.Now;
  1102. await _db.Ado.ExecuteCommandAsync(
  1103. """
  1104. UPDATE mdp_transform_run_log
  1105. SET status='SUCCESS', end_time=@EndTime, duration_ms=@DurationMs,
  1106. stage_rows=@StageRows, standard_rows=@StandardRows, dwd_rows=@DwdRows,
  1107. summary_json=@SummaryJson, update_time=CURRENT_TIMESTAMP
  1108. WHERE id=@Id
  1109. """,
  1110. new SugarParameter("@EndTime", finishedAt),
  1111. new SugarParameter("@DurationMs", (int)(finishedAt - startedAt).TotalMilliseconds),
  1112. new SugarParameter("@StageRows", result.StageRows),
  1113. new SugarParameter("@StandardRows", result.StandardRows),
  1114. new SugarParameter("@DwdRows", result.DwdRows),
  1115. new SugarParameter("@SummaryJson", BuildRunSummaryJson(result)),
  1116. new SugarParameter("@Id", runLogId));
  1117. }
  1118. private async Task MarkTransformRunFailedAsync(long runLogId, DateTime startedAt, string message)
  1119. {
  1120. try
  1121. {
  1122. var finishedAt = DateTime.Now;
  1123. await _db.Ado.ExecuteCommandAsync(
  1124. """
  1125. UPDATE mdp_transform_run_log
  1126. SET status='FAILED', end_time=@EndTime, duration_ms=@DurationMs,
  1127. error_message=@ErrorMessage, update_time=CURRENT_TIMESTAMP
  1128. WHERE id=@Id
  1129. """,
  1130. new SugarParameter("@EndTime", finishedAt),
  1131. new SugarParameter("@DurationMs", (int)(finishedAt - startedAt).TotalMilliseconds),
  1132. new SugarParameter("@ErrorMessage", Truncate(message, 2000)),
  1133. new SugarParameter("@Id", runLogId));
  1134. }
  1135. catch (Exception ex)
  1136. {
  1137. // 写库自身失败兜底(典型场景:远端 MySQL 瞬断导致 MarkFailed 自身也连不上):
  1138. // 避免再抛二次异常掩盖原错;遗留 RUNNING 行可由运维手动清理。
  1139. Console.Error.WriteLine($"[S1MdpSyncTransform] MarkTransformRunFailed write failed (runLogId={runLogId}): {ex.Message}");
  1140. }
  1141. }
  1142. private static S1MdpSqlCommand Cmd(string sql, string batchId, DateTime now)
  1143. {
  1144. return new S1MdpSqlCommand(sql, new[]
  1145. {
  1146. new SugarParameter("@BatchId", batchId),
  1147. new SugarParameter("@Now", now),
  1148. new SugarParameter("@StatDate", now.Date)
  1149. });
  1150. }
  1151. private static string BuildJsonObjectExpression(IEnumerable<string> columns)
  1152. {
  1153. var parts = columns.SelectMany(c => new[] { $"'{c.Replace("'", "''")}'", $"s.`{c}`" });
  1154. return $"JSON_OBJECT({string.Join(",", parts)})";
  1155. }
  1156. private static string BuildOptionalColumnExpr(IReadOnlyCollection<string> columns, string expected, string fallback)
  1157. {
  1158. return columns.Any(u => string.Equals(u, expected, StringComparison.OrdinalIgnoreCase))
  1159. ? $"s.`{FindColumn(columns, expected)}`"
  1160. : fallback;
  1161. }
  1162. private static string FindColumn(IEnumerable<string> columns, string expected)
  1163. {
  1164. return columns.First(u => string.Equals(u, expected, StringComparison.OrdinalIgnoreCase));
  1165. }
  1166. private static string NormalizeTriggerType(string? triggerType)
  1167. {
  1168. return string.IsNullOrWhiteSpace(triggerType) ? "AUTO" : triggerType.Trim().ToUpperInvariant();
  1169. }
  1170. private static string BuildRunSummaryJson(S1MdpSyncTransformResult result)
  1171. {
  1172. return $$"""{"batchId":"{{result.BatchId}}","stageRows":{{result.StageRows}},"standardRows":{{result.StandardRows}},"dwdRows":{{result.DwdRows}},"kpiRows":{{result.KpiRows}}}""";
  1173. }
  1174. private static string ResolveKpiValueTable(int metricLevel)
  1175. {
  1176. return metricLevel switch
  1177. {
  1178. 1 => "ado_s9_kpi_value_l1_day",
  1179. 2 => "ado_s9_kpi_value_l2_day",
  1180. 3 => "ado_s9_kpi_value_l3_day",
  1181. 4 => "ado_s9_kpi_value_l4_day",
  1182. _ => "ado_s9_kpi_value_l2_day"
  1183. };
  1184. }
  1185. private static decimal DefaultS1Target(string metricCode)
  1186. {
  1187. return metricCode switch
  1188. {
  1189. "S1_L1_001" or "S1_L2_001" or "S1_L2_004" or "S1_L2_010" => 3m,
  1190. "S1_L1_004" => 60m,
  1191. "S1_L1_002" or "S1_L2_002" or "S1_L2_005" or "S1_L2_011" or "S1_L2_013" or "S1_L2_014" or "S1_L2_015" => 95m,
  1192. "S1_L1_003" or "S1_L2_003" or "S1_L2_006" or "S1_L2_012" => 100m,
  1193. "S1_L3_001" or "S1_L3_101" => 8m,
  1194. "S1_L3_002" or "S1_L3_102" => 12m,
  1195. "S1_L3_003" or "S1_L3_103" => 8m,
  1196. "S1_L3_004" or "S1_L3_104" => 10m,
  1197. "S1_L3_005" or "S1_L3_105" => 2m,
  1198. "S1_L4_001" or "S1_L4_101" => 2m,
  1199. "S1_L4_002" or "S1_L4_102" => 3m,
  1200. "S1_L4_003" or "S1_L4_103" => 1m,
  1201. "S1_L4_004" or "S1_L4_104" => 2m,
  1202. _ => 0m
  1203. };
  1204. }
  1205. private static string ResolveKpiStatus(decimal actual, decimal target, string? direction, decimal? yellowThreshold, decimal? redThreshold)
  1206. {
  1207. if (target <= 0) return "gray";
  1208. var ratio = actual / target * 100m;
  1209. if (string.Equals(direction, "lower_is_better", StringComparison.OrdinalIgnoreCase))
  1210. {
  1211. if (actual <= target) return "green";
  1212. if (ratio <= (yellowThreshold ?? 110m)) return "yellow";
  1213. return ratio >= (redThreshold ?? 120m) ? "red" : "yellow";
  1214. }
  1215. if (actual >= target) return "green";
  1216. if (ratio >= (yellowThreshold ?? 95m)) return "yellow";
  1217. return ratio <= (redThreshold ?? 80m) ? "red" : "yellow";
  1218. }
  1219. private static string ResolveTrendFlag(decimal actual, decimal? previous)
  1220. {
  1221. if (previous == null) return "flat";
  1222. if (actual > previous.Value) return "up";
  1223. if (actual < previous.Value) return "down";
  1224. return "flat";
  1225. }
  1226. private static string Truncate(string? raw, int maxLength)
  1227. {
  1228. if (string.IsNullOrEmpty(raw)) return string.Empty;
  1229. return raw.Length <= maxLength ? raw : raw[..maxLength];
  1230. }
  1231. private sealed class S1ColumnRow
  1232. {
  1233. public string ColumnName { get; set; } = string.Empty;
  1234. }
  1235. private sealed class S1MdpEntityRow
  1236. {
  1237. public long Id { get; set; }
  1238. public string EntityName { get; set; } = string.Empty;
  1239. }
  1240. private sealed class S1KpiCalcRow
  1241. {
  1242. public long TenantId { get; set; }
  1243. public long FactoryId { get; set; }
  1244. public string MetricCode { get; set; } = string.Empty;
  1245. public decimal? MetricValue { get; set; }
  1246. }
  1247. private sealed class S1KpiMetaRow
  1248. {
  1249. public int MetricLevel { get; set; }
  1250. public string Direction { get; set; } = "higher_is_better";
  1251. public decimal? YellowThreshold { get; set; }
  1252. public decimal? RedThreshold { get; set; }
  1253. }
  1254. private sealed class S1KpiValueRow
  1255. {
  1256. public long Id { get; set; }
  1257. public decimal? MetricValue { get; set; }
  1258. public decimal? TargetValue { get; set; }
  1259. }
  1260. }
  1261. public sealed class S1MdpSyncTransformResult
  1262. {
  1263. public long RunLogId { get; set; }
  1264. public string BatchId { get; set; } = string.Empty;
  1265. public int StageRows { get; set; }
  1266. public int StandardRows { get; set; }
  1267. public int DwdRows { get; set; }
  1268. public int KpiRows { get; set; }
  1269. }
  1270. internal sealed record S1MdpSqlCommand(string Sql, SugarParameter[] Parameters);
  1271. internal sealed record S1MdpEntityConfig(
  1272. string EntityCode,
  1273. string SourceTable,
  1274. string TargetTable,
  1275. string SourceRowIdExpression,
  1276. string SourceBizKeyExpression)
  1277. {
  1278. public static readonly IReadOnlyList<S1MdpEntityConfig> All = new List<S1MdpEntityConfig>
  1279. {
  1280. new("S1_SEORDER", "crm_seorder", "mdp_stg_so", "Id", "COALESCE(s.`bill_no`, CAST(s.`Id` AS CHAR))"),
  1281. new("S1_SEORDER_ENTRY", "crm_seorderentry", "mdp_stg_so", "Id", "CONCAT(IFNULL(s.`bill_no`,''), ':', IFNULL(s.`entry_seq`, CAST(s.`Id` AS CHAR)))"),
  1282. new("S1_SEORDER_CHANGE", "crm_seorder_change", "mdp_stg_so", "Id", "CONCAT(IFNULL(s.`bill_no`,''), ':', CAST(s.`Id` AS CHAR))"),
  1283. new("S1_CONTRACT_REVIEW", "ado_contract_review", "mdp_stg_so", "RecID", "COALESCE(s.`BillNo`, CAST(s.`RecID` AS CHAR))"),
  1284. new("S1_CONTRACT_REVIEW_FLOW", "ado_contract_review_flow", "mdp_stg_so", "RecID", "CONCAT(IFNULL(s.`ReviewBillNo`,''), ':', IFNULL(s.`StageNo`,''), ':', CAST(s.`RecID` AS CHAR))"),
  1285. new("S1_PRODUCT_DESIGN", "ado_product_design", "mdp_stg_so", "Id", "COALESCE(s.`BillNo`, CAST(s.`Id` AS CHAR))"),
  1286. new("S1_PRODUCT_DESIGN_BOM", "ado_product_design_bom", "mdp_stg_so", "Id", "CONCAT(CAST(s.`ProductDesignId` AS CHAR), ':', CAST(s.`Id` AS CHAR))"),
  1287. new("S1_PRODUCT_DESIGN_ROUTING", "ado_product_design_routing", "mdp_stg_so", "Id", "CONCAT(CAST(s.`ProductDesignId` AS CHAR), ':', CAST(s.`Id` AS CHAR))"),
  1288. new("S1_REQUIREMENT_EXAMINE_RESULT", "b_examine_result", "mdp_stg_so", "Id", "CONCAT(IFNULL(s.`bill_no`,''), ':', IFNULL(s.`morder_no`,''), ':', CAST(s.`Id` AS CHAR))"),
  1289. new("S1_REQUIREMENT_EXAMINE_DETAIL", "b_bom_child_examine", "mdp_stg_so", "Id", "CONCAT(IFNULL(s.`examine_id`,''), ':', IFNULL(s.`item_number`,''), ':', CAST(s.`Id` AS CHAR))"),
  1290. new("S1_SHIPPING_PLAN", "ShippingPlan", "mdp_stg_ship_trans", "RecID", "COALESCE(s.`LotSerial`, CAST(s.`RecID` AS CHAR))"),
  1291. new("S1_SHIPPING_PLAN_DETAIL", "ShippingPlanDetail", "mdp_stg_ship_trans", "RecID", "CONCAT(IFNULL(s.`plan_id`,''), ':', IFNULL(s.`OrdNbr`,''), ':', CAST(s.`RecID` AS CHAR))"),
  1292. new("S1_ASN_SHIPPER_MASTER", "ASNBOLShipperMaster", "mdp_stg_ship_trans", "RecID", "COALESCE(s.`Id`, CONCAT(IFNULL(s.`OrdNbr`,''), ':', CAST(s.`RecID` AS CHAR)))"),
  1293. new("S1_ASN_SHIPPER_DETAIL", "ASNBOLShipperDetail", "mdp_stg_ship_trans", "RecID", "CONCAT(IFNULL(s.`Id`,''), ':', IFNULL(s.`Line`, CAST(s.`RecID` AS CHAR)))"),
  1294. new("S1_LINKAGE_PLAN", "LinkagePlan", "mdp_stg_ship_trans", "id", "CONCAT(IFNULL(s.`bill_no`,''), ':', IFNULL(s.`item_number`,''), ':', CAST(s.`id` AS CHAR))")
  1295. };
  1296. }