@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,131 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ ProductionOrderNotFoundError,
4
+ ProductionOrderNotUnreleasableError,
5
+ ExecutionAlreadyStartedError,
6
+ InventoryHandoffExistsError,
7
+ } from "../lib/errors.generated";
8
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
9
+
10
+ export interface UnreleaseProductionOrderInput {
11
+ id: string;
12
+ from?: string[];
13
+ }
14
+
15
+ /**
16
+ * Function: unreleaseProductionOrder
17
+ *
18
+ * Returns a released order to DRAFT when execution has not meaningfully
19
+ * started. Removes release artifacts so the planner can safely revise
20
+ * the order.
21
+ */
22
+ export async function run<CF extends Record<string, unknown>>(
23
+ db: Transaction,
24
+ input: UnreleaseProductionOrderInput & CF,
25
+ _ctx: CommandContext,
26
+ ) {
27
+ const { id, from, ...customFields } = input;
28
+ void customFields;
29
+
30
+ const allowedStatuses = from ?? ["RELEASED"];
31
+
32
+ // 1. Fetch production order with lock
33
+ const order = await db
34
+ .selectFrom("ProductionOrder")
35
+ .selectAll()
36
+ .where("id", "=", id)
37
+ .forUpdate()
38
+ .executeTakeFirst();
39
+
40
+ if (!order) {
41
+ return err(new ProductionOrderNotFoundError(id));
42
+ }
43
+
44
+ // 2. Validate status is unreleasable
45
+ if (!allowedStatuses.includes(order.status)) {
46
+ return err(new ProductionOrderNotUnreleasableError(id));
47
+ }
48
+
49
+ // 3. Check no work order has execution evidence
50
+ const workOrders = await db
51
+ .selectFrom("WorkOrder")
52
+ .selectAll()
53
+ .where("productionOrderId", "=", id)
54
+ .execute();
55
+
56
+ const hasExecution = workOrders.some(
57
+ (wo) =>
58
+ wo.status === "IN_PROGRESS" ||
59
+ wo.status === "PAUSED" ||
60
+ wo.status === "COMPLETE" ||
61
+ wo.completedQuantity > 0 ||
62
+ wo.actualStartDate != null,
63
+ );
64
+
65
+ if (hasExecution) {
66
+ return err(new ExecutionAlreadyStartedError(id));
67
+ }
68
+
69
+ // 4. Check no irreversible inventory handoff
70
+ const _materialRequirements = await db
71
+ .selectFrom("ProductionOrderMaterialRequirement")
72
+ .selectAll()
73
+ .where("productionOrderId", "=", id)
74
+ .execute();
75
+
76
+ // Check if any cost summary has actual costs recorded (inventory handoff evidence)
77
+ const costSummary = await db
78
+ .selectFrom("ManufacturingCostSummary")
79
+ .selectAll()
80
+ .where("productionOrderId", "=", id)
81
+ .executeTakeFirst();
82
+
83
+ if (
84
+ costSummary &&
85
+ (costSummary.actualMaterialCost > 0 ||
86
+ costSummary.actualLaborCost > 0 ||
87
+ costSummary.actualMachineCost > 0 ||
88
+ costSummary.actualOverheadCost > 0)
89
+ ) {
90
+ return err(new InventoryHandoffExistsError(id));
91
+ }
92
+
93
+ // 5. Remove release artifacts - delete work orders
94
+ await db.deleteFrom("WorkOrder").where("productionOrderId", "=", id).execute();
95
+
96
+ // 6. Remove material requirements
97
+ await db
98
+ .deleteFrom("ProductionOrderMaterialRequirement")
99
+ .where("productionOrderId", "=", id)
100
+ .execute();
101
+
102
+ // 7. Remove BOM snapshot
103
+ await db.deleteFrom("ProductionOrderBomSnapshot").where("productionOrderId", "=", id).execute();
104
+
105
+ // 8. Remove routing snapshot
106
+ await db
107
+ .deleteFrom("ProductionOrderRoutingSnapshot")
108
+ .where("productionOrderId", "=", id)
109
+ .execute();
110
+
111
+ // 9. Remove cost baseline
112
+ await db.deleteFrom("ProductionOrderCostBaseline").where("productionOrderId", "=", id).execute();
113
+
114
+ // 10. Remove cost summary
115
+ await db.deleteFrom("ManufacturingCostSummary").where("productionOrderId", "=", id).execute();
116
+
117
+ // 11. Set status to DRAFT
118
+ const draftOrder = await db
119
+ .updateTable("ProductionOrder")
120
+ .set({
121
+ selectedBomVersionId: null,
122
+ selectedRoutingRevisionId: null,
123
+ status: "DRAFT",
124
+ updatedAt: new Date(),
125
+ })
126
+ .where("id", "=", id)
127
+ .returningAll()
128
+ .executeTakeFirstOrThrow();
129
+
130
+ return ok({ productionOrder: draftOrder });
131
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./updateBillOfMaterial";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const updateBillOfMaterial = defineCommand(permissions.updateBillOfMaterial, run);
@@ -0,0 +1,149 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import {
5
+ BomNotFoundError,
6
+ BomNotMutableError,
7
+ InvalidComponentQuantityError,
8
+ ComponentItemInactiveError,
9
+ AmbiguousEffectivityRuleError,
10
+ } from "../lib/errors.generated";
11
+ import { baseDraftBom, baseActiveBom, baseBomLine } from "../testing/fixtures";
12
+ import { run } from "./updateBillOfMaterial";
13
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
14
+
15
+ describe("updateBillOfMaterial", () => {
16
+ const ctx: CommandContext = {
17
+ actorId: "test-actor",
18
+ permissions: ["manufacturing:updateBillOfMaterial"],
19
+ };
20
+
21
+ const validInput = {
22
+ id: "bom-1",
23
+ bomType: "PHANTOM" as const,
24
+ lines: [
25
+ {
26
+ itemId: "item-2",
27
+ requiredQuantity: 3.0,
28
+ unitOfMeasure: "EA",
29
+ },
30
+ ],
31
+ };
32
+
33
+ it("updates draft BOM metadata and component lines", async () => {
34
+ const { db, spies } = createMockDb<Transaction>();
35
+ const updatedBom = {
36
+ ...baseDraftBom,
37
+ bomType: "PHANTOM" as const,
38
+ };
39
+
40
+ // BOM exists and is DRAFT
41
+ spies.select.mockReturnValueOnce(baseDraftBom);
42
+ // component item exists
43
+ spies.select.mockReturnValueOnce({ id: "item-2" });
44
+ // update BOM header
45
+ spies.update.mockReturnValueOnce(updatedBom);
46
+ // delete old lines
47
+ spies.delete.mockReturnValueOnce(undefined);
48
+ // insert new line
49
+ spies.insert.mockReturnValueOnce({ ...baseBomLine, requiredQuantity: 3.0 });
50
+
51
+ const result = await run(db, validInput, ctx);
52
+
53
+ expect(result.ok).toBe(true);
54
+ if (result.ok) {
55
+ expect(result.value.billOfMaterial.bomType).toBe("PHANTOM");
56
+ }
57
+ expect(spies.update).toHaveBeenCalled();
58
+ });
59
+
60
+ it("returns error when the BOM does not exist", async () => {
61
+ const { db, spies } = createMockDb<Transaction>();
62
+
63
+ spies.select.mockReturnValueOnce(undefined);
64
+
65
+ const result = await run(db, validInput, ctx);
66
+
67
+ expect(result.ok).toBe(false);
68
+ if (!result.ok) {
69
+ expect(result.error).toBeInstanceOf(BomNotFoundError);
70
+ }
71
+ });
72
+
73
+ it("returns error when the BOM is not in DRAFT", async () => {
74
+ const { db, spies } = createMockDb<Transaction>();
75
+
76
+ spies.select.mockReturnValueOnce(baseActiveBom);
77
+
78
+ const result = await run(db, { ...validInput, id: "bom-2" }, ctx);
79
+
80
+ expect(result.ok).toBe(false);
81
+ if (!result.ok) {
82
+ expect(result.error).toBeInstanceOf(BomNotMutableError);
83
+ }
84
+ });
85
+
86
+ it("returns error when a revised component quantity is invalid", async () => {
87
+ const { db, spies } = createMockDb<Transaction>();
88
+
89
+ spies.select.mockReturnValueOnce(baseDraftBom);
90
+
91
+ const result = await run(
92
+ db,
93
+ {
94
+ ...validInput,
95
+ lines: [{ itemId: "item-2", requiredQuantity: -1, unitOfMeasure: "EA" }],
96
+ },
97
+ ctx,
98
+ );
99
+
100
+ expect(result.ok).toBe(false);
101
+ if (!result.ok) {
102
+ expect(result.error).toBeInstanceOf(InvalidComponentQuantityError);
103
+ }
104
+ });
105
+
106
+ it("returns error when a newly referenced component is inactive", async () => {
107
+ const { db, spies } = createMockDb<Transaction>();
108
+
109
+ // BOM exists and is DRAFT
110
+ spies.select.mockReturnValueOnce(baseDraftBom);
111
+ // component item not found (inactive)
112
+ spies.select.mockReturnValueOnce(undefined);
113
+
114
+ const result = await run(db, validInput, ctx);
115
+
116
+ expect(result.ok).toBe(false);
117
+ if (!result.ok) {
118
+ expect(result.error).toBeInstanceOf(ComponentItemInactiveError);
119
+ }
120
+ });
121
+
122
+ it("returns error when effectivity changes would create ambiguous selection", async () => {
123
+ const { db, spies } = createMockDb<Transaction>();
124
+
125
+ // BOM exists and is DRAFT
126
+ spies.select.mockReturnValueOnce(baseDraftBom);
127
+ // conflicting active BOM with same default selection
128
+ spies.select.mockReturnValueOnce({
129
+ ...baseActiveBom,
130
+ defaultSelection: true,
131
+ effectivityStartDate: null,
132
+ effectivityEndDate: null,
133
+ });
134
+
135
+ const result = await run(
136
+ db,
137
+ {
138
+ id: "bom-1",
139
+ defaultSelection: true,
140
+ },
141
+ ctx,
142
+ );
143
+
144
+ expect(result.ok).toBe(false);
145
+ if (!result.ok) {
146
+ expect(result.error).toBeInstanceOf(AmbiguousEffectivityRuleError);
147
+ }
148
+ });
149
+ });
@@ -0,0 +1,174 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ BomNotFoundError,
4
+ BomNotMutableError,
5
+ InvalidComponentQuantityError,
6
+ ComponentItemInactiveError,
7
+ AmbiguousEffectivityRuleError,
8
+ } from "../lib/errors.generated";
9
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
10
+
11
+ export interface UpdateBillOfMaterialLineInput {
12
+ itemId: string;
13
+ requiredQuantity: number;
14
+ unitOfMeasure?: string | null;
15
+ scrapAssumption?: number | null;
16
+ isSubassembly?: boolean | null;
17
+ }
18
+
19
+ export interface UpdateBillOfMaterialInput {
20
+ id: string;
21
+ bomType?: string | null;
22
+ effectivityStartDate?: Date | null;
23
+ effectivityEndDate?: Date | null;
24
+ defaultSelection?: boolean | null;
25
+ revisionNumber?: string | null;
26
+ lines?: UpdateBillOfMaterialLineInput[];
27
+ }
28
+
29
+ /**
30
+ * Function: updateBillOfMaterial
31
+ *
32
+ * Revises mutable draft BOM content before activation. Supports changes to
33
+ * effectivity, default flags, bomType, and component lines while preserving
34
+ * released production-order snapshots.
35
+ */
36
+ export async function run<CF extends Record<string, unknown>>(
37
+ db: Transaction,
38
+ input: UpdateBillOfMaterialInput & CF,
39
+ _ctx: CommandContext,
40
+ ) {
41
+ const {
42
+ id,
43
+ bomType,
44
+ effectivityStartDate,
45
+ effectivityEndDate,
46
+ defaultSelection,
47
+ revisionNumber,
48
+ lines,
49
+ ...customFields
50
+ } = input;
51
+
52
+ // 1. Fetch BOM with lock
53
+ const bom = await db
54
+ .selectFrom("BillOfMaterial")
55
+ .selectAll()
56
+ .where("id", "=", id)
57
+ .forUpdate()
58
+ .executeTakeFirst();
59
+
60
+ if (!bom) {
61
+ return err(new BomNotFoundError(id));
62
+ }
63
+
64
+ // 2. Only DRAFT BOMs are mutable
65
+ if (bom.status !== "DRAFT") {
66
+ return err(new BomNotMutableError(id));
67
+ }
68
+
69
+ // 3. Validate revised component lines if provided
70
+ if (lines) {
71
+ for (const line of lines) {
72
+ if (line.requiredQuantity <= 0) {
73
+ return err(new InvalidComponentQuantityError(line.itemId));
74
+ }
75
+
76
+ // Check component item is not inactive
77
+ const componentItem = await db
78
+ .selectFrom("Item")
79
+ .selectAll()
80
+ .where("id", "=", line.itemId)
81
+ .executeTakeFirst();
82
+
83
+ if (!componentItem) {
84
+ return err(new ComponentItemInactiveError(line.itemId));
85
+ }
86
+ }
87
+ }
88
+
89
+ // 4. Check effectivity ambiguity — if effectivity or default selection changed,
90
+ // verify no ambiguous active-selection plan would result once activated
91
+ const effectiveDefaultSelection =
92
+ defaultSelection !== undefined ? defaultSelection : bom.defaultSelection;
93
+ const effectiveStartDate =
94
+ effectivityStartDate !== undefined ? effectivityStartDate : bom.effectivityStartDate;
95
+ const effectiveEndDate =
96
+ effectivityEndDate !== undefined ? effectivityEndDate : bom.effectivityEndDate;
97
+
98
+ if (
99
+ defaultSelection !== undefined ||
100
+ effectivityStartDate !== undefined ||
101
+ effectivityEndDate !== undefined
102
+ ) {
103
+ // Find other active BOMs for same parent item and scope that could conflict
104
+ const conflictingBom = await db
105
+ .selectFrom("BillOfMaterial")
106
+ .selectAll()
107
+ .where("parentItemId", "=", bom.parentItemId)
108
+ .where("companyId", "=", bom.companyId)
109
+ .where("id", "!=", id)
110
+ .where("status", "=", "ACTIVE")
111
+ .where("defaultSelection", "=", effectiveDefaultSelection ?? false)
112
+ .executeTakeFirst();
113
+
114
+ if (conflictingBom && effectiveDefaultSelection) {
115
+ // Check date overlap
116
+ const hasOverlap =
117
+ !effectiveStartDate ||
118
+ !conflictingBom.effectivityEndDate ||
119
+ effectiveStartDate <= conflictingBom.effectivityEndDate;
120
+ const hasOverlap2 =
121
+ !effectiveEndDate ||
122
+ !conflictingBom.effectivityStartDate ||
123
+ effectiveEndDate >= conflictingBom.effectivityStartDate;
124
+
125
+ if (hasOverlap && hasOverlap2) {
126
+ return err(new AmbiguousEffectivityRuleError(id));
127
+ }
128
+ }
129
+ }
130
+
131
+ // 5. Build update set for BOM header
132
+ const updateSet: Record<string, unknown> = {
133
+ ...customFields,
134
+ updatedAt: new Date(),
135
+ };
136
+
137
+ if (bomType !== undefined) updateSet.bomType = bomType;
138
+ if (effectivityStartDate !== undefined) updateSet.effectivityStartDate = effectivityStartDate;
139
+ if (effectivityEndDate !== undefined) updateSet.effectivityEndDate = effectivityEndDate;
140
+ if (defaultSelection !== undefined) updateSet.defaultSelection = defaultSelection;
141
+ if (revisionNumber !== undefined) updateSet.revisionNumber = revisionNumber;
142
+
143
+ // 6. Persist BOM header changes
144
+ const updatedBom = await db
145
+ .updateTable("BillOfMaterial")
146
+ .set(updateSet)
147
+ .where("id", "=", id)
148
+ .returningAll()
149
+ .executeTakeFirstOrThrow();
150
+
151
+ // 7. Replace lines if provided (delete old, insert new)
152
+ if (lines) {
153
+ await db.deleteFrom("BillOfMaterialLine").where("billOfMaterialId", "=", id).execute();
154
+
155
+ for (const line of lines) {
156
+ await db
157
+ .insertInto("BillOfMaterialLine")
158
+ .values({
159
+ billOfMaterialId: id,
160
+ itemId: line.itemId,
161
+ requiredQuantity: line.requiredQuantity,
162
+ unitOfMeasure: line.unitOfMeasure ?? null,
163
+ scrapAssumption: line.scrapAssumption ?? null,
164
+ isSubassembly: line.isSubassembly ?? null,
165
+ createdAt: new Date(),
166
+ updatedAt: null,
167
+ })
168
+ .returningAll()
169
+ .executeTakeFirst();
170
+ }
171
+ }
172
+
173
+ return ok({ billOfMaterial: updatedBom });
174
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./updateProductionOrder";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const updateProductionOrder = defineCommand(permissions.updateProductionOrder, run);
@@ -0,0 +1,112 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import {
5
+ ProductionOrderNotFoundError,
6
+ ProductionOrderNotMutableError,
7
+ InvalidPlannedQuantityError,
8
+ ImmutableScopeFieldError,
9
+ CrossScopeMasterReferenceError,
10
+ } from "../lib/errors.generated";
11
+ import {
12
+ baseDraftProductionOrder,
13
+ baseReleasedProductionOrder,
14
+ baseActiveBom,
15
+ } from "../testing/fixtures";
16
+ import { run } from "./updateProductionOrder";
17
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
18
+
19
+ describe("updateProductionOrder", () => {
20
+ const ctx: CommandContext = {
21
+ actorId: "test-actor",
22
+ permissions: ["manufacturing:updateProductionOrder"],
23
+ };
24
+
25
+ it("updates draft planning fields on a production order", async () => {
26
+ const { db, spies } = createMockDb<Transaction>();
27
+ const updated = { ...baseDraftProductionOrder, plannedQuantity: 200 };
28
+
29
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
30
+ spies.update.mockReturnValue(updated);
31
+
32
+ const result = await run(db, { id: baseDraftProductionOrder.id, plannedQuantity: 200 }, ctx);
33
+
34
+ expect(result.ok).toBe(true);
35
+ if (result.ok) {
36
+ expect(result.value.productionOrder.plannedQuantity).toBe(200);
37
+ }
38
+ expect(spies.update).toHaveBeenCalled();
39
+ });
40
+
41
+ it("returns error when the order does not exist", async () => {
42
+ const { db, spies } = createMockDb<Transaction>();
43
+ spies.select.mockReturnValueOnce(undefined);
44
+
45
+ const result = await run(db, { id: "nonexistent" }, ctx);
46
+
47
+ expect(result.ok).toBe(false);
48
+ if (!result.ok) {
49
+ expect(result.error).toBeInstanceOf(ProductionOrderNotFoundError);
50
+ }
51
+ });
52
+
53
+ it("returns error when the order is not in DRAFT", async () => {
54
+ const { db, spies } = createMockDb<Transaction>();
55
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
56
+
57
+ const result = await run(db, { id: baseReleasedProductionOrder.id, plannedQuantity: 200 }, ctx);
58
+
59
+ expect(result.ok).toBe(false);
60
+ if (!result.ok) {
61
+ expect(result.error).toBeInstanceOf(ProductionOrderNotMutableError);
62
+ }
63
+ });
64
+
65
+ it("returns error when planned quantity is not positive", async () => {
66
+ const { db, spies } = createMockDb<Transaction>();
67
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
68
+
69
+ const result = await run(db, { id: baseDraftProductionOrder.id, plannedQuantity: 0 }, ctx);
70
+
71
+ expect(result.ok).toBe(false);
72
+ if (!result.ok) {
73
+ expect(result.error).toBeInstanceOf(InvalidPlannedQuantityError);
74
+ }
75
+ });
76
+
77
+ it("returns error when immutable scope identity is changed", async () => {
78
+ const { db, spies } = createMockDb<Transaction>();
79
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
80
+
81
+ const result = await run(
82
+ db,
83
+ { id: baseDraftProductionOrder.id, orderedItemId: "different-item" },
84
+ ctx,
85
+ );
86
+
87
+ expect(result.ok).toBe(false);
88
+ if (!result.ok) {
89
+ expect(result.error).toBeInstanceOf(ImmutableScopeFieldError);
90
+ }
91
+ });
92
+
93
+ it("returns error when BOM or routing references are out of scope", async () => {
94
+ const { db, spies } = createMockDb<Transaction>();
95
+ const crossScopeBom = { ...baseActiveBom, companyId: "other-company" };
96
+
97
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
98
+ // bom lookup
99
+ spies.select.mockReturnValueOnce(crossScopeBom);
100
+
101
+ const result = await run(
102
+ db,
103
+ { id: baseDraftProductionOrder.id, selectedBomVersionId: crossScopeBom.id },
104
+ ctx,
105
+ );
106
+
107
+ expect(result.ok).toBe(false);
108
+ if (!result.ok) {
109
+ expect(result.error).toBeInstanceOf(CrossScopeMasterReferenceError);
110
+ }
111
+ });
112
+ });