S8OrderFlowManufacturingSeedData.cs 21 KB

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