@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,168 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import {
5
+ RoutingItemNotFoundError,
6
+ OperationRequiredError,
7
+ DuplicateOperationSequenceError,
8
+ InvalidStandardTimeError,
9
+ WorkCenterNotFoundError,
10
+ CrossCompanyWorkCenterError,
11
+ } from "../lib/errors.generated";
12
+ import { baseDraftRouting, baseActiveWorkCenter } from "../testing/fixtures";
13
+ import { run } from "./createRouting";
14
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
15
+
16
+ describe("createRouting", () => {
17
+ const ctx: CommandContext = {
18
+ actorId: "test-actor",
19
+ permissions: ["manufacturing:createRouting"],
20
+ };
21
+
22
+ const validOperations = [
23
+ {
24
+ sequenceNumber: 10,
25
+ operationDescription: "Assembly step",
26
+ workCenterId: "work-center-2",
27
+ standardSetupTime: 30,
28
+ standardRunTime: 60,
29
+ operatorInstructions: null,
30
+ },
31
+ {
32
+ sequenceNumber: 20,
33
+ operationDescription: "Quality check",
34
+ workCenterId: "work-center-2",
35
+ standardSetupTime: 10,
36
+ standardRunTime: 15,
37
+ operatorInstructions: null,
38
+ },
39
+ ];
40
+
41
+ const validInput = {
42
+ parentItemId: "item-1",
43
+ companyId: "company-1",
44
+ siteId: null,
45
+ revisionNumber: "R1",
46
+ operations: validOperations,
47
+ };
48
+
49
+ it("creates a draft routing with ordered operations", async () => {
50
+ const { db, spies } = createMockDb<Transaction>();
51
+
52
+ // select: Item lookup
53
+ spies.select.mockReturnValueOnce({ id: "item-1" });
54
+ // select: WorkCenter lookup for op 1
55
+ spies.select.mockReturnValueOnce(baseActiveWorkCenter);
56
+ // select: WorkCenter lookup for op 2
57
+ spies.select.mockReturnValueOnce(baseActiveWorkCenter);
58
+ // insert: Routing
59
+ spies.insert.mockReturnValue(baseDraftRouting);
60
+
61
+ const result = await run(db, validInput, ctx);
62
+
63
+ expect(result.ok).toBe(true);
64
+ if (result.ok) {
65
+ expect(result.value.routing.status).toBe("DRAFT");
66
+ expect(result.value.routing.parentItemId).toBe("item-1");
67
+ }
68
+ expect(spies.insert).toHaveBeenCalled();
69
+ });
70
+
71
+ it("returns error when no operations are provided", async () => {
72
+ const { db, spies } = createMockDb<Transaction>();
73
+
74
+ // select: Item lookup
75
+ spies.select.mockReturnValueOnce({ id: "item-1" });
76
+
77
+ const result = await run(db, { ...validInput, operations: [] }, ctx);
78
+
79
+ expect(result.ok).toBe(false);
80
+ if (!result.ok) {
81
+ expect(result.error).toBeInstanceOf(OperationRequiredError);
82
+ }
83
+ });
84
+
85
+ it("returns error when two operations share the same sequence", async () => {
86
+ const { db, spies } = createMockDb<Transaction>();
87
+
88
+ // select: Item lookup
89
+ spies.select.mockReturnValueOnce({ id: "item-1" });
90
+
91
+ const duplicateOps = [
92
+ { ...validOperations[0], sequenceNumber: 10 },
93
+ { ...validOperations[1], sequenceNumber: 10 },
94
+ ];
95
+
96
+ const result = await run(db, { ...validInput, operations: duplicateOps }, ctx);
97
+
98
+ expect(result.ok).toBe(false);
99
+ if (!result.ok) {
100
+ expect(result.error).toBeInstanceOf(DuplicateOperationSequenceError);
101
+ }
102
+ });
103
+
104
+ it("returns error when a standard time is negative", async () => {
105
+ const { db, spies } = createMockDb<Transaction>();
106
+
107
+ // select: Item lookup
108
+ spies.select.mockReturnValueOnce({ id: "item-1" });
109
+
110
+ const badOps = [{ ...validOperations[0], standardSetupTime: -5 }];
111
+
112
+ const result = await run(db, { ...validInput, operations: badOps }, ctx);
113
+
114
+ expect(result.ok).toBe(false);
115
+ if (!result.ok) {
116
+ expect(result.error).toBeInstanceOf(InvalidStandardTimeError);
117
+ }
118
+ });
119
+
120
+ it("returns error when a referenced work center does not exist", async () => {
121
+ const { db, spies } = createMockDb<Transaction>();
122
+
123
+ // select: Item lookup
124
+ spies.select.mockReturnValueOnce({ id: "item-1" });
125
+ // select: WorkCenter lookup - not found
126
+ spies.select.mockReturnValueOnce(undefined);
127
+
128
+ const result = await run(db, validInput, ctx);
129
+
130
+ expect(result.ok).toBe(false);
131
+ if (!result.ok) {
132
+ expect(result.error).toBeInstanceOf(WorkCenterNotFoundError);
133
+ }
134
+ });
135
+
136
+ it("returns error when a work center is outside the routing company", async () => {
137
+ const { db, spies } = createMockDb<Transaction>();
138
+
139
+ // select: Item lookup
140
+ spies.select.mockReturnValueOnce({ id: "item-1" });
141
+ // select: WorkCenter lookup - different company
142
+ spies.select.mockReturnValueOnce({
143
+ ...baseActiveWorkCenter,
144
+ companyId: "other-company",
145
+ });
146
+
147
+ const result = await run(db, validInput, ctx);
148
+
149
+ expect(result.ok).toBe(false);
150
+ if (!result.ok) {
151
+ expect(result.error).toBeInstanceOf(CrossCompanyWorkCenterError);
152
+ }
153
+ });
154
+
155
+ it("returns error when parent item does not exist", async () => {
156
+ const { db, spies } = createMockDb<Transaction>();
157
+
158
+ // select: Item lookup - not found
159
+ spies.select.mockReturnValueOnce(undefined);
160
+
161
+ const result = await run(db, validInput, ctx);
162
+
163
+ expect(result.ok).toBe(false);
164
+ if (!result.ok) {
165
+ expect(result.error).toBeInstanceOf(RoutingItemNotFoundError);
166
+ }
167
+ });
168
+ });
@@ -0,0 +1,128 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ RoutingItemNotFoundError,
4
+ OperationRequiredError,
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 CreateRoutingOperationInput {
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 CreateRoutingInput {
22
+ parentItemId: string;
23
+ companyId: string;
24
+ siteId?: string | null;
25
+ revisionNumber?: string | null;
26
+ operations: CreateRoutingOperationInput[];
27
+ }
28
+
29
+ /**
30
+ * Function: createRouting
31
+ *
32
+ * Creates a new routing in DRAFT status with ordered operations.
33
+ * Validates parent item existence, operation sequence uniqueness,
34
+ * standard times, and work center references.
35
+ */
36
+ export async function run<CF extends Record<string, unknown>>(
37
+ db: Transaction,
38
+ input: CreateRoutingInput & CF,
39
+ _ctx: CommandContext,
40
+ ) {
41
+ const { parentItemId, companyId, siteId, revisionNumber, operations, ...customFields } = input;
42
+
43
+ // 1. Validate parent item exists
44
+ const parentItem = await db
45
+ .selectFrom("Item")
46
+ .selectAll()
47
+ .where("id", "=", parentItemId)
48
+ .executeTakeFirst();
49
+
50
+ if (!parentItem) {
51
+ return err(new RoutingItemNotFoundError(parentItemId));
52
+ }
53
+
54
+ // 2. Validate at least one operation
55
+ if (!operations || operations.length === 0) {
56
+ return err(new OperationRequiredError(parentItemId));
57
+ }
58
+
59
+ // 3. Validate unique sequence numbers
60
+ const seqNumbers = operations.map((op) => op.sequenceNumber);
61
+ const uniqueSeqs = new Set(seqNumbers);
62
+ if (uniqueSeqs.size !== seqNumbers.length) {
63
+ return err(new DuplicateOperationSequenceError(parentItemId));
64
+ }
65
+
66
+ // 4. Validate standard times >= 0
67
+ for (const op of operations) {
68
+ if (op.standardSetupTime < 0 || op.standardRunTime < 0) {
69
+ return err(new InvalidStandardTimeError(parentItemId));
70
+ }
71
+ }
72
+
73
+ // 5. Validate work centers exist and belong to same company
74
+ for (const op of operations) {
75
+ const workCenter = await db
76
+ .selectFrom("WorkCenter")
77
+ .selectAll()
78
+ .where("id", "=", op.workCenterId)
79
+ .executeTakeFirst();
80
+
81
+ if (!workCenter) {
82
+ return err(new WorkCenterNotFoundError(op.workCenterId));
83
+ }
84
+
85
+ if (workCenter.companyId !== companyId) {
86
+ return err(new CrossCompanyWorkCenterError(op.workCenterId));
87
+ }
88
+ }
89
+
90
+ // 6. Create routing in DRAFT status
91
+ const routing = await db
92
+ .insertInto("Routing")
93
+ .values({
94
+ ...(customFields as Record<string, unknown>),
95
+ parentItemId,
96
+ companyId,
97
+ siteId: siteId ?? null,
98
+ revisionNumber: revisionNumber ?? null,
99
+ status: "DRAFT",
100
+ createdAt: new Date(),
101
+ updatedAt: null,
102
+ })
103
+ .returningAll()
104
+ .executeTakeFirst();
105
+
106
+ // 7. Create routing operations
107
+ const createdOperations = [];
108
+ for (const op of operations) {
109
+ const operation = await db
110
+ .insertInto("RoutingOperation")
111
+ .values({
112
+ routingId: routing!.id,
113
+ sequenceNumber: op.sequenceNumber,
114
+ operationDescription: op.operationDescription ?? null,
115
+ workCenterId: op.workCenterId,
116
+ standardSetupTime: op.standardSetupTime,
117
+ standardRunTime: op.standardRunTime,
118
+ operatorInstructions: op.operatorInstructions ?? null,
119
+ createdAt: new Date(),
120
+ updatedAt: null,
121
+ })
122
+ .returningAll()
123
+ .executeTakeFirst();
124
+ createdOperations.push(operation!);
125
+ }
126
+
127
+ return ok({ routing: routing!, operations: createdOperations });
128
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./createWorkCenter";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const createWorkCenter = defineCommand(permissions.createWorkCenter, run);
@@ -0,0 +1,148 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import {
5
+ InvalidScopeError,
6
+ DuplicateWorkCenterCodeError,
7
+ InvalidCapacityError,
8
+ InvalidRateError,
9
+ InvalidOverheadMethodError,
10
+ OverheadCurrencyRequiredError,
11
+ } from "../lib/errors.generated";
12
+ import { baseDraftWorkCenter } from "../testing/fixtures";
13
+ import { run } from "./createWorkCenter";
14
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
15
+
16
+ describe("createWorkCenter", () => {
17
+ const ctx: CommandContext = {
18
+ actorId: "test-actor",
19
+ permissions: ["manufacturing:createWorkCenter"],
20
+ };
21
+
22
+ const validInput = {
23
+ code: "WC-NEW",
24
+ companyId: "company-1",
25
+ siteId: "site-1",
26
+ capacityAssumptions: 100,
27
+ laborRate: 25.0,
28
+ machineRate: 50.0,
29
+ calendarReference: "cal-standard",
30
+ };
31
+
32
+ it("creates a draft work center with positive capacity", async () => {
33
+ const { db, spies } = createMockDb<Transaction>();
34
+ const createdWorkCenter = {
35
+ ...baseDraftWorkCenter,
36
+ id: "new-wc-id",
37
+ code: "WC-NEW",
38
+ };
39
+
40
+ spies.select.mockReturnValueOnce(undefined);
41
+ spies.insert.mockReturnValue(createdWorkCenter);
42
+
43
+ const result = await run(db, validInput, ctx);
44
+
45
+ expect(result.ok).toBe(true);
46
+ if (result.ok) {
47
+ expect(result.value.workCenter.status).toBe("DRAFT");
48
+ expect(result.value.workCenter.code).toBe("WC-NEW");
49
+ }
50
+ expect(spies.insert).toHaveBeenCalled();
51
+ });
52
+
53
+ it("returns error when scope is missing", async () => {
54
+ const { db } = createMockDb<Transaction>();
55
+
56
+ const result = await run(db, { ...validInput, companyId: "" }, ctx);
57
+
58
+ expect(result.ok).toBe(false);
59
+ if (!result.ok) {
60
+ expect(result.error).toBeInstanceOf(InvalidScopeError);
61
+ }
62
+ });
63
+
64
+ it("creates a draft work center without siteId when not required", async () => {
65
+ const { db, spies } = createMockDb<Transaction>();
66
+ const createdWorkCenter = {
67
+ ...baseDraftWorkCenter,
68
+ id: "new-wc-id",
69
+ code: "WC-NEW",
70
+ siteId: null,
71
+ };
72
+
73
+ spies.select.mockReturnValueOnce(undefined);
74
+ spies.insert.mockReturnValue(createdWorkCenter);
75
+
76
+ const result = await run(db, { ...validInput, siteId: null }, ctx);
77
+
78
+ expect(result.ok).toBe(true);
79
+ if (result.ok) {
80
+ expect(result.value.workCenter.status).toBe("DRAFT");
81
+ }
82
+ expect(spies.insert).toHaveBeenCalled();
83
+ });
84
+
85
+ it("returns error when the code already exists in scope", async () => {
86
+ const { db, spies } = createMockDb<Transaction>();
87
+ spies.select.mockReturnValueOnce(baseDraftWorkCenter);
88
+
89
+ const result = await run(db, validInput, ctx);
90
+
91
+ expect(result.ok).toBe(false);
92
+ if (!result.ok) {
93
+ expect(result.error).toBeInstanceOf(DuplicateWorkCenterCodeError);
94
+ }
95
+ });
96
+
97
+ it("returns error when capacity is not positive", async () => {
98
+ const { db } = createMockDb<Transaction>();
99
+
100
+ const result = await run(db, { ...validInput, capacityAssumptions: 0 }, ctx);
101
+
102
+ expect(result.ok).toBe(false);
103
+ if (!result.ok) {
104
+ expect(result.error).toBeInstanceOf(InvalidCapacityError);
105
+ }
106
+ });
107
+
108
+ it("returns error when a rate is negative", async () => {
109
+ const { db } = createMockDb<Transaction>();
110
+
111
+ const result = await run(db, { ...validInput, laborRate: -1 }, ctx);
112
+
113
+ expect(result.ok).toBe(false);
114
+ if (!result.ok) {
115
+ expect(result.error).toBeInstanceOf(InvalidRateError);
116
+ }
117
+ });
118
+
119
+ it("returns error when overhead method is invalid", async () => {
120
+ const { db } = createMockDb<Transaction>();
121
+
122
+ const result = await run(db, { ...validInput, overheadAbsorptionMethod: "UNSUPPORTED" }, ctx);
123
+
124
+ expect(result.ok).toBe(false);
125
+ if (!result.ok) {
126
+ expect(result.error).toBeInstanceOf(InvalidOverheadMethodError);
127
+ }
128
+ });
129
+
130
+ it("returns error when fixed-amount overhead omits currency", async () => {
131
+ const { db } = createMockDb<Transaction>();
132
+
133
+ const result = await run(
134
+ db,
135
+ {
136
+ ...validInput,
137
+ overheadAbsorptionMethod: "FIXED_AMOUNT_PER_GOOD_UNIT",
138
+ overheadAbsorptionCurrency: null,
139
+ },
140
+ ctx,
141
+ );
142
+
143
+ expect(result.ok).toBe(false);
144
+ if (!result.ok) {
145
+ expect(result.error).toBeInstanceOf(OverheadCurrencyRequiredError);
146
+ }
147
+ });
148
+ });
@@ -0,0 +1,131 @@
1
+ import type { Transaction } from "../generated/kysely-tailordb";
2
+ import {
3
+ InvalidScopeError,
4
+ DuplicateWorkCenterCodeError,
5
+ InvalidCapacityError,
6
+ InvalidRateError,
7
+ InvalidOverheadMethodError,
8
+ OverheadCurrencyRequiredError,
9
+ } from "../lib/errors.generated";
10
+ import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
11
+
12
+ const VALID_OVERHEAD_METHODS = [
13
+ "PERCENT_OF_LABOR_COST",
14
+ "PERCENT_OF_MACHINE_COST",
15
+ "FIXED_AMOUNT_PER_GOOD_UNIT",
16
+ ] as const;
17
+
18
+ export interface CreateWorkCenterInput {
19
+ code: string;
20
+ companyId: string;
21
+ siteId?: string | null;
22
+ capacityAssumptions: number;
23
+ laborRate?: number | null;
24
+ machineRate?: number | null;
25
+ calendarReference?: string | null;
26
+ overheadAbsorptionMethod?: string | null;
27
+ overheadAbsorptionCurrency?: string | null;
28
+ }
29
+
30
+ /**
31
+ * Function: createWorkCenter
32
+ *
33
+ * Creates a new work center in DRAFT status with scope, capacity assumptions,
34
+ * rate context, and optional overhead-absorption policy.
35
+ */
36
+ export async function run<CF extends Record<string, unknown>>(
37
+ db: Transaction,
38
+ input: CreateWorkCenterInput & CF,
39
+ _ctx: CommandContext,
40
+ ) {
41
+ const {
42
+ code,
43
+ companyId,
44
+ siteId,
45
+ capacityAssumptions,
46
+ laborRate,
47
+ machineRate,
48
+ calendarReference,
49
+ overheadAbsorptionMethod,
50
+ overheadAbsorptionCurrency,
51
+ ...customFields
52
+ } = input;
53
+
54
+ // 1. Validate scope — company is always required
55
+ if (!companyId) {
56
+ return err(new InvalidScopeError(code));
57
+ }
58
+
59
+ // 2. Validate capacity > 0
60
+ if (capacityAssumptions <= 0) {
61
+ return err(new InvalidCapacityError(code));
62
+ }
63
+
64
+ // 3. Validate rates >= 0
65
+ if (laborRate != null && laborRate < 0) {
66
+ return err(new InvalidRateError(code));
67
+ }
68
+ if (machineRate != null && machineRate < 0) {
69
+ return err(new InvalidRateError(code));
70
+ }
71
+
72
+ // 4. Validate overhead method
73
+ if (
74
+ overheadAbsorptionMethod != null &&
75
+ !VALID_OVERHEAD_METHODS.includes(
76
+ overheadAbsorptionMethod as (typeof VALID_OVERHEAD_METHODS)[number],
77
+ )
78
+ ) {
79
+ return err(new InvalidOverheadMethodError(code));
80
+ }
81
+
82
+ // 5. Overhead currency required for FIXED_AMOUNT_PER_GOOD_UNIT
83
+ if (overheadAbsorptionMethod === "FIXED_AMOUNT_PER_GOOD_UNIT" && !overheadAbsorptionCurrency) {
84
+ return err(new OverheadCurrencyRequiredError(code));
85
+ }
86
+
87
+ // 6. Check code uniqueness within company+site scope
88
+ let existingQuery = db
89
+ .selectFrom("WorkCenter")
90
+ .selectAll()
91
+ .where("code", "=", code)
92
+ .where("companyId", "=", companyId);
93
+
94
+ if (siteId) {
95
+ existingQuery = existingQuery.where("siteId", "=", siteId);
96
+ }
97
+
98
+ const existing = await existingQuery.forUpdate().executeTakeFirst();
99
+
100
+ if (existing) {
101
+ return err(new DuplicateWorkCenterCodeError(code));
102
+ }
103
+
104
+ // 7. Create work center in DRAFT status
105
+ const workCenter = await db
106
+ .insertInto("WorkCenter")
107
+ .values({
108
+ ...(customFields as Record<string, unknown>),
109
+ code,
110
+ companyId,
111
+ siteId: siteId ?? null,
112
+ capacityAssumptions,
113
+ laborRate: laborRate ?? null,
114
+ machineRate: machineRate ?? null,
115
+ calendarReference: calendarReference ?? null,
116
+ overheadAbsorptionMethod:
117
+ (overheadAbsorptionMethod as
118
+ | "PERCENT_OF_LABOR_COST"
119
+ | "PERCENT_OF_MACHINE_COST"
120
+ | "FIXED_AMOUNT_PER_GOOD_UNIT"
121
+ | null) ?? null,
122
+ overheadAbsorptionCurrency: overheadAbsorptionCurrency ?? null,
123
+ status: "DRAFT",
124
+ createdAt: new Date(),
125
+ updatedAt: null,
126
+ })
127
+ .returningAll()
128
+ .executeTakeFirst();
129
+
130
+ return ok({ workCenter: workCenter! });
131
+ }
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./deactivateBillOfMaterial";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const deactivateBillOfMaterial = defineCommand(permissions.deactivateBillOfMaterial, run);
@@ -0,0 +1,103 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createMockDb } from "../../../testing/index";
3
+ import type { Transaction } from "../generated/kysely-tailordb";
4
+ import {
5
+ BomNotFoundError,
6
+ BomNotDeactivatableError,
7
+ ReplacementRequiredError,
8
+ } from "../lib/errors.generated";
9
+ import { baseDraftBom, baseActiveBom } from "../testing/fixtures";
10
+ import { run } from "./deactivateBillOfMaterial";
11
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
12
+
13
+ describe("deactivateBillOfMaterial", () => {
14
+ const ctx: CommandContext = {
15
+ actorId: "test-actor",
16
+ permissions: ["manufacturing:deactivateBillOfMaterial"],
17
+ };
18
+
19
+ it("deactivates an active BOM", async () => {
20
+ const { db, spies } = createMockDb<Transaction>();
21
+ const deactivatedBom = { ...baseActiveBom, status: "INACTIVE" as const };
22
+
23
+ // BOM exists and is ACTIVE, with defaultSelection = true
24
+ spies.select.mockReturnValueOnce(baseActiveBom);
25
+ // replacement BOM exists
26
+ spies.select.mockReturnValueOnce({ ...baseActiveBom, id: "bom-replacement" });
27
+ // update to INACTIVE
28
+ spies.update.mockReturnValue(deactivatedBom);
29
+
30
+ const result = await run(db, { id: "bom-2" }, ctx);
31
+
32
+ expect(result.ok).toBe(true);
33
+ if (result.ok) {
34
+ expect(result.value.billOfMaterial.status).toBe("INACTIVE");
35
+ }
36
+ expect(spies.update).toHaveBeenCalled();
37
+ });
38
+
39
+ it("returns error when the BOM does not exist", async () => {
40
+ const { db, spies } = createMockDb<Transaction>();
41
+
42
+ spies.select.mockReturnValueOnce(undefined);
43
+
44
+ const result = await run(db, { id: "bom-nonexistent" }, ctx);
45
+
46
+ expect(result.ok).toBe(false);
47
+ if (!result.ok) {
48
+ expect(result.error).toBeInstanceOf(BomNotFoundError);
49
+ }
50
+ });
51
+
52
+ it("returns error when the BOM is not active", async () => {
53
+ const { db, spies } = createMockDb<Transaction>();
54
+
55
+ spies.select.mockReturnValueOnce(baseDraftBom);
56
+
57
+ const result = await run(db, { id: "bom-1" }, ctx);
58
+
59
+ expect(result.ok).toBe(false);
60
+ if (!result.ok) {
61
+ expect(result.error).toBeInstanceOf(BomNotDeactivatableError);
62
+ }
63
+ });
64
+
65
+ it("returns error when replacement policy blocks deactivation", async () => {
66
+ const { db, spies } = createMockDb<Transaction>();
67
+
68
+ // BOM exists and is ACTIVE with default selection
69
+ spies.select.mockReturnValueOnce(baseActiveBom);
70
+ // no replacement BOM
71
+ spies.select.mockReturnValueOnce(undefined);
72
+
73
+ const result = await run(db, { id: "bom-2" }, ctx);
74
+
75
+ expect(result.ok).toBe(false);
76
+ if (!result.ok) {
77
+ expect(result.error).toBeInstanceOf(ReplacementRequiredError);
78
+ }
79
+ });
80
+
81
+ it("preserves released production-order snapshots after deactivation", async () => {
82
+ const { db, spies } = createMockDb<Transaction>();
83
+ const deactivatedBom = { ...baseActiveBom, status: "INACTIVE" as const };
84
+
85
+ // BOM exists and is ACTIVE with default selection
86
+ spies.select.mockReturnValueOnce(baseActiveBom);
87
+ // replacement BOM exists
88
+ spies.select.mockReturnValueOnce({ ...baseActiveBom, id: "bom-replacement" });
89
+ // update to INACTIVE
90
+ spies.update.mockReturnValue(deactivatedBom);
91
+
92
+ const result = await run(db, { id: "bom-2" }, ctx);
93
+
94
+ // Verify that the command only updates the BOM status —
95
+ // it does not touch ProductionOrderBomSnapshot at all
96
+ expect(result.ok).toBe(true);
97
+ if (result.ok) {
98
+ expect(result.value.billOfMaterial.status).toBe("INACTIVE");
99
+ }
100
+ // The update should only have been called once (for BOM status)
101
+ expect(spies.update).toHaveBeenCalledTimes(1);
102
+ });
103
+ });