@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,231 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import {
5
+ CostSummaryNotFoundError,
6
+ CostSummaryNotReviewableError,
7
+ ParentOrderNotTechnicallyCompleteError,
8
+ IncompleteVarianceBreakdownError,
9
+ ReviewerRequiredError,
10
+ VarianceCalculationFailedError,
11
+ } from "../lib/errors.generated";
12
+ import {
13
+ basePendingReviewCostSummary,
14
+ baseCollectingCostSummary,
15
+ baseTechCompleteProductionOrder,
16
+ baseInProgressProductionOrder,
17
+ } from "../testing/fixtures";
18
+ import { run } from "./reviewManufacturingCostSummary";
19
+ import type { VarianceBreakdownEntry } from "./reviewManufacturingCostSummary";
20
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
21
+
22
+ describe("reviewManufacturingCostSummary", () => {
23
+ const ctx: CommandContext = {
24
+ actorId: "test-actor",
25
+ permissions: ["manufacturing:reviewManufacturingCostSummary"],
26
+ };
27
+
28
+ const fullVarianceBreakdown: VarianceBreakdownEntry[] = [
29
+ {
30
+ varianceType: "MATERIAL_PRICE",
31
+ amount: 100,
32
+ accountReference: "acct-5001",
33
+ variancePercentage: 5.0,
34
+ },
35
+ {
36
+ varianceType: "MATERIAL_USAGE",
37
+ amount: -20,
38
+ accountReference: "acct-5002",
39
+ variancePercentage: -1.0,
40
+ },
41
+ {
42
+ varianceType: "LABOR_RATE",
43
+ amount: 30,
44
+ accountReference: "acct-5003",
45
+ variancePercentage: 6.0,
46
+ },
47
+ {
48
+ varianceType: "LABOR_EFFICIENCY",
49
+ amount: -10,
50
+ accountReference: "acct-5004",
51
+ variancePercentage: -2.0,
52
+ },
53
+ {
54
+ varianceType: "MACHINE_RATE",
55
+ amount: 50,
56
+ accountReference: "acct-5005",
57
+ variancePercentage: 5.0,
58
+ },
59
+ {
60
+ varianceType: "MACHINE_EFFICIENCY",
61
+ amount: 0,
62
+ accountReference: "acct-5006",
63
+ variancePercentage: 0.0,
64
+ },
65
+ { varianceType: "SCRAP", amount: 50, accountReference: "acct-5007", variancePercentage: 100.0 },
66
+ { varianceType: "YIELD", amount: 0, accountReference: "acct-5008", variancePercentage: 0.0 },
67
+ ];
68
+
69
+ const validInput = {
70
+ costSummaryId: "cost-summary-2",
71
+ reviewerId: "reviewer-1",
72
+ reviewerNotes: "Variances within acceptable range",
73
+ varianceBreakdown: fullVarianceBreakdown,
74
+ };
75
+
76
+ it("reviews a pending cost summary and freezes the variance breakdown", async () => {
77
+ const { db, spies } = createMockDb<Transaction>();
78
+
79
+ // select: cost summary, production order
80
+ spies.select.mockReturnValueOnce(basePendingReviewCostSummary);
81
+ spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
82
+
83
+ const updatedSummary = {
84
+ ...basePendingReviewCostSummary,
85
+ status: "VARIANCE_REVIEWED",
86
+ reviewerNotes: "Variances within acceptable range",
87
+ };
88
+ spies.insert.mockReturnValue({});
89
+ spies.update.mockReturnValue(updatedSummary);
90
+
91
+ const result = await run(db, validInput, ctx);
92
+
93
+ expect(result.ok).toBe(true);
94
+ if (result.ok) {
95
+ expect(result.value.costSummary.status).toBe("VARIANCE_REVIEWED");
96
+ }
97
+ // 8 variance lines inserted
98
+ expect(spies.insert).toHaveBeenCalledTimes(8);
99
+ expect(spies.update).toHaveBeenCalled();
100
+ });
101
+
102
+ it("returns error when the summary does not exist", async () => {
103
+ const { db, spies } = createMockDb<Transaction>();
104
+
105
+ spies.select.mockReturnValueOnce(undefined);
106
+
107
+ const result = await run(db, validInput, ctx);
108
+
109
+ expect(result.ok).toBe(false);
110
+ if (!result.ok) {
111
+ expect(result.error).toBeInstanceOf(CostSummaryNotFoundError);
112
+ }
113
+ });
114
+
115
+ it("returns error when the summary is not pending review", async () => {
116
+ const { db, spies } = createMockDb<Transaction>();
117
+
118
+ spies.select.mockReturnValueOnce(baseCollectingCostSummary); // COLLECTING, not PENDING_VARIANCE_REVIEW
119
+
120
+ const result = await run(db, validInput, ctx);
121
+
122
+ expect(result.ok).toBe(false);
123
+ if (!result.ok) {
124
+ expect(result.error).toBeInstanceOf(CostSummaryNotReviewableError);
125
+ }
126
+ });
127
+
128
+ it("returns error when the parent order is not technically complete", async () => {
129
+ const { db, spies } = createMockDb<Transaction>();
130
+
131
+ spies.select.mockReturnValueOnce(basePendingReviewCostSummary);
132
+ spies.select.mockReturnValueOnce(baseInProgressProductionOrder); // not TECHNICALLY_COMPLETE
133
+
134
+ const result = await run(db, validInput, ctx);
135
+
136
+ expect(result.ok).toBe(false);
137
+ if (!result.ok) {
138
+ expect(result.error).toBeInstanceOf(ParentOrderNotTechnicallyCompleteError);
139
+ }
140
+ });
141
+
142
+ it("returns error when the review result cannot represent all required variance types", async () => {
143
+ const { db, spies } = createMockDb<Transaction>();
144
+
145
+ spies.select.mockReturnValueOnce(basePendingReviewCostSummary);
146
+ spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
147
+
148
+ // Missing YIELD from the breakdown
149
+ const incompleteBreakdown = fullVarianceBreakdown.filter((v) => v.varianceType !== "YIELD");
150
+
151
+ const result = await run(db, { ...validInput, varianceBreakdown: incompleteBreakdown }, ctx);
152
+
153
+ expect(result.ok).toBe(false);
154
+ if (!result.ok) {
155
+ expect(result.error).toBeInstanceOf(IncompleteVarianceBreakdownError);
156
+ }
157
+ });
158
+
159
+ it("returns error when reviewer identity is missing", async () => {
160
+ const { db } = createMockDb<Transaction>();
161
+
162
+ const result = await run(db, { ...validInput, reviewerId: null }, ctx);
163
+
164
+ expect(result.ok).toBe(false);
165
+ if (!result.ok) {
166
+ expect(result.error).toBeInstanceOf(ReviewerRequiredError);
167
+ }
168
+ });
169
+
170
+ it("keeps all required variance types distinct during review", async () => {
171
+ const { db, spies } = createMockDb<Transaction>();
172
+
173
+ spies.select.mockReturnValueOnce(basePendingReviewCostSummary);
174
+ spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
175
+
176
+ const updatedSummary = {
177
+ ...basePendingReviewCostSummary,
178
+ status: "VARIANCE_REVIEWED",
179
+ };
180
+ spies.insert.mockReturnValue({});
181
+ spies.update.mockReturnValue(updatedSummary);
182
+
183
+ const result = await run(db, validInput, ctx);
184
+
185
+ expect(result.ok).toBe(true);
186
+ // Verify all 8 distinct variance lines were inserted
187
+ expect(spies.insert).toHaveBeenCalledTimes(8);
188
+
189
+ // Verify each call used the correct variance type through the values spy
190
+ const insertedTypes = new Set<string>();
191
+ for (const call of spies.values.mock.calls) {
192
+ const values = call[0] as Record<string, unknown>;
193
+ if (values.varianceType) {
194
+ insertedTypes.add(values.varianceType as string);
195
+ }
196
+ }
197
+ expect(insertedTypes.size).toBe(8);
198
+ expect(insertedTypes.has("MATERIAL_PRICE")).toBe(true);
199
+ expect(insertedTypes.has("MATERIAL_USAGE")).toBe(true);
200
+ expect(insertedTypes.has("LABOR_RATE")).toBe(true);
201
+ expect(insertedTypes.has("LABOR_EFFICIENCY")).toBe(true);
202
+ expect(insertedTypes.has("MACHINE_RATE")).toBe(true);
203
+ expect(insertedTypes.has("MACHINE_EFFICIENCY")).toBe(true);
204
+ expect(insertedTypes.has("SCRAP")).toBe(true);
205
+ expect(insertedTypes.has("YIELD")).toBe(true);
206
+ });
207
+
208
+ it("returns error when final variance cannot be recalculated consistently", async () => {
209
+ const { db, spies } = createMockDb<Transaction>();
210
+
211
+ spies.select.mockReturnValueOnce(basePendingReviewCostSummary);
212
+ spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
213
+
214
+ const result = await run(
215
+ db,
216
+ {
217
+ ...validInput,
218
+ varianceBreakdown: [
219
+ { ...fullVarianceBreakdown[0], amount: Number.NaN },
220
+ ...fullVarianceBreakdown.slice(1),
221
+ ],
222
+ },
223
+ ctx,
224
+ );
225
+
226
+ expect(result.ok).toBe(false);
227
+ if (!result.ok) {
228
+ expect(result.error).toBeInstanceOf(VarianceCalculationFailedError);
229
+ }
230
+ });
231
+ });
@@ -0,0 +1,137 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ CostSummaryNotFoundError,
4
+ CostSummaryNotReviewableError,
5
+ ParentOrderNotTechnicallyCompleteError,
6
+ IncompleteVarianceBreakdownError,
7
+ ReviewerRequiredError,
8
+ VarianceCalculationFailedError,
9
+ } from "../lib/errors.generated";
10
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
11
+
12
+ const REQUIRED_VARIANCE_TYPES = [
13
+ "MATERIAL_PRICE",
14
+ "MATERIAL_USAGE",
15
+ "LABOR_RATE",
16
+ "LABOR_EFFICIENCY",
17
+ "MACHINE_RATE",
18
+ "MACHINE_EFFICIENCY",
19
+ "SCRAP",
20
+ "YIELD",
21
+ ] as const;
22
+
23
+ type VarianceType = (typeof REQUIRED_VARIANCE_TYPES)[number];
24
+
25
+ export interface VarianceBreakdownEntry {
26
+ varianceType: VarianceType;
27
+ amount: number;
28
+ accountReference?: string | null;
29
+ variancePercentage?: number | null;
30
+ }
31
+
32
+ export interface ReviewManufacturingCostSummaryInput {
33
+ costSummaryId: string;
34
+ reviewerId?: string | null;
35
+ reviewerNotes?: string | null;
36
+ varianceBreakdown?: VarianceBreakdownEntry[] | null;
37
+ }
38
+
39
+ /**
40
+ * Function: reviewManufacturingCostSummary
41
+ *
42
+ * Recalculates the final planned-versus-actual result for a technically
43
+ * complete order and freezes the variance classification through an
44
+ * explicit reviewer approval step.
45
+ */
46
+ export async function run<CF extends Record<string, unknown>>(
47
+ db: Transaction,
48
+ input: ReviewManufacturingCostSummaryInput & CF,
49
+ _ctx: CommandContext,
50
+ ) {
51
+ const { costSummaryId, reviewerId, reviewerNotes, varianceBreakdown } = input;
52
+
53
+ // 1. Validate reviewer identity
54
+ if (!reviewerId) {
55
+ return err(new ReviewerRequiredError(costSummaryId));
56
+ }
57
+
58
+ // 2. Resolve cost summary
59
+ const costSummary = await db
60
+ .selectFrom("ManufacturingCostSummary")
61
+ .selectAll()
62
+ .where("id", "=", costSummaryId)
63
+ .forUpdate()
64
+ .executeTakeFirst();
65
+
66
+ if (!costSummary) {
67
+ return err(new CostSummaryNotFoundError(costSummaryId));
68
+ }
69
+
70
+ // 3. Summary must be PENDING_VARIANCE_REVIEW
71
+ if (costSummary.status !== "PENDING_VARIANCE_REVIEW") {
72
+ return err(new CostSummaryNotReviewableError(costSummaryId));
73
+ }
74
+
75
+ // 4. Parent production order must be TECHNICALLY_COMPLETE
76
+ const productionOrder = await db
77
+ .selectFrom("ProductionOrder")
78
+ .selectAll()
79
+ .where("id", "=", costSummary.productionOrderId)
80
+ .forUpdate()
81
+ .executeTakeFirst();
82
+
83
+ if (productionOrder?.status !== "TECHNICALLY_COMPLETE") {
84
+ return err(new ParentOrderNotTechnicallyCompleteError(costSummaryId));
85
+ }
86
+
87
+ // 5. Validate variance breakdown covers all required categories
88
+ if (!varianceBreakdown || !Array.isArray(varianceBreakdown)) {
89
+ return err(new IncompleteVarianceBreakdownError(costSummaryId));
90
+ }
91
+
92
+ const providedTypes = new Set(varianceBreakdown.map((v) => v.varianceType));
93
+ const missingTypes = REQUIRED_VARIANCE_TYPES.filter((t) => !providedTypes.has(t));
94
+
95
+ if (missingTypes.length > 0) {
96
+ return err(new IncompleteVarianceBreakdownError(costSummaryId));
97
+ }
98
+
99
+ // 6. Validate that variance amounts are consistent (no NaN or undefined)
100
+ for (const entry of varianceBreakdown) {
101
+ if (typeof entry.amount !== "number" || isNaN(entry.amount)) {
102
+ return err(new VarianceCalculationFailedError(costSummaryId));
103
+ }
104
+ }
105
+
106
+ // 7. Create cost variance lines for each breakdown entry
107
+ for (const entry of varianceBreakdown) {
108
+ await db
109
+ .insertInto("CostVarianceLine")
110
+ .values({
111
+ costSummaryId: costSummary.id,
112
+ varianceType: entry.varianceType,
113
+ amount: entry.amount,
114
+ accountReference: entry.accountReference ?? null,
115
+ variancePercentage: entry.variancePercentage ?? null,
116
+ createdAt: new Date(),
117
+ updatedAt: null,
118
+ })
119
+ .returningAll()
120
+ .executeTakeFirst();
121
+ }
122
+
123
+ // 8. Update summary to VARIANCE_REVIEWED
124
+ const updatedSummary = await db
125
+ .updateTable("ManufacturingCostSummary")
126
+ .set({
127
+ status: "VARIANCE_REVIEWED",
128
+ reviewedDate: new Date(),
129
+ reviewerNotes: reviewerNotes ?? null,
130
+ updatedAt: new Date(),
131
+ })
132
+ .where("id", "=", costSummary.id)
133
+ .returningAll()
134
+ .executeTakeFirst();
135
+
136
+ return ok({ costSummary: updatedSummary! });
137
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./startWorkOrder";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const startWorkOrder = defineCommand(permissions.startWorkOrder, run);
@@ -0,0 +1,118 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import {
5
+ WorkOrderNotFoundError,
6
+ WorkOrderNotStartableError,
7
+ ParentOrderNotExecutableError,
8
+ OperationSequenceBlockedError,
9
+ } from "../lib/errors.generated";
10
+ import {
11
+ basePendingWorkOrder,
12
+ baseInProgressWorkOrder,
13
+ baseReleasedProductionOrder,
14
+ baseDraftProductionOrder,
15
+ } from "../testing/fixtures";
16
+ import { run } from "./startWorkOrder";
17
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
18
+
19
+ describe("startWorkOrder", () => {
20
+ const ctx: CommandContext = {
21
+ actorId: "test-actor",
22
+ permissions: ["manufacturing:startWorkOrder"],
23
+ };
24
+
25
+ it("starts a pending work order", async () => {
26
+ const { db, spies } = createMockDb<Transaction>();
27
+ const startedWorkOrder = {
28
+ ...basePendingWorkOrder,
29
+ status: "IN_PROGRESS" as const,
30
+ actualStartDate: new Date(),
31
+ };
32
+
33
+ // work order lookup
34
+ spies.select.mockReturnValueOnce(basePendingWorkOrder);
35
+ // parent production order lookup
36
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
37
+ // sibling work orders (no predecessors)
38
+ spies.select.mockReturnValueOnce([basePendingWorkOrder]);
39
+ // update work order
40
+ spies.update.mockReturnValueOnce(startedWorkOrder);
41
+ // insert execution event
42
+ spies.insert.mockReturnValueOnce(undefined);
43
+ // update parent production order to IN_PROGRESS
44
+ spies.update.mockReturnValueOnce(undefined);
45
+
46
+ const result = await run(db, { id: basePendingWorkOrder.id }, ctx);
47
+
48
+ expect(result.ok).toBe(true);
49
+ if (result.ok) {
50
+ expect(result.value.workOrder.status).toBe("IN_PROGRESS");
51
+ }
52
+ expect(spies.update).toHaveBeenCalled();
53
+ expect(spies.insert).toHaveBeenCalled();
54
+ });
55
+
56
+ it("returns error when the work order does not exist", async () => {
57
+ const { db, spies } = createMockDb<Transaction>();
58
+ spies.select.mockReturnValueOnce(undefined);
59
+
60
+ const result = await run(db, { id: "nonexistent" }, ctx);
61
+
62
+ expect(result.ok).toBe(false);
63
+ if (!result.ok) {
64
+ expect(result.error).toBeInstanceOf(WorkOrderNotFoundError);
65
+ }
66
+ });
67
+
68
+ it("returns error when the work order is not pending", async () => {
69
+ const { db, spies } = createMockDb<Transaction>();
70
+ spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
71
+
72
+ const result = await run(db, { id: baseInProgressWorkOrder.id }, ctx);
73
+
74
+ expect(result.ok).toBe(false);
75
+ if (!result.ok) {
76
+ expect(result.error).toBeInstanceOf(WorkOrderNotStartableError);
77
+ }
78
+ });
79
+
80
+ it("returns error when the parent production order is not executable", async () => {
81
+ const { db, spies } = createMockDb<Transaction>();
82
+
83
+ // work order lookup
84
+ spies.select.mockReturnValueOnce(basePendingWorkOrder);
85
+ // parent production order - DRAFT is not executable
86
+ spies.select.mockReturnValueOnce(baseDraftProductionOrder);
87
+
88
+ const result = await run(db, { id: basePendingWorkOrder.id }, ctx);
89
+
90
+ expect(result.ok).toBe(false);
91
+ if (!result.ok) {
92
+ expect(result.error).toBeInstanceOf(ParentOrderNotExecutableError);
93
+ }
94
+ });
95
+
96
+ it("returns error when required predecessor work is incomplete", async () => {
97
+ const { db, spies } = createMockDb<Transaction>();
98
+ const secondWorkOrder = {
99
+ ...basePendingWorkOrder,
100
+ id: "work-order-second",
101
+ routingOperationSequenceNumber: 20,
102
+ };
103
+
104
+ // work order lookup (second operation)
105
+ spies.select.mockReturnValueOnce(secondWorkOrder);
106
+ // parent production order
107
+ spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
108
+ // sibling work orders - first is still PENDING
109
+ spies.select.mockReturnValueOnce([basePendingWorkOrder, secondWorkOrder]);
110
+
111
+ const result = await run(db, { id: secondWorkOrder.id }, ctx);
112
+
113
+ expect(result.ok).toBe(false);
114
+ if (!result.ok) {
115
+ expect(result.error).toBeInstanceOf(OperationSequenceBlockedError);
116
+ }
117
+ });
118
+ });
@@ -0,0 +1,126 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ WorkOrderNotFoundError,
4
+ WorkOrderNotStartableError,
5
+ ParentOrderNotExecutableError,
6
+ OperationSequenceBlockedError,
7
+ } from "../lib/errors.generated";
8
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
9
+
10
+ const EXECUTABLE_ORDER_STATUSES = ["RELEASED", "IN_PROGRESS"] as const;
11
+
12
+ export interface StartWorkOrderInput {
13
+ id: string;
14
+ }
15
+
16
+ /**
17
+ * Function: startWorkOrder
18
+ *
19
+ * Begins execution on a pending work order. Records the actual start timestamp,
20
+ * moves the work order to IN_PROGRESS, and transitions the parent production
21
+ * order to IN_PROGRESS if it was RELEASED.
22
+ */
23
+ export async function run<CF extends Record<string, unknown>>(
24
+ db: Transaction,
25
+ input: StartWorkOrderInput & CF,
26
+ _ctx: CommandContext,
27
+ ) {
28
+ const { id, ...customFields } = input;
29
+ void customFields;
30
+
31
+ // 1. Fetch work order with lock
32
+ const workOrder = await db
33
+ .selectFrom("WorkOrder")
34
+ .selectAll()
35
+ .where("id", "=", id)
36
+ .forUpdate()
37
+ .executeTakeFirst();
38
+
39
+ if (!workOrder) {
40
+ return err(new WorkOrderNotFoundError(id));
41
+ }
42
+
43
+ // 2. Validate status is PENDING
44
+ if (workOrder.status !== "PENDING") {
45
+ return err(new WorkOrderNotStartableError(id));
46
+ }
47
+
48
+ // 3. Check parent production order is in an execution-capable state
49
+ const parentOrder = await db
50
+ .selectFrom("ProductionOrder")
51
+ .selectAll()
52
+ .where("id", "=", workOrder.productionOrderId)
53
+ .forUpdate()
54
+ .executeTakeFirst();
55
+
56
+ if (
57
+ !parentOrder ||
58
+ !EXECUTABLE_ORDER_STATUSES.includes(
59
+ parentOrder.status as (typeof EXECUTABLE_ORDER_STATUSES)[number],
60
+ )
61
+ ) {
62
+ return err(new ParentOrderNotExecutableError(id));
63
+ }
64
+
65
+ // 4. Check preceding operations are complete (sequence order guard)
66
+ const siblingWorkOrders = await db
67
+ .selectFrom("WorkOrder")
68
+ .selectAll()
69
+ .where("productionOrderId", "=", workOrder.productionOrderId)
70
+ .execute();
71
+
72
+ const hasPendingPredecessor = siblingWorkOrders.some(
73
+ (wo) =>
74
+ wo.routingOperationSequenceNumber < workOrder.routingOperationSequenceNumber &&
75
+ wo.status !== "COMPLETE" &&
76
+ wo.status !== "CANCELLED",
77
+ );
78
+
79
+ if (hasPendingPredecessor) {
80
+ return err(new OperationSequenceBlockedError(id));
81
+ }
82
+
83
+ // 5. Record actual start and set IN_PROGRESS
84
+ const now = new Date();
85
+
86
+ const updatedWorkOrder = await db
87
+ .updateTable("WorkOrder")
88
+ .set({
89
+ status: "IN_PROGRESS",
90
+ actualStartDate: now,
91
+ updatedAt: now,
92
+ })
93
+ .where("id", "=", id)
94
+ .returningAll()
95
+ .executeTakeFirstOrThrow();
96
+
97
+ // 6. Create STARTED execution event
98
+ await db
99
+ .insertInto("WorkOrderExecutionEvent")
100
+ .values({
101
+ workOrderId: id,
102
+ eventType: "STARTED",
103
+ timestamp: now,
104
+ quantity: null,
105
+ timeValue: null,
106
+ scrapValue: null,
107
+ notes: null,
108
+ createdAt: now,
109
+ updatedAt: null,
110
+ })
111
+ .execute();
112
+
113
+ // 7. If parent production order is RELEASED, transition to IN_PROGRESS
114
+ if (parentOrder.status === "RELEASED") {
115
+ await db
116
+ .updateTable("ProductionOrder")
117
+ .set({
118
+ status: "IN_PROGRESS",
119
+ updatedAt: now,
120
+ })
121
+ .where("id", "=", parentOrder.id)
122
+ .execute();
123
+ }
124
+
125
+ return ok({ workOrder: updatedWorkOrder });
126
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./technicallyCompleteProductionOrder";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const technicallyCompleteProductionOrder = defineCommand(permissions.technicallyCompleteProductionOrder, run);