@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,151 @@
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
+ ProductionOrderNotCancellableError,
7
+ ExecutionAlreadyStartedError,
8
+ InventoryHandoffExistsError,
9
+ } from "../lib/errors.generated";
10
+ import {
11
+ baseDraftProductionOrder,
12
+ baseReleasedProductionOrder,
13
+ baseInProgressProductionOrder,
14
+ basePendingWorkOrder,
15
+ baseInProgressWorkOrder,
16
+ baseCollectingCostSummary,
17
+ } from "../testing/fixtures";
18
+ import { run } from "./cancelProductionOrder";
19
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
20
+
21
+ describe("cancelProductionOrder", () => {
22
+ const ctx: CommandContext = {
23
+ actorId: "test-actor",
24
+ permissions: ["manufacturing:cancelProductionOrder"],
25
+ };
26
+
27
+ it("cancels a draft production order", async () => {
28
+ const { db, spies } = createMockDb<Transaction>();
29
+ const cancelled = { ...baseDraftProductionOrder, status: "CANCELLED" as const };
30
+
31
+ // order lookup
32
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
33
+ // update order status
34
+ spies.update.mockReturnValue(cancelled);
35
+
36
+ const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
37
+
38
+ expect(result.ok).toBe(true);
39
+ if (result.ok) {
40
+ expect(result.value.productionOrder.status).toBe("CANCELLED");
41
+ }
42
+ expect(spies.update).toHaveBeenCalled();
43
+ });
44
+
45
+ it("cancels a released order before execution starts", async () => {
46
+ const { db, spies } = createMockDb<Transaction>();
47
+ const cancelled = { ...baseReleasedProductionOrder, status: "CANCELLED" as const };
48
+
49
+ // order lookup
50
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
51
+ // work orders - all pending
52
+ spies.select.mockReturnValueOnce([basePendingWorkOrder]);
53
+ // cost summary - no actuals
54
+ spies.select.mockReturnValueOnce(baseCollectingCostSummary);
55
+ // cascade cancel work orders
56
+ spies.update.mockReturnValueOnce(undefined);
57
+ // update order status
58
+ spies.update.mockReturnValue(cancelled);
59
+
60
+ const result = await run(db, { id: baseReleasedProductionOrder.id }, ctx);
61
+
62
+ expect(result.ok).toBe(true);
63
+ if (result.ok) {
64
+ expect(result.value.productionOrder.status).toBe("CANCELLED");
65
+ }
66
+ });
67
+
68
+ it("returns error when the order does not exist", async () => {
69
+ const { db, spies } = createMockDb<Transaction>();
70
+ spies.select.mockReturnValueOnce(undefined);
71
+
72
+ const result = await run(db, { id: "nonexistent" }, ctx);
73
+
74
+ expect(result.ok).toBe(false);
75
+ if (!result.ok) {
76
+ expect(result.error).toBeInstanceOf(ProductionOrderNotFoundError);
77
+ }
78
+ });
79
+
80
+ it("returns error when the status does not allow cancellation", async () => {
81
+ const { db, spies } = createMockDb<Transaction>();
82
+ spies.select.mockReturnValueOnce(baseInProgressProductionOrder);
83
+
84
+ const result = await run(db, { id: baseInProgressProductionOrder.id }, ctx);
85
+
86
+ expect(result.ok).toBe(false);
87
+ if (!result.ok) {
88
+ expect(result.error).toBeInstanceOf(ProductionOrderNotCancellableError);
89
+ }
90
+ });
91
+
92
+ it("returns error when execution evidence already exists", async () => {
93
+ const { db, spies } = createMockDb<Transaction>();
94
+
95
+ // order lookup
96
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
97
+ // work orders - one in progress
98
+ spies.select.mockReturnValueOnce([baseInProgressWorkOrder]);
99
+
100
+ const result = await run(db, { id: baseReleasedProductionOrder.id }, ctx);
101
+
102
+ expect(result.ok).toBe(false);
103
+ if (!result.ok) {
104
+ expect(result.error).toBeInstanceOf(ExecutionAlreadyStartedError);
105
+ }
106
+ });
107
+
108
+ it("returns error when inventory handoff evidence already exists", async () => {
109
+ const { db, spies } = createMockDb<Transaction>();
110
+ const costSummaryWithActuals = {
111
+ ...baseCollectingCostSummary,
112
+ actualMaterialCost: 500,
113
+ };
114
+
115
+ // order lookup
116
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
117
+ // work orders - all pending
118
+ spies.select.mockReturnValueOnce([basePendingWorkOrder]);
119
+ // cost summary with actual costs
120
+ spies.select.mockReturnValueOnce(costSummaryWithActuals);
121
+
122
+ const result = await run(db, { id: baseReleasedProductionOrder.id }, ctx);
123
+
124
+ expect(result.ok).toBe(false);
125
+ if (!result.ok) {
126
+ expect(result.error).toBeInstanceOf(InventoryHandoffExistsError);
127
+ }
128
+ });
129
+
130
+ it("cascades cancellation to pending work orders", async () => {
131
+ const { db, spies } = createMockDb<Transaction>();
132
+ const cancelled = { ...baseReleasedProductionOrder, status: "CANCELLED" as const };
133
+
134
+ // order lookup
135
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
136
+ // work orders - all pending
137
+ spies.select.mockReturnValueOnce([basePendingWorkOrder]);
138
+ // cost summary - no actuals
139
+ spies.select.mockReturnValueOnce(baseCollectingCostSummary);
140
+ // cascade cancel work orders (first update call)
141
+ spies.update.mockReturnValueOnce(undefined);
142
+ // update order status (second update call)
143
+ spies.update.mockReturnValue(cancelled);
144
+
145
+ const result = await run(db, { id: baseReleasedProductionOrder.id }, ctx);
146
+
147
+ expect(result.ok).toBe(true);
148
+ // update should be called at least twice: once for work orders, once for order
149
+ expect(spies.update).toHaveBeenCalled();
150
+ });
151
+ });
@@ -0,0 +1,114 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ ProductionOrderNotFoundError,
4
+ ProductionOrderNotCancellableError,
5
+ ExecutionAlreadyStartedError,
6
+ InventoryHandoffExistsError,
7
+ } from "../lib/errors.generated";
8
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
9
+
10
+ const CANCELLABLE_STATUSES = ["DRAFT", "RELEASED"] as const;
11
+
12
+ export interface CancelProductionOrderInput {
13
+ id: string;
14
+ from?: string[];
15
+ }
16
+
17
+ /**
18
+ * Function: cancelProductionOrder
19
+ *
20
+ * Abandons a draft or not-yet-started released order. Preserves audit
21
+ * history while blocking further execution updates and cascading
22
+ * cancellation to pending work orders.
23
+ */
24
+ export async function run<CF extends Record<string, unknown>>(
25
+ db: Transaction,
26
+ input: CancelProductionOrderInput & CF,
27
+ _ctx: CommandContext,
28
+ ) {
29
+ const { id, from, ...customFields } = input;
30
+ void customFields;
31
+
32
+ const allowedStatuses = from ?? [...CANCELLABLE_STATUSES];
33
+
34
+ // 1. Fetch production order with lock
35
+ const order = await db
36
+ .selectFrom("ProductionOrder")
37
+ .selectAll()
38
+ .where("id", "=", id)
39
+ .forUpdate()
40
+ .executeTakeFirst();
41
+
42
+ if (!order) {
43
+ return err(new ProductionOrderNotFoundError(id));
44
+ }
45
+
46
+ // 2. Validate status is cancellable
47
+ if (!allowedStatuses.includes(order.status)) {
48
+ return err(new ProductionOrderNotCancellableError(id));
49
+ }
50
+
51
+ // 3. For released orders, check execution and inventory evidence
52
+ if (order.status === "RELEASED") {
53
+ // Check work orders for execution evidence
54
+ const workOrders = await db
55
+ .selectFrom("WorkOrder")
56
+ .selectAll()
57
+ .where("productionOrderId", "=", id)
58
+ .execute();
59
+
60
+ const hasExecution = workOrders.some(
61
+ (wo) =>
62
+ wo.status === "IN_PROGRESS" ||
63
+ wo.status === "PAUSED" ||
64
+ wo.status === "COMPLETE" ||
65
+ wo.completedQuantity > 0 ||
66
+ wo.actualStartDate != null,
67
+ );
68
+
69
+ if (hasExecution) {
70
+ return err(new ExecutionAlreadyStartedError(id));
71
+ }
72
+
73
+ // Check for inventory handoff evidence
74
+ const costSummary = await db
75
+ .selectFrom("ManufacturingCostSummary")
76
+ .selectAll()
77
+ .where("productionOrderId", "=", id)
78
+ .executeTakeFirst();
79
+
80
+ if (
81
+ costSummary &&
82
+ (costSummary.actualMaterialCost > 0 ||
83
+ costSummary.actualLaborCost > 0 ||
84
+ costSummary.actualMachineCost > 0 ||
85
+ costSummary.actualOverheadCost > 0)
86
+ ) {
87
+ return err(new InventoryHandoffExistsError(id));
88
+ }
89
+
90
+ // 4. Cascade cancellation to pending work orders
91
+ await db
92
+ .updateTable("WorkOrder")
93
+ .set({
94
+ status: "CANCELLED",
95
+ updatedAt: new Date(),
96
+ })
97
+ .where("productionOrderId", "=", id)
98
+ .where("status", "=", "PENDING")
99
+ .execute();
100
+ }
101
+
102
+ // 5. Set order status to CANCELLED
103
+ const cancelledOrder = await db
104
+ .updateTable("ProductionOrder")
105
+ .set({
106
+ status: "CANCELLED",
107
+ updatedAt: new Date(),
108
+ })
109
+ .where("id", "=", id)
110
+ .returningAll()
111
+ .executeTakeFirstOrThrow();
112
+
113
+ return ok({ productionOrder: cancelledOrder });
114
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./closeProductionOrder";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const closeProductionOrder = defineCommand(permissions.closeProductionOrder, run);
@@ -0,0 +1,126 @@
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
+ ProductionOrderNotClosableError,
7
+ OpenWorkRemainsError,
8
+ CostSummaryNotSettledError,
9
+ } from "../lib/errors.generated";
10
+ import {
11
+ baseTechCompleteProductionOrder,
12
+ baseCompletedProductionOrder,
13
+ baseClosedProductionOrder,
14
+ baseCompleteWorkOrder,
15
+ baseCancelledWorkOrder,
16
+ basePendingWorkOrder,
17
+ baseSettledCostSummary,
18
+ basePendingReviewCostSummary,
19
+ } from "../testing/fixtures";
20
+ import { run } from "./closeProductionOrder";
21
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
22
+
23
+ describe("closeProductionOrder", () => {
24
+ const ctx: CommandContext = {
25
+ actorId: "test-actor",
26
+ permissions: ["manufacturing:closeProductionOrder"],
27
+ };
28
+
29
+ it("closes a technically complete order with a settled cost summary", async () => {
30
+ const { db, spies } = createMockDb<Transaction>();
31
+ const closed = { ...baseTechCompleteProductionOrder, status: "CLOSED" as const };
32
+
33
+ // order lookup
34
+ spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
35
+ // work orders - all complete or cancelled
36
+ spies.select.mockReturnValueOnce([baseCompleteWorkOrder, baseCancelledWorkOrder]);
37
+ // cost summary in SETTLED
38
+ spies.select.mockReturnValueOnce({
39
+ ...baseSettledCostSummary,
40
+ productionOrderId: baseTechCompleteProductionOrder.id,
41
+ });
42
+ // update order status
43
+ spies.update.mockReturnValue(closed);
44
+
45
+ const result = await run(db, { id: baseTechCompleteProductionOrder.id }, ctx);
46
+
47
+ expect(result.ok).toBe(true);
48
+ if (result.ok) {
49
+ expect(result.value.productionOrder.status).toBe("CLOSED");
50
+ }
51
+ expect(spies.update).toHaveBeenCalled();
52
+ });
53
+
54
+ it("returns error when the order does not exist", async () => {
55
+ const { db, spies } = createMockDb<Transaction>();
56
+ spies.select.mockReturnValueOnce(undefined);
57
+
58
+ const result = await run(db, { id: "nonexistent" }, ctx);
59
+
60
+ expect(result.ok).toBe(false);
61
+ if (!result.ok) {
62
+ expect(result.error).toBeInstanceOf(ProductionOrderNotFoundError);
63
+ }
64
+ });
65
+
66
+ it("returns error when the order is not technically complete", async () => {
67
+ const { db, spies } = createMockDb<Transaction>();
68
+ spies.select.mockReturnValueOnce(baseCompletedProductionOrder);
69
+
70
+ const result = await run(db, { id: baseCompletedProductionOrder.id }, ctx);
71
+
72
+ expect(result.ok).toBe(false);
73
+ if (!result.ok) {
74
+ expect(result.error).toBeInstanceOf(ProductionOrderNotClosableError);
75
+ }
76
+ });
77
+
78
+ it("returns error when open work or execution exceptions remain", async () => {
79
+ const { db, spies } = createMockDb<Transaction>();
80
+
81
+ // order lookup
82
+ spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
83
+ // work orders - one still pending
84
+ spies.select.mockReturnValueOnce([baseCompleteWorkOrder, basePendingWorkOrder]);
85
+
86
+ const result = await run(db, { id: baseTechCompleteProductionOrder.id }, ctx);
87
+
88
+ expect(result.ok).toBe(false);
89
+ if (!result.ok) {
90
+ expect(result.error).toBeInstanceOf(OpenWorkRemainsError);
91
+ }
92
+ });
93
+
94
+ it("returns error when the linked cost summary is not settled", async () => {
95
+ const { db, spies } = createMockDb<Transaction>();
96
+
97
+ // order lookup
98
+ spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
99
+ // work orders - all complete
100
+ spies.select.mockReturnValueOnce([baseCompleteWorkOrder]);
101
+ // cost summary not settled
102
+ spies.select.mockReturnValueOnce({
103
+ ...basePendingReviewCostSummary,
104
+ productionOrderId: baseTechCompleteProductionOrder.id,
105
+ });
106
+
107
+ const result = await run(db, { id: baseTechCompleteProductionOrder.id }, ctx);
108
+
109
+ expect(result.ok).toBe(false);
110
+ if (!result.ok) {
111
+ expect(result.error).toBeInstanceOf(CostSummaryNotSettledError);
112
+ }
113
+ });
114
+
115
+ it("blocks further execution commands after close", async () => {
116
+ const { db, spies } = createMockDb<Transaction>();
117
+ spies.select.mockReturnValueOnce(baseClosedProductionOrder);
118
+
119
+ const result = await run(db, { id: baseClosedProductionOrder.id }, ctx);
120
+
121
+ expect(result.ok).toBe(false);
122
+ if (!result.ok) {
123
+ expect(result.error).toBeInstanceOf(ProductionOrderNotClosableError);
124
+ }
125
+ });
126
+ });
@@ -0,0 +1,87 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ ProductionOrderNotFoundError,
4
+ ProductionOrderNotClosableError,
5
+ OpenWorkRemainsError,
6
+ CostSummaryNotSettledError,
7
+ } from "../lib/errors.generated";
8
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
9
+
10
+ export interface CloseProductionOrderInput {
11
+ id: string;
12
+ from?: string[];
13
+ }
14
+
15
+ /**
16
+ * Function: closeProductionOrder
17
+ *
18
+ * Performs the final administrative close after technical completion and
19
+ * downstream cost settlement are done. The command is the last lifecycle
20
+ * step for the production order.
21
+ */
22
+ export async function run<CF extends Record<string, unknown>>(
23
+ db: Transaction,
24
+ input: CloseProductionOrderInput & CF,
25
+ _ctx: CommandContext,
26
+ ) {
27
+ const { id, from, ...customFields } = input;
28
+ void customFields;
29
+
30
+ const allowedStatuses = from ?? ["TECHNICALLY_COMPLETE"];
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 closable
45
+ if (!allowedStatuses.includes(order.status)) {
46
+ return err(new ProductionOrderNotClosableError(id));
47
+ }
48
+
49
+ // 3. Check all work orders are resolved (COMPLETE or CANCELLED)
50
+ const workOrders = await db
51
+ .selectFrom("WorkOrder")
52
+ .selectAll()
53
+ .where("productionOrderId", "=", id)
54
+ .execute();
55
+
56
+ const hasOpenWork = workOrders.some(
57
+ (wo) => wo.status !== "COMPLETE" && wo.status !== "CANCELLED",
58
+ );
59
+
60
+ if (hasOpenWork) {
61
+ return err(new OpenWorkRemainsError(id));
62
+ }
63
+
64
+ // 4. Check cost summary is SETTLED
65
+ const costSummary = await db
66
+ .selectFrom("ManufacturingCostSummary")
67
+ .selectAll()
68
+ .where("productionOrderId", "=", id)
69
+ .executeTakeFirst();
70
+
71
+ if (costSummary?.status !== "SETTLED") {
72
+ return err(new CostSummaryNotSettledError(id));
73
+ }
74
+
75
+ // 5. Set order status to CLOSED
76
+ const closedOrder = await db
77
+ .updateTable("ProductionOrder")
78
+ .set({
79
+ status: "CLOSED",
80
+ updatedAt: new Date(),
81
+ })
82
+ .where("id", "=", id)
83
+ .returningAll()
84
+ .executeTakeFirstOrThrow();
85
+
86
+ return ok({ productionOrder: closedOrder });
87
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./completeProductionOrder";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const completeProductionOrder = defineCommand(permissions.completeProductionOrder, run);
@@ -0,0 +1,132 @@
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
+ ProductionOrderNotCompletableError,
7
+ OpenWorkOrderRemainsError,
8
+ FinalOutputRequiredError,
9
+ FinalReceiptRequiredError,
10
+ } from "../lib/errors.generated";
11
+ import {
12
+ baseInProgressProductionOrder,
13
+ baseDraftProductionOrder,
14
+ baseCompleteWorkOrder,
15
+ baseCancelledWorkOrder,
16
+ baseInProgressWorkOrder,
17
+ baseCollectingCostSummary,
18
+ } from "../testing/fixtures";
19
+ import { run } from "./completeProductionOrder";
20
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
21
+
22
+ describe("completeProductionOrder", () => {
23
+ const ctx: CommandContext = {
24
+ actorId: "test-actor",
25
+ permissions: ["manufacturing:completeProductionOrder"],
26
+ };
27
+
28
+ it("completes an in-progress order after all required work orders finish", async () => {
29
+ const { db, spies } = createMockDb<Transaction>();
30
+ const completed = { ...baseInProgressProductionOrder, status: "COMPLETED" as const };
31
+
32
+ // order lookup
33
+ spies.select.mockReturnValueOnce(baseInProgressProductionOrder);
34
+ // work orders - all complete or cancelled
35
+ spies.select.mockReturnValueOnce([baseCompleteWorkOrder, baseCancelledWorkOrder]);
36
+ // cost summary with actual costs
37
+ spies.select.mockReturnValueOnce({
38
+ ...baseCollectingCostSummary,
39
+ productionOrderId: baseInProgressProductionOrder.id,
40
+ actualMaterialCost: 500,
41
+ });
42
+ // update order status
43
+ spies.update.mockReturnValue(completed);
44
+
45
+ const result = await run(db, { id: baseInProgressProductionOrder.id }, ctx);
46
+
47
+ expect(result.ok).toBe(true);
48
+ if (result.ok) {
49
+ expect(result.value.productionOrder.status).toBe("COMPLETED");
50
+ }
51
+ expect(spies.update).toHaveBeenCalled();
52
+ });
53
+
54
+ it("returns error when the order does not exist", async () => {
55
+ const { db, spies } = createMockDb<Transaction>();
56
+ spies.select.mockReturnValueOnce(undefined);
57
+
58
+ const result = await run(db, { id: "nonexistent" }, ctx);
59
+
60
+ expect(result.ok).toBe(false);
61
+ if (!result.ok) {
62
+ expect(result.error).toBeInstanceOf(ProductionOrderNotFoundError);
63
+ }
64
+ });
65
+
66
+ it("returns error when the order is not in IN_PROGRESS", async () => {
67
+ const { db, spies } = createMockDb<Transaction>();
68
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
69
+
70
+ const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
71
+
72
+ expect(result.ok).toBe(false);
73
+ if (!result.ok) {
74
+ expect(result.error).toBeInstanceOf(ProductionOrderNotCompletableError);
75
+ }
76
+ });
77
+
78
+ it("returns error when required work orders remain open", async () => {
79
+ const { db, spies } = createMockDb<Transaction>();
80
+
81
+ // order lookup
82
+ spies.select.mockReturnValueOnce(baseInProgressProductionOrder);
83
+ // work orders - one still in progress
84
+ spies.select.mockReturnValueOnce([baseInProgressWorkOrder, baseCompleteWorkOrder]);
85
+
86
+ const result = await run(db, { id: baseInProgressProductionOrder.id }, ctx);
87
+
88
+ expect(result.ok).toBe(false);
89
+ if (!result.ok) {
90
+ expect(result.error).toBeInstanceOf(OpenWorkOrderRemainsError);
91
+ }
92
+ });
93
+
94
+ it("returns error when final output reporting is incomplete", async () => {
95
+ const { db, spies } = createMockDb<Transaction>();
96
+ const completedWithZero = { ...baseCompleteWorkOrder, completedQuantity: 0 };
97
+
98
+ // order lookup
99
+ spies.select.mockReturnValueOnce(baseInProgressProductionOrder);
100
+ // work orders - all cancelled (no completed output)
101
+ spies.select.mockReturnValueOnce([baseCancelledWorkOrder, completedWithZero]);
102
+
103
+ const result = await run(db, { id: baseInProgressProductionOrder.id }, ctx);
104
+
105
+ expect(result.ok).toBe(false);
106
+ if (!result.ok) {
107
+ expect(result.error).toBeInstanceOf(FinalOutputRequiredError);
108
+ }
109
+ });
110
+
111
+ it("returns error when required receipt handoff evidence is missing", async () => {
112
+ const { db, spies } = createMockDb<Transaction>();
113
+
114
+ // order lookup
115
+ spies.select.mockReturnValueOnce(baseInProgressProductionOrder);
116
+ // work orders - all complete with output
117
+ spies.select.mockReturnValueOnce([baseCompleteWorkOrder]);
118
+ // cost summary with no actual costs
119
+ spies.select.mockReturnValueOnce({
120
+ ...baseCollectingCostSummary,
121
+ productionOrderId: baseInProgressProductionOrder.id,
122
+ actualMaterialCost: 0,
123
+ });
124
+
125
+ const result = await run(db, { id: baseInProgressProductionOrder.id }, ctx);
126
+
127
+ expect(result.ok).toBe(false);
128
+ if (!result.ok) {
129
+ expect(result.error).toBeInstanceOf(FinalReceiptRequiredError);
130
+ }
131
+ });
132
+ });