@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,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);
@@ -0,0 +1,185 @@
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
+ ProductionOrderNotReschedulableError,
7
+ ExecutionAlreadyStartedError,
8
+ InvalidDateRangeError,
9
+ } from "../lib/errors.generated";
10
+ import {
11
+ baseDraftProductionOrder,
12
+ baseReleasedProductionOrder,
13
+ basePendingWorkOrder,
14
+ baseInProgressWorkOrder,
15
+ } from "../testing/fixtures";
16
+ import { run } from "./rescheduleProductionOrder";
17
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
18
+
19
+ describe("rescheduleProductionOrder", () => {
20
+ const ctx: CommandContext = {
21
+ actorId: "test-actor",
22
+ permissions: ["manufacturing:rescheduleProductionOrder"],
23
+ };
24
+
25
+ const newStartDate = new Date("2024-04-01T00:00:00.000Z");
26
+ const newEndDate = new Date("2024-04-15T00:00:00.000Z");
27
+
28
+ it("reschedules a released order before execution starts", async () => {
29
+ const { db, spies } = createMockDb<Transaction>();
30
+ const rescheduled = {
31
+ ...baseReleasedProductionOrder,
32
+ plannedStartDate: newStartDate,
33
+ plannedEndDate: newEndDate,
34
+ };
35
+
36
+ // order lookup
37
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
38
+ // work orders - all pending
39
+ spies.select.mockReturnValueOnce([basePendingWorkOrder]);
40
+ // update
41
+ spies.update.mockReturnValue(rescheduled);
42
+
43
+ const result = await run(
44
+ db,
45
+ {
46
+ id: baseReleasedProductionOrder.id,
47
+ plannedStartDate: newStartDate,
48
+ plannedEndDate: newEndDate,
49
+ },
50
+ ctx,
51
+ );
52
+
53
+ expect(result.ok).toBe(true);
54
+ if (result.ok) {
55
+ expect(result.value.productionOrder.plannedStartDate).toBe(newStartDate);
56
+ expect(result.value.productionOrder.plannedEndDate).toBe(newEndDate);
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(
66
+ db,
67
+ { id: "nonexistent", plannedStartDate: newStartDate, plannedEndDate: newEndDate },
68
+ ctx,
69
+ );
70
+
71
+ expect(result.ok).toBe(false);
72
+ if (!result.ok) {
73
+ expect(result.error).toBeInstanceOf(ProductionOrderNotFoundError);
74
+ }
75
+ });
76
+
77
+ it("returns error when the order is not in RELEASED", async () => {
78
+ const { db, spies } = createMockDb<Transaction>();
79
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
80
+
81
+ const result = await run(
82
+ db,
83
+ {
84
+ id: baseDraftProductionOrder.id,
85
+ plannedStartDate: newStartDate,
86
+ plannedEndDate: newEndDate,
87
+ },
88
+ ctx,
89
+ );
90
+
91
+ expect(result.ok).toBe(false);
92
+ if (!result.ok) {
93
+ expect(result.error).toBeInstanceOf(ProductionOrderNotReschedulableError);
94
+ }
95
+ });
96
+
97
+ it("returns error when execution has already started", async () => {
98
+ const { db, spies } = createMockDb<Transaction>();
99
+
100
+ // order lookup
101
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
102
+ // work orders - one in progress
103
+ spies.select.mockReturnValueOnce([baseInProgressWorkOrder]);
104
+
105
+ const result = await run(
106
+ db,
107
+ {
108
+ id: baseReleasedProductionOrder.id,
109
+ plannedStartDate: newStartDate,
110
+ plannedEndDate: newEndDate,
111
+ },
112
+ ctx,
113
+ );
114
+
115
+ expect(result.ok).toBe(false);
116
+ if (!result.ok) {
117
+ expect(result.error).toBeInstanceOf(ExecutionAlreadyStartedError);
118
+ }
119
+ });
120
+
121
+ it("returns error when planned dates are invalid", async () => {
122
+ const { db, spies } = createMockDb<Transaction>();
123
+ const invalidStart = new Date("2024-04-15T00:00:00.000Z");
124
+ const invalidEnd = new Date("2024-04-01T00:00:00.000Z");
125
+
126
+ // order lookup
127
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
128
+ // work orders - all pending
129
+ spies.select.mockReturnValueOnce([basePendingWorkOrder]);
130
+
131
+ const result = await run(
132
+ db,
133
+ {
134
+ id: baseReleasedProductionOrder.id,
135
+ plannedStartDate: invalidStart,
136
+ plannedEndDate: invalidEnd,
137
+ },
138
+ ctx,
139
+ );
140
+
141
+ expect(result.ok).toBe(false);
142
+ if (!result.ok) {
143
+ expect(result.error).toBeInstanceOf(InvalidDateRangeError);
144
+ }
145
+ });
146
+
147
+ it("preserves release snapshots after rescheduling", async () => {
148
+ const { db, spies } = createMockDb<Transaction>();
149
+ const rescheduled = {
150
+ ...baseReleasedProductionOrder,
151
+ plannedStartDate: newStartDate,
152
+ plannedEndDate: newEndDate,
153
+ };
154
+
155
+ // order lookup
156
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
157
+ // work orders - all pending
158
+ spies.select.mockReturnValueOnce([basePendingWorkOrder]);
159
+ // update
160
+ spies.update.mockReturnValue(rescheduled);
161
+
162
+ const result = await run(
163
+ db,
164
+ {
165
+ id: baseReleasedProductionOrder.id,
166
+ plannedStartDate: newStartDate,
167
+ plannedEndDate: newEndDate,
168
+ },
169
+ ctx,
170
+ );
171
+
172
+ expect(result.ok).toBe(true);
173
+ if (result.ok) {
174
+ // Status remains RELEASED - snapshots are preserved
175
+ expect(result.value.productionOrder.selectedBomVersionId).toBe(
176
+ baseReleasedProductionOrder.selectedBomVersionId,
177
+ );
178
+ expect(result.value.productionOrder.selectedRoutingRevisionId).toBe(
179
+ baseReleasedProductionOrder.selectedRoutingRevisionId,
180
+ );
181
+ }
182
+ // No delete calls should have been made
183
+ expect(spies.delete).not.toHaveBeenCalled();
184
+ });
185
+ });