S8OrderFlowManufacturingSeedData.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. using Admin.NET.Plugin.AiDOP.Service.S8.OrderFlow;
  2. using Admin.NET.Plugin.AiDOP.Entity.S8.OrderFlow;
  3. namespace Admin.NET.Plugin.AiDOP.SeedData;
  4. /// <summary>
  5. /// S8-ORDER-CHAIN-BODY-PRODUCTION-ORDER-LEVEL-SEED-FIX-1:本体生产「工序明细」/「损失因素」/「操作员表现」三表 seed。
  6. ///
  7. /// ──────────────────────────────────────────────────────────────────────
  8. /// S8-...-FIX-1S(阶段 2 失败修正):本项目实际 SEED 数据落地路径是 UpdateScripts SQL
  9. /// 的 DELETE+INSERT(与 procurement 1.0.148.sql 模式一致),不是 SqlSugar IncreSeed
  10. /// 自动写入。阶段 2 实测启动后三表创建成功但 IncreSeed 0 行写入。
  11. /// 处理:移除 IncreSeed attribute,保留本算法 SOT 作为 SQL 生成依据与未来 IMPORT/AGG
  12. /// 接入的算法 owner;本批次 221 行 SEED 数据由 WIP-S8-BODY-PRODUCTION-ORDER-LEVEL-SEED.sql
  13. /// 的 INSERT 语句落地。
  14. /// ──────────────────────────────────────────────────────────────────────
  15. ///
  16. /// baseline 行(order_id=NULL)来自 MANUFACTURING_DETAIL_FIXTURE 逐字段对齐:
  17. /// - process:(P10/P20/P30/P40/TOTAL) × (pi, actual, plan_qty, rate, status)
  18. /// - loss factor:(MATERIAL/EQUIPMENT/QUALITY/EFFICIENCY/SUBTOTAL) × (count, ratio_pct, loss_hours)
  19. /// - operator:(张/李/王) × (avg_hours, status)
  20. ///
  21. /// 订单级行(order_id≠NULL,scenario=ORDER_LEVEL):
  22. /// 对每个 BODY_PRODUCTION stage.status != "pending" 的订单(16 单),按 A=stage.actualDays 与 baseline 比例派生:
  23. /// ratio = A / 6.8
  24. /// process 行:
  25. /// baseline_actuals = [2.4, 0.8, 3.0, 0.6]
  26. /// actual[0..2] = round(baseline_actuals[i] * A / 6.8, 3)
  27. /// actual[3] = A - sum(actual[0..2]) // 守恒尾差修正
  28. /// plan_qty[i] = round(baseline_plan[i] * ratio)
  29. /// achievement_rate[i] = baseline_rate[i] (per-process 固定属性)
  30. /// achievement_status[i] = ≥0.95 green / ≥0.80 yellow / 否则 red (由 rate 派生)
  31. /// cycle_status[i] = ≤pi green / ≤pi*1.2 yellow / 否则 red (由 actual vs pi 派生)
  32. /// TOTAL 行:pi=6, actual=A, plan_qty=NULL,
  33. /// rate=0.9000 / status="yellow" 锚定 fixture (S8-...-FIX-1R),
  34. /// 不再做 plan_qty 加权计算,与 baseline TOTAL 一致;
  35. /// cycle_status 仍由 actual vs 6 派生。SEED 过渡口径,IMPORT/AGG 接入后替换。
  36. /// loss factor 行:
  37. /// count[i] = round(baseline_count[i] * ratio)
  38. /// ratio_pct[i] = baseline_ratio_pct[i] // 结构保留
  39. /// loss_hours[i] = round(baseline_loss_hours[i] * ratio, 2)
  40. /// SUBTOTAL: count=round(30*ratio), ratio_pct=100, loss_hours=round(7.5*ratio,2) // fixture-pinned 比例缩放
  41. /// operator 行:
  42. /// avg_hours[i] = round(baseline_hours[i] * ratio, 2)
  43. /// status[i] = baseline_status[i] // 每操作员固定 status
  44. ///
  45. /// 真实数据源(IMPORT / AGG)接入后,service 优先采用 IMPORT/AGG,同 (order_code, process/factor/operator) SEED 行仅兜底。
  46. /// </summary>
  47. // S8-...-FIX-1S:[IncreSeed] 已移除(项目实际不通过 SqlSugar IncreSeed 自动写入)。
  48. // 数据落地见 UpdateScripts/WIP-S8-BODY-PRODUCTION-ORDER-LEVEL-SEED.sql 的 INSERT。
  49. public class S8OrderFlowManufacturingProcessSeedData : ISqlSugarEntitySeedData<AdoS8OrderFlowManufacturingProcess>
  50. {
  51. public IEnumerable<AdoS8OrderFlowManufacturingProcess> HasData()
  52. => S8OrderFlowManufacturingDataset.BuildProcessBaselineRows()
  53. .Concat(S8OrderFlowManufacturingDataset.BuildProcessOrderLevelRows());
  54. }
  55. // S8-...-FIX-1S:[IncreSeed] 已移除(项目实际不通过 SqlSugar IncreSeed 自动写入)。
  56. // 数据落地见 UpdateScripts/WIP-S8-BODY-PRODUCTION-ORDER-LEVEL-SEED.sql 的 INSERT。
  57. public class S8OrderFlowManufacturingLossFactorSeedData : ISqlSugarEntitySeedData<AdoS8OrderFlowManufacturingLossFactor>
  58. {
  59. public IEnumerable<AdoS8OrderFlowManufacturingLossFactor> HasData()
  60. => S8OrderFlowManufacturingDataset.BuildLossFactorBaselineRows()
  61. .Concat(S8OrderFlowManufacturingDataset.BuildLossFactorOrderLevelRows());
  62. }
  63. // S8-...-FIX-1S:[IncreSeed] 已移除(项目实际不通过 SqlSugar IncreSeed 自动写入)。
  64. // 数据落地见 UpdateScripts/WIP-S8-BODY-PRODUCTION-ORDER-LEVEL-SEED.sql 的 INSERT。
  65. public class S8OrderFlowManufacturingOperatorSeedData : ISqlSugarEntitySeedData<AdoS8OrderFlowManufacturingOperator>
  66. {
  67. public IEnumerable<AdoS8OrderFlowManufacturingOperator> HasData()
  68. => S8OrderFlowManufacturingDataset.BuildOperatorBaselineRows()
  69. .Concat(S8OrderFlowManufacturingDataset.BuildOperatorOrderLevelRows());
  70. }
  71. internal static class S8OrderFlowManufacturingDataset
  72. {
  73. // ─────────────── ID base ranges(与 procurement-pivot 同号段策略) ───────────────
  74. internal const long ProcessBaselineIdBase = 1329909160000L;
  75. internal const long ProcessOrderLevelIdBase = 1329909170000L;
  76. internal const long LossFactorBaselineIdBase = 1329909180000L;
  77. internal const long LossFactorOrderLevelIdBase = 1329909190000L;
  78. internal const long OperatorBaselineIdBase = 1329909200000L;
  79. internal const long OperatorOrderLevelIdBase = 1329909210000L;
  80. internal const string ScenarioBaseline = "BASELINE_PPT";
  81. internal const string ScenarioOrderLevel = "ORDER_LEVEL";
  82. // BODY_PRODUCTION 在 5 阶段流水中固定为索引 3(order_review→product_design→material_procurement→body_production→final_assembly_shipping)。
  83. private const int BodyProductionStageIndex = 3;
  84. // ─────────────── baseline 真值(与 MANUFACTURING_DETAIL_FIXTURE 逐字段对齐) ───────────────
  85. private sealed record ProcessBaseline(
  86. int SortNo, string Code, string Name,
  87. decimal Pi, decimal Actual,
  88. int? PlanQty, decimal? Rate, string AchStatus);
  89. /// <summary>fixture 工序基线:(sort, code, name, pi, actual, plan, rate, ach_status)。</summary>
  90. private static readonly ProcessBaseline[] ProcessBaselines =
  91. {
  92. new(1, "P10", "10工序", 1.0m, 2.4m, 45, 0.5500m, "red"),
  93. new(2, "P20", "20工序", 1.0m, 0.8m, 3, 0.9700m, "yellow"),
  94. new(3, "P30", "30工序", 2.5m, 3.0m, 15, 0.8500m, "green"),
  95. new(4, "P40", "40工序", 1.5m, 0.6m, 2, 0.9800m, "red"),
  96. // TOTAL:pi=6, actual=6.8, plan_qty=NULL, rate=0.90 (fixture-pinned), achievement_status=yellow(来自 fixture 表格颜色)。
  97. new(5, "TOTAL", "合计", 6.0m, 6.8m, null, 0.9000m, "yellow"),
  98. };
  99. private sealed record LossFactorBaseline(
  100. int SortNo, string Code, string Name,
  101. int? Count, decimal? RatioPct, decimal? LossHours);
  102. /// <summary>fixture 损失因素基线:(sort, code, name, count, ratio_pct, loss_hours)。</summary>
  103. /// <remarks>SUBTOTAL loss_hours=7.50 来自 fixture,不等于 4 因素之和 26.15;保留 fixture 锚定。</remarks>
  104. private static readonly LossFactorBaseline[] LossFactorBaselines =
  105. {
  106. new(1, "MATERIAL", "材料影响", 9, 30.00m, 9.00m),
  107. new(2, "EQUIPMENT", "设备影响", 6, 20.00m, 4.50m),
  108. new(3, "QUALITY", "质量影响", 3, 10.00m, 3.40m),
  109. new(4, "EFFICIENCY", "作业效率损失", 12, 40.00m, 9.25m),
  110. new(5, "SUBTOTAL", "损失小计", 30, 100.00m, 7.50m),
  111. };
  112. private sealed record OperatorBaseline(int SortNo, string Code, string Name, decimal Hours, string Status);
  113. /// <summary>fixture 操作员基线:(sort, code, name, avg_hours, status)。</summary>
  114. private static readonly OperatorBaseline[] OperatorBaselines =
  115. {
  116. new(1, "OP_ZHANG", "张师傅", 20.00m, "red"),
  117. new(2, "OP_LI", "李师傅", 10.00m, "yellow"),
  118. new(3, "OP_WANG", "王师傅", 8.00m, "green"),
  119. };
  120. /// <summary>baseline TOTAL.actual_days(6.8)作为订单级 ratio = A/6.8 的归一化分母。</summary>
  121. private const decimal BaselineTotalActualDays = 6.8m;
  122. private const decimal BaselineTotalPiDays = 6.0m;
  123. // ────────────────────────────────────────────────────────────
  124. // Process 行
  125. // ────────────────────────────────────────────────────────────
  126. public static IEnumerable<AdoS8OrderFlowManufacturingProcess> BuildProcessBaselineRows()
  127. {
  128. long seq = 0;
  129. foreach (var b in ProcessBaselines)
  130. {
  131. yield return new AdoS8OrderFlowManufacturingProcess
  132. {
  133. Id = ProcessBaselineIdBase + (++seq),
  134. OrderId = null,
  135. OrderCode = null,
  136. ProcessCode = b.Code,
  137. ProcessName = b.Name,
  138. PiDays = b.Pi,
  139. ActualDays = b.Actual,
  140. CycleStatus = ClassifyCycleStatus(b.Actual, b.Pi),
  141. PlanQty = b.PlanQty,
  142. AchievementRate = b.Rate,
  143. AchievementStatus = b.AchStatus,
  144. SortNo = b.SortNo,
  145. ScenarioCode = ScenarioBaseline,
  146. DataSource = "SEED",
  147. TenantId = OrderFlowConstants.DemoTenantId,
  148. FactoryId = OrderFlowConstants.DemoFactoryId,
  149. CreatedAt = S8OrderFlowDataset.CreatedAt,
  150. UpdatedAt = null,
  151. IsDeleted = false,
  152. };
  153. }
  154. }
  155. public static IEnumerable<AdoS8OrderFlowManufacturingProcess> BuildProcessOrderLevelRows()
  156. {
  157. long seq = 0;
  158. foreach (var spec in S8OrderFlowDataset.Specs)
  159. {
  160. var lifecycle = S8OrderFlowStageDataset.BuildLifecycleValues(spec);
  161. var stage = lifecycle[BodyProductionStageIndex];
  162. if (stage.status == "pending") continue;
  163. var A = stage.actualDays;
  164. var ratio = A / BaselineTotalActualDays;
  165. var orderId = S8OrderFlowDataset.OrderIdBase + spec.Idx;
  166. // 4 个工序 actual:前 3 个直接缩放,第 4 个尾差修正以满足 sum == A。
  167. var actuals = new decimal[4];
  168. decimal sumFirstThree = 0m;
  169. for (var i = 0; i < 3; i++)
  170. {
  171. actuals[i] = decimal.Round(ProcessBaselines[i].Actual * ratio, 3);
  172. sumFirstThree += actuals[i];
  173. }
  174. actuals[3] = decimal.Round(A - sumFirstThree, 3);
  175. // 4 工序 plan_qty(TOTAL 行不计入;取 baseline.PlanQty 非 null 即可,TOTAL 是第 5 行)
  176. var planQtys = new int[4];
  177. for (var i = 0; i < 4; i++)
  178. {
  179. var basePlan = ProcessBaselines[i].PlanQty ?? 0;
  180. planQtys[i] = (int)decimal.Round(basePlan * ratio, MidpointRounding.AwayFromZero);
  181. }
  182. // 4 工序行
  183. for (var i = 0; i < 4; i++)
  184. {
  185. var baseRow = ProcessBaselines[i];
  186. var rate = baseRow.Rate!.Value;
  187. yield return new AdoS8OrderFlowManufacturingProcess
  188. {
  189. Id = ProcessOrderLevelIdBase + (++seq),
  190. OrderId = orderId,
  191. OrderCode = spec.OrderCode,
  192. ProcessCode = baseRow.Code,
  193. ProcessName = baseRow.Name,
  194. PiDays = baseRow.Pi,
  195. ActualDays = actuals[i],
  196. CycleStatus = ClassifyCycleStatus(actuals[i], baseRow.Pi),
  197. PlanQty = planQtys[i],
  198. AchievementRate = rate,
  199. AchievementStatus = ClassifyAchievementStatus(rate),
  200. SortNo = baseRow.SortNo,
  201. ScenarioCode = ScenarioOrderLevel,
  202. DataSource = "SEED",
  203. TenantId = OrderFlowConstants.DemoTenantId,
  204. FactoryId = OrderFlowConstants.DemoFactoryId,
  205. CreatedAt = S8OrderFlowDataset.CreatedAt,
  206. UpdatedAt = null,
  207. IsDeleted = false,
  208. };
  209. }
  210. // TOTAL 行:plan_qty=NULL。
  211. // S8-...-FIX-1R:achievement_rate / achievement_status 锚定 fixture(ProcessBaselines[4],
  212. // 即 0.9000 / yellow),不再用 4 工序 plan_qty 加权计算,与 baseline TOTAL 保持一致。
  213. // SEED 过渡口径;真实 IMPORT/AGG 接入后由真实生产达成数据替换。
  214. var fixtureTotalRate = ProcessBaselines[4].Rate!.Value; // 0.9000m
  215. var fixtureTotalStatus = ProcessBaselines[4].AchStatus; // "yellow"
  216. yield return new AdoS8OrderFlowManufacturingProcess
  217. {
  218. Id = ProcessOrderLevelIdBase + (++seq),
  219. OrderId = orderId,
  220. OrderCode = spec.OrderCode,
  221. ProcessCode = ProcessBaselines[4].Code,
  222. ProcessName = ProcessBaselines[4].Name,
  223. PiDays = BaselineTotalPiDays,
  224. ActualDays = decimal.Round(A, 3),
  225. CycleStatus = ClassifyCycleStatus(A, BaselineTotalPiDays),
  226. PlanQty = null,
  227. AchievementRate = fixtureTotalRate,
  228. AchievementStatus = fixtureTotalStatus,
  229. SortNo = ProcessBaselines[4].SortNo,
  230. ScenarioCode = ScenarioOrderLevel,
  231. DataSource = "SEED",
  232. TenantId = OrderFlowConstants.DemoTenantId,
  233. FactoryId = OrderFlowConstants.DemoFactoryId,
  234. CreatedAt = S8OrderFlowDataset.CreatedAt,
  235. UpdatedAt = null,
  236. IsDeleted = false,
  237. };
  238. }
  239. }
  240. // ────────────────────────────────────────────────────────────
  241. // Loss factor 行
  242. // ────────────────────────────────────────────────────────────
  243. public static IEnumerable<AdoS8OrderFlowManufacturingLossFactor> BuildLossFactorBaselineRows()
  244. {
  245. long seq = 0;
  246. foreach (var b in LossFactorBaselines)
  247. {
  248. yield return new AdoS8OrderFlowManufacturingLossFactor
  249. {
  250. Id = LossFactorBaselineIdBase + (++seq),
  251. OrderId = null,
  252. OrderCode = null,
  253. FactorCode = b.Code,
  254. FactorName = b.Name,
  255. CountValue = b.Count,
  256. RatioPct = b.RatioPct,
  257. LossHours = b.LossHours,
  258. SortNo = b.SortNo,
  259. ScenarioCode = ScenarioBaseline,
  260. DataSource = "SEED",
  261. TenantId = OrderFlowConstants.DemoTenantId,
  262. FactoryId = OrderFlowConstants.DemoFactoryId,
  263. CreatedAt = S8OrderFlowDataset.CreatedAt,
  264. UpdatedAt = null,
  265. IsDeleted = false,
  266. };
  267. }
  268. }
  269. public static IEnumerable<AdoS8OrderFlowManufacturingLossFactor> BuildLossFactorOrderLevelRows()
  270. {
  271. long seq = 0;
  272. foreach (var spec in S8OrderFlowDataset.Specs)
  273. {
  274. var lifecycle = S8OrderFlowStageDataset.BuildLifecycleValues(spec);
  275. var stage = lifecycle[BodyProductionStageIndex];
  276. if (stage.status == "pending") continue;
  277. var A = stage.actualDays;
  278. var ratio = A / BaselineTotalActualDays;
  279. var orderId = S8OrderFlowDataset.OrderIdBase + spec.Idx;
  280. foreach (var b in LossFactorBaselines)
  281. {
  282. int? count = b.Count.HasValue
  283. ? (int)decimal.Round(b.Count.Value * ratio, MidpointRounding.AwayFromZero)
  284. : null;
  285. decimal? hours = b.LossHours.HasValue
  286. ? decimal.Round(b.LossHours.Value * ratio, 2)
  287. : null;
  288. yield return new AdoS8OrderFlowManufacturingLossFactor
  289. {
  290. Id = LossFactorOrderLevelIdBase + (++seq),
  291. OrderId = orderId,
  292. OrderCode = spec.OrderCode,
  293. FactorCode = b.Code,
  294. FactorName = b.Name,
  295. CountValue = count,
  296. RatioPct = b.RatioPct, // 结构保留:baseline ratio_pct 原值复制
  297. LossHours = hours,
  298. SortNo = b.SortNo,
  299. ScenarioCode = ScenarioOrderLevel,
  300. DataSource = "SEED",
  301. TenantId = OrderFlowConstants.DemoTenantId,
  302. FactoryId = OrderFlowConstants.DemoFactoryId,
  303. CreatedAt = S8OrderFlowDataset.CreatedAt,
  304. UpdatedAt = null,
  305. IsDeleted = false,
  306. };
  307. }
  308. }
  309. }
  310. // ────────────────────────────────────────────────────────────
  311. // Operator 行
  312. // ────────────────────────────────────────────────────────────
  313. public static IEnumerable<AdoS8OrderFlowManufacturingOperator> BuildOperatorBaselineRows()
  314. {
  315. long seq = 0;
  316. foreach (var b in OperatorBaselines)
  317. {
  318. yield return new AdoS8OrderFlowManufacturingOperator
  319. {
  320. Id = OperatorBaselineIdBase + (++seq),
  321. OrderId = null,
  322. OrderCode = null,
  323. OperatorCode = b.Code,
  324. OperatorName = b.Name,
  325. AvgHours = b.Hours,
  326. Status = b.Status,
  327. SortNo = b.SortNo,
  328. ScenarioCode = ScenarioBaseline,
  329. DataSource = "SEED",
  330. TenantId = OrderFlowConstants.DemoTenantId,
  331. FactoryId = OrderFlowConstants.DemoFactoryId,
  332. CreatedAt = S8OrderFlowDataset.CreatedAt,
  333. UpdatedAt = null,
  334. IsDeleted = false,
  335. };
  336. }
  337. }
  338. public static IEnumerable<AdoS8OrderFlowManufacturingOperator> BuildOperatorOrderLevelRows()
  339. {
  340. long seq = 0;
  341. foreach (var spec in S8OrderFlowDataset.Specs)
  342. {
  343. var lifecycle = S8OrderFlowStageDataset.BuildLifecycleValues(spec);
  344. var stage = lifecycle[BodyProductionStageIndex];
  345. if (stage.status == "pending") continue;
  346. var A = stage.actualDays;
  347. var ratio = A / BaselineTotalActualDays;
  348. var orderId = S8OrderFlowDataset.OrderIdBase + spec.Idx;
  349. foreach (var b in OperatorBaselines)
  350. {
  351. var hours = decimal.Round(b.Hours * ratio, 2);
  352. yield return new AdoS8OrderFlowManufacturingOperator
  353. {
  354. Id = OperatorOrderLevelIdBase + (++seq),
  355. OrderId = orderId,
  356. OrderCode = spec.OrderCode,
  357. OperatorCode = b.Code,
  358. OperatorName = b.Name,
  359. AvgHours = hours,
  360. Status = b.Status, // 结构保留:每操作员固定 status
  361. SortNo = b.SortNo,
  362. ScenarioCode = ScenarioOrderLevel,
  363. DataSource = "SEED",
  364. TenantId = OrderFlowConstants.DemoTenantId,
  365. FactoryId = OrderFlowConstants.DemoFactoryId,
  366. CreatedAt = S8OrderFlowDataset.CreatedAt,
  367. UpdatedAt = null,
  368. IsDeleted = false,
  369. };
  370. }
  371. }
  372. }
  373. // ────────────────────────────────────────────────────────────
  374. // status classifiers(seed-time 一次性,运行期不再重判)
  375. // ────────────────────────────────────────────────────────────
  376. /// <summary>cycle_status:actual ≤ pi → green;≤ pi*1.2 → yellow;否则 red。</summary>
  377. private static string ClassifyCycleStatus(decimal actual, decimal pi)
  378. {
  379. if (actual <= pi) return "green";
  380. if (actual <= pi * 1.2m) return "yellow";
  381. return "red";
  382. }
  383. /// <summary>achievement_status:rate ≥ 0.95 green / ≥ 0.80 yellow / 否则 red。</summary>
  384. private static string ClassifyAchievementStatus(decimal rate)
  385. {
  386. if (rate >= 0.95m) return "green";
  387. if (rate >= 0.80m) return "yellow";
  388. return "red";
  389. }
  390. }