@tailor-platform/erp-kit 0.5.1 → 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 (251) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/cli.mjs +103 -23
  3. package/package.json +1 -1
  4. package/skills/erp-kit-app-5-impl-backend/SKILL.md +7 -4
  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/lib/discovery.test.ts +13 -3
  9. package/src/commands/lib/discovery.ts +10 -2
  10. package/src/commands/lib/paths.ts +4 -2
  11. package/src/commands/lib/sync-check-tests.test.ts +84 -6
  12. package/src/commands/lib/sync-check-tests.ts +63 -3
  13. package/src/commands/sync-check.ts +7 -3
  14. package/src/generator/generate-app-code.ts +51 -16
  15. package/src/generator/generate-stubs.ts +4 -0
  16. package/src/generator/stub-templates.test.ts +11 -0
  17. package/src/generator/stub-templates.ts +22 -1
  18. package/src/modules/inventory/docs/features/inventory-adjustment.md +2 -1
  19. package/src/modules/inventory/docs/features/scrap-management.md +39 -1
  20. package/src/modules/manufacturing/README.md +63 -0
  21. package/src/modules/manufacturing/command/.gitkeep +0 -0
  22. package/src/modules/manufacturing/command/activateBillOfMaterial.generated.ts +6 -0
  23. package/src/modules/manufacturing/command/activateBillOfMaterial.test.ts +166 -0
  24. package/src/modules/manufacturing/command/activateBillOfMaterial.ts +173 -0
  25. package/src/modules/manufacturing/command/activateRouting.generated.ts +6 -0
  26. package/src/modules/manufacturing/command/activateRouting.test.ts +152 -0
  27. package/src/modules/manufacturing/command/activateRouting.ts +92 -0
  28. package/src/modules/manufacturing/command/activateWorkCenter.generated.ts +6 -0
  29. package/src/modules/manufacturing/command/activateWorkCenter.test.ts +135 -0
  30. package/src/modules/manufacturing/command/activateWorkCenter.ts +91 -0
  31. package/src/modules/manufacturing/command/cancelProductionOrder.generated.ts +6 -0
  32. package/src/modules/manufacturing/command/cancelProductionOrder.test.ts +151 -0
  33. package/src/modules/manufacturing/command/cancelProductionOrder.ts +114 -0
  34. package/src/modules/manufacturing/command/closeProductionOrder.generated.ts +6 -0
  35. package/src/modules/manufacturing/command/closeProductionOrder.test.ts +126 -0
  36. package/src/modules/manufacturing/command/closeProductionOrder.ts +87 -0
  37. package/src/modules/manufacturing/command/completeProductionOrder.generated.ts +6 -0
  38. package/src/modules/manufacturing/command/completeProductionOrder.test.ts +132 -0
  39. package/src/modules/manufacturing/command/completeProductionOrder.ts +97 -0
  40. package/src/modules/manufacturing/command/completeWorkOrder.generated.ts +6 -0
  41. package/src/modules/manufacturing/command/completeWorkOrder.test.ts +369 -0
  42. package/src/modules/manufacturing/command/completeWorkOrder.ts +212 -0
  43. package/src/modules/manufacturing/command/createBillOfMaterial.generated.ts +6 -0
  44. package/src/modules/manufacturing/command/createBillOfMaterial.test.ts +210 -0
  45. package/src/modules/manufacturing/command/createBillOfMaterial.ts +176 -0
  46. package/src/modules/manufacturing/command/createProductionOrder.generated.ts +6 -0
  47. package/src/modules/manufacturing/command/createProductionOrder.test.ts +160 -0
  48. package/src/modules/manufacturing/command/createProductionOrder.ts +129 -0
  49. package/src/modules/manufacturing/command/createRouting.generated.ts +6 -0
  50. package/src/modules/manufacturing/command/createRouting.test.ts +168 -0
  51. package/src/modules/manufacturing/command/createRouting.ts +128 -0
  52. package/src/modules/manufacturing/command/createWorkCenter.generated.ts +6 -0
  53. package/src/modules/manufacturing/command/createWorkCenter.test.ts +148 -0
  54. package/src/modules/manufacturing/command/createWorkCenter.ts +131 -0
  55. package/src/modules/manufacturing/command/deactivateBillOfMaterial.generated.ts +6 -0
  56. package/src/modules/manufacturing/command/deactivateBillOfMaterial.test.ts +103 -0
  57. package/src/modules/manufacturing/command/deactivateBillOfMaterial.ts +78 -0
  58. package/src/modules/manufacturing/command/deactivateRouting.generated.ts +6 -0
  59. package/src/modules/manufacturing/command/deactivateRouting.test.ts +112 -0
  60. package/src/modules/manufacturing/command/deactivateRouting.ts +76 -0
  61. package/src/modules/manufacturing/command/deactivateWorkCenter.generated.ts +6 -0
  62. package/src/modules/manufacturing/command/deactivateWorkCenter.test.ts +113 -0
  63. package/src/modules/manufacturing/command/deactivateWorkCenter.ts +85 -0
  64. package/src/modules/manufacturing/command/pauseWorkOrder.generated.ts +6 -0
  65. package/src/modules/manufacturing/command/pauseWorkOrder.test.ts +118 -0
  66. package/src/modules/manufacturing/command/pauseWorkOrder.ts +82 -0
  67. package/src/modules/manufacturing/command/recordInventoryIssueOutcome.generated.ts +6 -0
  68. package/src/modules/manufacturing/command/recordInventoryIssueOutcome.test.ts +183 -0
  69. package/src/modules/manufacturing/command/recordInventoryIssueOutcome.ts +139 -0
  70. package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.generated.ts +6 -0
  71. package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.test.ts +120 -0
  72. package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.ts +110 -0
  73. package/src/modules/manufacturing/command/releaseProductionOrder.generated.ts +6 -0
  74. package/src/modules/manufacturing/command/releaseProductionOrder.test.ts +220 -0
  75. package/src/modules/manufacturing/command/releaseProductionOrder.ts +450 -0
  76. package/src/modules/manufacturing/command/reopenProductionOrder.generated.ts +6 -0
  77. package/src/modules/manufacturing/command/reopenProductionOrder.test.ts +196 -0
  78. package/src/modules/manufacturing/command/reopenProductionOrder.ts +98 -0
  79. package/src/modules/manufacturing/command/reportWorkOrderProgress.generated.ts +6 -0
  80. package/src/modules/manufacturing/command/reportWorkOrderProgress.test.ts +204 -0
  81. package/src/modules/manufacturing/command/reportWorkOrderProgress.ts +129 -0
  82. package/src/modules/manufacturing/command/rescheduleProductionOrder.generated.ts +6 -0
  83. package/src/modules/manufacturing/command/rescheduleProductionOrder.test.ts +185 -0
  84. package/src/modules/manufacturing/command/rescheduleProductionOrder.ts +95 -0
  85. package/src/modules/manufacturing/command/resumeWorkOrder.generated.ts +6 -0
  86. package/src/modules/manufacturing/command/resumeWorkOrder.test.ts +122 -0
  87. package/src/modules/manufacturing/command/resumeWorkOrder.ts +94 -0
  88. package/src/modules/manufacturing/command/reviewManufacturingCostSummary.generated.ts +6 -0
  89. package/src/modules/manufacturing/command/reviewManufacturingCostSummary.test.ts +231 -0
  90. package/src/modules/manufacturing/command/reviewManufacturingCostSummary.ts +137 -0
  91. package/src/modules/manufacturing/command/startWorkOrder.generated.ts +6 -0
  92. package/src/modules/manufacturing/command/startWorkOrder.test.ts +118 -0
  93. package/src/modules/manufacturing/command/startWorkOrder.ts +126 -0
  94. package/src/modules/manufacturing/command/technicallyCompleteProductionOrder.generated.ts +6 -0
  95. package/src/modules/manufacturing/command/technicallyCompleteProductionOrder.test.ts +153 -0
  96. package/src/modules/manufacturing/command/technicallyCompleteProductionOrder.ts +106 -0
  97. package/src/modules/manufacturing/command/unreleaseProductionOrder.generated.ts +6 -0
  98. package/src/modules/manufacturing/command/unreleaseProductionOrder.test.ts +140 -0
  99. package/src/modules/manufacturing/command/unreleaseProductionOrder.ts +131 -0
  100. package/src/modules/manufacturing/command/updateBillOfMaterial.generated.ts +6 -0
  101. package/src/modules/manufacturing/command/updateBillOfMaterial.test.ts +149 -0
  102. package/src/modules/manufacturing/command/updateBillOfMaterial.ts +174 -0
  103. package/src/modules/manufacturing/command/updateProductionOrder.generated.ts +6 -0
  104. package/src/modules/manufacturing/command/updateProductionOrder.test.ts +112 -0
  105. package/src/modules/manufacturing/command/updateProductionOrder.ts +145 -0
  106. package/src/modules/manufacturing/command/updateRouting.generated.ts +6 -0
  107. package/src/modules/manufacturing/command/updateRouting.test.ts +211 -0
  108. package/src/modules/manufacturing/command/updateRouting.ts +124 -0
  109. package/src/modules/manufacturing/command/updateWorkCenter.generated.ts +6 -0
  110. package/src/modules/manufacturing/command/updateWorkCenter.test.ts +152 -0
  111. package/src/modules/manufacturing/command/updateWorkCenter.ts +137 -0
  112. package/src/modules/manufacturing/db/.gitkeep +0 -0
  113. package/src/modules/manufacturing/db/billOfMaterial.ts +70 -0
  114. package/src/modules/manufacturing/db/billOfMaterialLine.ts +49 -0
  115. package/src/modules/manufacturing/db/costVarianceLine.ts +53 -0
  116. package/src/modules/manufacturing/db/manufacturingCostLine.ts +35 -0
  117. package/src/modules/manufacturing/db/manufacturingCostSettlementRecord.ts +39 -0
  118. package/src/modules/manufacturing/db/manufacturingCostSummary.ts +59 -0
  119. package/src/modules/manufacturing/db/productionOrder.ts +83 -0
  120. package/src/modules/manufacturing/db/productionOrderBomSnapshot.ts +44 -0
  121. package/src/modules/manufacturing/db/productionOrderCostBaseline.ts +44 -0
  122. package/src/modules/manufacturing/db/productionOrderMaterialRequirement.ts +57 -0
  123. package/src/modules/manufacturing/db/productionOrderRoutingSnapshot.ts +43 -0
  124. package/src/modules/manufacturing/db/routing.ts +63 -0
  125. package/src/modules/manufacturing/db/routingOperation.ts +57 -0
  126. package/src/modules/manufacturing/db/workCenter.ts +87 -0
  127. package/src/modules/manufacturing/db/workOrder.ts +65 -0
  128. package/src/modules/manufacturing/db/workOrderExecutionEvent.ts +54 -0
  129. package/src/modules/manufacturing/docs/commands/ActivateBillOfMaterial.md +50 -0
  130. package/src/modules/manufacturing/docs/commands/ActivateRouting.md +48 -0
  131. package/src/modules/manufacturing/docs/commands/ActivateWorkCenter.md +49 -0
  132. package/src/modules/manufacturing/docs/commands/CancelProductionOrder.md +48 -0
  133. package/src/modules/manufacturing/docs/commands/CloseProductionOrder.md +46 -0
  134. package/src/modules/manufacturing/docs/commands/CompleteProductionOrder.md +48 -0
  135. package/src/modules/manufacturing/docs/commands/CompleteWorkOrder.md +66 -0
  136. package/src/modules/manufacturing/docs/commands/CreateBillOfMaterial.md +54 -0
  137. package/src/modules/manufacturing/docs/commands/CreateProductionOrder.md +49 -0
  138. package/src/modules/manufacturing/docs/commands/CreateRouting.md +50 -0
  139. package/src/modules/manufacturing/docs/commands/CreateWorkCenter.md +51 -0
  140. package/src/modules/manufacturing/docs/commands/DeactivateBillOfMaterial.md +45 -0
  141. package/src/modules/manufacturing/docs/commands/DeactivateRouting.md +45 -0
  142. package/src/modules/manufacturing/docs/commands/DeactivateWorkCenter.md +45 -0
  143. package/src/modules/manufacturing/docs/commands/PauseWorkOrder.md +44 -0
  144. package/src/modules/manufacturing/docs/commands/RecordInventoryIssueOutcome.md +59 -0
  145. package/src/modules/manufacturing/docs/commands/RecordManufacturingCostSettlementAcknowledgment.md +49 -0
  146. package/src/modules/manufacturing/docs/commands/ReleaseProductionOrder.md +57 -0
  147. package/src/modules/manufacturing/docs/commands/ReopenProductionOrder.md +54 -0
  148. package/src/modules/manufacturing/docs/commands/ReportWorkOrderProgress.md +53 -0
  149. package/src/modules/manufacturing/docs/commands/RescheduleProductionOrder.md +45 -0
  150. package/src/modules/manufacturing/docs/commands/ResumeWorkOrder.md +44 -0
  151. package/src/modules/manufacturing/docs/commands/ReviewManufacturingCostSummary.md +52 -0
  152. package/src/modules/manufacturing/docs/commands/StartWorkOrder.md +46 -0
  153. package/src/modules/manufacturing/docs/commands/TechnicallyCompleteProductionOrder.md +51 -0
  154. package/src/modules/manufacturing/docs/commands/UnreleaseProductionOrder.md +46 -0
  155. package/src/modules/manufacturing/docs/commands/UpdateBillOfMaterial.md +48 -0
  156. package/src/modules/manufacturing/docs/commands/UpdateProductionOrder.md +48 -0
  157. package/src/modules/manufacturing/docs/commands/UpdateRouting.md +52 -0
  158. package/src/modules/manufacturing/docs/commands/UpdateWorkCenter.md +48 -0
  159. package/src/modules/manufacturing/docs/features/bill-of-material-management.md +83 -0
  160. package/src/modules/manufacturing/docs/features/manufacturing-cost-and-variance.md +191 -0
  161. package/src/modules/manufacturing/docs/features/production-order-lifecycle.md +103 -0
  162. package/src/modules/manufacturing/docs/features/routing-and-work-center-definition.md +63 -0
  163. package/src/modules/manufacturing/docs/features/work-order-execution.md +115 -0
  164. package/src/modules/manufacturing/docs/models/BillOfMaterial.md +60 -0
  165. package/src/modules/manufacturing/docs/models/ManufacturingCostSummary.md +66 -0
  166. package/src/modules/manufacturing/docs/models/ProductionOrder.md +76 -0
  167. package/src/modules/manufacturing/docs/models/Routing.md +58 -0
  168. package/src/modules/manufacturing/docs/models/WorkCenter.md +56 -0
  169. package/src/modules/manufacturing/docs/models/WorkOrder.md +63 -0
  170. package/src/modules/manufacturing/docs/queries/DetectBillOfMaterialCircularReference.md +39 -0
  171. package/src/modules/manufacturing/docs/queries/ExplodeBillOfMaterial.md +56 -0
  172. package/src/modules/manufacturing/docs/queries/GetBillOfMaterial.md +37 -0
  173. package/src/modules/manufacturing/docs/queries/GetManufacturingCostSummary.md +39 -0
  174. package/src/modules/manufacturing/docs/queries/GetProductionOrder.md +37 -0
  175. package/src/modules/manufacturing/docs/queries/GetRouting.md +39 -0
  176. package/src/modules/manufacturing/docs/queries/GetWorkCenter.md +35 -0
  177. package/src/modules/manufacturing/docs/queries/GetWorkOrder.md +38 -0
  178. package/src/modules/manufacturing/docs/queries/ListBillOfMaterialsByItem.md +42 -0
  179. package/src/modules/manufacturing/docs/queries/ListManufacturingCostSummariesByStatus.md +41 -0
  180. package/src/modules/manufacturing/docs/queries/ListProductionOrdersByStatus.md +41 -0
  181. package/src/modules/manufacturing/docs/queries/ListRoutingsByItem.md +42 -0
  182. package/src/modules/manufacturing/docs/queries/ListWorkCentersBySite.md +38 -0
  183. package/src/modules/manufacturing/docs/queries/ListWorkOrdersByProductionOrder.md +39 -0
  184. package/src/modules/manufacturing/docs/queries/ListWorkOrdersByWorkCenter.md +43 -0
  185. package/src/modules/manufacturing/executor/.gitkeep +0 -0
  186. package/src/modules/manufacturing/generated/enums.ts +113 -0
  187. package/src/modules/manufacturing/generated/kysely-tailordb.ts +247 -0
  188. package/src/modules/manufacturing/index.ts +2 -0
  189. package/src/modules/manufacturing/lib/_db_deps.ts +22 -0
  190. package/src/modules/manufacturing/lib/errors.generated.ts +592 -0
  191. package/src/modules/manufacturing/lib/permissions.generated.ts +35 -0
  192. package/src/modules/manufacturing/lib/types.ts +111 -0
  193. package/src/modules/manufacturing/module.ts +226 -0
  194. package/src/modules/manufacturing/permissions.ts +3 -0
  195. package/src/modules/manufacturing/query/.gitkeep +0 -0
  196. package/src/modules/manufacturing/query/detectBillOfMaterialCircularReference.generated.ts +5 -0
  197. package/src/modules/manufacturing/query/detectBillOfMaterialCircularReference.test.ts +115 -0
  198. package/src/modules/manufacturing/query/detectBillOfMaterialCircularReference.ts +79 -0
  199. package/src/modules/manufacturing/query/explodeBillOfMaterial.generated.ts +5 -0
  200. package/src/modules/manufacturing/query/explodeBillOfMaterial.test.ts +445 -0
  201. package/src/modules/manufacturing/query/explodeBillOfMaterial.ts +306 -0
  202. package/src/modules/manufacturing/query/getBillOfMaterial.generated.ts +5 -0
  203. package/src/modules/manufacturing/query/getBillOfMaterial.test.ts +64 -0
  204. package/src/modules/manufacturing/query/getBillOfMaterial.ts +27 -0
  205. package/src/modules/manufacturing/query/getManufacturingCostSummary.generated.ts +5 -0
  206. package/src/modules/manufacturing/query/getManufacturingCostSummary.test.ts +147 -0
  207. package/src/modules/manufacturing/query/getManufacturingCostSummary.ts +46 -0
  208. package/src/modules/manufacturing/query/getProductionOrder.generated.ts +5 -0
  209. package/src/modules/manufacturing/query/getProductionOrder.test.ts +139 -0
  210. package/src/modules/manufacturing/query/getProductionOrder.ts +84 -0
  211. package/src/modules/manufacturing/query/getRouting.generated.ts +5 -0
  212. package/src/modules/manufacturing/query/getRouting.test.ts +71 -0
  213. package/src/modules/manufacturing/query/getRouting.ts +34 -0
  214. package/src/modules/manufacturing/query/getWorkCenter.generated.ts +5 -0
  215. package/src/modules/manufacturing/query/getWorkCenter.test.ts +37 -0
  216. package/src/modules/manufacturing/query/getWorkCenter.ts +21 -0
  217. package/src/modules/manufacturing/query/getWorkOrder.generated.ts +5 -0
  218. package/src/modules/manufacturing/query/getWorkOrder.test.ts +73 -0
  219. package/src/modules/manufacturing/query/getWorkOrder.ts +28 -0
  220. package/src/modules/manufacturing/query/listBillOfMaterialsByItem.generated.ts +5 -0
  221. package/src/modules/manufacturing/query/listBillOfMaterialsByItem.test.ts +107 -0
  222. package/src/modules/manufacturing/query/listBillOfMaterialsByItem.ts +58 -0
  223. package/src/modules/manufacturing/query/listManufacturingCostSummariesByStatus.generated.ts +5 -0
  224. package/src/modules/manufacturing/query/listManufacturingCostSummariesByStatus.test.ts +96 -0
  225. package/src/modules/manufacturing/query/listManufacturingCostSummariesByStatus.ts +77 -0
  226. package/src/modules/manufacturing/query/listProductionOrdersByStatus.generated.ts +5 -0
  227. package/src/modules/manufacturing/query/listProductionOrdersByStatus.test.ts +121 -0
  228. package/src/modules/manufacturing/query/listProductionOrdersByStatus.ts +83 -0
  229. package/src/modules/manufacturing/query/listRoutingsByItem.generated.ts +5 -0
  230. package/src/modules/manufacturing/query/listRoutingsByItem.test.ts +110 -0
  231. package/src/modules/manufacturing/query/listRoutingsByItem.ts +54 -0
  232. package/src/modules/manufacturing/query/listWorkCentersBySite.generated.ts +5 -0
  233. package/src/modules/manufacturing/query/listWorkCentersBySite.test.ts +81 -0
  234. package/src/modules/manufacturing/query/listWorkCentersBySite.ts +70 -0
  235. package/src/modules/manufacturing/query/listWorkOrdersByProductionOrder.generated.ts +5 -0
  236. package/src/modules/manufacturing/query/listWorkOrdersByProductionOrder.test.ts +102 -0
  237. package/src/modules/manufacturing/query/listWorkOrdersByProductionOrder.ts +53 -0
  238. package/src/modules/manufacturing/query/listWorkOrdersByWorkCenter.generated.ts +5 -0
  239. package/src/modules/manufacturing/query/listWorkOrdersByWorkCenter.test.ts +143 -0
  240. package/src/modules/manufacturing/query/listWorkOrdersByWorkCenter.ts +56 -0
  241. package/src/modules/manufacturing/seed/index.ts +19 -0
  242. package/src/modules/manufacturing/tailor.config.ts +13 -0
  243. package/src/modules/manufacturing/tailor.d.ts +13 -0
  244. package/src/modules/manufacturing/testing/commandTestUtils.ts +29 -0
  245. package/src/modules/manufacturing/testing/fixtures.ts +402 -0
  246. package/templates/scaffold/app/backend/package.json +9 -2
  247. package/templates/scaffold/app/backend/src/tests/utils/graphql-client.ts +66 -0
  248. package/templates/scaffold/app/backend/src/tests/utils/setup.ts +21 -0
  249. package/templates/scaffold/app/backend/tsconfig.json +9 -2
  250. package/templates/scaffold/app/backend/vitest.config.ts +35 -0
  251. package/templates/scaffold/app/frontend/package.json +2 -2
@@ -0,0 +1,220 @@
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
+ ProductionOrderNotReleasableError,
7
+ BomNotResolvedError,
8
+ RoutingNotResolvedError,
9
+ CrossCompanyMasterReferenceError,
10
+ CrossSiteMasterReferenceError,
11
+ PlannedMaterialCostUnavailableError,
12
+ } from "../lib/errors.generated";
13
+ import {
14
+ baseDraftProductionOrder,
15
+ baseReleasedProductionOrder,
16
+ baseActiveBom,
17
+ baseActiveRouting,
18
+ baseBomLine,
19
+ baseRoutingOperation,
20
+ baseActiveWorkCenter,
21
+ } from "../testing/fixtures";
22
+ import { run } from "./releaseProductionOrder";
23
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
24
+
25
+ const baseReleaseValuation = {
26
+ id: "item-2",
27
+ valuationMethod: "AVCO",
28
+ costPerUnit: 10,
29
+ currencyCode: "USD",
30
+ valuationReference: "valuation-1",
31
+ };
32
+
33
+ describe("releaseProductionOrder", () => {
34
+ const ctx: CommandContext = {
35
+ actorId: "test-actor",
36
+ permissions: ["manufacturing:releaseProductionOrder"],
37
+ };
38
+
39
+ it("releases a draft order with valid BOM and routing snapshots", async () => {
40
+ const { db, spies } = createMockDb<Transaction>();
41
+ const released = { ...baseDraftProductionOrder, status: "RELEASED" as const };
42
+
43
+ // order lookup
44
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
45
+ // bom resolution (no selectedBomVersionId, so default resolution)
46
+ spies.select.mockReturnValueOnce(baseActiveBom);
47
+ // routing resolution (no selectedRoutingRevisionId, so default resolution)
48
+ spies.select.mockReturnValueOnce(baseActiveRouting);
49
+ // BOM lines query
50
+ spies.select.mockReturnValueOnce([baseBomLine]);
51
+ // material cost valuation for bomLine itemId
52
+ spies.select.mockReturnValueOnce(baseReleaseValuation);
53
+ // routing operations query
54
+ spies.select.mockReturnValueOnce([baseRoutingOperation]);
55
+ // work center lookup for operation
56
+ spies.select.mockReturnValueOnce(baseActiveWorkCenter);
57
+ // inserts: bom snapshot, routing snapshot, cost baseline, material requirement, work order, cost summary
58
+ spies.insert.mockReturnValue({});
59
+ // update: order status
60
+ spies.update.mockReturnValue(released);
61
+
62
+ const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
63
+
64
+ expect(result.ok).toBe(true);
65
+ if (result.ok) {
66
+ expect(result.value.productionOrder.status).toBe("RELEASED");
67
+ }
68
+ const snapshotValues = spies.values.mock.calls.find(
69
+ ([values]) =>
70
+ typeof values === "object" &&
71
+ values !== null &&
72
+ "snapshotData" in (values as Record<string, unknown>),
73
+ )?.[0] as Record<string, unknown> | undefined;
74
+ expect(snapshotValues?.snapshotData).not.toBeNull();
75
+ });
76
+
77
+ it("returns error when the order does not exist", async () => {
78
+ const { db, spies } = createMockDb<Transaction>();
79
+ spies.select.mockReturnValueOnce(undefined);
80
+
81
+ const result = await run(db, { id: "nonexistent" }, ctx);
82
+
83
+ expect(result.ok).toBe(false);
84
+ if (!result.ok) {
85
+ expect(result.error).toBeInstanceOf(ProductionOrderNotFoundError);
86
+ }
87
+ });
88
+
89
+ it("returns error when the order is not in DRAFT", async () => {
90
+ const { db, spies } = createMockDb<Transaction>();
91
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
92
+
93
+ const result = await run(db, { id: baseReleasedProductionOrder.id }, ctx);
94
+
95
+ expect(result.ok).toBe(false);
96
+ if (!result.ok) {
97
+ expect(result.error).toBeInstanceOf(ProductionOrderNotReleasableError);
98
+ }
99
+ });
100
+
101
+ it("returns error when no active BOM can be resolved", async () => {
102
+ const { db, spies } = createMockDb<Transaction>();
103
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
104
+ // bom resolution returns nothing
105
+ spies.select.mockReturnValueOnce(undefined);
106
+
107
+ const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
108
+
109
+ expect(result.ok).toBe(false);
110
+ if (!result.ok) {
111
+ expect(result.error).toBeInstanceOf(BomNotResolvedError);
112
+ }
113
+ });
114
+
115
+ it("returns error when no active routing can be resolved", async () => {
116
+ const { db, spies } = createMockDb<Transaction>();
117
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
118
+ // bom resolution
119
+ spies.select.mockReturnValueOnce(baseActiveBom);
120
+ // routing resolution returns nothing
121
+ spies.select.mockReturnValueOnce(undefined);
122
+
123
+ const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
124
+
125
+ expect(result.ok).toBe(false);
126
+ if (!result.ok) {
127
+ expect(result.error).toBeInstanceOf(RoutingNotResolvedError);
128
+ }
129
+ });
130
+
131
+ it("returns error when BOM or routing belongs to another company", async () => {
132
+ const { db, spies } = createMockDb<Transaction>();
133
+ const crossCompanyBom = { ...baseActiveBom, companyId: "other-company" };
134
+
135
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
136
+ // bom resolution
137
+ spies.select.mockReturnValueOnce(crossCompanyBom);
138
+
139
+ const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
140
+
141
+ expect(result.ok).toBe(false);
142
+ if (!result.ok) {
143
+ expect(result.error).toBeInstanceOf(CrossCompanyMasterReferenceError);
144
+ }
145
+ });
146
+
147
+ it("returns error when BOM or routing belongs to another site in the same company", async () => {
148
+ const { db, spies } = createMockDb<Transaction>();
149
+ const crossSiteBom = { ...baseActiveBom, companyId: "company-1", siteId: "other-site" };
150
+
151
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
152
+ // bom resolution
153
+ spies.select.mockReturnValueOnce(crossSiteBom);
154
+
155
+ const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
156
+
157
+ expect(result.ok).toBe(false);
158
+ if (!result.ok) {
159
+ expect(result.error).toBeInstanceOf(CrossSiteMasterReferenceError);
160
+ }
161
+ });
162
+
163
+ it("returns error when inventory valuation for a required component is unavailable", async () => {
164
+ const { db, spies } = createMockDb<Transaction>();
165
+
166
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
167
+ // bom resolution
168
+ spies.select.mockReturnValueOnce(baseActiveBom);
169
+ // routing resolution
170
+ spies.select.mockReturnValueOnce(baseActiveRouting);
171
+ // BOM lines query
172
+ spies.select.mockReturnValueOnce([baseBomLine]);
173
+ // material cost valuation returns nothing
174
+ spies.select.mockReturnValueOnce(undefined);
175
+
176
+ const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
177
+
178
+ expect(result.ok).toBe(false);
179
+ if (!result.ok) {
180
+ expect(result.error).toBeInstanceOf(PlannedMaterialCostUnavailableError);
181
+ }
182
+ });
183
+
184
+ it("creates work orders and a collecting cost summary at release", async () => {
185
+ const { db, spies } = createMockDb<Transaction>();
186
+ const released = {
187
+ ...baseDraftProductionOrder,
188
+ status: "RELEASED" as const,
189
+ selectedBomVersionId: baseActiveBom.id,
190
+ selectedRoutingRevisionId: baseActiveRouting.id,
191
+ };
192
+
193
+ // order lookup
194
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
195
+ // bom resolution
196
+ spies.select.mockReturnValueOnce(baseActiveBom);
197
+ // routing resolution
198
+ spies.select.mockReturnValueOnce(baseActiveRouting);
199
+ // BOM lines query
200
+ spies.select.mockReturnValueOnce([baseBomLine]);
201
+ // material cost valuation
202
+ spies.select.mockReturnValueOnce(baseReleaseValuation);
203
+ // routing operations query
204
+ spies.select.mockReturnValueOnce([baseRoutingOperation]);
205
+ // work center lookup
206
+ spies.select.mockReturnValueOnce(baseActiveWorkCenter);
207
+ // inserts
208
+ spies.insert.mockReturnValue({});
209
+ // update
210
+ spies.update.mockReturnValue(released);
211
+
212
+ const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
213
+
214
+ expect(result.ok).toBe(true);
215
+ // Verify inserts were called: bom snapshot + routing snapshot + cost baseline +
216
+ // material requirements + work orders + cost summary
217
+ expect(spies.insert).toHaveBeenCalled();
218
+ expect(spies.update).toHaveBeenCalled();
219
+ });
220
+ });
@@ -0,0 +1,450 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ ProductionOrderNotFoundError,
4
+ ProductionOrderNotReleasableError,
5
+ BomNotResolvedError,
6
+ RoutingNotResolvedError,
7
+ CrossCompanyMasterReferenceError,
8
+ CrossSiteMasterReferenceError,
9
+ PlannedMaterialCostUnavailableError,
10
+ } from "../lib/errors.generated";
11
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
12
+
13
+ export interface ReleaseProductionOrderInput {
14
+ id: string;
15
+ from?: string[];
16
+ }
17
+
18
+ interface ReleaseMaterialValuationSnapshot {
19
+ plannedUnitCost: number;
20
+ currencyCode: string;
21
+ valuationMethod: string;
22
+ sourceValuationReference: string | null;
23
+ }
24
+
25
+ function getOptionalNumber(record: Record<string, unknown>, key: string): number | null {
26
+ const value = record[key];
27
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
28
+ }
29
+
30
+ function getOptionalString(record: Record<string, unknown>, key: string): string | null {
31
+ const value = record[key];
32
+ return typeof value === "string" && value.length > 0 ? value : null;
33
+ }
34
+
35
+ function resolveReleaseMaterialValuation(
36
+ itemRecord: Record<string, unknown> | undefined,
37
+ ): ReleaseMaterialValuationSnapshot | null {
38
+ if (!itemRecord) {
39
+ return null;
40
+ }
41
+
42
+ const valuationMethod =
43
+ getOptionalString(itemRecord, "valuationMethod") ??
44
+ getOptionalString(itemRecord, "costingMethod") ??
45
+ (getOptionalNumber(itemRecord, "standardCostRate") != null ? "STANDARD_COST" : null) ??
46
+ (getOptionalNumber(itemRecord, "costPerUnit") != null ? "AVCO" : null);
47
+
48
+ if (!valuationMethod) {
49
+ return null;
50
+ }
51
+
52
+ const plannedUnitCost =
53
+ valuationMethod === "STANDARD_COST"
54
+ ? (getOptionalNumber(itemRecord, "standardCostRate") ??
55
+ getOptionalNumber(itemRecord, "costPerUnit"))
56
+ : (getOptionalNumber(itemRecord, "costPerUnit") ??
57
+ getOptionalNumber(itemRecord, "standardCostRate"));
58
+
59
+ if (plannedUnitCost == null) {
60
+ return null;
61
+ }
62
+
63
+ return {
64
+ plannedUnitCost,
65
+ currencyCode:
66
+ getOptionalString(itemRecord, "currencyCode") ??
67
+ getOptionalString(itemRecord, "valuationCurrency") ??
68
+ "USD",
69
+ valuationMethod,
70
+ sourceValuationReference:
71
+ getOptionalString(itemRecord, "valuationReference") ??
72
+ getOptionalString(itemRecord, "valuationPolicyId") ??
73
+ getOptionalString(itemRecord, "itemValuationId"),
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Function: releaseProductionOrder
79
+ *
80
+ * Turns a draft plan into executable work by resolving active BOM and routing
81
+ * content, freezing snapshots, creating work orders and material requirements,
82
+ * and opening manufacturing cost collection.
83
+ */
84
+ export async function run<CF extends Record<string, unknown>>(
85
+ db: Transaction,
86
+ input: ReleaseProductionOrderInput & CF,
87
+ _ctx: CommandContext,
88
+ ) {
89
+ const { id, from, ...customFields } = input;
90
+ void customFields;
91
+
92
+ const allowedStatuses = from ?? ["DRAFT"];
93
+ const now = new Date();
94
+
95
+ // 1. Fetch production order with lock
96
+ const order = await db
97
+ .selectFrom("ProductionOrder")
98
+ .selectAll()
99
+ .where("id", "=", id)
100
+ .forUpdate()
101
+ .executeTakeFirst();
102
+
103
+ if (!order) {
104
+ return err(new ProductionOrderNotFoundError(id));
105
+ }
106
+
107
+ // 2. Validate status is releasable
108
+ if (!allowedStatuses.includes(order.status)) {
109
+ return err(new ProductionOrderNotReleasableError(id));
110
+ }
111
+
112
+ // 3. Resolve active BOM
113
+ let bom;
114
+ if (order.selectedBomVersionId) {
115
+ bom = await db
116
+ .selectFrom("BillOfMaterial")
117
+ .selectAll()
118
+ .where("id", "=", order.selectedBomVersionId)
119
+ .where("status", "=", "ACTIVE")
120
+ .executeTakeFirst();
121
+ } else {
122
+ bom = await db
123
+ .selectFrom("BillOfMaterial")
124
+ .selectAll()
125
+ .where("parentItemId", "=", order.orderedItemId)
126
+ .where("companyId", "=", order.companyId)
127
+ .where("status", "=", "ACTIVE")
128
+ .executeTakeFirst();
129
+ }
130
+
131
+ if (!bom) {
132
+ return err(new BomNotResolvedError(id));
133
+ }
134
+
135
+ // 4. Validate BOM company scope
136
+ if (bom.companyId !== order.companyId) {
137
+ return err(new CrossCompanyMasterReferenceError(id));
138
+ }
139
+
140
+ // 5. Validate BOM site scope
141
+ if (bom.siteId != null && bom.siteId !== order.siteId) {
142
+ return err(new CrossSiteMasterReferenceError(id));
143
+ }
144
+
145
+ // 6. Resolve active routing
146
+ let routing;
147
+ if (order.selectedRoutingRevisionId) {
148
+ routing = await db
149
+ .selectFrom("Routing")
150
+ .selectAll()
151
+ .where("id", "=", order.selectedRoutingRevisionId)
152
+ .where("status", "=", "ACTIVE")
153
+ .executeTakeFirst();
154
+ } else {
155
+ routing = await db
156
+ .selectFrom("Routing")
157
+ .selectAll()
158
+ .where("parentItemId", "=", order.orderedItemId)
159
+ .where("companyId", "=", order.companyId)
160
+ .where("status", "=", "ACTIVE")
161
+ .executeTakeFirst();
162
+ }
163
+
164
+ if (!routing) {
165
+ return err(new RoutingNotResolvedError(id));
166
+ }
167
+
168
+ // 7. Validate routing company scope
169
+ if (routing.companyId !== order.companyId) {
170
+ return err(new CrossCompanyMasterReferenceError(id));
171
+ }
172
+
173
+ // 8. Validate routing site scope
174
+ if (routing.siteId != null && routing.siteId !== order.siteId) {
175
+ return err(new CrossSiteMasterReferenceError(id));
176
+ }
177
+
178
+ // 9. Fetch BOM lines for material requirements
179
+ const bomLines = await db
180
+ .selectFrom("BillOfMaterialLine")
181
+ .selectAll()
182
+ .where("billOfMaterialId", "=", bom.id)
183
+ .execute();
184
+
185
+ // 10. Resolve planned material cost for each component
186
+ let totalPlannedMaterialCost = 0;
187
+ let baselineCurrencyCode = "USD";
188
+ const materialRequirements: {
189
+ itemId: string;
190
+ requiredQuantity: number;
191
+ unitOfMeasure: string | null;
192
+ plannedUnitCost: number;
193
+ valuationMethod: string;
194
+ valuationCurrencyCode: string;
195
+ sourceValuationReference: string | null;
196
+ }[] = [];
197
+
198
+ for (const line of bomLines) {
199
+ // Resolve release-time valuation from inventory-owned item valuation context.
200
+ const itemValuationRecord = (await db
201
+ .selectFrom("Item")
202
+ .selectAll()
203
+ .where("id", "=", line.itemId)
204
+ .executeTakeFirst()) as Record<string, unknown> | undefined;
205
+ const valuation = resolveReleaseMaterialValuation(itemValuationRecord);
206
+
207
+ if (!valuation) {
208
+ return err(new PlannedMaterialCostUnavailableError(line.itemId));
209
+ }
210
+
211
+ const requiredQuantity = line.requiredQuantity * order.plannedQuantity;
212
+ totalPlannedMaterialCost += valuation.plannedUnitCost * requiredQuantity;
213
+ baselineCurrencyCode = valuation.currencyCode;
214
+
215
+ materialRequirements.push({
216
+ itemId: line.itemId,
217
+ requiredQuantity,
218
+ unitOfMeasure: line.unitOfMeasure ?? null,
219
+ plannedUnitCost: valuation.plannedUnitCost,
220
+ valuationMethod: valuation.valuationMethod,
221
+ valuationCurrencyCode: valuation.currencyCode,
222
+ sourceValuationReference: valuation.sourceValuationReference,
223
+ });
224
+ }
225
+
226
+ // 11. Fetch routing operations for work orders
227
+ const operations = await db
228
+ .selectFrom("RoutingOperation")
229
+ .selectAll()
230
+ .where("routingId", "=", routing.id)
231
+ .execute();
232
+
233
+ // 12. Calculate planned labor and machine costs from operations and work centers
234
+ let totalPlannedLaborCost = 0;
235
+ let totalPlannedMachineCost = 0;
236
+ let totalPlannedOverheadCost = 0;
237
+ const workCenterSnapshots = new Map<
238
+ string,
239
+ {
240
+ workCenterId: string;
241
+ code: string | null;
242
+ laborRate: number | null;
243
+ machineRate: number | null;
244
+ overheadAbsorptionMethod: string | null;
245
+ overheadAbsorptionRate: number | null;
246
+ overheadAbsorptionCurrency: string | null;
247
+ }
248
+ >();
249
+
250
+ for (const op of operations) {
251
+ const wc = await db
252
+ .selectFrom("WorkCenter")
253
+ .selectAll()
254
+ .where("id", "=", op.workCenterId)
255
+ .executeTakeFirst();
256
+
257
+ if (wc) {
258
+ const duration = op.standardSetupTime + op.standardRunTime * order.plannedQuantity;
259
+ const laborCost = (wc.laborRate ?? 0) * duration;
260
+ const machineCost = (wc.machineRate ?? 0) * duration;
261
+ totalPlannedLaborCost += laborCost;
262
+ totalPlannedMachineCost += machineCost;
263
+
264
+ const wcRecord = wc as Record<string, unknown>;
265
+ const overheadRate = getOptionalNumber(wcRecord, "overheadAbsorptionRate");
266
+ if (wc.overheadAbsorptionMethod === "PERCENT_OF_LABOR_COST" && overheadRate != null) {
267
+ totalPlannedOverheadCost += laborCost * (overheadRate / 100);
268
+ } else if (
269
+ wc.overheadAbsorptionMethod === "PERCENT_OF_MACHINE_COST" &&
270
+ overheadRate != null
271
+ ) {
272
+ totalPlannedOverheadCost += machineCost * (overheadRate / 100);
273
+ } else if (
274
+ wc.overheadAbsorptionMethod === "FIXED_AMOUNT_PER_GOOD_UNIT" &&
275
+ overheadRate != null
276
+ ) {
277
+ totalPlannedOverheadCost += order.plannedQuantity * overheadRate;
278
+ }
279
+
280
+ workCenterSnapshots.set(op.workCenterId, {
281
+ workCenterId: wc.id,
282
+ code: wc.code,
283
+ laborRate: wc.laborRate ?? null,
284
+ machineRate: wc.machineRate ?? null,
285
+ overheadAbsorptionMethod: wc.overheadAbsorptionMethod ?? null,
286
+ overheadAbsorptionRate: overheadRate,
287
+ overheadAbsorptionCurrency: wc.overheadAbsorptionCurrency ?? null,
288
+ });
289
+ }
290
+ }
291
+
292
+ // 13. Create BOM snapshot
293
+ await db
294
+ .insertInto("ProductionOrderBomSnapshot")
295
+ .values({
296
+ productionOrderId: order.id,
297
+ parentItemId: bom.parentItemId,
298
+ bomType: bom.bomType,
299
+ snapshotData: JSON.stringify({
300
+ bomId: bom.id,
301
+ revisionNumber: bom.revisionNumber,
302
+ companyId: bom.companyId,
303
+ siteId: bom.siteId,
304
+ effectivityStartDate: bom.effectivityStartDate,
305
+ effectivityEndDate: bom.effectivityEndDate,
306
+ defaultSelection: bom.defaultSelection,
307
+ lines: bomLines.map((line) => {
308
+ const requirement = materialRequirements.find((req) => req.itemId === line.itemId);
309
+ return {
310
+ itemId: line.itemId,
311
+ requiredQuantity: line.requiredQuantity,
312
+ unitOfMeasure: line.unitOfMeasure,
313
+ scrapAssumption: line.scrapAssumption,
314
+ isSubassembly: line.isSubassembly,
315
+ plannedUnitCost: requirement?.plannedUnitCost ?? null,
316
+ currencyCode: requirement?.valuationCurrencyCode ?? null,
317
+ valuationMethod: requirement?.valuationMethod ?? null,
318
+ sourceValuationReference: requirement?.sourceValuationReference ?? null,
319
+ };
320
+ }),
321
+ }),
322
+ createdAt: now,
323
+ updatedAt: null,
324
+ })
325
+ .returningAll()
326
+ .executeTakeFirst();
327
+
328
+ // 14. Create routing snapshot
329
+ await db
330
+ .insertInto("ProductionOrderRoutingSnapshot")
331
+ .values({
332
+ productionOrderId: order.id,
333
+ routingId: routing.id,
334
+ snapshotData: JSON.stringify({
335
+ routingId: routing.id,
336
+ revisionNumber: routing.revisionNumber,
337
+ companyId: routing.companyId,
338
+ siteId: routing.siteId,
339
+ operations: operations.map((op) => ({
340
+ id: op.id,
341
+ sequenceNumber: op.sequenceNumber,
342
+ operationDescription: op.operationDescription,
343
+ workCenterId: op.workCenterId,
344
+ standardSetupTime: op.standardSetupTime,
345
+ standardRunTime: op.standardRunTime,
346
+ operatorInstructions: op.operatorInstructions,
347
+ workCenterSnapshot: workCenterSnapshots.get(op.workCenterId) ?? null,
348
+ })),
349
+ }),
350
+ createdAt: now,
351
+ updatedAt: null,
352
+ })
353
+ .returningAll()
354
+ .executeTakeFirst();
355
+
356
+ // 15. Create cost baseline
357
+ await db
358
+ .insertInto("ProductionOrderCostBaseline")
359
+ .values({
360
+ productionOrderId: order.id,
361
+ plannedMaterialCost: totalPlannedMaterialCost,
362
+ plannedLaborCost: totalPlannedLaborCost,
363
+ plannedMachineCost: totalPlannedMachineCost,
364
+ plannedOverheadCost: totalPlannedOverheadCost,
365
+ currencyCode: baselineCurrencyCode,
366
+ createdAt: now,
367
+ updatedAt: null,
368
+ })
369
+ .returningAll()
370
+ .executeTakeFirst();
371
+
372
+ // 16. Create material requirements
373
+ for (const matReq of materialRequirements) {
374
+ await db
375
+ .insertInto("ProductionOrderMaterialRequirement")
376
+ .values({
377
+ productionOrderId: order.id,
378
+ itemId: matReq.itemId,
379
+ requiredQuantity: matReq.requiredQuantity,
380
+ unitOfMeasure: matReq.unitOfMeasure,
381
+ plannedUnitCost: matReq.plannedUnitCost,
382
+ createdAt: now,
383
+ updatedAt: null,
384
+ })
385
+ .returningAll()
386
+ .executeTakeFirst();
387
+ }
388
+
389
+ // 17. Create work orders from routing operations
390
+ for (const op of operations) {
391
+ await db
392
+ .insertInto("WorkOrder")
393
+ .values({
394
+ productionOrderId: order.id,
395
+ routingOperationSequenceNumber: op.sequenceNumber,
396
+ workCenterId: op.workCenterId,
397
+ plannedQuantity: order.plannedQuantity,
398
+ completedQuantity: 0,
399
+ scrapQuantity: 0,
400
+ actualSetupTime: 0,
401
+ actualRunTime: 0,
402
+ actualStartDate: null,
403
+ pauseReason: null,
404
+ executionNotes: null,
405
+ status: "PENDING",
406
+ createdAt: now,
407
+ updatedAt: null,
408
+ })
409
+ .returningAll()
410
+ .executeTakeFirst();
411
+ }
412
+
413
+ // 18. Create manufacturing cost summary in COLLECTING status
414
+ await db
415
+ .insertInto("ManufacturingCostSummary")
416
+ .values({
417
+ productionOrderId: order.id,
418
+ plannedMaterialCost: totalPlannedMaterialCost,
419
+ plannedLaborCost: totalPlannedLaborCost,
420
+ plannedMachineCost: totalPlannedMachineCost,
421
+ plannedOverheadCost: totalPlannedOverheadCost,
422
+ actualMaterialCost: 0,
423
+ actualLaborCost: 0,
424
+ actualMachineCost: 0,
425
+ actualOverheadCost: 0,
426
+ currencyCode: baselineCurrencyCode,
427
+ reviewedDate: null,
428
+ reviewerNotes: null,
429
+ status: "COLLECTING",
430
+ createdAt: now,
431
+ updatedAt: null,
432
+ })
433
+ .returningAll()
434
+ .executeTakeFirst();
435
+
436
+ // 19. Update order status to RELEASED with selected BOM/routing IDs
437
+ const releasedOrder = await db
438
+ .updateTable("ProductionOrder")
439
+ .set({
440
+ selectedBomVersionId: bom.id,
441
+ selectedRoutingRevisionId: routing.id,
442
+ status: "RELEASED",
443
+ updatedAt: now,
444
+ })
445
+ .where("id", "=", id)
446
+ .returningAll()
447
+ .executeTakeFirstOrThrow();
448
+
449
+ return ok({ productionOrder: releasedOrder });
450
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./reopenProductionOrder";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const reopenProductionOrder = defineCommand(permissions.reopenProductionOrder, run);