@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,145 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ ProductionOrderNotFoundError,
4
+ ProductionOrderNotMutableError,
5
+ InvalidPlannedQuantityError,
6
+ ImmutableScopeFieldError,
7
+ CrossScopeMasterReferenceError,
8
+ } from "../lib/errors.generated";
9
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
10
+
11
+ export interface UpdateProductionOrderInput {
12
+ id: string;
13
+ plannedQuantity?: number | null;
14
+ plannedStartDate?: Date | null;
15
+ plannedEndDate?: Date | null;
16
+ selectedBomVersionId?: string | null;
17
+ selectedRoutingRevisionId?: string | null;
18
+ orderedItemId?: string | null;
19
+ companyId?: string | null;
20
+ siteId?: string | null;
21
+ }
22
+
23
+ /**
24
+ * Function: updateProductionOrder
25
+ *
26
+ * Revises mutable draft planning fields such as dates, quantity, and
27
+ * preferred BOM or routing before release freezes execution assumptions.
28
+ */
29
+ export async function run<CF extends Record<string, unknown>>(
30
+ db: Transaction,
31
+ input: UpdateProductionOrderInput & CF,
32
+ _ctx: CommandContext,
33
+ ) {
34
+ const {
35
+ id,
36
+ plannedQuantity,
37
+ plannedStartDate,
38
+ plannedEndDate,
39
+ selectedBomVersionId,
40
+ selectedRoutingRevisionId,
41
+ orderedItemId,
42
+ companyId,
43
+ siteId,
44
+ ...customFields
45
+ } = input;
46
+
47
+ // 1. Fetch production order with lock
48
+ const order = await db
49
+ .selectFrom("ProductionOrder")
50
+ .selectAll()
51
+ .where("id", "=", id)
52
+ .forUpdate()
53
+ .executeTakeFirst();
54
+
55
+ if (!order) {
56
+ return err(new ProductionOrderNotFoundError(id));
57
+ }
58
+
59
+ // 2. Only DRAFT orders are mutable
60
+ if (order.status !== "DRAFT") {
61
+ return err(new ProductionOrderNotMutableError(id));
62
+ }
63
+
64
+ // 3. Validate planned quantity if provided
65
+ if (plannedQuantity != null && plannedQuantity <= 0) {
66
+ return err(new InvalidPlannedQuantityError(id));
67
+ }
68
+
69
+ // 4. Immutable scope fields
70
+ if (
71
+ orderedItemId !== undefined &&
72
+ orderedItemId !== null &&
73
+ orderedItemId !== order.orderedItemId
74
+ ) {
75
+ return err(new ImmutableScopeFieldError(id));
76
+ }
77
+ if (companyId !== undefined && companyId !== null && companyId !== order.companyId) {
78
+ return err(new ImmutableScopeFieldError(id));
79
+ }
80
+ if (siteId !== undefined && siteId !== null && siteId !== order.siteId) {
81
+ return err(new ImmutableScopeFieldError(id));
82
+ }
83
+
84
+ // 5. Validate optional BOM reference scope
85
+ const effectiveBomId =
86
+ selectedBomVersionId !== undefined ? selectedBomVersionId : order.selectedBomVersionId;
87
+ if (effectiveBomId) {
88
+ const bom = await db
89
+ .selectFrom("BillOfMaterial")
90
+ .selectAll()
91
+ .where("id", "=", effectiveBomId)
92
+ .executeTakeFirst();
93
+
94
+ if (
95
+ bom &&
96
+ (bom.companyId !== order.companyId || (bom.siteId != null && bom.siteId !== order.siteId))
97
+ ) {
98
+ return err(new CrossScopeMasterReferenceError(id));
99
+ }
100
+ }
101
+
102
+ // 6. Validate optional routing reference scope
103
+ const effectiveRoutingId =
104
+ selectedRoutingRevisionId !== undefined
105
+ ? selectedRoutingRevisionId
106
+ : order.selectedRoutingRevisionId;
107
+ if (effectiveRoutingId) {
108
+ const routing = await db
109
+ .selectFrom("Routing")
110
+ .selectAll()
111
+ .where("id", "=", effectiveRoutingId)
112
+ .executeTakeFirst();
113
+
114
+ if (
115
+ routing &&
116
+ (routing.companyId !== order.companyId ||
117
+ (routing.siteId != null && routing.siteId !== order.siteId))
118
+ ) {
119
+ return err(new CrossScopeMasterReferenceError(id));
120
+ }
121
+ }
122
+
123
+ // 7. Build update set
124
+ const updateSet: Record<string, unknown> = {
125
+ ...customFields,
126
+ updatedAt: new Date(),
127
+ };
128
+
129
+ if (plannedQuantity !== undefined) updateSet.plannedQuantity = plannedQuantity;
130
+ if (plannedStartDate !== undefined) updateSet.plannedStartDate = plannedStartDate;
131
+ if (plannedEndDate !== undefined) updateSet.plannedEndDate = plannedEndDate;
132
+ if (selectedBomVersionId !== undefined) updateSet.selectedBomVersionId = selectedBomVersionId;
133
+ if (selectedRoutingRevisionId !== undefined)
134
+ updateSet.selectedRoutingRevisionId = selectedRoutingRevisionId;
135
+
136
+ // 8. Persist changes
137
+ const updated = await db
138
+ .updateTable("ProductionOrder")
139
+ .set(updateSet)
140
+ .where("id", "=", id)
141
+ .returningAll()
142
+ .executeTakeFirstOrThrow();
143
+
144
+ return ok({ productionOrder: updated });
145
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./updateRouting";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const updateRouting = defineCommand(permissions.updateRouting, run);
@@ -0,0 +1,211 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import {
5
+ RoutingNotFoundError,
6
+ RoutingNotMutableError,
7
+ DuplicateOperationSequenceError,
8
+ InvalidStandardTimeError,
9
+ WorkCenterNotFoundError,
10
+ CrossCompanyWorkCenterError,
11
+ } from "../lib/errors.generated";
12
+ import { baseDraftRouting, baseActiveRouting, baseActiveWorkCenter } from "../testing/fixtures";
13
+ import { run } from "./updateRouting";
14
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
15
+
16
+ describe("updateRouting", () => {
17
+ const ctx: CommandContext = {
18
+ actorId: "test-actor",
19
+ permissions: ["manufacturing:updateRouting"],
20
+ };
21
+
22
+ const validOperations = [
23
+ {
24
+ sequenceNumber: 10,
25
+ operationDescription: "Revised assembly",
26
+ workCenterId: "work-center-2",
27
+ standardSetupTime: 25,
28
+ standardRunTime: 55,
29
+ operatorInstructions: null,
30
+ },
31
+ {
32
+ sequenceNumber: 20,
33
+ operationDescription: "Revised QC",
34
+ workCenterId: "work-center-2",
35
+ standardSetupTime: 15,
36
+ standardRunTime: 20,
37
+ operatorInstructions: null,
38
+ },
39
+ ];
40
+
41
+ const validInput = {
42
+ id: "routing-1",
43
+ operations: validOperations,
44
+ };
45
+
46
+ it("updates draft routing operations", async () => {
47
+ const { db, spies } = createMockDb<Transaction>();
48
+
49
+ // select: Routing lookup
50
+ spies.select.mockReturnValueOnce(baseDraftRouting);
51
+ // select: WorkCenter lookup for op 1
52
+ spies.select.mockReturnValueOnce(baseActiveWorkCenter);
53
+ // select: WorkCenter lookup for op 2
54
+ spies.select.mockReturnValueOnce(baseActiveWorkCenter);
55
+ // update: Routing header
56
+ spies.update.mockReturnValue({ ...baseDraftRouting, updatedAt: new Date() });
57
+ // insert: new operations
58
+ spies.insert.mockReturnValue({
59
+ id: "op-new",
60
+ routingId: "routing-1",
61
+ sequenceNumber: 10,
62
+ operationDescription: "Revised assembly",
63
+ workCenterId: "work-center-2",
64
+ standardSetupTime: 25,
65
+ standardRunTime: 55,
66
+ operatorInstructions: null,
67
+ createdAt: new Date(),
68
+ updatedAt: null,
69
+ });
70
+
71
+ const result = await run(db, validInput, ctx);
72
+
73
+ expect(result.ok).toBe(true);
74
+ if (result.ok) {
75
+ expect(result.value.routing).toBeDefined();
76
+ }
77
+ expect(spies.update).toHaveBeenCalled();
78
+ });
79
+
80
+ it("returns error when the routing does not exist", async () => {
81
+ const { db, spies } = createMockDb<Transaction>();
82
+
83
+ // select: Routing lookup - not found
84
+ spies.select.mockReturnValueOnce(undefined);
85
+
86
+ const result = await run(db, validInput, ctx);
87
+
88
+ expect(result.ok).toBe(false);
89
+ if (!result.ok) {
90
+ expect(result.error).toBeInstanceOf(RoutingNotFoundError);
91
+ }
92
+ });
93
+
94
+ it("returns error when the routing is not in DRAFT", async () => {
95
+ const { db, spies } = createMockDb<Transaction>();
96
+
97
+ // select: Routing lookup - ACTIVE
98
+ spies.select.mockReturnValueOnce(baseActiveRouting);
99
+
100
+ const result = await run(db, { ...validInput, id: "routing-2" }, ctx);
101
+
102
+ expect(result.ok).toBe(false);
103
+ if (!result.ok) {
104
+ expect(result.error).toBeInstanceOf(RoutingNotMutableError);
105
+ }
106
+ });
107
+
108
+ it("returns error when revised sequence numbers conflict", async () => {
109
+ const { db, spies } = createMockDb<Transaction>();
110
+
111
+ // select: Routing lookup
112
+ spies.select.mockReturnValueOnce(baseDraftRouting);
113
+
114
+ const duplicateOps = [
115
+ { ...validOperations[0], sequenceNumber: 10 },
116
+ { ...validOperations[1], sequenceNumber: 10 },
117
+ ];
118
+
119
+ const result = await run(db, { ...validInput, operations: duplicateOps }, ctx);
120
+
121
+ expect(result.ok).toBe(false);
122
+ if (!result.ok) {
123
+ expect(result.error).toBeInstanceOf(DuplicateOperationSequenceError);
124
+ }
125
+ });
126
+
127
+ it("returns error when a revised standard time is negative", async () => {
128
+ const { db, spies } = createMockDb<Transaction>();
129
+
130
+ // select: Routing lookup
131
+ spies.select.mockReturnValueOnce(baseDraftRouting);
132
+
133
+ const badOps = [{ ...validOperations[0], standardRunTime: -1 }];
134
+
135
+ const result = await run(db, { ...validInput, operations: badOps }, ctx);
136
+
137
+ expect(result.ok).toBe(false);
138
+ if (!result.ok) {
139
+ expect(result.error).toBeInstanceOf(InvalidStandardTimeError);
140
+ }
141
+ });
142
+
143
+ it("returns error when a referenced work center does not exist", async () => {
144
+ const { db, spies } = createMockDb<Transaction>();
145
+
146
+ // select: Routing lookup
147
+ spies.select.mockReturnValueOnce(baseDraftRouting);
148
+ // select: WorkCenter lookup - not found
149
+ spies.select.mockReturnValueOnce(undefined);
150
+
151
+ const result = await run(db, validInput, ctx);
152
+
153
+ expect(result.ok).toBe(false);
154
+ if (!result.ok) {
155
+ expect(result.error).toBeInstanceOf(WorkCenterNotFoundError);
156
+ }
157
+ });
158
+
159
+ it("returns error when a referenced work center is outside the routing company", async () => {
160
+ const { db, spies } = createMockDb<Transaction>();
161
+
162
+ // select: Routing lookup
163
+ spies.select.mockReturnValueOnce(baseDraftRouting);
164
+ // select: WorkCenter lookup - different company
165
+ spies.select.mockReturnValueOnce({
166
+ ...baseActiveWorkCenter,
167
+ companyId: "other-company",
168
+ });
169
+
170
+ const result = await run(db, validInput, ctx);
171
+
172
+ expect(result.ok).toBe(false);
173
+ if (!result.ok) {
174
+ expect(result.error).toBeInstanceOf(CrossCompanyWorkCenterError);
175
+ }
176
+ });
177
+
178
+ it("preserves released routing snapshots after update", async () => {
179
+ const { db, spies } = createMockDb<Transaction>();
180
+
181
+ // select: Routing lookup - DRAFT (only DRAFT can be updated)
182
+ spies.select.mockReturnValueOnce(baseDraftRouting);
183
+ // select: WorkCenter lookup for op 1
184
+ spies.select.mockReturnValueOnce(baseActiveWorkCenter);
185
+ // update: Routing header
186
+ spies.update.mockReturnValue({ ...baseDraftRouting, updatedAt: new Date() });
187
+ // insert: new operations
188
+ spies.insert.mockReturnValue({
189
+ id: "op-new",
190
+ routingId: "routing-1",
191
+ sequenceNumber: 10,
192
+ operationDescription: "Revised assembly",
193
+ workCenterId: "work-center-2",
194
+ standardSetupTime: 25,
195
+ standardRunTime: 55,
196
+ operatorInstructions: null,
197
+ createdAt: new Date(),
198
+ updatedAt: null,
199
+ });
200
+
201
+ const singleOp = [validOperations[0]];
202
+ const result = await run(db, { ...validInput, operations: singleOp }, ctx);
203
+
204
+ // The update only touches the Routing and RoutingOperation tables,
205
+ // not ProductionOrderRoutingSnapshot, preserving frozen snapshots.
206
+ expect(result.ok).toBe(true);
207
+ if (result.ok) {
208
+ expect(result.value.routing.status).toBe("DRAFT");
209
+ }
210
+ });
211
+ });
@@ -0,0 +1,124 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ RoutingNotFoundError,
4
+ RoutingNotMutableError,
5
+ DuplicateOperationSequenceError,
6
+ InvalidStandardTimeError,
7
+ WorkCenterNotFoundError,
8
+ CrossCompanyWorkCenterError,
9
+ } from "../lib/errors.generated";
10
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
11
+
12
+ export interface UpdateRoutingOperationInput {
13
+ sequenceNumber: number;
14
+ operationDescription?: string | null;
15
+ workCenterId: string;
16
+ standardSetupTime: number;
17
+ standardRunTime: number;
18
+ operatorInstructions?: string | null;
19
+ }
20
+
21
+ export interface UpdateRoutingInput {
22
+ id: string;
23
+ revisionNumber?: string | null;
24
+ operations: UpdateRoutingOperationInput[];
25
+ }
26
+
27
+ /**
28
+ * Function: updateRouting
29
+ *
30
+ * Updates a draft routing's operations. Only DRAFT routings are mutable.
31
+ * Replaces all existing operations with the revised set.
32
+ */
33
+ export async function run<CF extends Record<string, unknown>>(
34
+ db: Transaction,
35
+ input: UpdateRoutingInput & CF,
36
+ _ctx: CommandContext,
37
+ ) {
38
+ const { id, revisionNumber, operations, ...customFields } = input;
39
+
40
+ // 1. Fetch routing
41
+ const routing = await db
42
+ .selectFrom("Routing")
43
+ .selectAll()
44
+ .where("id", "=", id)
45
+ .forUpdate()
46
+ .executeTakeFirst();
47
+
48
+ if (!routing) {
49
+ return err(new RoutingNotFoundError(id));
50
+ }
51
+
52
+ // 2. Verify DRAFT status
53
+ if (routing.status !== "DRAFT") {
54
+ return err(new RoutingNotMutableError(id));
55
+ }
56
+
57
+ // 3. Validate unique sequence numbers
58
+ const seqNumbers = operations.map((op) => op.sequenceNumber);
59
+ const uniqueSeqs = new Set(seqNumbers);
60
+ if (uniqueSeqs.size !== seqNumbers.length) {
61
+ return err(new DuplicateOperationSequenceError(id));
62
+ }
63
+
64
+ // 4. Validate standard times >= 0
65
+ for (const op of operations) {
66
+ if (op.standardSetupTime < 0 || op.standardRunTime < 0) {
67
+ return err(new InvalidStandardTimeError(id));
68
+ }
69
+ }
70
+
71
+ // 5. Validate work centers exist and belong to same company
72
+ for (const op of operations) {
73
+ const workCenter = await db
74
+ .selectFrom("WorkCenter")
75
+ .selectAll()
76
+ .where("id", "=", op.workCenterId)
77
+ .executeTakeFirst();
78
+
79
+ if (!workCenter) {
80
+ return err(new WorkCenterNotFoundError(op.workCenterId));
81
+ }
82
+
83
+ if (workCenter.companyId !== routing.companyId) {
84
+ return err(new CrossCompanyWorkCenterError(op.workCenterId));
85
+ }
86
+ }
87
+
88
+ // 6. Update routing header
89
+ const updatedRouting = await db
90
+ .updateTable("Routing")
91
+ .set({
92
+ ...(customFields as Record<string, unknown>),
93
+ ...(revisionNumber !== undefined ? { revisionNumber } : {}),
94
+ updatedAt: new Date(),
95
+ })
96
+ .where("id", "=", id)
97
+ .returningAll()
98
+ .executeTakeFirst();
99
+
100
+ // 7. Delete existing operations and replace
101
+ await db.deleteFrom("RoutingOperation").where("routingId", "=", id).execute();
102
+
103
+ const createdOperations = [];
104
+ for (const op of operations) {
105
+ const operation = await db
106
+ .insertInto("RoutingOperation")
107
+ .values({
108
+ routingId: id,
109
+ sequenceNumber: op.sequenceNumber,
110
+ operationDescription: op.operationDescription ?? null,
111
+ workCenterId: op.workCenterId,
112
+ standardSetupTime: op.standardSetupTime,
113
+ standardRunTime: op.standardRunTime,
114
+ operatorInstructions: op.operatorInstructions ?? null,
115
+ createdAt: new Date(),
116
+ updatedAt: null,
117
+ })
118
+ .returningAll()
119
+ .executeTakeFirst();
120
+ createdOperations.push(operation!);
121
+ }
122
+
123
+ return ok({ routing: updatedRouting!, operations: createdOperations });
124
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./updateWorkCenter";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const updateWorkCenter = defineCommand(permissions.updateWorkCenter, run);
@@ -0,0 +1,152 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import {
5
+ WorkCenterNotFoundError,
6
+ InvalidCapacityError,
7
+ InvalidRateError,
8
+ InvalidOverheadMethodError,
9
+ OverheadCurrencyRequiredError,
10
+ ImmutableScopeFieldError,
11
+ } from "../lib/errors.generated";
12
+ import { baseActiveWorkCenter } from "../testing/fixtures";
13
+ import { run } from "./updateWorkCenter";
14
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
15
+
16
+ describe("updateWorkCenter", () => {
17
+ const ctx: CommandContext = {
18
+ actorId: "test-actor",
19
+ permissions: ["manufacturing:updateWorkCenter"],
20
+ };
21
+
22
+ const validInput = {
23
+ id: baseActiveWorkCenter.id,
24
+ capacityAssumptions: 200,
25
+ laborRate: 30.0,
26
+ };
27
+
28
+ it("updates an active work center without changing scope identity", async () => {
29
+ const { db, spies } = createMockDb<Transaction>();
30
+ spies.select.mockReturnValueOnce(baseActiveWorkCenter);
31
+ spies.update.mockReturnValue({
32
+ ...baseActiveWorkCenter,
33
+ capacityAssumptions: 200,
34
+ laborRate: 30.0,
35
+ });
36
+
37
+ const result = await run(db, validInput, ctx);
38
+
39
+ expect(result.ok).toBe(true);
40
+ if (result.ok) {
41
+ expect(result.value.workCenter.capacityAssumptions).toBe(200);
42
+ expect(result.value.workCenter.laborRate).toBe(30.0);
43
+ }
44
+ expect(spies.update).toHaveBeenCalled();
45
+ });
46
+
47
+ it("returns error when the work center does not exist", async () => {
48
+ const { db, spies } = createMockDb<Transaction>();
49
+ spies.select.mockReturnValueOnce(undefined);
50
+
51
+ const result = await run(db, validInput, ctx);
52
+
53
+ expect(result.ok).toBe(false);
54
+ if (!result.ok) {
55
+ expect(result.error).toBeInstanceOf(WorkCenterNotFoundError);
56
+ }
57
+ });
58
+
59
+ it("returns error when immutable scope fields are changed", async () => {
60
+ const { db } = createMockDb<Transaction>();
61
+
62
+ const result = await run(
63
+ db,
64
+ { ...validInput, companyId: "other-company" } as Parameters<typeof run>[1],
65
+ ctx,
66
+ );
67
+
68
+ expect(result.ok).toBe(false);
69
+ if (!result.ok) {
70
+ expect(result.error).toBeInstanceOf(ImmutableScopeFieldError);
71
+ }
72
+ });
73
+
74
+ it("returns error when capacity is not positive", async () => {
75
+ const { db, spies } = createMockDb<Transaction>();
76
+ spies.select.mockReturnValueOnce(baseActiveWorkCenter);
77
+
78
+ const result = await run(db, { ...validInput, capacityAssumptions: 0 }, ctx);
79
+
80
+ expect(result.ok).toBe(false);
81
+ if (!result.ok) {
82
+ expect(result.error).toBeInstanceOf(InvalidCapacityError);
83
+ }
84
+ });
85
+
86
+ it("returns error when a rate is negative", async () => {
87
+ const { db, spies } = createMockDb<Transaction>();
88
+ spies.select.mockReturnValueOnce(baseActiveWorkCenter);
89
+
90
+ const result = await run(db, { ...validInput, laborRate: -1 }, ctx);
91
+
92
+ expect(result.ok).toBe(false);
93
+ if (!result.ok) {
94
+ expect(result.error).toBeInstanceOf(InvalidRateError);
95
+ }
96
+ });
97
+
98
+ it("preserves released cost baselines after update", async () => {
99
+ const { db, spies } = createMockDb<Transaction>();
100
+ spies.select.mockReturnValueOnce(baseActiveWorkCenter);
101
+ spies.update.mockReturnValue({
102
+ ...baseActiveWorkCenter,
103
+ machineRate: 75.0,
104
+ });
105
+
106
+ const result = await run(db, { id: baseActiveWorkCenter.id, machineRate: 75.0 }, ctx);
107
+
108
+ expect(result.ok).toBe(true);
109
+ expect(spies.update).toHaveBeenCalledTimes(1);
110
+ expect(spies.set).toHaveBeenCalledWith(expect.objectContaining({ machineRate: 75.0 }));
111
+ expect(spies.set).toHaveBeenCalledWith(
112
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
113
+ expect.not.objectContaining({ companyId: expect.anything() }),
114
+ );
115
+ });
116
+
117
+ it("returns error when the overhead method is not supported", async () => {
118
+ const { db, spies } = createMockDb<Transaction>();
119
+ spies.select.mockReturnValueOnce(baseActiveWorkCenter);
120
+
121
+ const result = await run(
122
+ db,
123
+ { id: baseActiveWorkCenter.id, overheadAbsorptionMethod: "PER_ORDER" },
124
+ ctx,
125
+ );
126
+
127
+ expect(result.ok).toBe(false);
128
+ if (!result.ok) {
129
+ expect(result.error).toBeInstanceOf(InvalidOverheadMethodError);
130
+ }
131
+ });
132
+
133
+ it("returns error when fixed-amount overhead omits currency", async () => {
134
+ const { db, spies } = createMockDb<Transaction>();
135
+ spies.select.mockReturnValueOnce(baseActiveWorkCenter);
136
+
137
+ const result = await run(
138
+ db,
139
+ {
140
+ id: baseActiveWorkCenter.id,
141
+ overheadAbsorptionMethod: "FIXED_AMOUNT_PER_GOOD_UNIT",
142
+ overheadAbsorptionCurrency: null,
143
+ },
144
+ ctx,
145
+ );
146
+
147
+ expect(result.ok).toBe(false);
148
+ if (!result.ok) {
149
+ expect(result.error).toBeInstanceOf(OverheadCurrencyRequiredError);
150
+ }
151
+ });
152
+ });