@tailor-platform/erp-kit 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/cli.mjs +139 -35
  3. package/package.json +1 -1
  4. package/skills/erp-kit-app-5-impl-backend/SKILL.md +10 -5
  5. package/skills/erp-kit-app-7-impl-review/SKILL.md +1 -1
  6. package/skills/erp-kit-module-6-impl-review/SKILL.md +39 -17
  7. package/src/commands/generate-doc.ts +1 -1
  8. package/src/commands/init-module.test.ts +17 -3
  9. package/src/commands/init-module.ts +0 -12
  10. package/src/commands/lib/discovery.test.ts +13 -3
  11. package/src/commands/lib/discovery.ts +10 -2
  12. package/src/commands/lib/paths.ts +4 -2
  13. package/src/commands/lib/sync-check-tests.test.ts +84 -6
  14. package/src/commands/lib/sync-check-tests.ts +63 -3
  15. package/src/commands/sync-check.ts +7 -3
  16. package/src/generator/generate-app-code.ts +51 -16
  17. package/src/generator/generate-code-boilerplate.test.ts +9 -1
  18. package/src/generator/generate-stubs.ts +4 -0
  19. package/src/generator/scaffold.ts +6 -2
  20. package/src/generator/stub-templates.test.ts +11 -0
  21. package/src/generator/stub-templates.ts +22 -1
  22. package/src/mdschema.ts +39 -3
  23. package/src/modules/inventory/docs/features/inventory-adjustment.md +2 -1
  24. package/src/modules/inventory/docs/features/scrap-management.md +39 -1
  25. package/src/modules/manufacturing/README.md +63 -0
  26. package/src/modules/manufacturing/command/activateBillOfMaterial.generated.ts +6 -0
  27. package/src/modules/manufacturing/command/activateBillOfMaterial.test.ts +166 -0
  28. package/src/modules/manufacturing/command/activateBillOfMaterial.ts +173 -0
  29. package/src/modules/manufacturing/command/activateRouting.generated.ts +6 -0
  30. package/src/modules/manufacturing/command/activateRouting.test.ts +152 -0
  31. package/src/modules/manufacturing/command/activateRouting.ts +92 -0
  32. package/src/modules/manufacturing/command/activateWorkCenter.generated.ts +6 -0
  33. package/src/modules/manufacturing/command/activateWorkCenter.test.ts +135 -0
  34. package/src/modules/manufacturing/command/activateWorkCenter.ts +91 -0
  35. package/src/modules/manufacturing/command/cancelProductionOrder.generated.ts +6 -0
  36. package/src/modules/manufacturing/command/cancelProductionOrder.test.ts +151 -0
  37. package/src/modules/manufacturing/command/cancelProductionOrder.ts +114 -0
  38. package/src/modules/manufacturing/command/closeProductionOrder.generated.ts +6 -0
  39. package/src/modules/manufacturing/command/closeProductionOrder.test.ts +126 -0
  40. package/src/modules/manufacturing/command/closeProductionOrder.ts +87 -0
  41. package/src/modules/manufacturing/command/completeProductionOrder.generated.ts +6 -0
  42. package/src/modules/manufacturing/command/completeProductionOrder.test.ts +132 -0
  43. package/src/modules/manufacturing/command/completeProductionOrder.ts +97 -0
  44. package/src/modules/manufacturing/command/completeWorkOrder.generated.ts +6 -0
  45. package/src/modules/manufacturing/command/completeWorkOrder.test.ts +369 -0
  46. package/src/modules/manufacturing/command/completeWorkOrder.ts +212 -0
  47. package/src/modules/manufacturing/command/createBillOfMaterial.generated.ts +6 -0
  48. package/src/modules/manufacturing/command/createBillOfMaterial.test.ts +210 -0
  49. package/src/modules/manufacturing/command/createBillOfMaterial.ts +176 -0
  50. package/src/modules/manufacturing/command/createProductionOrder.generated.ts +6 -0
  51. package/src/modules/manufacturing/command/createProductionOrder.test.ts +160 -0
  52. package/src/modules/manufacturing/command/createProductionOrder.ts +129 -0
  53. package/src/modules/manufacturing/command/createRouting.generated.ts +6 -0
  54. package/src/modules/manufacturing/command/createRouting.test.ts +168 -0
  55. package/src/modules/manufacturing/command/createRouting.ts +128 -0
  56. package/src/modules/manufacturing/command/createWorkCenter.generated.ts +6 -0
  57. package/src/modules/manufacturing/command/createWorkCenter.test.ts +148 -0
  58. package/src/modules/manufacturing/command/createWorkCenter.ts +131 -0
  59. package/src/modules/manufacturing/command/deactivateBillOfMaterial.generated.ts +6 -0
  60. package/src/modules/manufacturing/command/deactivateBillOfMaterial.test.ts +103 -0
  61. package/src/modules/manufacturing/command/deactivateBillOfMaterial.ts +78 -0
  62. package/src/modules/manufacturing/command/deactivateRouting.generated.ts +6 -0
  63. package/src/modules/manufacturing/command/deactivateRouting.test.ts +112 -0
  64. package/src/modules/manufacturing/command/deactivateRouting.ts +76 -0
  65. package/src/modules/manufacturing/command/deactivateWorkCenter.generated.ts +6 -0
  66. package/src/modules/manufacturing/command/deactivateWorkCenter.test.ts +113 -0
  67. package/src/modules/manufacturing/command/deactivateWorkCenter.ts +85 -0
  68. package/src/modules/manufacturing/command/pauseWorkOrder.generated.ts +6 -0
  69. package/src/modules/manufacturing/command/pauseWorkOrder.test.ts +118 -0
  70. package/src/modules/manufacturing/command/pauseWorkOrder.ts +82 -0
  71. package/src/modules/manufacturing/command/recordInventoryIssueOutcome.generated.ts +6 -0
  72. package/src/modules/manufacturing/command/recordInventoryIssueOutcome.test.ts +183 -0
  73. package/src/modules/manufacturing/command/recordInventoryIssueOutcome.ts +139 -0
  74. package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.generated.ts +6 -0
  75. package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.test.ts +120 -0
  76. package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.ts +110 -0
  77. package/src/modules/manufacturing/command/releaseProductionOrder.generated.ts +6 -0
  78. package/src/modules/manufacturing/command/releaseProductionOrder.test.ts +220 -0
  79. package/src/modules/manufacturing/command/releaseProductionOrder.ts +450 -0
  80. package/src/modules/manufacturing/command/reopenProductionOrder.generated.ts +6 -0
  81. package/src/modules/manufacturing/command/reopenProductionOrder.test.ts +196 -0
  82. package/src/modules/manufacturing/command/reopenProductionOrder.ts +98 -0
  83. package/src/modules/manufacturing/command/reportWorkOrderProgress.generated.ts +6 -0
  84. package/src/modules/manufacturing/command/reportWorkOrderProgress.test.ts +204 -0
  85. package/src/modules/manufacturing/command/reportWorkOrderProgress.ts +129 -0
  86. package/src/modules/manufacturing/command/rescheduleProductionOrder.generated.ts +6 -0
  87. package/src/modules/manufacturing/command/rescheduleProductionOrder.test.ts +185 -0
  88. package/src/modules/manufacturing/command/rescheduleProductionOrder.ts +95 -0
  89. package/src/modules/manufacturing/command/resumeWorkOrder.generated.ts +6 -0
  90. package/src/modules/manufacturing/command/resumeWorkOrder.test.ts +122 -0
  91. package/src/modules/manufacturing/command/resumeWorkOrder.ts +94 -0
  92. package/src/modules/manufacturing/command/reviewManufacturingCostSummary.generated.ts +6 -0
  93. package/src/modules/manufacturing/command/reviewManufacturingCostSummary.test.ts +231 -0
  94. package/src/modules/manufacturing/command/reviewManufacturingCostSummary.ts +137 -0
  95. package/src/modules/manufacturing/command/startWorkOrder.generated.ts +6 -0
  96. package/src/modules/manufacturing/command/startWorkOrder.test.ts +118 -0
  97. package/src/modules/manufacturing/command/startWorkOrder.ts +126 -0
  98. package/src/modules/manufacturing/command/technicallyCompleteProductionOrder.generated.ts +6 -0
  99. package/src/modules/manufacturing/command/technicallyCompleteProductionOrder.test.ts +153 -0
  100. package/src/modules/manufacturing/command/technicallyCompleteProductionOrder.ts +106 -0
  101. package/src/modules/manufacturing/command/unreleaseProductionOrder.generated.ts +6 -0
  102. package/src/modules/manufacturing/command/unreleaseProductionOrder.test.ts +140 -0
  103. package/src/modules/manufacturing/command/unreleaseProductionOrder.ts +131 -0
  104. package/src/modules/manufacturing/command/updateBillOfMaterial.generated.ts +6 -0
  105. package/src/modules/manufacturing/command/updateBillOfMaterial.test.ts +149 -0
  106. package/src/modules/manufacturing/command/updateBillOfMaterial.ts +174 -0
  107. package/src/modules/manufacturing/command/updateProductionOrder.generated.ts +6 -0
  108. package/src/modules/manufacturing/command/updateProductionOrder.test.ts +112 -0
  109. package/src/modules/manufacturing/command/updateProductionOrder.ts +145 -0
  110. package/src/modules/manufacturing/command/updateRouting.generated.ts +6 -0
  111. package/src/modules/manufacturing/command/updateRouting.test.ts +211 -0
  112. package/src/modules/manufacturing/command/updateRouting.ts +124 -0
  113. package/src/modules/manufacturing/command/updateWorkCenter.generated.ts +6 -0
  114. package/src/modules/manufacturing/command/updateWorkCenter.test.ts +152 -0
  115. package/src/modules/manufacturing/command/updateWorkCenter.ts +137 -0
  116. package/src/modules/manufacturing/db/.gitkeep +0 -0
  117. package/src/modules/manufacturing/db/billOfMaterial.ts +70 -0
  118. package/src/modules/manufacturing/db/billOfMaterialLine.ts +49 -0
  119. package/src/modules/manufacturing/db/costVarianceLine.ts +53 -0
  120. package/src/modules/manufacturing/db/manufacturingCostLine.ts +35 -0
  121. package/src/modules/manufacturing/db/manufacturingCostSettlementRecord.ts +39 -0
  122. package/src/modules/manufacturing/db/manufacturingCostSummary.ts +59 -0
  123. package/src/modules/manufacturing/db/productionOrder.ts +83 -0
  124. package/src/modules/manufacturing/db/productionOrderBomSnapshot.ts +44 -0
  125. package/src/modules/manufacturing/db/productionOrderCostBaseline.ts +44 -0
  126. package/src/modules/manufacturing/db/productionOrderMaterialRequirement.ts +57 -0
  127. package/src/modules/manufacturing/db/productionOrderRoutingSnapshot.ts +43 -0
  128. package/src/modules/manufacturing/db/routing.ts +63 -0
  129. package/src/modules/manufacturing/db/routingOperation.ts +57 -0
  130. package/src/modules/manufacturing/db/workCenter.ts +87 -0
  131. package/src/modules/manufacturing/db/workOrder.ts +65 -0
  132. package/src/modules/manufacturing/db/workOrderExecutionEvent.ts +54 -0
  133. package/src/modules/manufacturing/docs/commands/ActivateBillOfMaterial.md +50 -0
  134. package/src/modules/manufacturing/docs/commands/ActivateRouting.md +48 -0
  135. package/src/modules/manufacturing/docs/commands/ActivateWorkCenter.md +49 -0
  136. package/src/modules/manufacturing/docs/commands/CancelProductionOrder.md +48 -0
  137. package/src/modules/manufacturing/docs/commands/CloseProductionOrder.md +46 -0
  138. package/src/modules/manufacturing/docs/commands/CompleteProductionOrder.md +48 -0
  139. package/src/modules/manufacturing/docs/commands/CompleteWorkOrder.md +66 -0
  140. package/src/modules/manufacturing/docs/commands/CreateBillOfMaterial.md +54 -0
  141. package/src/modules/manufacturing/docs/commands/CreateProductionOrder.md +49 -0
  142. package/src/modules/manufacturing/docs/commands/CreateRouting.md +50 -0
  143. package/src/modules/manufacturing/docs/commands/CreateWorkCenter.md +51 -0
  144. package/src/modules/manufacturing/docs/commands/DeactivateBillOfMaterial.md +45 -0
  145. package/src/modules/manufacturing/docs/commands/DeactivateRouting.md +45 -0
  146. package/src/modules/manufacturing/docs/commands/DeactivateWorkCenter.md +45 -0
  147. package/src/modules/manufacturing/docs/commands/PauseWorkOrder.md +44 -0
  148. package/src/modules/manufacturing/docs/commands/RecordInventoryIssueOutcome.md +59 -0
  149. package/src/modules/manufacturing/docs/commands/RecordManufacturingCostSettlementAcknowledgment.md +49 -0
  150. package/src/modules/manufacturing/docs/commands/ReleaseProductionOrder.md +57 -0
  151. package/src/modules/manufacturing/docs/commands/ReopenProductionOrder.md +54 -0
  152. package/src/modules/manufacturing/docs/commands/ReportWorkOrderProgress.md +53 -0
  153. package/src/modules/manufacturing/docs/commands/RescheduleProductionOrder.md +45 -0
  154. package/src/modules/manufacturing/docs/commands/ResumeWorkOrder.md +44 -0
  155. package/src/modules/manufacturing/docs/commands/ReviewManufacturingCostSummary.md +52 -0
  156. package/src/modules/manufacturing/docs/commands/StartWorkOrder.md +46 -0
  157. package/src/modules/manufacturing/docs/commands/TechnicallyCompleteProductionOrder.md +51 -0
  158. package/src/modules/manufacturing/docs/commands/UnreleaseProductionOrder.md +46 -0
  159. package/src/modules/manufacturing/docs/commands/UpdateBillOfMaterial.md +48 -0
  160. package/src/modules/manufacturing/docs/commands/UpdateProductionOrder.md +48 -0
  161. package/src/modules/manufacturing/docs/commands/UpdateRouting.md +52 -0
  162. package/src/modules/manufacturing/docs/commands/UpdateWorkCenter.md +48 -0
  163. package/src/modules/manufacturing/docs/features/bill-of-material-management.md +83 -0
  164. package/src/modules/manufacturing/docs/features/manufacturing-cost-and-variance.md +191 -0
  165. package/src/modules/manufacturing/docs/features/production-order-lifecycle.md +103 -0
  166. package/src/modules/manufacturing/docs/features/routing-and-work-center-definition.md +63 -0
  167. package/src/modules/manufacturing/docs/features/work-order-execution.md +115 -0
  168. package/src/modules/manufacturing/docs/models/BillOfMaterial.md +60 -0
  169. package/src/modules/manufacturing/docs/models/ManufacturingCostSummary.md +66 -0
  170. package/src/modules/manufacturing/docs/models/ProductionOrder.md +76 -0
  171. package/src/modules/manufacturing/docs/models/Routing.md +58 -0
  172. package/src/modules/manufacturing/docs/models/WorkCenter.md +56 -0
  173. package/src/modules/manufacturing/docs/models/WorkOrder.md +63 -0
  174. package/src/modules/manufacturing/docs/queries/DetectBillOfMaterialCircularReference.md +39 -0
  175. package/src/modules/manufacturing/docs/queries/ExplodeBillOfMaterial.md +56 -0
  176. package/src/modules/manufacturing/docs/queries/GetBillOfMaterial.md +37 -0
  177. package/src/modules/manufacturing/docs/queries/GetManufacturingCostSummary.md +39 -0
  178. package/src/modules/manufacturing/docs/queries/GetProductionOrder.md +37 -0
  179. package/src/modules/manufacturing/docs/queries/GetRouting.md +39 -0
  180. package/src/modules/manufacturing/docs/queries/GetWorkCenter.md +35 -0
  181. package/src/modules/manufacturing/docs/queries/GetWorkOrder.md +38 -0
  182. package/src/modules/manufacturing/docs/queries/ListBillOfMaterialsByItem.md +42 -0
  183. package/src/modules/manufacturing/docs/queries/ListManufacturingCostSummariesByStatus.md +41 -0
  184. package/src/modules/manufacturing/docs/queries/ListProductionOrdersByStatus.md +41 -0
  185. package/src/modules/manufacturing/docs/queries/ListRoutingsByItem.md +42 -0
  186. package/src/modules/manufacturing/docs/queries/ListWorkCentersBySite.md +38 -0
  187. package/src/modules/manufacturing/docs/queries/ListWorkOrdersByProductionOrder.md +39 -0
  188. package/src/modules/manufacturing/docs/queries/ListWorkOrdersByWorkCenter.md +43 -0
  189. package/src/modules/manufacturing/executor/.gitkeep +0 -0
  190. package/src/modules/manufacturing/generated/enums.ts +113 -0
  191. package/src/modules/manufacturing/generated/kysely-tailordb.ts +247 -0
  192. package/src/modules/manufacturing/index.ts +2 -0
  193. package/src/modules/manufacturing/lib/_db_deps.ts +22 -0
  194. package/src/modules/manufacturing/lib/errors.generated.ts +592 -0
  195. package/src/modules/manufacturing/lib/permissions.generated.ts +35 -0
  196. package/src/modules/manufacturing/lib/types.ts +111 -0
  197. package/src/modules/manufacturing/module.ts +226 -0
  198. package/src/modules/manufacturing/permissions.ts +3 -0
  199. package/src/modules/manufacturing/query/.gitkeep +0 -0
  200. package/src/modules/manufacturing/query/detectBillOfMaterialCircularReference.generated.ts +5 -0
  201. package/src/modules/manufacturing/query/detectBillOfMaterialCircularReference.test.ts +115 -0
  202. package/src/modules/manufacturing/query/detectBillOfMaterialCircularReference.ts +79 -0
  203. package/src/modules/manufacturing/query/explodeBillOfMaterial.generated.ts +5 -0
  204. package/src/modules/manufacturing/query/explodeBillOfMaterial.test.ts +445 -0
  205. package/src/modules/manufacturing/query/explodeBillOfMaterial.ts +306 -0
  206. package/src/modules/manufacturing/query/getBillOfMaterial.generated.ts +5 -0
  207. package/src/modules/manufacturing/query/getBillOfMaterial.test.ts +64 -0
  208. package/src/modules/manufacturing/query/getBillOfMaterial.ts +27 -0
  209. package/src/modules/manufacturing/query/getManufacturingCostSummary.generated.ts +5 -0
  210. package/src/modules/manufacturing/query/getManufacturingCostSummary.test.ts +147 -0
  211. package/src/modules/manufacturing/query/getManufacturingCostSummary.ts +46 -0
  212. package/src/modules/manufacturing/query/getProductionOrder.generated.ts +5 -0
  213. package/src/modules/manufacturing/query/getProductionOrder.test.ts +139 -0
  214. package/src/modules/manufacturing/query/getProductionOrder.ts +84 -0
  215. package/src/modules/manufacturing/query/getRouting.generated.ts +5 -0
  216. package/src/modules/manufacturing/query/getRouting.test.ts +71 -0
  217. package/src/modules/manufacturing/query/getRouting.ts +34 -0
  218. package/src/modules/manufacturing/query/getWorkCenter.generated.ts +5 -0
  219. package/src/modules/manufacturing/query/getWorkCenter.test.ts +37 -0
  220. package/src/modules/manufacturing/query/getWorkCenter.ts +21 -0
  221. package/src/modules/manufacturing/query/getWorkOrder.generated.ts +5 -0
  222. package/src/modules/manufacturing/query/getWorkOrder.test.ts +73 -0
  223. package/src/modules/manufacturing/query/getWorkOrder.ts +28 -0
  224. package/src/modules/manufacturing/query/listBillOfMaterialsByItem.generated.ts +5 -0
  225. package/src/modules/manufacturing/query/listBillOfMaterialsByItem.test.ts +107 -0
  226. package/src/modules/manufacturing/query/listBillOfMaterialsByItem.ts +58 -0
  227. package/src/modules/manufacturing/query/listManufacturingCostSummariesByStatus.generated.ts +5 -0
  228. package/src/modules/manufacturing/query/listManufacturingCostSummariesByStatus.test.ts +96 -0
  229. package/src/modules/manufacturing/query/listManufacturingCostSummariesByStatus.ts +77 -0
  230. package/src/modules/manufacturing/query/listProductionOrdersByStatus.generated.ts +5 -0
  231. package/src/modules/manufacturing/query/listProductionOrdersByStatus.test.ts +121 -0
  232. package/src/modules/manufacturing/query/listProductionOrdersByStatus.ts +83 -0
  233. package/src/modules/manufacturing/query/listRoutingsByItem.generated.ts +5 -0
  234. package/src/modules/manufacturing/query/listRoutingsByItem.test.ts +110 -0
  235. package/src/modules/manufacturing/query/listRoutingsByItem.ts +54 -0
  236. package/src/modules/manufacturing/query/listWorkCentersBySite.generated.ts +5 -0
  237. package/src/modules/manufacturing/query/listWorkCentersBySite.test.ts +81 -0
  238. package/src/modules/manufacturing/query/listWorkCentersBySite.ts +70 -0
  239. package/src/modules/manufacturing/query/listWorkOrdersByProductionOrder.generated.ts +5 -0
  240. package/src/modules/manufacturing/query/listWorkOrdersByProductionOrder.test.ts +102 -0
  241. package/src/modules/manufacturing/query/listWorkOrdersByProductionOrder.ts +53 -0
  242. package/src/modules/manufacturing/query/listWorkOrdersByWorkCenter.generated.ts +5 -0
  243. package/src/modules/manufacturing/query/listWorkOrdersByWorkCenter.test.ts +143 -0
  244. package/src/modules/manufacturing/query/listWorkOrdersByWorkCenter.ts +56 -0
  245. package/src/modules/manufacturing/seed/index.ts +19 -0
  246. package/src/modules/manufacturing/tailor.config.ts +13 -0
  247. package/src/modules/manufacturing/tailor.d.ts +13 -0
  248. package/src/modules/manufacturing/testing/commandTestUtils.ts +29 -0
  249. package/src/modules/manufacturing/testing/fixtures.ts +402 -0
  250. package/templates/scaffold/app/backend/package.json +9 -2
  251. package/templates/scaffold/app/backend/src/tests/utils/graphql-client.ts +66 -0
  252. package/templates/scaffold/app/backend/src/tests/utils/setup.ts +21 -0
  253. package/templates/scaffold/app/backend/tsconfig.json +9 -2
  254. package/templates/scaffold/app/backend/vitest.config.ts +35 -0
  255. package/templates/scaffold/app/frontend/package.json +2 -2
  256. package/templates/scaffold/module/__dot__gitignore +3 -0
  257. package/templates/scaffold/module/eslint.config.js +31 -0
  258. package/templates/scaffold/module/generated/kysely-tailordb.ts +3 -0
  259. package/templates/scaffold/module/lib/types.ts +1 -6
  260. package/templates/scaffold/module/package.json +26 -0
  261. package/templates/scaffold/module/tsconfig.json +16 -0
  262. /package/{templates/scaffold/module/generated → src/modules/manufacturing/command}/.gitkeep +0 -0
@@ -0,0 +1,95 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ ProductionOrderNotFoundError,
4
+ ProductionOrderNotReschedulableError,
5
+ ExecutionAlreadyStartedError,
6
+ InvalidDateRangeError,
7
+ } from "../lib/errors.generated";
8
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
9
+
10
+ export interface RescheduleProductionOrderInput {
11
+ id: string;
12
+ plannedStartDate: Date;
13
+ plannedEndDate: Date;
14
+ from?: string[];
15
+ }
16
+
17
+ /**
18
+ * Function: rescheduleProductionOrder
19
+ *
20
+ * Changes the planned execution dates on a released order before execution
21
+ * starts. Preserves the released snapshots and keeps the change auditable.
22
+ */
23
+ export async function run<CF extends Record<string, unknown>>(
24
+ db: Transaction,
25
+ input: RescheduleProductionOrderInput & CF,
26
+ _ctx: CommandContext,
27
+ ) {
28
+ const { id, plannedStartDate, plannedEndDate, from, ...customFields } = input;
29
+ void customFields;
30
+
31
+ const allowedStatuses = from ?? ["RELEASED"];
32
+
33
+ // 1. Fetch production order with lock
34
+ const order = await db
35
+ .selectFrom("ProductionOrder")
36
+ .selectAll()
37
+ .where("id", "=", id)
38
+ .forUpdate()
39
+ .executeTakeFirst();
40
+
41
+ if (!order) {
42
+ return err(new ProductionOrderNotFoundError(id));
43
+ }
44
+
45
+ // 2. Validate status is reschedulable
46
+ if (!allowedStatuses.includes(order.status)) {
47
+ return err(new ProductionOrderNotReschedulableError(id));
48
+ }
49
+
50
+ // 3. Check no work order has execution evidence
51
+ const workOrders = await db
52
+ .selectFrom("WorkOrder")
53
+ .selectAll()
54
+ .where("productionOrderId", "=", id)
55
+ .execute();
56
+
57
+ const hasExecution = workOrders.some(
58
+ (wo) =>
59
+ wo.status === "IN_PROGRESS" ||
60
+ wo.status === "PAUSED" ||
61
+ wo.status === "COMPLETE" ||
62
+ wo.completedQuantity > 0 ||
63
+ wo.actualStartDate != null,
64
+ );
65
+
66
+ if (hasExecution) {
67
+ return err(new ExecutionAlreadyStartedError(id));
68
+ }
69
+
70
+ // 4. Validate revised dates
71
+ if (!plannedStartDate || !plannedEndDate) {
72
+ return err(new InvalidDateRangeError(id));
73
+ }
74
+
75
+ const start = new Date(plannedStartDate);
76
+ const end = new Date(plannedEndDate);
77
+
78
+ if (isNaN(start.getTime()) || isNaN(end.getTime()) || start >= end) {
79
+ return err(new InvalidDateRangeError(id));
80
+ }
81
+
82
+ // 5. Persist scheduling update
83
+ const rescheduled = await db
84
+ .updateTable("ProductionOrder")
85
+ .set({
86
+ plannedStartDate,
87
+ plannedEndDate,
88
+ updatedAt: new Date(),
89
+ })
90
+ .where("id", "=", id)
91
+ .returningAll()
92
+ .executeTakeFirstOrThrow();
93
+
94
+ return ok({ productionOrder: rescheduled });
95
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./resumeWorkOrder";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const resumeWorkOrder = defineCommand(permissions.resumeWorkOrder, run);
@@ -0,0 +1,122 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import {
5
+ WorkOrderNotFoundError,
6
+ WorkOrderNotResumableError,
7
+ ParentOrderNotExecutableError,
8
+ } from "../lib/errors.generated";
9
+ import {
10
+ basePausedWorkOrder,
11
+ basePendingWorkOrder,
12
+ baseInProgressProductionOrder,
13
+ baseDraftProductionOrder,
14
+ } from "../testing/fixtures";
15
+ import { run } from "./resumeWorkOrder";
16
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
17
+
18
+ describe("resumeWorkOrder", () => {
19
+ const ctx: CommandContext = {
20
+ actorId: "test-actor",
21
+ permissions: ["manufacturing:resumeWorkOrder"],
22
+ };
23
+
24
+ it("resumes a paused work order", async () => {
25
+ const { db, spies } = createMockDb<Transaction>();
26
+ const resumedWorkOrder = {
27
+ ...basePausedWorkOrder,
28
+ status: "IN_PROGRESS" as const,
29
+ pauseReason: null,
30
+ };
31
+
32
+ // work order lookup
33
+ spies.select.mockReturnValueOnce(basePausedWorkOrder);
34
+ // parent production order lookup
35
+ spies.select.mockReturnValueOnce(baseInProgressProductionOrder);
36
+ // update work order
37
+ spies.update.mockReturnValueOnce(resumedWorkOrder);
38
+ // insert execution event
39
+ spies.insert.mockReturnValueOnce(undefined);
40
+
41
+ const result = await run(db, { id: basePausedWorkOrder.id }, ctx);
42
+
43
+ expect(result.ok).toBe(true);
44
+ if (result.ok) {
45
+ expect(result.value.workOrder.status).toBe("IN_PROGRESS");
46
+ expect(result.value.workOrder.pauseReason).toBeNull();
47
+ }
48
+ expect(spies.update).toHaveBeenCalled();
49
+ expect(spies.insert).toHaveBeenCalled();
50
+ });
51
+
52
+ it("returns error when the work order does not exist", async () => {
53
+ const { db, spies } = createMockDb<Transaction>();
54
+ spies.select.mockReturnValueOnce(undefined);
55
+
56
+ const result = await run(db, { id: "nonexistent" }, ctx);
57
+
58
+ expect(result.ok).toBe(false);
59
+ if (!result.ok) {
60
+ expect(result.error).toBeInstanceOf(WorkOrderNotFoundError);
61
+ }
62
+ });
63
+
64
+ it("returns error when the work order is not paused", async () => {
65
+ const { db, spies } = createMockDb<Transaction>();
66
+ spies.select.mockReturnValueOnce(basePendingWorkOrder);
67
+
68
+ const result = await run(db, { id: basePendingWorkOrder.id }, ctx);
69
+
70
+ expect(result.ok).toBe(false);
71
+ if (!result.ok) {
72
+ expect(result.error).toBeInstanceOf(WorkOrderNotResumableError);
73
+ }
74
+ });
75
+
76
+ it("returns error when the parent production order no longer allows execution", async () => {
77
+ const { db, spies } = createMockDb<Transaction>();
78
+
79
+ // work order lookup
80
+ spies.select.mockReturnValueOnce(basePausedWorkOrder);
81
+ // parent production order - DRAFT is not executable
82
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
83
+
84
+ const result = await run(db, { id: basePausedWorkOrder.id }, ctx);
85
+
86
+ expect(result.ok).toBe(false);
87
+ if (!result.ok) {
88
+ expect(result.error).toBeInstanceOf(ParentOrderNotExecutableError);
89
+ }
90
+ });
91
+
92
+ it("preserves prior execution history after resume", async () => {
93
+ const { db, spies } = createMockDb<Transaction>();
94
+ const resumedWorkOrder = {
95
+ ...basePausedWorkOrder,
96
+ status: "IN_PROGRESS" as const,
97
+ pauseReason: null,
98
+ completedQuantity: basePausedWorkOrder.completedQuantity,
99
+ scrapQuantity: basePausedWorkOrder.scrapQuantity,
100
+ actualSetupTime: basePausedWorkOrder.actualSetupTime,
101
+ actualRunTime: basePausedWorkOrder.actualRunTime,
102
+ };
103
+
104
+ // work order lookup
105
+ spies.select.mockReturnValueOnce(basePausedWorkOrder);
106
+ // parent production order lookup
107
+ spies.select.mockReturnValueOnce(baseInProgressProductionOrder);
108
+ // update work order
109
+ spies.update.mockReturnValueOnce(resumedWorkOrder);
110
+ // insert execution event
111
+ spies.insert.mockReturnValueOnce(undefined);
112
+
113
+ const result = await run(db, { id: basePausedWorkOrder.id }, ctx);
114
+
115
+ expect(result.ok).toBe(true);
116
+ if (result.ok) {
117
+ expect(result.value.workOrder.completedQuantity).toBe(basePausedWorkOrder.completedQuantity);
118
+ expect(result.value.workOrder.actualSetupTime).toBe(basePausedWorkOrder.actualSetupTime);
119
+ expect(result.value.workOrder.actualRunTime).toBe(basePausedWorkOrder.actualRunTime);
120
+ }
121
+ });
122
+ });
@@ -0,0 +1,94 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ WorkOrderNotFoundError,
4
+ WorkOrderNotResumableError,
5
+ ParentOrderNotExecutableError,
6
+ } from "../lib/errors.generated";
7
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
8
+
9
+ const EXECUTABLE_ORDER_STATUSES = ["RELEASED", "IN_PROGRESS"] as const;
10
+
11
+ export interface ResumeWorkOrderInput {
12
+ id: string;
13
+ }
14
+
15
+ /**
16
+ * Function: resumeWorkOrder
17
+ *
18
+ * Restarts a paused work order without losing the accumulated execution
19
+ * history recorded before the interruption. Clears the pause reason.
20
+ */
21
+ export async function run<CF extends Record<string, unknown>>(
22
+ db: Transaction,
23
+ input: ResumeWorkOrderInput & CF,
24
+ _ctx: CommandContext,
25
+ ) {
26
+ const { id, ...customFields } = input;
27
+ void customFields;
28
+
29
+ // 1. Fetch work order with lock
30
+ const workOrder = await db
31
+ .selectFrom("WorkOrder")
32
+ .selectAll()
33
+ .where("id", "=", id)
34
+ .forUpdate()
35
+ .executeTakeFirst();
36
+
37
+ if (!workOrder) {
38
+ return err(new WorkOrderNotFoundError(id));
39
+ }
40
+
41
+ // 2. Validate status is PAUSED
42
+ if (workOrder.status !== "PAUSED") {
43
+ return err(new WorkOrderNotResumableError(id));
44
+ }
45
+
46
+ // 3. Check parent production order is still execution-capable
47
+ const parentOrder = await db
48
+ .selectFrom("ProductionOrder")
49
+ .selectAll()
50
+ .where("id", "=", workOrder.productionOrderId)
51
+ .forUpdate()
52
+ .executeTakeFirst();
53
+
54
+ if (
55
+ !parentOrder ||
56
+ !EXECUTABLE_ORDER_STATUSES.includes(
57
+ parentOrder.status as (typeof EXECUTABLE_ORDER_STATUSES)[number],
58
+ )
59
+ ) {
60
+ return err(new ParentOrderNotExecutableError(id));
61
+ }
62
+
63
+ // 4. Record resume event and set IN_PROGRESS, clear pause reason
64
+ const now = new Date();
65
+
66
+ const updatedWorkOrder = await db
67
+ .updateTable("WorkOrder")
68
+ .set({
69
+ status: "IN_PROGRESS",
70
+ pauseReason: null,
71
+ updatedAt: now,
72
+ })
73
+ .where("id", "=", id)
74
+ .returningAll()
75
+ .executeTakeFirstOrThrow();
76
+
77
+ // 5. Create RESUMED execution event
78
+ await db
79
+ .insertInto("WorkOrderExecutionEvent")
80
+ .values({
81
+ workOrderId: id,
82
+ eventType: "RESUMED",
83
+ timestamp: now,
84
+ quantity: null,
85
+ timeValue: null,
86
+ scrapValue: null,
87
+ notes: null,
88
+ createdAt: now,
89
+ updatedAt: null,
90
+ })
91
+ .execute();
92
+
93
+ return ok({ workOrder: updatedWorkOrder });
94
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./reviewManufacturingCostSummary";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const reviewManufacturingCostSummary = defineCommand(permissions.reviewManufacturingCostSummary, run);
@@ -0,0 +1,231 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import {
5
+ CostSummaryNotFoundError,
6
+ CostSummaryNotReviewableError,
7
+ ParentOrderNotTechnicallyCompleteError,
8
+ IncompleteVarianceBreakdownError,
9
+ ReviewerRequiredError,
10
+ VarianceCalculationFailedError,
11
+ } from "../lib/errors.generated";
12
+ import {
13
+ basePendingReviewCostSummary,
14
+ baseCollectingCostSummary,
15
+ baseTechCompleteProductionOrder,
16
+ baseInProgressProductionOrder,
17
+ } from "../testing/fixtures";
18
+ import { run } from "./reviewManufacturingCostSummary";
19
+ import type { VarianceBreakdownEntry } from "./reviewManufacturingCostSummary";
20
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
21
+
22
+ describe("reviewManufacturingCostSummary", () => {
23
+ const ctx: CommandContext = {
24
+ actorId: "test-actor",
25
+ permissions: ["manufacturing:reviewManufacturingCostSummary"],
26
+ };
27
+
28
+ const fullVarianceBreakdown: VarianceBreakdownEntry[] = [
29
+ {
30
+ varianceType: "MATERIAL_PRICE",
31
+ amount: 100,
32
+ accountReference: "acct-5001",
33
+ variancePercentage: 5.0,
34
+ },
35
+ {
36
+ varianceType: "MATERIAL_USAGE",
37
+ amount: -20,
38
+ accountReference: "acct-5002",
39
+ variancePercentage: -1.0,
40
+ },
41
+ {
42
+ varianceType: "LABOR_RATE",
43
+ amount: 30,
44
+ accountReference: "acct-5003",
45
+ variancePercentage: 6.0,
46
+ },
47
+ {
48
+ varianceType: "LABOR_EFFICIENCY",
49
+ amount: -10,
50
+ accountReference: "acct-5004",
51
+ variancePercentage: -2.0,
52
+ },
53
+ {
54
+ varianceType: "MACHINE_RATE",
55
+ amount: 50,
56
+ accountReference: "acct-5005",
57
+ variancePercentage: 5.0,
58
+ },
59
+ {
60
+ varianceType: "MACHINE_EFFICIENCY",
61
+ amount: 0,
62
+ accountReference: "acct-5006",
63
+ variancePercentage: 0.0,
64
+ },
65
+ { varianceType: "SCRAP", amount: 50, accountReference: "acct-5007", variancePercentage: 100.0 },
66
+ { varianceType: "YIELD", amount: 0, accountReference: "acct-5008", variancePercentage: 0.0 },
67
+ ];
68
+
69
+ const validInput = {
70
+ costSummaryId: "cost-summary-2",
71
+ reviewerId: "reviewer-1",
72
+ reviewerNotes: "Variances within acceptable range",
73
+ varianceBreakdown: fullVarianceBreakdown,
74
+ };
75
+
76
+ it("reviews a pending cost summary and freezes the variance breakdown", async () => {
77
+ const { db, spies } = createMockDb<Transaction>();
78
+
79
+ // select: cost summary, production order
80
+ spies.select.mockReturnValueOnce(basePendingReviewCostSummary);
81
+ spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
82
+
83
+ const updatedSummary = {
84
+ ...basePendingReviewCostSummary,
85
+ status: "VARIANCE_REVIEWED",
86
+ reviewerNotes: "Variances within acceptable range",
87
+ };
88
+ spies.insert.mockReturnValue({});
89
+ spies.update.mockReturnValue(updatedSummary);
90
+
91
+ const result = await run(db, validInput, ctx);
92
+
93
+ expect(result.ok).toBe(true);
94
+ if (result.ok) {
95
+ expect(result.value.costSummary.status).toBe("VARIANCE_REVIEWED");
96
+ }
97
+ // 8 variance lines inserted
98
+ expect(spies.insert).toHaveBeenCalledTimes(8);
99
+ expect(spies.update).toHaveBeenCalled();
100
+ });
101
+
102
+ it("returns error when the summary does not exist", async () => {
103
+ const { db, spies } = createMockDb<Transaction>();
104
+
105
+ spies.select.mockReturnValueOnce(undefined);
106
+
107
+ const result = await run(db, validInput, ctx);
108
+
109
+ expect(result.ok).toBe(false);
110
+ if (!result.ok) {
111
+ expect(result.error).toBeInstanceOf(CostSummaryNotFoundError);
112
+ }
113
+ });
114
+
115
+ it("returns error when the summary is not pending review", async () => {
116
+ const { db, spies } = createMockDb<Transaction>();
117
+
118
+ spies.select.mockReturnValueOnce(baseCollectingCostSummary); // COLLECTING, not PENDING_VARIANCE_REVIEW
119
+
120
+ const result = await run(db, validInput, ctx);
121
+
122
+ expect(result.ok).toBe(false);
123
+ if (!result.ok) {
124
+ expect(result.error).toBeInstanceOf(CostSummaryNotReviewableError);
125
+ }
126
+ });
127
+
128
+ it("returns error when the parent order is not technically complete", async () => {
129
+ const { db, spies } = createMockDb<Transaction>();
130
+
131
+ spies.select.mockReturnValueOnce(basePendingReviewCostSummary);
132
+ spies.select.mockReturnValueOnce(baseInProgressProductionOrder); // not TECHNICALLY_COMPLETE
133
+
134
+ const result = await run(db, validInput, ctx);
135
+
136
+ expect(result.ok).toBe(false);
137
+ if (!result.ok) {
138
+ expect(result.error).toBeInstanceOf(ParentOrderNotTechnicallyCompleteError);
139
+ }
140
+ });
141
+
142
+ it("returns error when the review result cannot represent all required variance types", async () => {
143
+ const { db, spies } = createMockDb<Transaction>();
144
+
145
+ spies.select.mockReturnValueOnce(basePendingReviewCostSummary);
146
+ spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
147
+
148
+ // Missing YIELD from the breakdown
149
+ const incompleteBreakdown = fullVarianceBreakdown.filter((v) => v.varianceType !== "YIELD");
150
+
151
+ const result = await run(db, { ...validInput, varianceBreakdown: incompleteBreakdown }, ctx);
152
+
153
+ expect(result.ok).toBe(false);
154
+ if (!result.ok) {
155
+ expect(result.error).toBeInstanceOf(IncompleteVarianceBreakdownError);
156
+ }
157
+ });
158
+
159
+ it("returns error when reviewer identity is missing", async () => {
160
+ const { db } = createMockDb<Transaction>();
161
+
162
+ const result = await run(db, { ...validInput, reviewerId: null }, ctx);
163
+
164
+ expect(result.ok).toBe(false);
165
+ if (!result.ok) {
166
+ expect(result.error).toBeInstanceOf(ReviewerRequiredError);
167
+ }
168
+ });
169
+
170
+ it("keeps all required variance types distinct during review", async () => {
171
+ const { db, spies } = createMockDb<Transaction>();
172
+
173
+ spies.select.mockReturnValueOnce(basePendingReviewCostSummary);
174
+ spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
175
+
176
+ const updatedSummary = {
177
+ ...basePendingReviewCostSummary,
178
+ status: "VARIANCE_REVIEWED",
179
+ };
180
+ spies.insert.mockReturnValue({});
181
+ spies.update.mockReturnValue(updatedSummary);
182
+
183
+ const result = await run(db, validInput, ctx);
184
+
185
+ expect(result.ok).toBe(true);
186
+ // Verify all 8 distinct variance lines were inserted
187
+ expect(spies.insert).toHaveBeenCalledTimes(8);
188
+
189
+ // Verify each call used the correct variance type through the values spy
190
+ const insertedTypes = new Set<string>();
191
+ for (const call of spies.values.mock.calls) {
192
+ const values = call[0] as Record<string, unknown>;
193
+ if (values.varianceType) {
194
+ insertedTypes.add(values.varianceType as string);
195
+ }
196
+ }
197
+ expect(insertedTypes.size).toBe(8);
198
+ expect(insertedTypes.has("MATERIAL_PRICE")).toBe(true);
199
+ expect(insertedTypes.has("MATERIAL_USAGE")).toBe(true);
200
+ expect(insertedTypes.has("LABOR_RATE")).toBe(true);
201
+ expect(insertedTypes.has("LABOR_EFFICIENCY")).toBe(true);
202
+ expect(insertedTypes.has("MACHINE_RATE")).toBe(true);
203
+ expect(insertedTypes.has("MACHINE_EFFICIENCY")).toBe(true);
204
+ expect(insertedTypes.has("SCRAP")).toBe(true);
205
+ expect(insertedTypes.has("YIELD")).toBe(true);
206
+ });
207
+
208
+ it("returns error when final variance cannot be recalculated consistently", async () => {
209
+ const { db, spies } = createMockDb<Transaction>();
210
+
211
+ spies.select.mockReturnValueOnce(basePendingReviewCostSummary);
212
+ spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
213
+
214
+ const result = await run(
215
+ db,
216
+ {
217
+ ...validInput,
218
+ varianceBreakdown: [
219
+ { ...fullVarianceBreakdown[0], amount: Number.NaN },
220
+ ...fullVarianceBreakdown.slice(1),
221
+ ],
222
+ },
223
+ ctx,
224
+ );
225
+
226
+ expect(result.ok).toBe(false);
227
+ if (!result.ok) {
228
+ expect(result.error).toBeInstanceOf(VarianceCalculationFailedError);
229
+ }
230
+ });
231
+ });
@@ -0,0 +1,137 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ CostSummaryNotFoundError,
4
+ CostSummaryNotReviewableError,
5
+ ParentOrderNotTechnicallyCompleteError,
6
+ IncompleteVarianceBreakdownError,
7
+ ReviewerRequiredError,
8
+ VarianceCalculationFailedError,
9
+ } from "../lib/errors.generated";
10
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
11
+
12
+ const REQUIRED_VARIANCE_TYPES = [
13
+ "MATERIAL_PRICE",
14
+ "MATERIAL_USAGE",
15
+ "LABOR_RATE",
16
+ "LABOR_EFFICIENCY",
17
+ "MACHINE_RATE",
18
+ "MACHINE_EFFICIENCY",
19
+ "SCRAP",
20
+ "YIELD",
21
+ ] as const;
22
+
23
+ type VarianceType = (typeof REQUIRED_VARIANCE_TYPES)[number];
24
+
25
+ export interface VarianceBreakdownEntry {
26
+ varianceType: VarianceType;
27
+ amount: number;
28
+ accountReference?: string | null;
29
+ variancePercentage?: number | null;
30
+ }
31
+
32
+ export interface ReviewManufacturingCostSummaryInput {
33
+ costSummaryId: string;
34
+ reviewerId?: string | null;
35
+ reviewerNotes?: string | null;
36
+ varianceBreakdown?: VarianceBreakdownEntry[] | null;
37
+ }
38
+
39
+ /**
40
+ * Function: reviewManufacturingCostSummary
41
+ *
42
+ * Recalculates the final planned-versus-actual result for a technically
43
+ * complete order and freezes the variance classification through an
44
+ * explicit reviewer approval step.
45
+ */
46
+ export async function run<CF extends Record<string, unknown>>(
47
+ db: Transaction,
48
+ input: ReviewManufacturingCostSummaryInput & CF,
49
+ _ctx: CommandContext,
50
+ ) {
51
+ const { costSummaryId, reviewerId, reviewerNotes, varianceBreakdown } = input;
52
+
53
+ // 1. Validate reviewer identity
54
+ if (!reviewerId) {
55
+ return err(new ReviewerRequiredError(costSummaryId));
56
+ }
57
+
58
+ // 2. Resolve cost summary
59
+ const costSummary = await db
60
+ .selectFrom("ManufacturingCostSummary")
61
+ .selectAll()
62
+ .where("id", "=", costSummaryId)
63
+ .forUpdate()
64
+ .executeTakeFirst();
65
+
66
+ if (!costSummary) {
67
+ return err(new CostSummaryNotFoundError(costSummaryId));
68
+ }
69
+
70
+ // 3. Summary must be PENDING_VARIANCE_REVIEW
71
+ if (costSummary.status !== "PENDING_VARIANCE_REVIEW") {
72
+ return err(new CostSummaryNotReviewableError(costSummaryId));
73
+ }
74
+
75
+ // 4. Parent production order must be TECHNICALLY_COMPLETE
76
+ const productionOrder = await db
77
+ .selectFrom("ProductionOrder")
78
+ .selectAll()
79
+ .where("id", "=", costSummary.productionOrderId)
80
+ .forUpdate()
81
+ .executeTakeFirst();
82
+
83
+ if (productionOrder?.status !== "TECHNICALLY_COMPLETE") {
84
+ return err(new ParentOrderNotTechnicallyCompleteError(costSummaryId));
85
+ }
86
+
87
+ // 5. Validate variance breakdown covers all required categories
88
+ if (!varianceBreakdown || !Array.isArray(varianceBreakdown)) {
89
+ return err(new IncompleteVarianceBreakdownError(costSummaryId));
90
+ }
91
+
92
+ const providedTypes = new Set(varianceBreakdown.map((v) => v.varianceType));
93
+ const missingTypes = REQUIRED_VARIANCE_TYPES.filter((t) => !providedTypes.has(t));
94
+
95
+ if (missingTypes.length > 0) {
96
+ return err(new IncompleteVarianceBreakdownError(costSummaryId));
97
+ }
98
+
99
+ // 6. Validate that variance amounts are consistent (no NaN or undefined)
100
+ for (const entry of varianceBreakdown) {
101
+ if (typeof entry.amount !== "number" || isNaN(entry.amount)) {
102
+ return err(new VarianceCalculationFailedError(costSummaryId));
103
+ }
104
+ }
105
+
106
+ // 7. Create cost variance lines for each breakdown entry
107
+ for (const entry of varianceBreakdown) {
108
+ await db
109
+ .insertInto("CostVarianceLine")
110
+ .values({
111
+ costSummaryId: costSummary.id,
112
+ varianceType: entry.varianceType,
113
+ amount: entry.amount,
114
+ accountReference: entry.accountReference ?? null,
115
+ variancePercentage: entry.variancePercentage ?? null,
116
+ createdAt: new Date(),
117
+ updatedAt: null,
118
+ })
119
+ .returningAll()
120
+ .executeTakeFirst();
121
+ }
122
+
123
+ // 8. Update summary to VARIANCE_REVIEWED
124
+ const updatedSummary = await db
125
+ .updateTable("ManufacturingCostSummary")
126
+ .set({
127
+ status: "VARIANCE_REVIEWED",
128
+ reviewedDate: new Date(),
129
+ reviewerNotes: reviewerNotes ?? null,
130
+ updatedAt: new Date(),
131
+ })
132
+ .where("id", "=", costSummary.id)
133
+ .returningAll()
134
+ .executeTakeFirst();
135
+
136
+ return ok({ costSummary: updatedSummary! });
137
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./startWorkOrder";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const startWorkOrder = defineCommand(permissions.startWorkOrder, run);