@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,196 @@
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
+ ProductionOrderNotReopenableError,
7
+ ReopenReasonRequiredError,
8
+ CostSummaryNotReopenableError,
9
+ OrderAlreadyClosedError,
10
+ } from "../lib/errors.generated";
11
+ import {
12
+ baseTechCompleteProductionOrder,
13
+ baseCompletedProductionOrder,
14
+ baseClosedProductionOrder,
15
+ basePendingReviewCostSummary,
16
+ baseReviewedCostSummary,
17
+ baseSettledCostSummary,
18
+ } from "../testing/fixtures";
19
+ import { run } from "./reopenProductionOrder";
20
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
21
+
22
+ describe("reopenProductionOrder", () => {
23
+ const ctx: CommandContext = {
24
+ actorId: "test-actor",
25
+ permissions: ["manufacturing:reopenProductionOrder"],
26
+ };
27
+
28
+ const validInput = {
29
+ id: baseTechCompleteProductionOrder.id,
30
+ reason: "Additional rework needed for quality defects",
31
+ };
32
+
33
+ it("reopens a technically complete order for additional execution", async () => {
34
+ const { db, spies } = createMockDb<Transaction>();
35
+ const reopened = { ...baseTechCompleteProductionOrder, status: "IN_PROGRESS" as const };
36
+
37
+ // order lookup
38
+ spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
39
+ // cost summary in PENDING_VARIANCE_REVIEW
40
+ spies.select.mockReturnValueOnce({
41
+ ...basePendingReviewCostSummary,
42
+ productionOrderId: baseTechCompleteProductionOrder.id,
43
+ });
44
+ // update cost summary
45
+ spies.update.mockReturnValueOnce(undefined);
46
+ // update order status
47
+ spies.update.mockReturnValue(reopened);
48
+
49
+ const result = await run(db, validInput, ctx);
50
+
51
+ expect(result.ok).toBe(true);
52
+ if (result.ok) {
53
+ expect(result.value.productionOrder.status).toBe("IN_PROGRESS");
54
+ }
55
+ expect(spies.update).toHaveBeenCalled();
56
+ });
57
+
58
+ it("returns error when the order does not exist", async () => {
59
+ const { db, spies } = createMockDb<Transaction>();
60
+ spies.select.mockReturnValueOnce(undefined);
61
+
62
+ const result = await run(db, { id: "nonexistent", reason: "rework" }, ctx);
63
+
64
+ expect(result.ok).toBe(false);
65
+ if (!result.ok) {
66
+ expect(result.error).toBeInstanceOf(ProductionOrderNotFoundError);
67
+ }
68
+ });
69
+
70
+ it("returns error when the order is not technically complete", async () => {
71
+ const { db, spies } = createMockDb<Transaction>();
72
+ spies.select.mockReturnValueOnce(baseCompletedProductionOrder);
73
+
74
+ const result = await run(db, { id: baseCompletedProductionOrder.id, reason: "rework" }, ctx);
75
+
76
+ expect(result.ok).toBe(false);
77
+ if (!result.ok) {
78
+ expect(result.error).toBeInstanceOf(ProductionOrderNotReopenableError);
79
+ }
80
+ });
81
+
82
+ it("returns error when no reopen reason is provided", async () => {
83
+ const { db, spies } = createMockDb<Transaction>();
84
+ spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
85
+
86
+ const result = await run(db, { id: baseTechCompleteProductionOrder.id, reason: "" }, ctx);
87
+
88
+ expect(result.ok).toBe(false);
89
+ if (!result.ok) {
90
+ expect(result.error).toBeInstanceOf(ReopenReasonRequiredError);
91
+ }
92
+ });
93
+
94
+ it("reopens an order when the linked cost summary is PENDING_VARIANCE_REVIEW", async () => {
95
+ const { db, spies } = createMockDb<Transaction>();
96
+ const reopened = { ...baseTechCompleteProductionOrder, status: "IN_PROGRESS" as const };
97
+
98
+ // order lookup
99
+ spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
100
+ // cost summary in PENDING_VARIANCE_REVIEW
101
+ spies.select.mockReturnValueOnce({
102
+ ...basePendingReviewCostSummary,
103
+ productionOrderId: baseTechCompleteProductionOrder.id,
104
+ });
105
+ // update cost summary
106
+ spies.update.mockReturnValueOnce(undefined);
107
+ // update order status
108
+ spies.update.mockReturnValue(reopened);
109
+
110
+ const result = await run(db, validInput, ctx);
111
+
112
+ expect(result.ok).toBe(true);
113
+ if (result.ok) {
114
+ expect(result.value.productionOrder.status).toBe("IN_PROGRESS");
115
+ }
116
+ });
117
+
118
+ it("reopens an order when the linked cost summary is VARIANCE_REVIEWED", async () => {
119
+ const { db, spies } = createMockDb<Transaction>();
120
+ const reopened = { ...baseTechCompleteProductionOrder, status: "IN_PROGRESS" as const };
121
+
122
+ // order lookup
123
+ spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
124
+ // cost summary in VARIANCE_REVIEWED
125
+ spies.select.mockReturnValueOnce({
126
+ ...baseReviewedCostSummary,
127
+ productionOrderId: baseTechCompleteProductionOrder.id,
128
+ });
129
+ // update cost summary
130
+ spies.update.mockReturnValueOnce(undefined);
131
+ // update order status
132
+ spies.update.mockReturnValue(reopened);
133
+
134
+ const result = await run(db, validInput, ctx);
135
+
136
+ expect(result.ok).toBe(true);
137
+ if (result.ok) {
138
+ expect(result.value.productionOrder.status).toBe("IN_PROGRESS");
139
+ }
140
+ });
141
+
142
+ it("returns error when the linked cost summary is already SETTLED or otherwise not reopenable", async () => {
143
+ const { db, spies } = createMockDb<Transaction>();
144
+
145
+ // order lookup
146
+ spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
147
+ // cost summary already SETTLED
148
+ spies.select.mockReturnValueOnce({
149
+ ...baseSettledCostSummary,
150
+ productionOrderId: baseTechCompleteProductionOrder.id,
151
+ });
152
+
153
+ const result = await run(db, validInput, ctx);
154
+
155
+ expect(result.ok).toBe(false);
156
+ if (!result.ok) {
157
+ expect(result.error).toBeInstanceOf(CostSummaryNotReopenableError);
158
+ }
159
+ });
160
+
161
+ it("returns error when the order is already closed", async () => {
162
+ const { db, spies } = createMockDb<Transaction>();
163
+ spies.select.mockReturnValueOnce(baseClosedProductionOrder);
164
+
165
+ const result = await run(db, { id: baseClosedProductionOrder.id, reason: "rework" }, ctx);
166
+
167
+ expect(result.ok).toBe(false);
168
+ if (!result.ok) {
169
+ expect(result.error).toBeInstanceOf(OrderAlreadyClosedError);
170
+ }
171
+ });
172
+
173
+ it("returns the linked cost summary to collecting on reopen", async () => {
174
+ const { db, spies } = createMockDb<Transaction>();
175
+ const reopened = { ...baseTechCompleteProductionOrder, status: "IN_PROGRESS" as const };
176
+
177
+ // order lookup
178
+ spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
179
+ // cost summary in PENDING_VARIANCE_REVIEW
180
+ spies.select.mockReturnValueOnce({
181
+ ...basePendingReviewCostSummary,
182
+ productionOrderId: baseTechCompleteProductionOrder.id,
183
+ });
184
+ // update cost summary to COLLECTING
185
+ spies.update.mockReturnValueOnce(undefined);
186
+ // update order status
187
+ spies.update.mockReturnValue(reopened);
188
+
189
+ const result = await run(db, validInput, ctx);
190
+
191
+ expect(result.ok).toBe(true);
192
+ // The first update call should be for cost summary (back to COLLECTING)
193
+ // The second update call should be for order status (to IN_PROGRESS)
194
+ expect(spies.update).toHaveBeenCalledTimes(2);
195
+ });
196
+ });
@@ -0,0 +1,98 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ ProductionOrderNotFoundError,
4
+ ProductionOrderNotReopenableError,
5
+ ReopenReasonRequiredError,
6
+ CostSummaryNotReopenableError,
7
+ OrderAlreadyClosedError,
8
+ } from "../lib/errors.generated";
9
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
10
+
11
+ export interface ReopenProductionOrderInput {
12
+ id: string;
13
+ reason: string;
14
+ from?: string[];
15
+ }
16
+
17
+ /**
18
+ * Function: reopenProductionOrder
19
+ *
20
+ * Re-enables execution after a technically complete order needs more
21
+ * shop-floor work. It reverses the execution freeze and returns the linked
22
+ * cost summary to active collection.
23
+ */
24
+ export async function run<CF extends Record<string, unknown>>(
25
+ db: Transaction,
26
+ input: ReopenProductionOrderInput & CF,
27
+ _ctx: CommandContext,
28
+ ) {
29
+ const { id, reason, from, ...customFields } = input;
30
+ void customFields;
31
+
32
+ const allowedStatuses = from ?? ["TECHNICALLY_COMPLETE"];
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. Check if order is already closed
47
+ if (order.status === "CLOSED") {
48
+ return err(new OrderAlreadyClosedError(id));
49
+ }
50
+
51
+ // 3. Validate status is reopenable
52
+ if (!allowedStatuses.includes(order.status)) {
53
+ return err(new ProductionOrderNotReopenableError(id));
54
+ }
55
+
56
+ // 4. Validate reopen reason
57
+ if (!reason || reason.trim() === "") {
58
+ return err(new ReopenReasonRequiredError(id));
59
+ }
60
+
61
+ // 5. Find and validate cost summary
62
+ const costSummary = await db
63
+ .selectFrom("ManufacturingCostSummary")
64
+ .selectAll()
65
+ .where("productionOrderId", "=", id)
66
+ .forUpdate()
67
+ .executeTakeFirst();
68
+
69
+ if (
70
+ !costSummary ||
71
+ (costSummary.status !== "PENDING_VARIANCE_REVIEW" && costSummary.status !== "VARIANCE_REVIEWED")
72
+ ) {
73
+ return err(new CostSummaryNotReopenableError(id));
74
+ }
75
+
76
+ // 6. Return cost summary to COLLECTING
77
+ await db
78
+ .updateTable("ManufacturingCostSummary")
79
+ .set({
80
+ status: "COLLECTING",
81
+ updatedAt: new Date(),
82
+ })
83
+ .where("id", "=", costSummary.id)
84
+ .execute();
85
+
86
+ // 7. Set order status to IN_PROGRESS
87
+ const reopenedOrder = await db
88
+ .updateTable("ProductionOrder")
89
+ .set({
90
+ status: "IN_PROGRESS",
91
+ updatedAt: new Date(),
92
+ })
93
+ .where("id", "=", id)
94
+ .returningAll()
95
+ .executeTakeFirstOrThrow();
96
+
97
+ return ok({ productionOrder: reopenedOrder });
98
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./reportWorkOrderProgress";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const reportWorkOrderProgress = defineCommand(permissions.reportWorkOrderProgress, run);
@@ -0,0 +1,204 @@
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
+ WorkOrderNotReportableError,
7
+ InvalidReportedQuantityError,
8
+ EmptyProgressTransactionError,
9
+ ScrapHandoffRequiredError,
10
+ } from "../lib/errors.generated";
11
+ import { baseInProgressWorkOrder, basePendingWorkOrder } from "../testing/fixtures";
12
+ import { run } from "./reportWorkOrderProgress";
13
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
14
+
15
+ describe("reportWorkOrderProgress", () => {
16
+ const ctx: CommandContext = {
17
+ actorId: "test-actor",
18
+ permissions: ["manufacturing:reportWorkOrderProgress"],
19
+ };
20
+
21
+ it("records partial completed quantity and time on an in-progress work order", async () => {
22
+ const { db, spies } = createMockDb<Transaction>();
23
+ const updatedWorkOrder = {
24
+ ...baseInProgressWorkOrder,
25
+ completedQuantity: baseInProgressWorkOrder.completedQuantity + 10,
26
+ actualRunTime: baseInProgressWorkOrder.actualRunTime + 30,
27
+ };
28
+
29
+ // work order lookup
30
+ spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
31
+ // update work order
32
+ spies.update.mockReturnValueOnce(updatedWorkOrder);
33
+ // insert execution event
34
+ spies.insert.mockReturnValueOnce(undefined);
35
+ // select: parent order lookup for rollup
36
+ spies.select.mockReturnValueOnce({ id: "production-order-2", status: "IN_PROGRESS" });
37
+
38
+ const result = await run(
39
+ db,
40
+ { id: baseInProgressWorkOrder.id, completedQuantity: 10, actualRunTime: 30 },
41
+ ctx,
42
+ );
43
+
44
+ expect(result.ok).toBe(true);
45
+ if (result.ok) {
46
+ expect(result.value.workOrder.completedQuantity).toBe(
47
+ baseInProgressWorkOrder.completedQuantity + 10,
48
+ );
49
+ expect(result.value.workOrder.actualRunTime).toBe(baseInProgressWorkOrder.actualRunTime + 30);
50
+ }
51
+ expect(spies.update).toHaveBeenCalled();
52
+ expect(spies.insert).toHaveBeenCalled();
53
+ });
54
+
55
+ it("returns error when the work order does not exist", async () => {
56
+ const { db, spies } = createMockDb<Transaction>();
57
+ spies.select.mockReturnValueOnce(undefined);
58
+
59
+ const result = await run(db, { id: "nonexistent", completedQuantity: 5 }, ctx);
60
+
61
+ expect(result.ok).toBe(false);
62
+ if (!result.ok) {
63
+ expect(result.error).toBeInstanceOf(WorkOrderNotFoundError);
64
+ }
65
+ });
66
+
67
+ it("returns error when the work order is not in progress", async () => {
68
+ const { db, spies } = createMockDb<Transaction>();
69
+ spies.select.mockReturnValueOnce(basePendingWorkOrder);
70
+
71
+ const result = await run(db, { id: basePendingWorkOrder.id, completedQuantity: 5 }, ctx);
72
+
73
+ expect(result.ok).toBe(false);
74
+ if (!result.ok) {
75
+ expect(result.error).toBeInstanceOf(WorkOrderNotReportableError);
76
+ }
77
+ });
78
+
79
+ it("returns error when completed or scrap quantity is negative", async () => {
80
+ const { db } = createMockDb<Transaction>();
81
+
82
+ const result = await run(db, { id: baseInProgressWorkOrder.id, completedQuantity: -5 }, ctx);
83
+
84
+ expect(result.ok).toBe(false);
85
+ if (!result.ok) {
86
+ expect(result.error).toBeInstanceOf(InvalidReportedQuantityError);
87
+ }
88
+ });
89
+
90
+ it("returns error when no positive quantity or time is provided", async () => {
91
+ const { db } = createMockDb<Transaction>();
92
+
93
+ const result = await run(
94
+ db,
95
+ { id: baseInProgressWorkOrder.id, completedQuantity: 0, scrapQuantity: 0 },
96
+ ctx,
97
+ );
98
+
99
+ expect(result.ok).toBe(false);
100
+ if (!result.ok) {
101
+ expect(result.error).toBeInstanceOf(EmptyProgressTransactionError);
102
+ }
103
+ });
104
+
105
+ it("emits manufacturing scrap handoff when positive scrap is reported", async () => {
106
+ const { db, spies } = createMockDb<Transaction>();
107
+ const updatedWorkOrder = {
108
+ ...baseInProgressWorkOrder,
109
+ scrapQuantity: baseInProgressWorkOrder.scrapQuantity + 3,
110
+ completedQuantity: baseInProgressWorkOrder.completedQuantity + 10,
111
+ };
112
+
113
+ // work order lookup
114
+ spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
115
+ // update work order
116
+ spies.update.mockReturnValueOnce(updatedWorkOrder);
117
+ // insert execution event
118
+ spies.insert.mockReturnValueOnce(undefined);
119
+ // select: parent order lookup for rollup
120
+ spies.select.mockReturnValueOnce({ id: "production-order-2", status: "IN_PROGRESS" });
121
+
122
+ const result = await run(
123
+ db,
124
+ {
125
+ id: baseInProgressWorkOrder.id,
126
+ completedQuantity: 10,
127
+ scrapQuantity: 3,
128
+ scrapHandoffData: { reason: "defective material" },
129
+ },
130
+ ctx,
131
+ );
132
+
133
+ expect(result.ok).toBe(true);
134
+ if (result.ok) {
135
+ expect(result.value.workOrder.scrapQuantity).toBe(baseInProgressWorkOrder.scrapQuantity + 3);
136
+ }
137
+ });
138
+
139
+ it("returns error when positive scrap is reported without scrap handoff data", async () => {
140
+ const { db, spies } = createMockDb<Transaction>();
141
+
142
+ // work order lookup
143
+ spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
144
+
145
+ const result = await run(
146
+ db,
147
+ {
148
+ id: baseInProgressWorkOrder.id,
149
+ completedQuantity: 10,
150
+ scrapQuantity: 3,
151
+ },
152
+ ctx,
153
+ );
154
+
155
+ expect(result.ok).toBe(false);
156
+ if (!result.ok) {
157
+ expect(result.error).toBeInstanceOf(ScrapHandoffRequiredError);
158
+ }
159
+ });
160
+
161
+ it("rolls up execution progress to the parent order", async () => {
162
+ const { db, spies } = createMockDb<Transaction>();
163
+ const updatedWorkOrder = {
164
+ ...baseInProgressWorkOrder,
165
+ completedQuantity: baseInProgressWorkOrder.completedQuantity + 20,
166
+ actualSetupTime: baseInProgressWorkOrder.actualSetupTime + 5,
167
+ actualRunTime: baseInProgressWorkOrder.actualRunTime + 45,
168
+ };
169
+
170
+ // work order lookup
171
+ spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
172
+ // update work order
173
+ spies.update.mockReturnValueOnce(updatedWorkOrder);
174
+ // insert execution event
175
+ spies.insert.mockReturnValueOnce(undefined);
176
+ // select: parent order lookup for rollup
177
+ spies.select.mockReturnValueOnce({ id: "production-order-2", status: "IN_PROGRESS" });
178
+
179
+ const result = await run(
180
+ db,
181
+ {
182
+ id: baseInProgressWorkOrder.id,
183
+ completedQuantity: 20,
184
+ actualSetupTime: 5,
185
+ actualRunTime: 45,
186
+ },
187
+ ctx,
188
+ );
189
+
190
+ expect(result.ok).toBe(true);
191
+ if (result.ok) {
192
+ expect(result.value.workOrder.completedQuantity).toBe(
193
+ baseInProgressWorkOrder.completedQuantity + 20,
194
+ );
195
+ expect(result.value.workOrder.actualSetupTime).toBe(
196
+ baseInProgressWorkOrder.actualSetupTime + 5,
197
+ );
198
+ expect(result.value.workOrder.actualRunTime).toBe(baseInProgressWorkOrder.actualRunTime + 45);
199
+ }
200
+ // Verify parent production order was updated (rollup)
201
+ expect(spies.update).toHaveBeenCalledTimes(2);
202
+ expect(spies.select).toHaveBeenCalledTimes(2);
203
+ });
204
+ });
@@ -0,0 +1,129 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ WorkOrderNotFoundError,
4
+ WorkOrderNotReportableError,
5
+ InvalidReportedQuantityError,
6
+ EmptyProgressTransactionError,
7
+ ScrapHandoffRequiredError,
8
+ } from "../lib/errors.generated";
9
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
10
+
11
+ export interface ReportWorkOrderProgressInput {
12
+ id: string;
13
+ completedQuantity?: number;
14
+ scrapQuantity?: number;
15
+ actualSetupTime?: number;
16
+ actualRunTime?: number;
17
+ notes?: string | null;
18
+ scrapHandoffData?: Record<string, unknown> | null;
19
+ }
20
+
21
+ /**
22
+ * Function: reportWorkOrderProgress
23
+ *
24
+ * Records partial execution evidence such as completed quantity, scrap quantity,
25
+ * actual time, and exception notes. Emits ManufacturingScrapHandoff when the
26
+ * report contains scrapped quantity.
27
+ */
28
+ export async function run<CF extends Record<string, unknown>>(
29
+ db: Transaction,
30
+ input: ReportWorkOrderProgressInput & CF,
31
+ _ctx: CommandContext,
32
+ ) {
33
+ const {
34
+ id,
35
+ completedQuantity = 0,
36
+ scrapQuantity = 0,
37
+ actualSetupTime = 0,
38
+ actualRunTime = 0,
39
+ notes = null,
40
+ scrapHandoffData = null,
41
+ ...customFields
42
+ } = input;
43
+ void customFields;
44
+
45
+ // 1. Validate quantities are non-negative
46
+ if (completedQuantity < 0 || scrapQuantity < 0) {
47
+ return err(new InvalidReportedQuantityError(id));
48
+ }
49
+
50
+ // 2. Validate at least one positive value was reported
51
+ if (completedQuantity <= 0 && scrapQuantity <= 0 && actualSetupTime <= 0 && actualRunTime <= 0) {
52
+ return err(new EmptyProgressTransactionError(id));
53
+ }
54
+
55
+ // 3. Fetch work order with lock
56
+ const workOrder = await db
57
+ .selectFrom("WorkOrder")
58
+ .selectAll()
59
+ .where("id", "=", id)
60
+ .forUpdate()
61
+ .executeTakeFirst();
62
+
63
+ if (!workOrder) {
64
+ return err(new WorkOrderNotFoundError(id));
65
+ }
66
+
67
+ // 4. Validate status is IN_PROGRESS
68
+ if (workOrder.status !== "IN_PROGRESS") {
69
+ return err(new WorkOrderNotReportableError(id));
70
+ }
71
+
72
+ // 5. Validate scrap handoff when positive scrap is reported
73
+ if (scrapQuantity > 0 && !scrapHandoffData) {
74
+ return err(new ScrapHandoffRequiredError(id));
75
+ }
76
+
77
+ // 6. Accumulate quantities and time
78
+ const now = new Date();
79
+
80
+ const updatedWorkOrder = await db
81
+ .updateTable("WorkOrder")
82
+ .set({
83
+ completedQuantity: workOrder.completedQuantity + completedQuantity,
84
+ scrapQuantity: workOrder.scrapQuantity + scrapQuantity,
85
+ actualSetupTime: workOrder.actualSetupTime + actualSetupTime,
86
+ actualRunTime: workOrder.actualRunTime + actualRunTime,
87
+ executionNotes: notes ?? workOrder.executionNotes,
88
+ updatedAt: now,
89
+ })
90
+ .where("id", "=", id)
91
+ .returningAll()
92
+ .executeTakeFirstOrThrow();
93
+
94
+ // 7. Create PROGRESS_REPORTED execution event
95
+ await db
96
+ .insertInto("WorkOrderExecutionEvent")
97
+ .values({
98
+ workOrderId: id,
99
+ eventType: "PROGRESS_REPORTED",
100
+ timestamp: now,
101
+ quantity: completedQuantity,
102
+ timeValue: actualSetupTime + actualRunTime,
103
+ scrapValue: scrapQuantity > 0 ? scrapQuantity : null,
104
+ notes,
105
+ createdAt: now,
106
+ updatedAt: null,
107
+ })
108
+ .execute();
109
+
110
+ // 8. Roll up progress to parent production order
111
+ const parentOrder = await db
112
+ .selectFrom("ProductionOrder")
113
+ .selectAll()
114
+ .where("id", "=", workOrder.productionOrderId)
115
+ .forUpdate()
116
+ .executeTakeFirst();
117
+
118
+ if (parentOrder) {
119
+ await db
120
+ .updateTable("ProductionOrder")
121
+ .set({
122
+ updatedAt: now,
123
+ })
124
+ .where("id", "=", parentOrder.id)
125
+ .execute();
126
+ }
127
+
128
+ return ok({ workOrder: updatedWorkOrder });
129
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./rescheduleProductionOrder";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const rescheduleProductionOrder = defineCommand(permissions.rescheduleProductionOrder, run);