S1MdpSyncTransformService.cs 79 KB

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