SmartOpsKpiAtomicBuildService.cs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. using Admin.NET.Core;
  2. using Admin.NET.Plugin.AiDOP.DataPlatform.S1Refresh;
  3. using Admin.NET.Plugin.AiDOP.Infrastructure;
  4. using SqlSugar;
  5. namespace Admin.NET.Plugin.AiDOP.SmartOps;
  6. /// <summary>
  7. /// 从 DWD/STD 事实写入 KPI 原子聚合层(最小颗粒度,不做二次汇总)。
  8. /// </summary>
  9. public class SmartOpsKpiAtomicBuildService : ITransient
  10. {
  11. private readonly ISqlSugarClient _db;
  12. public SmartOpsKpiAtomicBuildService(ISqlSugarClient db)
  13. {
  14. _db = db;
  15. }
  16. public async Task<int> BuildOrderDeliveryDomainForAllDatesAsync(string? batchId, CancellationToken cancellationToken = default)
  17. {
  18. AidopTenantMigration.EnsureKpiAtomicTable(_db);
  19. var scopes = await _db.Ado.SqlQueryAsync<S1MdpScopeRow>(
  20. """
  21. SELECT DISTINCT tenant_id AS TenantId, COALESCE(NULLIF(factory_id, 0), 1) AS FactoryId
  22. FROM mdp_std_so
  23. WHERE tenant_id > 0 AND order_date IS NOT NULL
  24. """);
  25. var total = 0;
  26. foreach (var scope in scopes)
  27. {
  28. cancellationToken.ThrowIfCancellationRequested();
  29. total += await BuildOrderDeliveryDomainForAllDatesAsync(scope.TenantId, scope.FactoryId, batchId, cancellationToken);
  30. }
  31. return total;
  32. }
  33. public async Task<int> BuildOrderDeliveryDomainForAllDatesAsync(
  34. long tenantId,
  35. long factoryId,
  36. string? batchId,
  37. CancellationToken cancellationToken = default)
  38. {
  39. if (tenantId <= 0 || factoryId <= 0)
  40. throw new InvalidOperationException("S1 Atomic 必须指定有效 tenantId/factoryId");
  41. AidopTenantMigration.EnsureKpiAtomicTable(_db);
  42. var dates = await _db.Ado.SqlQueryAsync<DateTime>(
  43. """
  44. SELECT DISTINCT DATE(order_date) AS D
  45. FROM mdp_std_so
  46. WHERE order_date IS NOT NULL
  47. AND tenant_id=@TenantId
  48. AND COALESCE(NULLIF(factory_id, 0), 1)=@FactoryId
  49. ORDER BY D
  50. """,
  51. new { TenantId = tenantId, FactoryId = factoryId });
  52. var total = 0;
  53. foreach (var date in dates)
  54. {
  55. cancellationToken.ThrowIfCancellationRequested();
  56. total += await BuildOrderDeliveryDomainAsync(date, tenantId, factoryId, batchId, cancellationToken);
  57. }
  58. return total;
  59. }
  60. public async Task<int> BuildWorkScheduleDomainForAllDatesAsync(string? batchId, CancellationToken cancellationToken = default)
  61. {
  62. AidopTenantMigration.EnsureKpiAtomicTable(_db);
  63. var scopes = await _db.Ado.SqlQueryAsync<S1MdpScopeRow>(
  64. """
  65. SELECT DISTINCT tenant_id AS TenantId, COALESCE(NULLIF(factory_id, 0), 1) AS FactoryId
  66. FROM dwd_order_schedule_trans
  67. WHERE tenant_id > 0 AND stat_date IS NOT NULL
  68. """);
  69. var total = 0;
  70. foreach (var scope in scopes)
  71. {
  72. cancellationToken.ThrowIfCancellationRequested();
  73. total += await BuildWorkScheduleDomainForAllDatesAsync(scope.TenantId, scope.FactoryId, batchId, cancellationToken);
  74. }
  75. return total;
  76. }
  77. public async Task<int> BuildWorkScheduleDomainForAllDatesAsync(
  78. long tenantId,
  79. long factoryId,
  80. string? batchId,
  81. CancellationToken cancellationToken = default)
  82. {
  83. if (tenantId <= 0 || factoryId <= 0)
  84. throw new InvalidOperationException("S2 Atomic 必须指定有效 tenantId/factoryId");
  85. AidopTenantMigration.EnsureKpiAtomicTable(_db);
  86. var dates = await _db.Ado.SqlQueryAsync<DateTime>(
  87. """
  88. SELECT DISTINCT stat_date AS D
  89. FROM dwd_order_schedule_trans
  90. WHERE stat_date IS NOT NULL
  91. AND tenant_id=@TenantId
  92. AND COALESCE(NULLIF(factory_id, 0), 1)=@FactoryId
  93. ORDER BY D
  94. """,
  95. new { TenantId = tenantId, FactoryId = factoryId });
  96. var total = 0;
  97. foreach (var date in dates)
  98. {
  99. cancellationToken.ThrowIfCancellationRequested();
  100. total += await BuildWorkScheduleDomainAsync(date, tenantId, factoryId, batchId, cancellationToken);
  101. }
  102. return total;
  103. }
  104. public async Task<int> BuildSupplyPurchaseDomainForAllDatesAsync(string? batchId, CancellationToken cancellationToken = default)
  105. {
  106. AidopTenantMigration.EnsureKpiAtomicTable(_db);
  107. var scopes = await _db.Ado.SqlQueryAsync<S1MdpScopeRow>(
  108. """
  109. SELECT DISTINCT tenant_id AS TenantId, COALESCE(NULLIF(factory_id, 0), 1) AS FactoryId
  110. FROM dwd_supplier_delivery
  111. WHERE tenant_id > 0 AND stat_date IS NOT NULL
  112. """);
  113. var total = 0;
  114. foreach (var scope in scopes)
  115. {
  116. cancellationToken.ThrowIfCancellationRequested();
  117. total += await BuildSupplyPurchaseDomainForAllDatesAsync(scope.TenantId, scope.FactoryId, batchId, cancellationToken);
  118. }
  119. return total;
  120. }
  121. public async Task<int> BuildSupplyPurchaseDomainForAllDatesAsync(
  122. long tenantId,
  123. long factoryId,
  124. string? batchId,
  125. CancellationToken cancellationToken = default)
  126. {
  127. if (tenantId <= 0 || factoryId <= 0)
  128. throw new InvalidOperationException("S3 Atomic 必须指定有效 tenantId/factoryId");
  129. AidopTenantMigration.EnsureKpiAtomicTable(_db);
  130. var dates = await _db.Ado.SqlQueryAsync<DateTime>(
  131. """
  132. SELECT DISTINCT stat_date AS D
  133. FROM dwd_supplier_delivery
  134. WHERE stat_date IS NOT NULL
  135. AND tenant_id=@TenantId
  136. AND COALESCE(NULLIF(factory_id, 0), 1)=@FactoryId
  137. ORDER BY D
  138. """,
  139. new { TenantId = tenantId, FactoryId = factoryId });
  140. var total = 0;
  141. foreach (var date in dates)
  142. {
  143. cancellationToken.ThrowIfCancellationRequested();
  144. total += await BuildSupplyPurchaseDomainAsync(date, tenantId, factoryId, batchId, cancellationToken);
  145. }
  146. return total;
  147. }
  148. public async Task<int> BuildInventoryDomainForAllDatesAsync(string? batchId, CancellationToken cancellationToken = default)
  149. {
  150. AidopTenantMigration.EnsureKpiAtomicTable(_db);
  151. var scopes = await _db.Ado.SqlQueryAsync<S1MdpScopeRow>(
  152. """
  153. SELECT DISTINCT tenant_id AS TenantId, factory_id AS FactoryId
  154. FROM dwd_material_readiness
  155. WHERE tenant_id > 0 AND stat_date IS NOT NULL
  156. """);
  157. var total = 0;
  158. foreach (var scope in scopes)
  159. {
  160. cancellationToken.ThrowIfCancellationRequested();
  161. total += await BuildInventoryDomainForAllDatesAsync(scope.TenantId, scope.FactoryId, batchId, cancellationToken);
  162. }
  163. return total;
  164. }
  165. public async Task<int> BuildInventoryDomainForAllDatesAsync(
  166. long tenantId,
  167. long factoryId,
  168. string? batchId,
  169. CancellationToken cancellationToken = default)
  170. {
  171. if (tenantId <= 0 || factoryId <= 0)
  172. throw new InvalidOperationException("S3 Atomic 必须指定有效 tenantId/factoryId");
  173. AidopTenantMigration.EnsureKpiAtomicTable(_db);
  174. var dates = await _db.Ado.SqlQueryAsync<DateTime>(
  175. """
  176. SELECT DISTINCT stat_date AS D
  177. FROM dwd_material_readiness
  178. WHERE stat_date IS NOT NULL
  179. AND tenant_id=@TenantId
  180. AND factory_id=@FactoryId
  181. ORDER BY D
  182. """,
  183. new { TenantId = tenantId, FactoryId = factoryId });
  184. var total = 0;
  185. foreach (var date in dates)
  186. {
  187. cancellationToken.ThrowIfCancellationRequested();
  188. total += await BuildInventoryDomainAsync(date, tenantId, factoryId, batchId, cancellationToken);
  189. }
  190. return total;
  191. }
  192. public async Task<int> BuildOrderDeliveryDomainAsync(
  193. DateTime statDate,
  194. long tenantId,
  195. long factoryId,
  196. string? batchId,
  197. CancellationToken cancellationToken = default)
  198. {
  199. if (tenantId <= 0 || factoryId <= 0)
  200. throw new InvalidOperationException("S1 Atomic 必须指定有效 tenantId/factoryId");
  201. AidopTenantMigration.EnsureKpiAtomicTable(_db);
  202. var date = statDate.Date;
  203. var now = DateTime.Now;
  204. var affected = 0;
  205. affected += await _db.Ado.ExecuteCommandAsync(
  206. """
  207. DELETE FROM ado_smart_ops_kpi_atomic_day
  208. WHERE domain_code = @Domain
  209. AND stat_date = @StatDate
  210. AND tenant_id = @TenantId
  211. AND factory_id = @FactoryId
  212. """,
  213. new { Domain = SmartOpsKpiAggregateRuleRegistry.DomainOrderDelivery, StatDate = date, TenantId = tenantId, FactoryId = factoryId });
  214. foreach (var metricCode in new[] { "S1_L1_001", "S7_L1_001", "S9_L1_002" })
  215. {
  216. affected += await _db.Ado.ExecuteCommandAsync(
  217. """
  218. INSERT INTO ado_smart_ops_kpi_atomic_day
  219. (tenant_id, factory_id, domain_code, metric_code, metric_level, stat_date,
  220. customer_code, product_code, order_no, production_line,
  221. sum_value, sample_count, batch_id, calc_time, grain, is_deleted)
  222. SELECT
  223. so.tenant_id,
  224. COALESCE(NULLIF(so.factory_id, 0), 1),
  225. @Domain,
  226. @MetricCode,
  227. 1,
  228. DATE(so.order_date),
  229. IFNULL(so.customer_no, ''),
  230. IFNULL(so.item_code, ''),
  231. IFNULL(so.order_no, ''),
  232. '',
  233. ROUND(SUM(TIMESTAMPDIFF(HOUR, so.order_date,
  234. COALESCE(so.promised_delivery_date, so.capacity_date, so.material_ready_date, so.plan_delivery_date)) / 24), 6),
  235. COUNT(1),
  236. @BatchId,
  237. @Now,
  238. 'day',
  239. 0
  240. FROM mdp_std_so so
  241. WHERE so.order_date IS NOT NULL
  242. AND COALESCE(so.promised_delivery_date, so.capacity_date, so.material_ready_date, so.plan_delivery_date) IS NOT NULL
  243. AND COALESCE(so.promised_delivery_date, so.capacity_date, so.material_ready_date, so.plan_delivery_date) >= so.order_date
  244. AND DATE(so.order_date) = @StatDate
  245. AND so.tenant_id = @TenantId
  246. AND COALESCE(NULLIF(so.factory_id, 0), 1) = @FactoryId
  247. GROUP BY so.tenant_id, COALESCE(NULLIF(so.factory_id, 0), 1),
  248. DATE(so.order_date), IFNULL(so.customer_no, ''), IFNULL(so.item_code, ''), IFNULL(so.order_no, '')
  249. """,
  250. new
  251. {
  252. Domain = SmartOpsKpiAggregateRuleRegistry.DomainOrderDelivery,
  253. MetricCode = metricCode,
  254. StatDate = date,
  255. BatchId = batchId,
  256. Now = now,
  257. TenantId = tenantId,
  258. FactoryId = factoryId
  259. });
  260. }
  261. foreach (var metricCode in new[] { "S1_L1_002", "S7_L1_002", "S9_L1_003" })
  262. {
  263. affected += await _db.Ado.ExecuteCommandAsync(
  264. """
  265. INSERT INTO ado_smart_ops_kpi_atomic_day
  266. (tenant_id, factory_id, domain_code, metric_code, metric_level, stat_date,
  267. customer_code, product_code, order_no, production_line,
  268. numerator_value, denominator_value, batch_id, calc_time, grain, is_deleted)
  269. SELECT
  270. so.tenant_id,
  271. COALESCE(NULLIF(so.factory_id, 0), 1),
  272. @Domain,
  273. @MetricCode,
  274. 1,
  275. DATE(so.order_date),
  276. IFNULL(so.customer_no, ''),
  277. IFNULL(so.item_code, ''),
  278. IFNULL(so.order_no, ''),
  279. '',
  280. SUM(CASE WHEN TIMESTAMPDIFF(HOUR, so.order_date,
  281. COALESCE(so.promised_delivery_date, so.capacity_date, so.material_ready_date, so.plan_delivery_date)) <= 72 THEN 1 ELSE 0 END),
  282. COUNT(1),
  283. @BatchId,
  284. @Now,
  285. 'day',
  286. 0
  287. FROM mdp_std_so so
  288. WHERE so.order_date IS NOT NULL
  289. AND COALESCE(so.promised_delivery_date, so.capacity_date, so.material_ready_date, so.plan_delivery_date) IS NOT NULL
  290. AND DATE(so.order_date) = @StatDate
  291. AND so.tenant_id = @TenantId
  292. AND COALESCE(NULLIF(so.factory_id, 0), 1) = @FactoryId
  293. GROUP BY so.tenant_id, COALESCE(NULLIF(so.factory_id, 0), 1),
  294. DATE(so.order_date), IFNULL(so.customer_no, ''), IFNULL(so.item_code, ''), IFNULL(so.order_no, '')
  295. """,
  296. new
  297. {
  298. Domain = SmartOpsKpiAggregateRuleRegistry.DomainOrderDelivery,
  299. MetricCode = metricCode,
  300. StatDate = date,
  301. BatchId = batchId,
  302. Now = now,
  303. TenantId = tenantId,
  304. FactoryId = factoryId
  305. });
  306. }
  307. foreach (var metricCode in new[] { "S1_L1_003", "S7_L1_003" })
  308. {
  309. affected += await _db.Ado.ExecuteCommandAsync(
  310. """
  311. INSERT INTO ado_smart_ops_kpi_atomic_day
  312. (tenant_id, factory_id, domain_code, metric_code, metric_level, stat_date,
  313. customer_code, product_code, order_no, production_line,
  314. numerator_value, denominator_value, batch_id, calc_time, grain, is_deleted)
  315. SELECT
  316. so.tenant_id,
  317. COALESCE(NULLIF(so.factory_id, 0), 1),
  318. @Domain,
  319. @MetricCode,
  320. 1,
  321. DATE(so.order_date),
  322. IFNULL(so.customer_no, ''),
  323. IFNULL(so.item_code, ''),
  324. IFNULL(so.order_no, ''),
  325. '',
  326. COUNT(1),
  327. GREATEST(COUNT(DISTINCT NULLIF(so.planner_no, '')), 1),
  328. @BatchId,
  329. @Now,
  330. 'day',
  331. 0
  332. FROM mdp_std_so so
  333. WHERE so.order_date IS NOT NULL
  334. AND DATE(so.order_date) = @StatDate
  335. AND so.tenant_id = @TenantId
  336. AND COALESCE(NULLIF(so.factory_id, 0), 1) = @FactoryId
  337. GROUP BY so.tenant_id, COALESCE(NULLIF(so.factory_id, 0), 1),
  338. DATE(so.order_date), IFNULL(so.customer_no, ''), IFNULL(so.item_code, ''), IFNULL(so.order_no, '')
  339. """,
  340. new
  341. {
  342. Domain = SmartOpsKpiAggregateRuleRegistry.DomainOrderDelivery,
  343. MetricCode = metricCode,
  344. StatDate = date,
  345. BatchId = batchId,
  346. Now = now,
  347. TenantId = tenantId,
  348. FactoryId = factoryId
  349. });
  350. }
  351. return affected;
  352. }
  353. public async Task<int> BuildSupplyPurchaseDomainAsync(
  354. DateTime statDate, long tenantId, long factoryId, string? batchId, CancellationToken cancellationToken = default)
  355. {
  356. if (tenantId <= 0 || factoryId <= 0)
  357. throw new InvalidOperationException("S3 Atomic 必须指定有效 tenantId/factoryId");
  358. AidopTenantMigration.EnsureKpiAtomicTable(_db);
  359. var date = statDate.Date;
  360. var now = DateTime.Now;
  361. var affected = 0;
  362. affected += await _db.Ado.ExecuteCommandAsync(
  363. """
  364. DELETE FROM ado_smart_ops_kpi_atomic_day
  365. WHERE domain_code = @Domain
  366. AND stat_date = @StatDate
  367. AND tenant_id = @TenantId
  368. AND factory_id = @FactoryId
  369. """,
  370. new { Domain = SmartOpsKpiAggregateRuleRegistry.DomainSupplyPurchase, StatDate = date, TenantId = tenantId, FactoryId = factoryId });
  371. foreach (var metricCode in new[] { "S3_L1_001", "S4_L1_001" })
  372. {
  373. affected += await _db.Ado.ExecuteCommandAsync(
  374. """
  375. INSERT INTO ado_smart_ops_kpi_atomic_day
  376. (tenant_id, factory_id, domain_code, metric_code, metric_level, stat_date,
  377. supplier_code, material_code, po_no,
  378. sum_value, sample_count, batch_id, calc_time, grain, is_deleted)
  379. SELECT tenant_id, 1, @Domain, @MetricCode, 1, DATE(request_date),
  380. IFNULL(supplier_code, ''), IFNULL(item_code, ''), IFNULL(po_no, ''),
  381. ROUND(SUM(TIMESTAMPDIFF(HOUR, request_date, submit_date) / 24), 6),
  382. COUNT(1), @BatchId, @Now, 'day', 0
  383. FROM mdp_std_delivery_schedule
  384. WHERE request_date IS NOT NULL AND submit_date IS NOT NULL AND submit_date >= request_date
  385. AND DATE(request_date) = @StatDate
  386. AND tenant_id = @TenantId
  387. GROUP BY tenant_id, DATE(request_date), IFNULL(supplier_code, ''), IFNULL(item_code, ''), IFNULL(po_no, '')
  388. """,
  389. new
  390. {
  391. Domain = SmartOpsKpiAggregateRuleRegistry.DomainSupplyPurchase,
  392. MetricCode = metricCode,
  393. StatDate = date,
  394. BatchId = batchId,
  395. Now = now,
  396. TenantId = tenantId,
  397. FactoryId = factoryId
  398. });
  399. }
  400. foreach (var metricCode in new[] { "S4_L1_002", "S3_L1_002" })
  401. {
  402. affected += await _db.Ado.ExecuteCommandAsync(
  403. """
  404. INSERT INTO ado_smart_ops_kpi_atomic_day
  405. (tenant_id, factory_id, domain_code, metric_code, metric_level, stat_date,
  406. supplier_code, material_code, po_no,
  407. numerator_value, denominator_value, batch_id, calc_time, grain, is_deleted)
  408. SELECT tenant_id, factory_id, @Domain, @MetricCode, 1, d.stat_date,
  409. IFNULL(d.supplier_code, ''), IFNULL(d.item_code, ''), IFNULL(d.po_no, ''),
  410. SUM(CASE WHEN IFNULL(d.receipt_qty, 0) >= IFNULL(d.order_qty, 0) AND IFNULL(d.order_qty, 0) > 0 THEN 1
  411. WHEN d.delivery_status = 'COMPLETED' THEN 1 ELSE 0 END),
  412. SUM(CASE WHEN IFNULL(d.order_qty, 0) > 0 THEN 1 ELSE 0 END),
  413. @BatchId, @Now, 'day', 0
  414. FROM dwd_supplier_delivery d
  415. WHERE d.stat_date = @StatDate AND d.tenant_id = @TenantId AND d.factory_id = @FactoryId
  416. GROUP BY d.tenant_id, d.factory_id, d.stat_date, IFNULL(d.supplier_code, ''), IFNULL(d.item_code, ''), IFNULL(d.po_no, '')
  417. HAVING SUM(CASE WHEN IFNULL(d.order_qty, 0) > 0 THEN 1 ELSE 0 END) > 0
  418. """,
  419. new
  420. {
  421. Domain = SmartOpsKpiAggregateRuleRegistry.DomainSupplyPurchase,
  422. MetricCode = metricCode,
  423. StatDate = date,
  424. BatchId = batchId,
  425. Now = now,
  426. TenantId = tenantId,
  427. FactoryId = factoryId
  428. });
  429. }
  430. foreach (var metricCode in new[] { "S4_L1_003", "S3_L1_003" })
  431. {
  432. affected += await _db.Ado.ExecuteCommandAsync(
  433. """
  434. INSERT INTO ado_smart_ops_kpi_atomic_day
  435. (tenant_id, factory_id, domain_code, metric_code, metric_level, stat_date,
  436. supplier_code, material_code, po_no,
  437. numerator_value, denominator_value, batch_id, calc_time, grain, is_deleted)
  438. SELECT tenant_id, factory_id, @Domain, @MetricCode, 1, d.stat_date,
  439. IFNULL(d.supplier_code, ''), IFNULL(d.item_code, ''), IFNULL(d.po_no, ''),
  440. SUM(IFNULL(d.receipt_qty, 0)),
  441. GREATEST(COUNT(DISTINCT NULLIF(d.supplier_code, '')), 1),
  442. @BatchId, @Now, 'day', 0
  443. FROM dwd_supplier_delivery d
  444. WHERE d.stat_date = @StatDate AND d.tenant_id = @TenantId AND d.factory_id = @FactoryId
  445. GROUP BY d.tenant_id, d.factory_id, d.stat_date, IFNULL(d.supplier_code, ''), IFNULL(d.item_code, ''), IFNULL(d.po_no, '')
  446. """,
  447. new
  448. {
  449. Domain = SmartOpsKpiAggregateRuleRegistry.DomainSupplyPurchase,
  450. MetricCode = metricCode,
  451. StatDate = date,
  452. BatchId = batchId,
  453. Now = now,
  454. TenantId = tenantId,
  455. FactoryId = factoryId
  456. });
  457. }
  458. return affected;
  459. }
  460. public async Task<int> BuildWorkScheduleDomainAsync(
  461. DateTime statDate, long tenantId, long factoryId, string? batchId, CancellationToken cancellationToken = default)
  462. {
  463. if (tenantId <= 0 || factoryId <= 0)
  464. throw new InvalidOperationException("S2 Atomic 必须指定有效 tenantId/factoryId");
  465. AidopTenantMigration.EnsureKpiAtomicTable(_db);
  466. var date = statDate.Date;
  467. var now = DateTime.Now;
  468. var affected = 0;
  469. var domain = SmartOpsKpiAggregateRuleRegistry.DomainWorkSchedule;
  470. affected += await _db.Ado.ExecuteCommandAsync(
  471. """
  472. DELETE FROM ado_smart_ops_kpi_atomic_day
  473. WHERE domain_code = @Domain AND stat_date = @StatDate
  474. AND tenant_id = @TenantId AND factory_id = @FactoryId
  475. """,
  476. new { Domain = domain, StatDate = date, TenantId = tenantId, FactoryId = factoryId });
  477. // 口径归位(S6-L1-KPI-CONTRACT-RESOLUTION-1):S2 排程周期只属 S2_L1_001,
  478. // 不再别名写 S6_L1_001(订单制造周期≠排程周期,S6 L1 由 Phase 2 订单口径实现)。
  479. foreach (var metricCode in new[] { "S2_L1_001" })
  480. {
  481. affected += await _db.Ado.ExecuteCommandAsync(
  482. """
  483. INSERT INTO ado_smart_ops_kpi_atomic_day
  484. (tenant_id, factory_id, domain_code, metric_code, metric_level, stat_date,
  485. product_code, order_no, production_line, work_order_no,
  486. sum_value, sample_count, batch_id, calc_time, grain, is_deleted)
  487. SELECT tenant_id, factory_id, @Domain, @MetricCode, 1, stat_date,
  488. IFNULL(item_code, ''), IFNULL(sales_order_no, ''), IFNULL(prod_line, ''), IFNULL(work_order, ''),
  489. ROUND(SUM(schedule_cycle_days), 6), COUNT(1), @BatchId, @Now, 'day', 0
  490. FROM dwd_order_schedule_trans
  491. WHERE stat_date = @StatDate AND tenant_id = @TenantId AND COALESCE(NULLIF(factory_id, 0), 1) = @FactoryId AND schedule_cycle_days IS NOT NULL AND schedule_cycle_days >= 0
  492. GROUP BY tenant_id, factory_id, stat_date, IFNULL(item_code, ''), IFNULL(sales_order_no, ''),
  493. IFNULL(prod_line, ''), IFNULL(work_order, '')
  494. """,
  495. new { Domain = domain, MetricCode = metricCode, StatDate = date, BatchId = batchId, Now = now, TenantId = tenantId, FactoryId = factoryId });
  496. }
  497. // 口径归位:S2 排程满足率只属 S2_L1_002,不再别名写 S6_L1_002。
  498. foreach (var metricCode in new[] { "S2_L1_002" })
  499. {
  500. affected += await _db.Ado.ExecuteCommandAsync(
  501. """
  502. INSERT INTO ado_smart_ops_kpi_atomic_day
  503. (tenant_id, factory_id, domain_code, metric_code, metric_level, stat_date,
  504. product_code, order_no, production_line, work_order_no,
  505. numerator_value, denominator_value, batch_id, calc_time, grain, is_deleted)
  506. SELECT tenant_id, factory_id, @Domain, @MetricCode, 1, stat_date,
  507. IFNULL(item_code, ''), IFNULL(sales_order_no, ''), IFNULL(prod_line, ''), IFNULL(work_order, ''),
  508. SUM(IFNULL(schedule_satisfaction_flag, 0)), COUNT(1), @BatchId, @Now, 'day', 0
  509. FROM dwd_order_schedule_trans
  510. WHERE stat_date = @StatDate AND tenant_id = @TenantId AND COALESCE(NULLIF(factory_id, 0), 1) = @FactoryId
  511. GROUP BY tenant_id, factory_id, stat_date, IFNULL(item_code, ''), IFNULL(sales_order_no, ''),
  512. IFNULL(prod_line, ''), IFNULL(work_order, '')
  513. """,
  514. new { Domain = domain, MetricCode = metricCode, StatDate = date, BatchId = batchId, Now = now, TenantId = tenantId, FactoryId = factoryId });
  515. }
  516. // 口径归位:S2 排程人效只属 S2_L1_003,不再别名写 S6_L1_003(订单制造人效由 Phase 2 订单口径实现)。
  517. foreach (var metricCode in new[] { "S2_L1_003", "S9_L1_004" })
  518. {
  519. affected += await _db.Ado.ExecuteCommandAsync(
  520. """
  521. INSERT INTO ado_smart_ops_kpi_atomic_day
  522. (tenant_id, factory_id, domain_code, metric_code, metric_level, stat_date,
  523. product_code, order_no, production_line, work_order_no,
  524. numerator_value, denominator_value, batch_id, calc_time, grain, is_deleted)
  525. SELECT tenant_id, factory_id, @Domain, @MetricCode, 1, stat_date,
  526. IFNULL(item_code, ''), IFNULL(sales_order_no, ''), IFNULL(prod_line, ''), IFNULL(work_order, ''),
  527. COUNT(1), GREATEST(SUM(CASE WHEN IFNULL(resource_person_count, 0) > 0 THEN resource_person_count ELSE 1 END), 1),
  528. @BatchId, @Now, 'day', 0
  529. FROM dwd_order_schedule_trans
  530. WHERE stat_date = @StatDate AND tenant_id = @TenantId AND COALESCE(NULLIF(factory_id, 0), 1) = @FactoryId
  531. GROUP BY tenant_id, factory_id, stat_date, IFNULL(item_code, ''), IFNULL(sales_order_no, ''),
  532. IFNULL(prod_line, ''), IFNULL(work_order, '')
  533. """,
  534. new { Domain = domain, MetricCode = metricCode, StatDate = date, BatchId = batchId, Now = now, TenantId = tenantId, FactoryId = factoryId });
  535. }
  536. return affected;
  537. }
  538. public async Task<int> BuildInventoryDomainAsync(
  539. DateTime statDate, long tenantId, long factoryId, string? batchId, CancellationToken cancellationToken = default)
  540. {
  541. if (tenantId <= 0 || factoryId <= 0)
  542. throw new InvalidOperationException("S3 Atomic 必须指定有效 tenantId/factoryId");
  543. AidopTenantMigration.EnsureKpiAtomicTable(_db);
  544. var date = statDate.Date;
  545. var now = DateTime.Now;
  546. var affected = 0;
  547. var domain = SmartOpsKpiAggregateRuleRegistry.DomainInventory;
  548. affected += await _db.Ado.ExecuteCommandAsync(
  549. """
  550. DELETE FROM ado_smart_ops_kpi_atomic_day
  551. WHERE domain_code = @Domain AND stat_date = @StatDate
  552. AND tenant_id = @TenantId AND factory_id = @FactoryId
  553. """,
  554. new { Domain = domain, StatDate = date, TenantId = tenantId, FactoryId = factoryId });
  555. affected += await _db.Ado.ExecuteCommandAsync(
  556. """
  557. INSERT INTO ado_smart_ops_kpi_atomic_day
  558. (tenant_id, factory_id, domain_code, metric_code, metric_level, stat_date,
  559. warehouse_code, material_code, work_order_no,
  560. sum_value, sample_count, batch_id, calc_time, grain, is_deleted)
  561. SELECT tenant_id, factory_id, @Domain, 'S5_L1_001', 1, stat_date,
  562. '', IFNULL(component_item_code, ''), IFNULL(work_order, ''),
  563. ROUND(SUM(GREATEST(TIMESTAMPDIFF(DAY, stat_date, IFNULL(need_date, stat_date)), 0)), 6),
  564. COUNT(1), @BatchId, @Now, 'day', 0
  565. FROM dwd_material_readiness
  566. WHERE stat_date = @StatDate AND tenant_id = @TenantId AND factory_id = @FactoryId
  567. GROUP BY tenant_id, factory_id, stat_date, IFNULL(component_item_code, ''), IFNULL(work_order, '')
  568. """,
  569. new { Domain = domain, StatDate = date, BatchId = batchId, Now = now, TenantId = tenantId, FactoryId = factoryId });
  570. affected += await _db.Ado.ExecuteCommandAsync(
  571. """
  572. INSERT INTO ado_smart_ops_kpi_atomic_day
  573. (tenant_id, factory_id, domain_code, metric_code, metric_level, stat_date,
  574. warehouse_code, material_code, work_order_no,
  575. numerator_value, denominator_value, batch_id, calc_time, grain, is_deleted)
  576. SELECT tenant_id, factory_id, @Domain, 'S5_L1_002', 1, stat_date,
  577. '', IFNULL(component_item_code, ''), IFNULL(work_order, ''),
  578. SUM(CASE WHEN IFNULL(shortage_qty, 0) <= 0 OR UPPER(IFNULL(ready_status, '')) IN ('READY', 'SUFFICIENT', 'OK') THEN 1 ELSE 0 END),
  579. COUNT(1), @BatchId, @Now, 'day', 0
  580. FROM dwd_material_readiness
  581. WHERE stat_date = @StatDate AND tenant_id = @TenantId AND factory_id = @FactoryId
  582. GROUP BY tenant_id, factory_id, stat_date, IFNULL(component_item_code, ''), IFNULL(work_order, '')
  583. """,
  584. new { Domain = domain, StatDate = date, BatchId = batchId, Now = now, TenantId = tenantId, FactoryId = factoryId });
  585. affected += await _db.Ado.ExecuteCommandAsync(
  586. """
  587. INSERT INTO ado_smart_ops_kpi_atomic_day
  588. (tenant_id, factory_id, domain_code, metric_code, metric_level, stat_date,
  589. warehouse_code, material_code, work_order_no,
  590. numerator_value, denominator_value, batch_id, calc_time, grain, is_deleted)
  591. SELECT tenant_id, factory_id, @Domain, 'S5_L1_003', 1, stat_date,
  592. '', IFNULL(component_item_code, ''), IFNULL(work_order, ''),
  593. COUNT(1), GREATEST(COUNT(DISTINCT NULLIF(supplier_code, '')), 1),
  594. @BatchId, @Now, 'day', 0
  595. FROM dwd_material_readiness
  596. WHERE stat_date = @StatDate AND tenant_id = @TenantId AND factory_id = @FactoryId
  597. GROUP BY tenant_id, factory_id, stat_date, IFNULL(component_item_code, ''), IFNULL(work_order, '')
  598. """,
  599. new { Domain = domain, StatDate = date, BatchId = batchId, Now = now, TenantId = tenantId, FactoryId = factoryId });
  600. affected += await _db.Ado.ExecuteCommandAsync(
  601. """
  602. INSERT INTO ado_smart_ops_kpi_atomic_day
  603. (tenant_id, factory_id, domain_code, metric_code, metric_level, stat_date,
  604. warehouse_code, material_code, work_order_no,
  605. sum_value, sample_count, batch_id, calc_time, grain, is_deleted)
  606. SELECT tenant_id, factory_id, @Domain, 'S9_L1_005', 1, stat_date,
  607. '', IFNULL(component_item_code, ''), IFNULL(work_order, ''),
  608. ROUND(SUM(GREATEST(IFNULL(shortage_qty, 0), 0)), 6),
  609. COUNT(1), @BatchId, @Now, 'day', 0
  610. FROM dwd_material_readiness
  611. WHERE stat_date = @StatDate AND tenant_id = @TenantId AND factory_id = @FactoryId
  612. GROUP BY tenant_id, factory_id, stat_date, IFNULL(component_item_code, ''), IFNULL(work_order, '')
  613. """,
  614. new { Domain = domain, StatDate = date, BatchId = batchId, Now = now, TenantId = tenantId, FactoryId = factoryId });
  615. return affected;
  616. }
  617. }