@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,118 @@
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
+ WorkOrderNotStartableError,
7
+ ParentOrderNotExecutableError,
8
+ OperationSequenceBlockedError,
9
+ } from "../lib/errors.generated";
10
+ import {
11
+ basePendingWorkOrder,
12
+ baseInProgressWorkOrder,
13
+ baseReleasedProductionOrder,
14
+ baseDraftProductionOrder,
15
+ } from "../testing/fixtures";
16
+ import { run } from "./startWorkOrder";
17
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
18
+
19
+ describe("startWorkOrder", () => {
20
+ const ctx: CommandContext = {
21
+ actorId: "test-actor",
22
+ permissions: ["manufacturing:startWorkOrder"],
23
+ };
24
+
25
+ it("starts a pending work order", async () => {
26
+ const { db, spies } = createMockDb<Transaction>();
27
+ const startedWorkOrder = {
28
+ ...basePendingWorkOrder,
29
+ status: "IN_PROGRESS" as const,
30
+ actualStartDate: new Date(),
31
+ };
32
+
33
+ // work order lookup
34
+ spies.select.mockReturnValueOnce(basePendingWorkOrder);
35
+ // parent production order lookup
36
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
37
+ // sibling work orders (no predecessors)
38
+ spies.select.mockReturnValueOnce([basePendingWorkOrder]);
39
+ // update work order
40
+ spies.update.mockReturnValueOnce(startedWorkOrder);
41
+ // insert execution event
42
+ spies.insert.mockReturnValueOnce(undefined);
43
+ // update parent production order to IN_PROGRESS
44
+ spies.update.mockReturnValueOnce(undefined);
45
+
46
+ const result = await run(db, { id: basePendingWorkOrder.id }, ctx);
47
+
48
+ expect(result.ok).toBe(true);
49
+ if (result.ok) {
50
+ expect(result.value.workOrder.status).toBe("IN_PROGRESS");
51
+ }
52
+ expect(spies.update).toHaveBeenCalled();
53
+ expect(spies.insert).toHaveBeenCalled();
54
+ });
55
+
56
+ it("returns error when the work order does not exist", async () => {
57
+ const { db, spies } = createMockDb<Transaction>();
58
+ spies.select.mockReturnValueOnce(undefined);
59
+
60
+ const result = await run(db, { id: "nonexistent" }, ctx);
61
+
62
+ expect(result.ok).toBe(false);
63
+ if (!result.ok) {
64
+ expect(result.error).toBeInstanceOf(WorkOrderNotFoundError);
65
+ }
66
+ });
67
+
68
+ it("returns error when the work order is not pending", async () => {
69
+ const { db, spies } = createMockDb<Transaction>();
70
+ spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
71
+
72
+ const result = await run(db, { id: baseInProgressWorkOrder.id }, ctx);
73
+
74
+ expect(result.ok).toBe(false);
75
+ if (!result.ok) {
76
+ expect(result.error).toBeInstanceOf(WorkOrderNotStartableError);
77
+ }
78
+ });
79
+
80
+ it("returns error when the parent production order is not executable", async () => {
81
+ const { db, spies } = createMockDb<Transaction>();
82
+
83
+ // work order lookup
84
+ spies.select.mockReturnValueOnce(basePendingWorkOrder);
85
+ // parent production order - DRAFT is not executable
86
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
87
+
88
+ const result = await run(db, { id: basePendingWorkOrder.id }, ctx);
89
+
90
+ expect(result.ok).toBe(false);
91
+ if (!result.ok) {
92
+ expect(result.error).toBeInstanceOf(ParentOrderNotExecutableError);
93
+ }
94
+ });
95
+
96
+ it("returns error when required predecessor work is incomplete", async () => {
97
+ const { db, spies } = createMockDb<Transaction>();
98
+ const secondWorkOrder = {
99
+ ...basePendingWorkOrder,
100
+ id: "work-order-second",
101
+ routingOperationSequenceNumber: 20,
102
+ };
103
+
104
+ // work order lookup (second operation)
105
+ spies.select.mockReturnValueOnce(secondWorkOrder);
106
+ // parent production order
107
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
108
+ // sibling work orders - first is still PENDING
109
+ spies.select.mockReturnValueOnce([basePendingWorkOrder, secondWorkOrder]);
110
+
111
+ const result = await run(db, { id: secondWorkOrder.id }, ctx);
112
+
113
+ expect(result.ok).toBe(false);
114
+ if (!result.ok) {
115
+ expect(result.error).toBeInstanceOf(OperationSequenceBlockedError);
116
+ }
117
+ });
118
+ });
@@ -0,0 +1,126 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ WorkOrderNotFoundError,
4
+ WorkOrderNotStartableError,
5
+ ParentOrderNotExecutableError,
6
+ OperationSequenceBlockedError,
7
+ } from "../lib/errors.generated";
8
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
9
+
10
+ const EXECUTABLE_ORDER_STATUSES = ["RELEASED", "IN_PROGRESS"] as const;
11
+
12
+ export interface StartWorkOrderInput {
13
+ id: string;
14
+ }
15
+
16
+ /**
17
+ * Function: startWorkOrder
18
+ *
19
+ * Begins execution on a pending work order. Records the actual start timestamp,
20
+ * moves the work order to IN_PROGRESS, and transitions the parent production
21
+ * order to IN_PROGRESS if it was RELEASED.
22
+ */
23
+ export async function run<CF extends Record<string, unknown>>(
24
+ db: Transaction,
25
+ input: StartWorkOrderInput & CF,
26
+ _ctx: CommandContext,
27
+ ) {
28
+ const { id, ...customFields } = input;
29
+ void customFields;
30
+
31
+ // 1. Fetch work order with lock
32
+ const workOrder = await db
33
+ .selectFrom("WorkOrder")
34
+ .selectAll()
35
+ .where("id", "=", id)
36
+ .forUpdate()
37
+ .executeTakeFirst();
38
+
39
+ if (!workOrder) {
40
+ return err(new WorkOrderNotFoundError(id));
41
+ }
42
+
43
+ // 2. Validate status is PENDING
44
+ if (workOrder.status !== "PENDING") {
45
+ return err(new WorkOrderNotStartableError(id));
46
+ }
47
+
48
+ // 3. Check parent production order is in an execution-capable state
49
+ const parentOrder = await db
50
+ .selectFrom("ProductionOrder")
51
+ .selectAll()
52
+ .where("id", "=", workOrder.productionOrderId)
53
+ .forUpdate()
54
+ .executeTakeFirst();
55
+
56
+ if (
57
+ !parentOrder ||
58
+ !EXECUTABLE_ORDER_STATUSES.includes(
59
+ parentOrder.status as (typeof EXECUTABLE_ORDER_STATUSES)[number],
60
+ )
61
+ ) {
62
+ return err(new ParentOrderNotExecutableError(id));
63
+ }
64
+
65
+ // 4. Check preceding operations are complete (sequence order guard)
66
+ const siblingWorkOrders = await db
67
+ .selectFrom("WorkOrder")
68
+ .selectAll()
69
+ .where("productionOrderId", "=", workOrder.productionOrderId)
70
+ .execute();
71
+
72
+ const hasPendingPredecessor = siblingWorkOrders.some(
73
+ (wo) =>
74
+ wo.routingOperationSequenceNumber < workOrder.routingOperationSequenceNumber &&
75
+ wo.status !== "COMPLETE" &&
76
+ wo.status !== "CANCELLED",
77
+ );
78
+
79
+ if (hasPendingPredecessor) {
80
+ return err(new OperationSequenceBlockedError(id));
81
+ }
82
+
83
+ // 5. Record actual start and set IN_PROGRESS
84
+ const now = new Date();
85
+
86
+ const updatedWorkOrder = await db
87
+ .updateTable("WorkOrder")
88
+ .set({
89
+ status: "IN_PROGRESS",
90
+ actualStartDate: now,
91
+ updatedAt: now,
92
+ })
93
+ .where("id", "=", id)
94
+ .returningAll()
95
+ .executeTakeFirstOrThrow();
96
+
97
+ // 6. Create STARTED execution event
98
+ await db
99
+ .insertInto("WorkOrderExecutionEvent")
100
+ .values({
101
+ workOrderId: id,
102
+ eventType: "STARTED",
103
+ timestamp: now,
104
+ quantity: null,
105
+ timeValue: null,
106
+ scrapValue: null,
107
+ notes: null,
108
+ createdAt: now,
109
+ updatedAt: null,
110
+ })
111
+ .execute();
112
+
113
+ // 7. If parent production order is RELEASED, transition to IN_PROGRESS
114
+ if (parentOrder.status === "RELEASED") {
115
+ await db
116
+ .updateTable("ProductionOrder")
117
+ .set({
118
+ status: "IN_PROGRESS",
119
+ updatedAt: now,
120
+ })
121
+ .where("id", "=", parentOrder.id)
122
+ .execute();
123
+ }
124
+
125
+ return ok({ workOrder: updatedWorkOrder });
126
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./technicallyCompleteProductionOrder";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const technicallyCompleteProductionOrder = defineCommand(permissions.technicallyCompleteProductionOrder, run);
@@ -0,0 +1,153 @@
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
+ ProductionOrderNotTechnicallyCompletableError,
7
+ ExecutionExceptionRemainsError,
8
+ PendingMaterialIssueRequestsError,
9
+ CostSummaryNotReadyError,
10
+ } from "../lib/errors.generated";
11
+ import {
12
+ baseCompletedProductionOrder,
13
+ baseInProgressProductionOrder,
14
+ baseCompleteWorkOrder,
15
+ baseCancelledWorkOrder,
16
+ baseInProgressWorkOrder,
17
+ basePausedWorkOrder,
18
+ basePendingWorkOrder,
19
+ baseCollectingCostSummary,
20
+ basePendingReviewCostSummary,
21
+ } from "../testing/fixtures";
22
+ import { run } from "./technicallyCompleteProductionOrder";
23
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
24
+
25
+ describe("technicallyCompleteProductionOrder", () => {
26
+ const ctx: CommandContext = {
27
+ actorId: "test-actor",
28
+ permissions: ["manufacturing:technicallyCompleteProductionOrder"],
29
+ };
30
+
31
+ it("technically completes a completed order and opens variance review", async () => {
32
+ const { db, spies } = createMockDb<Transaction>();
33
+ const techComplete = {
34
+ ...baseCompletedProductionOrder,
35
+ status: "TECHNICALLY_COMPLETE" as const,
36
+ };
37
+
38
+ // order lookup
39
+ spies.select.mockReturnValueOnce(baseCompletedProductionOrder);
40
+ // work orders - all complete or cancelled
41
+ spies.select.mockReturnValueOnce([baseCompleteWorkOrder, baseCancelledWorkOrder]);
42
+ // cost summary in COLLECTING
43
+ spies.select.mockReturnValueOnce({
44
+ ...baseCollectingCostSummary,
45
+ productionOrderId: baseCompletedProductionOrder.id,
46
+ });
47
+ // update cost summary
48
+ spies.update.mockReturnValueOnce(undefined);
49
+ // update order status
50
+ spies.update.mockReturnValue(techComplete);
51
+
52
+ const result = await run(db, { id: baseCompletedProductionOrder.id }, ctx);
53
+
54
+ expect(result.ok).toBe(true);
55
+ if (result.ok) {
56
+ expect(result.value.productionOrder.status).toBe("TECHNICALLY_COMPLETE");
57
+ }
58
+ expect(spies.update).toHaveBeenCalled();
59
+ });
60
+
61
+ it("returns error when the order does not exist", async () => {
62
+ const { db, spies } = createMockDb<Transaction>();
63
+ spies.select.mockReturnValueOnce(undefined);
64
+
65
+ const result = await run(db, { id: "nonexistent" }, ctx);
66
+
67
+ expect(result.ok).toBe(false);
68
+ if (!result.ok) {
69
+ expect(result.error).toBeInstanceOf(ProductionOrderNotFoundError);
70
+ }
71
+ });
72
+
73
+ it("returns error when the order is not in COMPLETED", async () => {
74
+ const { db, spies } = createMockDb<Transaction>();
75
+ spies.select.mockReturnValueOnce(baseInProgressProductionOrder);
76
+
77
+ const result = await run(db, { id: baseInProgressProductionOrder.id }, ctx);
78
+
79
+ expect(result.ok).toBe(false);
80
+ if (!result.ok) {
81
+ expect(result.error).toBeInstanceOf(ProductionOrderNotTechnicallyCompletableError);
82
+ }
83
+ });
84
+
85
+ it("returns error when unresolved execution exceptions remain", async () => {
86
+ const { db, spies } = createMockDb<Transaction>();
87
+
88
+ // order lookup
89
+ spies.select.mockReturnValueOnce(baseCompletedProductionOrder);
90
+ // work orders - one still in progress
91
+ spies.select.mockReturnValueOnce([baseInProgressWorkOrder]);
92
+
93
+ const result = await run(db, { id: baseCompletedProductionOrder.id }, ctx);
94
+
95
+ expect(result.ok).toBe(false);
96
+ if (!result.ok) {
97
+ expect(result.error).toBeInstanceOf(ExecutionExceptionRemainsError);
98
+ }
99
+ });
100
+
101
+ it("returns error when pending material issue requests or unresolved backflush intents remain", async () => {
102
+ const { db, spies } = createMockDb<Transaction>();
103
+
104
+ // order lookup
105
+ spies.select.mockReturnValueOnce(baseCompletedProductionOrder);
106
+ // work orders - one still pending
107
+ spies.select.mockReturnValueOnce([baseCompleteWorkOrder, basePendingWorkOrder]);
108
+
109
+ const result = await run(db, { id: baseCompletedProductionOrder.id }, ctx);
110
+
111
+ expect(result.ok).toBe(false);
112
+ if (!result.ok) {
113
+ expect(result.error).toBeInstanceOf(PendingMaterialIssueRequestsError);
114
+ }
115
+ });
116
+
117
+ it("returns error when the linked cost summary is not ready for review", async () => {
118
+ const { db, spies } = createMockDb<Transaction>();
119
+
120
+ // order lookup
121
+ spies.select.mockReturnValueOnce(baseCompletedProductionOrder);
122
+ // work orders - all complete
123
+ spies.select.mockReturnValueOnce([baseCompleteWorkOrder]);
124
+ // cost summary already in PENDING_VARIANCE_REVIEW (not COLLECTING)
125
+ spies.select.mockReturnValueOnce({
126
+ ...basePendingReviewCostSummary,
127
+ productionOrderId: baseCompletedProductionOrder.id,
128
+ });
129
+
130
+ const result = await run(db, { id: baseCompletedProductionOrder.id }, ctx);
131
+
132
+ expect(result.ok).toBe(false);
133
+ if (!result.ok) {
134
+ expect(result.error).toBeInstanceOf(CostSummaryNotReadyError);
135
+ }
136
+ });
137
+
138
+ it("blocks further shop-floor reporting after technical completion", async () => {
139
+ const { db, spies } = createMockDb<Transaction>();
140
+
141
+ // order lookup - already paused work order still open
142
+ spies.select.mockReturnValueOnce(baseCompletedProductionOrder);
143
+ // work orders - one paused (execution exception)
144
+ spies.select.mockReturnValueOnce([basePausedWorkOrder]);
145
+
146
+ const result = await run(db, { id: baseCompletedProductionOrder.id }, ctx);
147
+
148
+ expect(result.ok).toBe(false);
149
+ if (!result.ok) {
150
+ expect(result.error).toBeInstanceOf(ExecutionExceptionRemainsError);
151
+ }
152
+ });
153
+ });
@@ -0,0 +1,106 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ ProductionOrderNotFoundError,
4
+ ProductionOrderNotTechnicallyCompletableError,
5
+ ExecutionExceptionRemainsError,
6
+ PendingMaterialIssueRequestsError,
7
+ CostSummaryNotReadyError,
8
+ } from "../lib/errors.generated";
9
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
10
+
11
+ export interface TechnicallyCompleteProductionOrderInput {
12
+ id: string;
13
+ from?: string[];
14
+ }
15
+
16
+ /**
17
+ * Function: technicallyCompleteProductionOrder
18
+ *
19
+ * Freezes the production order after physical completion and moves the linked
20
+ * manufacturing cost summary into variance review. Marks the point where no
21
+ * more normal execution or rescheduling is expected.
22
+ */
23
+ export async function run<CF extends Record<string, unknown>>(
24
+ db: Transaction,
25
+ input: TechnicallyCompleteProductionOrderInput & CF,
26
+ _ctx: CommandContext,
27
+ ) {
28
+ const { id, from, ...customFields } = input;
29
+ void customFields;
30
+
31
+ const allowedStatuses = from ?? ["COMPLETED"];
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
46
+ if (!allowedStatuses.includes(order.status)) {
47
+ return err(new ProductionOrderNotTechnicallyCompletableError(id));
48
+ }
49
+
50
+ // 3. Check for open execution exceptions (work orders not COMPLETE or CANCELLED)
51
+ const workOrders = await db
52
+ .selectFrom("WorkOrder")
53
+ .selectAll()
54
+ .where("productionOrderId", "=", id)
55
+ .execute();
56
+
57
+ const hasExecutionExceptions = workOrders.some(
58
+ (wo) => wo.status === "IN_PROGRESS" || wo.status === "PAUSED",
59
+ );
60
+
61
+ if (hasExecutionExceptions) {
62
+ return err(new ExecutionExceptionRemainsError(id));
63
+ }
64
+
65
+ // 4. Check for pending material issue requests (work orders still PENDING)
66
+ const hasPendingWork = workOrders.some((wo) => wo.status === "PENDING");
67
+
68
+ if (hasPendingWork) {
69
+ return err(new PendingMaterialIssueRequestsError(id));
70
+ }
71
+
72
+ // 5. Find and validate cost summary
73
+ const costSummary = await db
74
+ .selectFrom("ManufacturingCostSummary")
75
+ .selectAll()
76
+ .where("productionOrderId", "=", id)
77
+ .forUpdate()
78
+ .executeTakeFirst();
79
+
80
+ if (costSummary?.status !== "COLLECTING") {
81
+ return err(new CostSummaryNotReadyError(id));
82
+ }
83
+
84
+ // 6. Move cost summary to PENDING_VARIANCE_REVIEW
85
+ await db
86
+ .updateTable("ManufacturingCostSummary")
87
+ .set({
88
+ status: "PENDING_VARIANCE_REVIEW",
89
+ updatedAt: new Date(),
90
+ })
91
+ .where("id", "=", costSummary.id)
92
+ .execute();
93
+
94
+ // 7. Set order status to TECHNICALLY_COMPLETE
95
+ const techCompleteOrder = await db
96
+ .updateTable("ProductionOrder")
97
+ .set({
98
+ status: "TECHNICALLY_COMPLETE",
99
+ updatedAt: new Date(),
100
+ })
101
+ .where("id", "=", id)
102
+ .returningAll()
103
+ .executeTakeFirstOrThrow();
104
+
105
+ return ok({ productionOrder: techCompleteOrder });
106
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./unreleaseProductionOrder";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const unreleaseProductionOrder = defineCommand(permissions.unreleaseProductionOrder, run);
@@ -0,0 +1,140 @@
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
+ ProductionOrderNotUnreleasableError,
7
+ ExecutionAlreadyStartedError,
8
+ InventoryHandoffExistsError,
9
+ } from "../lib/errors.generated";
10
+ import {
11
+ baseDraftProductionOrder,
12
+ baseReleasedProductionOrder,
13
+ basePendingWorkOrder,
14
+ baseInProgressWorkOrder,
15
+ baseMaterialRequirement,
16
+ baseCollectingCostSummary,
17
+ } from "../testing/fixtures";
18
+ import { run } from "./unreleaseProductionOrder";
19
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
20
+
21
+ describe("unreleaseProductionOrder", () => {
22
+ const ctx: CommandContext = {
23
+ actorId: "test-actor",
24
+ permissions: ["manufacturing:unreleaseProductionOrder"],
25
+ };
26
+
27
+ it("unreleases a released order with no execution evidence", async () => {
28
+ const { db, spies } = createMockDb<Transaction>();
29
+ const draftOrder = { ...baseReleasedProductionOrder, status: "DRAFT" as const };
30
+
31
+ // order lookup
32
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
33
+ // work orders - all pending
34
+ spies.select.mockReturnValueOnce([basePendingWorkOrder]);
35
+ // material requirements
36
+ spies.select.mockReturnValueOnce([baseMaterialRequirement]);
37
+ // cost summary - no actuals
38
+ spies.select.mockReturnValueOnce(baseCollectingCostSummary);
39
+ // delete operations return
40
+ spies.delete.mockReturnValue(undefined);
41
+ // update order status
42
+ spies.update.mockReturnValue(draftOrder);
43
+
44
+ const result = await run(db, { id: baseReleasedProductionOrder.id }, ctx);
45
+
46
+ expect(result.ok).toBe(true);
47
+ if (result.ok) {
48
+ expect(result.value.productionOrder.status).toBe("DRAFT");
49
+ }
50
+ expect(spies.update).toHaveBeenCalled();
51
+ });
52
+
53
+ it("returns error when the order does not exist", async () => {
54
+ const { db, spies } = createMockDb<Transaction>();
55
+ spies.select.mockReturnValueOnce(undefined);
56
+
57
+ const result = await run(db, { id: "nonexistent" }, ctx);
58
+
59
+ expect(result.ok).toBe(false);
60
+ if (!result.ok) {
61
+ expect(result.error).toBeInstanceOf(ProductionOrderNotFoundError);
62
+ }
63
+ });
64
+
65
+ it("returns error when the order is not in RELEASED", async () => {
66
+ const { db, spies } = createMockDb<Transaction>();
67
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
68
+
69
+ const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
70
+
71
+ expect(result.ok).toBe(false);
72
+ if (!result.ok) {
73
+ expect(result.error).toBeInstanceOf(ProductionOrderNotUnreleasableError);
74
+ }
75
+ });
76
+
77
+ it("returns error when a work order has already started", async () => {
78
+ const { db, spies } = createMockDb<Transaction>();
79
+
80
+ // order lookup
81
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
82
+ // work orders - one in progress
83
+ spies.select.mockReturnValueOnce([baseInProgressWorkOrder]);
84
+
85
+ const result = await run(db, { id: baseReleasedProductionOrder.id }, ctx);
86
+
87
+ expect(result.ok).toBe(false);
88
+ if (!result.ok) {
89
+ expect(result.error).toBeInstanceOf(ExecutionAlreadyStartedError);
90
+ }
91
+ });
92
+
93
+ it("returns error when inventory handoff evidence already exists", async () => {
94
+ const { db, spies } = createMockDb<Transaction>();
95
+ const costSummaryWithActuals = {
96
+ ...baseCollectingCostSummary,
97
+ actualMaterialCost: 500,
98
+ };
99
+
100
+ // order lookup
101
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
102
+ // work orders - all pending
103
+ spies.select.mockReturnValueOnce([basePendingWorkOrder]);
104
+ // material requirements
105
+ spies.select.mockReturnValueOnce([baseMaterialRequirement]);
106
+ // cost summary with actual costs
107
+ spies.select.mockReturnValueOnce(costSummaryWithActuals);
108
+
109
+ const result = await run(db, { id: baseReleasedProductionOrder.id }, ctx);
110
+
111
+ expect(result.ok).toBe(false);
112
+ if (!result.ok) {
113
+ expect(result.error).toBeInstanceOf(InventoryHandoffExistsError);
114
+ }
115
+ });
116
+
117
+ it("removes work orders and material requirements when unrelease succeeds", async () => {
118
+ const { db, spies } = createMockDb<Transaction>();
119
+ const draftOrder = { ...baseReleasedProductionOrder, status: "DRAFT" as const };
120
+
121
+ // order lookup
122
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
123
+ // work orders - all pending
124
+ spies.select.mockReturnValueOnce([basePendingWorkOrder]);
125
+ // material requirements
126
+ spies.select.mockReturnValueOnce([]);
127
+ // cost summary
128
+ spies.select.mockReturnValueOnce(baseCollectingCostSummary);
129
+ // delete operations
130
+ spies.delete.mockReturnValue(undefined);
131
+ // update order
132
+ spies.update.mockReturnValue(draftOrder);
133
+
134
+ const result = await run(db, { id: baseReleasedProductionOrder.id }, ctx);
135
+
136
+ expect(result.ok).toBe(true);
137
+ expect(spies.delete).toHaveBeenCalled();
138
+ expect(spies.update).toHaveBeenCalled();
139
+ });
140
+ });