@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
@@ -2,16 +2,16 @@ import path from "node:path";
2
2
  import fs from "node:fs";
3
3
  import fg from "fast-glob";
4
4
  import { parseTestCasesFromDoc, parseItDescriptionsFromTest } from "../parse-doc-test-cases";
5
- import { testCaseCategories } from "./discovery";
5
+ import { moduleTestCaseCategories, appTestCaseCategories } from "./discovery";
6
6
  import type { CheckError } from "./sync-check-source";
7
7
 
8
8
  function toCamelCase(pascalCase: string): string {
9
9
  return pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1);
10
10
  }
11
11
 
12
- export async function runTestCaseSyncCheck(root: string, cwd: string): Promise<CheckError[]> {
12
+ export async function runModuleTestCaseSyncCheck(root: string, cwd: string): Promise<CheckError[]> {
13
13
  const errors: CheckError[] = [];
14
- const categories = testCaseCategories(root);
14
+ const categories = moduleTestCaseCategories(root);
15
15
 
16
16
  for (const category of categories) {
17
17
  const docPaths = await fg(category.docPattern, { cwd });
@@ -67,3 +67,63 @@ export async function runTestCaseSyncCheck(root: string, cwd: string): Promise<C
67
67
 
68
68
  return errors;
69
69
  }
70
+
71
+ function resolveStoryTestPath(docPath: string, testDir: string): string | null {
72
+ const regex = /^(.+)\/docs\/business-flow\/([^/]+)\/story\/([^/]+)\.md$/;
73
+ const match = regex.exec(docPath);
74
+ if (!match) return null;
75
+ const [, appPath, flow, name] = match;
76
+ return `${appPath}/${testDir}/${flow}/${name}.test.ts`;
77
+ }
78
+
79
+ export async function runAppTestCaseSyncCheck(appRoot: string, cwd: string): Promise<CheckError[]> {
80
+ const errors: CheckError[] = [];
81
+ const config = appTestCaseCategories(appRoot);
82
+ const docPaths = await fg(config.docPattern, { cwd });
83
+
84
+ for (const docPath of docPaths) {
85
+ const docFullPath = path.join(cwd, docPath);
86
+ const docContent = fs.readFileSync(docFullPath, "utf-8");
87
+ const docTestCases = parseTestCasesFromDoc(docContent);
88
+
89
+ if (docTestCases.length === 0) continue;
90
+
91
+ const testPath = resolveStoryTestPath(docPath, config.testDir);
92
+ if (!testPath) continue;
93
+
94
+ const testFullPath = path.join(cwd, testPath);
95
+ if (!fs.existsSync(testFullPath)) continue;
96
+
97
+ const testContent = fs.readFileSync(testFullPath, "utf-8");
98
+ const itDescriptions = parseItDescriptionsFromTest(testContent);
99
+
100
+ const docSet = new Set(docTestCases);
101
+ const testSet = new Set(itDescriptions);
102
+
103
+ for (const docCase of docSet) {
104
+ if (!testSet.has(docCase)) {
105
+ errors.push({
106
+ type: "missing-test-case",
107
+ category: config.name,
108
+ docPath,
109
+ sourcePath: testPath,
110
+ expectedBasename: docCase,
111
+ });
112
+ }
113
+ }
114
+
115
+ for (const testCase of testSet) {
116
+ if (!docSet.has(testCase)) {
117
+ errors.push({
118
+ type: "extra-test-case",
119
+ category: config.name,
120
+ docPath,
121
+ sourcePath: testPath,
122
+ expectedBasename: testCase,
123
+ });
124
+ }
125
+ }
126
+ }
127
+
128
+ return errors;
129
+ }
@@ -1,6 +1,6 @@
1
1
  import chalk from "chalk";
2
2
  import { runSourceSyncCheck, type SyncCheckResult, type CheckError } from "./lib/sync-check-source";
3
- import { runTestCaseSyncCheck } from "./lib/sync-check-tests";
3
+ import { runModuleTestCaseSyncCheck, runAppTestCaseSyncCheck } from "./lib/sync-check-tests";
4
4
 
5
5
  export type { SyncCheckResult, CheckError } from "./lib/sync-check-source";
6
6
 
@@ -10,10 +10,14 @@ export async function runSyncCheck(
10
10
  ): Promise<SyncCheckResult> {
11
11
  const sourceResult = await runSourceSyncCheck(config, cwd);
12
12
  if (config.modulesRoot) {
13
- const testCaseErrors = await runTestCaseSyncCheck(config.modulesRoot, cwd);
13
+ const testCaseErrors = await runModuleTestCaseSyncCheck(config.modulesRoot, cwd);
14
14
  sourceResult.errors.push(...testCaseErrors);
15
- sourceResult.exitCode = sourceResult.errors.length > 0 ? 1 : 0;
16
15
  }
16
+ if (config.appRoot) {
17
+ const storyErrors = await runAppTestCaseSyncCheck(config.appRoot, cwd);
18
+ sourceResult.errors.push(...storyErrors);
19
+ }
20
+ sourceResult.exitCode = sourceResult.errors.length > 0 ? 1 : 0;
17
21
  return sourceResult;
18
22
  }
19
23
 
@@ -1,29 +1,33 @@
1
- import fs from "node:fs";
1
+ import fs, { globSync } from "node:fs";
2
2
  import path from "node:path";
3
+ import { parseTestCasesFromDoc } from "../commands/parse-doc-test-cases";
4
+ import { APP_PATHS } from "../commands/lib/paths";
3
5
  import { parseResolverDoc } from "./parse-resolver-doc";
4
- import { generateResolverStub, generateResolverTestStub } from "./generate-stubs";
6
+ import {
7
+ generateResolverStub,
8
+ generateResolverTestStub,
9
+ generateStoryTestStub,
10
+ } from "./generate-stubs";
5
11
  import { scaffoldAppBoilerplate } from "./scaffold";
6
12
 
7
13
  export function runGenerateAppCode(appPath: string): number {
8
14
  const appName = path.basename(appPath);
9
15
  scaffoldAppBoilerplate(appPath, appName);
10
16
 
11
- const docsDir = path.join(appPath, "docs", "resolver");
12
- const resolverDir = path.join(appPath, "backend", "src", "resolvers");
17
+ generateResolverStubs(appPath, appName);
18
+ generateStoryTestStubs(appPath, appName);
13
19
 
14
- if (!fs.existsSync(docsDir)) {
15
- console.log(`No docs/resolver/ directory found — skipping resolver generation`);
16
- console.log(`Generated boilerplate for ${appName}`);
17
- return 0;
18
- }
20
+ return 0;
21
+ }
22
+
23
+ function generateResolverStubs(appPath: string, appName: string): void {
24
+ const docsDir = path.join(appPath, "docs", "resolver");
25
+ if (!fs.existsSync(docsDir)) return;
19
26
 
20
27
  const mdFiles = fs.readdirSync(docsDir).filter((f) => f.endsWith(".md"));
21
- if (mdFiles.length === 0) {
22
- console.log(`No resolver docs found — skipping resolver generation`);
23
- console.log(`Generated boilerplate for ${appName}`);
24
- return 0;
25
- }
28
+ if (mdFiles.length === 0) return;
26
29
 
30
+ const resolverDir = path.join(appPath, "backend", "src", "resolvers");
27
31
  fs.mkdirSync(resolverDir, { recursive: true });
28
32
  let generated = 0;
29
33
 
@@ -46,6 +50,37 @@ export function runGenerateAppCode(appPath: string): number {
46
50
  }
47
51
  }
48
52
 
49
- console.log(`Scaffolded ${generated} resolver(s) for ${appName}`);
50
- return 0;
53
+ if (generated > 0) {
54
+ console.log(`Scaffolded ${generated} resolver(s) for ${appName}`);
55
+ }
56
+ }
57
+
58
+ function generateStoryTestStubs(appPath: string, appName: string): void {
59
+ const storyPattern = path.join(appPath, "docs/business-flow/*/story/*.md");
60
+ const storyDocs = globSync(storyPattern);
61
+ let generated = 0;
62
+
63
+ for (const storyDocPath of storyDocs) {
64
+ const content = fs.readFileSync(storyDocPath, "utf-8");
65
+ const testCases = parseTestCasesFromDoc(content);
66
+ if (testCases.length === 0) continue;
67
+
68
+ const regex = /\/docs\/business-flow\/([^/]+)\/story\/([^/]+)\.md$/;
69
+ const match = regex.exec(storyDocPath);
70
+ if (!match) continue;
71
+ const [, flow, storyName] = match;
72
+
73
+ const testDir = path.join(appPath, APP_PATHS.tests.stories, flow);
74
+ const testFile = path.join(testDir, `${storyName}.test.ts`);
75
+ if (fs.existsSync(testFile)) continue;
76
+
77
+ fs.mkdirSync(testDir, { recursive: true });
78
+ fs.writeFileSync(testFile, generateStoryTestStub(storyName, testCases));
79
+ console.log(` scaffolded ${path.relative(appPath, testFile)}`);
80
+ generated++;
81
+ }
82
+
83
+ if (generated > 0) {
84
+ console.log(`Scaffolded ${generated} story test(s) for ${appName}`);
85
+ }
51
86
  }
@@ -28,4 +28,8 @@ export function generateResolverTestStub(resolverName: string): string {
28
28
  return renderStub("resolverTest", resolverName);
29
29
  }
30
30
 
31
+ export function generateStoryTestStub(name: string, testCases: string[]): string {
32
+ return renderStub("storyTest", name, testCases);
33
+ }
34
+
31
35
  export { renderStub, type StubType } from "./stub-templates";
@@ -52,4 +52,15 @@ describe("renderStub", () => {
52
52
  expect(result).toContain('import("./createItem")');
53
53
  expect(result).toContain("resolver.default");
54
54
  });
55
+
56
+ it("storyTest: generates integration test with graphql client and test cases", () => {
57
+ const result = renderStub("storyTest", "admin--create-item", [
58
+ "creates item successfully",
59
+ "rejects duplicate SKU",
60
+ ]);
61
+ expect(result).toContain('describe("admin--create-item"');
62
+ expect(result).toContain("createGraphQLClient");
63
+ expect(result).toContain('it("creates item successfully"');
64
+ expect(result).toContain('it("rejects duplicate SKU"');
65
+ });
55
66
  });
@@ -133,6 +133,22 @@ describe("${name}", () => {
133
133
  expect(resolver.default.name).toBe("${name}");
134
134
  });
135
135
  });
136
+ `;
137
+ },
138
+ },
139
+ storyTest: {
140
+ render: (name: string, testCases: string[]) => {
141
+ const its = testCases
142
+ .map((tc) => ` it("${tc}", async () => {\n // TODO: implement\n });`)
143
+ .join("\n\n");
144
+ return `import { describe, expect, inject, it } from "vitest";
145
+ import { createGraphQLClient } from "@/tests/utils/graphql-client";
146
+
147
+ describe("${name}", () => {
148
+ const graphQLClient = createGraphQLClient(inject("url"), inject("token"));
149
+
150
+ ${its}
151
+ });
136
152
  `;
137
153
  },
138
154
  },
@@ -140,6 +156,11 @@ describe("${name}", () => {
140
156
 
141
157
  export type StubType = keyof typeof templates;
142
158
 
143
- export function renderStub(type: StubType, name: string): string {
159
+ export function renderStub(type: "storyTest", name: string, testCases: string[]): string;
160
+ export function renderStub(type: Exclude<StubType, "storyTest">, name: string): string;
161
+ export function renderStub(type: StubType, name: string, testCases?: string[]): string {
162
+ if (type === "storyTest") {
163
+ return templates.storyTest.render(name, testCases!);
164
+ }
144
165
  return templates[type].render(name);
145
166
  }
@@ -39,7 +39,7 @@ flowchart TD
39
39
  - **Shrinkage / theft**: Periodic checks or count results show missing inventory with no corresponding shipment. A negative adjustment with reason code "Shrinkage" records the loss.
40
40
  - **Counting error correction**: A cycle count reveals more units on the shelf than the system shows. A positive adjustment with reason code "Counting Error" increases the on-hand balance.
41
41
  - **Receiving error correction**: Goods receipt recorded the wrong quantity. A positive or negative adjustment with reason code "Receiving Error" corrects the discrepancy.
42
- - **Production variance**: Actual yield differs from the expected output of a manufacturing process. An adjustment with reason code "Production Variance" reconciles the difference.
42
+ - **Production variance fallback**: Actual yield differs from expected output, but the site is not using the manufacturing module or the discrepancy is discovered with no resolvable production-order or work-order source document. In that limited case, a direct adjustment with reason code "Production Variance" reconciles the difference.
43
43
  - **Threshold-based approval**: An adjustment exceeding a configured quantity or value limit is routed to a supervisor for approval before confirmation.
44
44
  - **Bulk post-count adjustment**: After a full physical inventory count, the system generates one adjustment record per variance line, allowing batch review, reason-code assignment, and confirmation.
45
45
 
@@ -56,6 +56,7 @@ flowchart TD
56
56
  - Adjustments originating from an inventory count link back to the count session for traceability
57
57
  - Confirmed adjustments update inventory valuation consistent with the organization's costing method
58
58
  - Adjustments are immutable after confirmation; corrections require a new, offsetting adjustment
59
+ - A direct adjustment with reason code `Production Variance` must be rejected or rerouted when a resolvable manufacturing `productionOrderReference` or `workOrderReference` exists; those cases must enter inventory through `ManufacturingScrapHandoff`
59
60
 
60
61
  ## Reference Links
61
62
 
@@ -2,10 +2,12 @@
2
2
 
3
3
  ## Overview
4
4
 
5
- Scrap management handles the controlled removal of damaged, expired, defective, or obsolete stock from usable inventory. Scrap is modeled as an InventoryAdjustment with `adjustmentType = SCRAP`. When confirmed, the adjustment moves stock from the source storage location to a virtual "scrap" location, effectively removing it from available stock and reducing inventory valuation.
5
+ Scrap management handles the controlled removal of damaged, expired, defective, or obsolete stock from usable inventory. Scrap is modeled as an `InventoryAdjustment` with `adjustmentType = SCRAP`. When confirmed, the adjustment moves stock from the source storage location to a virtual "scrap" location, effectively removing it from available stock and reducing inventory valuation.
6
6
 
7
7
  Organizations may enforce approval workflows for scrap adjustments that exceed a defined value threshold, ensuring proper oversight of material losses. Scrap adjustments follow the same approval workflow as correction adjustments (DRAFT → PENDING_APPROVAL → CONFIRMED). Once confirmed, the adjustment is immutable — if scrap was created in error, a new CORRECTION adjustment with a positive quantityDelta can restore the stock.
8
8
 
9
+ Inventory accepts scrap input both from direct inventory users and from upstream manufacturing execution. Manufacturing-originated scrap enters inventory through the `ManufacturingScrapHandoff` contract defined in manufacturing `work-order-execution.md`, after which inventory creates or updates the `InventoryAdjustment` that owns stock and valuation effects.
10
+
9
11
  ## Business Purpose
10
12
 
11
13
  - Remove physically damaged, expired, or quality-rejected stock from available inventory so it is not accidentally picked, sold, or consumed
@@ -38,9 +40,42 @@ flowchart TD
38
40
  - **Quality failure scrap**: Items that fail incoming or in-process quality inspection are immediately scrapped, with the quality rejection linked as the scrap reason
39
41
  - **Obsolescence write-off**: Slow-moving or discontinued items are scrapped as part of a periodic inventory review to reflect their true recoverable value
40
42
  - **High-value approval scrap**: A scrap adjustment exceeds the organization's value threshold, triggering a manager approval step before the stock movement is executed
43
+ - **Manufacturing-originated scrap**: A work order reports scrap and sends `ManufacturingScrapHandoff`; inventory resolves the source location, creates a SCRAP adjustment, and runs the same approval policy as any other scrap event
41
44
  - **Scrap error correction**: A scrap adjustment is found to have been created in error; a new CORRECTION adjustment with positive quantityDelta restores the stock to the source location
42
45
  - **Lot/serial-tracked scrap**: Specific lots or serial numbers are scrapped, maintaining full traceability from receipt through disposal. Serial numbers transition to SCRAPPED state on confirmation.
43
46
 
47
+ Inventory accepts `ManufacturingScrapHandoff` from manufacturing work-order execution with the following minimum fields:
48
+
49
+ - `productionOrderReference`
50
+ - `workOrderReference`
51
+ - `itemReference`
52
+ - `scrapQuantity`
53
+ - `unitOfMeasure`
54
+ - `scrapReasonCode`
55
+ - `siteReference`
56
+ - `postingDate`
57
+
58
+ Optional and conditional fields:
59
+
60
+ - `sourceStorageLocationReference`: optional when inventory can derive the source location from prior issue, backflush, or site-level manufacturing scrap policy; otherwise required
61
+ - `inventoryLotReference`: required when the scrapped item is lot-tracked
62
+ - `serialReferences`: required when the scrapped item is serial-tracked
63
+ - `scrapDispositionCode`: optional classification retained for downstream analysis
64
+
65
+ Inventory maps the handoff into an inventory-owned `InventoryAdjustment` as follows:
66
+
67
+ - `adjustmentType = SCRAP`
68
+ - `itemReference`, `siteReference`, `postingDate`, and reason fields come directly from the handoff
69
+ - `productionOrderReference` and `workOrderReference` are retained as source-document traceability fields on the adjustment
70
+ - `sourceStorageLocationReference` is taken from the payload when present; otherwise inventory must derive it from manufacturing issue history, staging-location policy, or other site-level configuration before the adjustment can proceed
71
+ - `inventoryLotReference` and `serialReferences` flow into the same lot or serial validation rules used by direct inventory scrap
72
+
73
+ Approval and confirmation rules are inventory-owned:
74
+
75
+ - If the resulting scrap value exceeds the configured approval threshold, inventory creates the adjustment in `PENDING_APPROVAL`
76
+ - If the threshold is not exceeded, inventory may confirm the adjustment immediately
77
+ - If location, lot, serial, or quantity resolution fails, inventory rejects the handoff and does not mutate stock
78
+
44
79
  ## Test Cases
45
80
 
46
81
  - Creating a SCRAP adjustment with positive quantityDelta is rejected
@@ -55,6 +90,9 @@ flowchart TD
55
90
  - Scrapped items cannot re-enter available stock without a new CORRECTION adjustment
56
91
  - Scrap reason is mandatory and must be one of the defined reason categories
57
92
  - Listing adjustments filtered by `adjustmentType = SCRAP` returns only scrap records
93
+ - A valid `ManufacturingScrapHandoff` must create or update a SCRAP adjustment that preserves the production-order and work-order references for traceability
94
+ - Manufacturing-originated scrap must follow the same approval threshold policy as manually entered scrap adjustments
95
+ - If inventory cannot resolve the source storage location required for `ManufacturingScrapHandoff`, the handoff must be rejected without mutating stock
58
96
 
59
97
  ## Reference Links
60
98
 
@@ -0,0 +1,63 @@
1
+ # README
2
+
3
+ ## Overview
4
+
5
+ The Manufacturing module manages production planning and execution from recipe and process definition through order release, shop-floor progress tracking, completion, and cost capture. It owns the master and transactional records that explain what should be produced, how it should be produced, where capacity exists to produce it, what work is in progress, what quantities were consumed or completed, and how actual manufacturing results differ from the plan.
6
+
7
+ Manufacturing sits between upstream demand or supply signals and downstream stock or finance execution. It references items from item-management, company and site context from organization, units and currencies from primitives, and coordinates with inventory for component issue, finished-goods receipt, and scrap posting. That coordination includes named cross-module contracts such as `ManufacturingReceiptHandoff`, `ManufacturingScrapHandoff`, `InventoryIssueOutcomeEvent`, and `ManufacturingCostSettlementAcknowledgment`, which let inventory and downstream accounting remain the owners of stock movement, valuation, and settlement while manufacturing remains the owner of production intent and variance analysis. It may prepare cost and account-reference data for downstream accounting, but journal posting and ledger ownership remain outside the module.
8
+
9
+ ## Key Features
10
+
11
+ - **[Bill of Material Management](docs/features/bill-of-material-management.md)**: Define versioned multi-level bills of material for finished goods and subassemblies, including component quantities, scrap assumptions, effectivity, and default selection rules used by production orders
12
+ - **[Routing and Work Center Definition](docs/features/routing-and-work-center-definition.md)**: Define routings as ordered manufacturing steps and configure work centers with capacity, calendars, standard times, and cost rates used for planning and execution
13
+ - **[Production Order Lifecycle](docs/features/production-order-lifecycle.md)**: Create, schedule, release, reschedule, cancel, complete, technically complete, and close production orders with BOM and routing snapshots plus progress visibility from planned order through settled closeout
14
+ - **[Work Order Execution](docs/features/work-order-execution.md)**: Execute operation-level work orders with start, pause, report, complete, material issue or backflush, scrap capture, and finished or intermediate receipt handoff to inventory
15
+ - **[Manufacturing Cost and Variance](docs/features/manufacturing-cost-and-variance.md)**: Capture planned versus actual component, labor, machine, and overhead cost at production-order level and classify manufacturing variances for downstream accounting consumption
16
+
17
+ ## Module Scope
18
+
19
+ ### In Scope
20
+
21
+ - Bill of material ownership, including component lines, versioning, effectivity windows, multi-level subassembly references, and `bomType` semantics for `MANUFACTURE`, `PHANTOM`, and `KIT`
22
+ - Routing ownership, including ordered operations, standard setup or run times, operation instructions, and work-center assignment
23
+ - Work center master data, including company or site scope, availability calendar, capacity assumptions, and standard cost rates
24
+ - Production order planning and control, including creation, scheduling, BOM or routing selection, release, cancellation, completion, technical completion, and closeout
25
+ - Work order execution at operation level, including start, pause, resume, quantity reporting, time capture, scrap reporting, and exception notes
26
+ - Material requirement handoff to inventory for reservation, issue, backflush, finished-goods receipt, intermediate receipt, and scrap posting
27
+ - Planned-versus-actual manufacturing cost rollup using component issue values from inventory plus labor or machine rates from work centers, with explicit variance categories and downstream account references
28
+ - Production status, progress, and auditability scoped by company and site
29
+
30
+ ### Out of Scope
31
+
32
+ - Forecasting, MRP netting, replenishment proposals, and automated procurement planning
33
+ - Advanced finite-capacity optimization, drag-and-drop scheduling boards, and AI or rules-based sequencing
34
+ - Quality inspection plans, nonconformance, CAPA, rework loops, and regulated electronic batch record workflows
35
+ - Subcontracting, outside processing, contract manufacturing, and supplier-operated work centers
36
+ - Co-products, by-products, and joint-production costing
37
+ - Detailed operator kiosk UX, barcode device workflows, PLC or IoT machine integration, and MES hardware connectivity
38
+ - Warehouse ownership, stock balances, lot or serial master records, and inventory valuation logic
39
+ - Journal entry posting, WIP settlement, period close, and general-ledger balance ownership
40
+ - Preventive maintenance, OEE analytics, and HR or payroll-driven labor management
41
+
42
+ ### Scope Decision Rationale
43
+
44
+ Manufacturing is scoped to the **production definition and execution layer**: product recipe, process steps, capacity context, production order control, and operation-level reporting. These concepts depend on one another and change together, so they belong in one module rather than being split prematurely across planning, execution, and costing.
45
+
46
+ Inventory remains the owner of **physical stock state**. Manufacturing decides that materials should be issued, scrap should be recorded, or finished goods should be received, but inventory owns the resulting stock movement, lot or serial handling, warehouse location state, and valuation basis. Manufacturing can request issue execution, but actual material cost becomes manufacturing-visible only when inventory returns the named `InventoryIssueOutcomeEvent` after inventory-owned valuation is complete. This keeps manufacturing focused on production intent and progress rather than warehouse internals.
47
+
48
+ Sales and purchase remain the owners of **commercial demand and supply commitments**. Manufacturing can consume sales demand, open supply status, or manual planner input when creating production orders, but it must not own customer orders, purchase orders, or supplier receipts. That separation preserves clean module boundaries and allows manufacturing to serve both make-to-stock and make-to-order flows without duplicating commercial logic.
49
+
50
+ Accounting remains the owner of **financial posting and settlement**. Manufacturing can capture planned and actual production cost, classify variances into price, usage, rate, efficiency, scrap, and yield categories, and reference WIP or variance accounts from coa-management, but posting journal entries and enforcing period-close rules belong to downstream accounting modules. This keeps cost insight in manufacturing without pulling ledger behavior into the shop-floor domain.
51
+
52
+ ## Module Dependencies
53
+
54
+ - [item-management](../item-management/README.md) — Finished goods, subassemblies, raw materials, and consumables are referenced as Items or SKUs
55
+ - [organization](../organization/README.md) — Company and Site scope for BOM defaults, work centers, and production execution context
56
+ - [primitives](../primitives/README.md) — Unit-of-measure conversions for component and output quantities plus currency references for costing
57
+ - [inventory](../inventory/README.md) — Downstream execution of component issue, backflush, finished-goods receipt, intermediate receipt, scrap, lot assignment, and actual material cost sourcing through `InventoryIssueOutcomeEvent`
58
+ - [user-management](../user-management/README.md) — Permissions for create, release, reschedule, start, complete, technically complete, cancel, close, and cost-review actions
59
+ - [audit](../audit/README.md) — Immutable audit trail for BOM revisions, routing changes, order transitions, and execution events
60
+ - [coa-management](../coa-management/README.md) — WIP, absorption, material price, material usage, labor rate, labor efficiency, machine rate, machine efficiency, scrap, and yield account references for downstream cost posting
61
+ - [sales](../sales/README.md) — Optional demand signal source for make-to-order or priority-driven production planning
62
+ - [purchase](../purchase/README.md) — Optional material availability and ETA signal source for release decisions and shortage visibility
63
+ - [product-management](../product-management/README.md) — Optional product or variant metadata source when aligning commercial catalog structure to manufacturing definitions
File without changes
@@ -0,0 +1,6 @@
1
+ // @generated — do not edit
2
+ import { permissions } from "../lib/permissions.generated";
3
+ import { run } from "./activateBillOfMaterial";
4
+ import { defineCommand } from "@tailor-platform/erp-kit/module";
5
+
6
+ export const activateBillOfMaterial = defineCommand(permissions.activateBillOfMaterial, run);
@@ -0,0 +1,166 @@
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
+ BomNotActivatableError,
7
+ ComponentLineRequiredError,
8
+ ComponentItemInactiveError,
9
+ CircularBomReferenceError,
10
+ EffectivityConflictError,
11
+ } from "../lib/errors.generated";
12
+ import { baseDraftBom, baseActiveBom, baseBomLine } from "../testing/fixtures";
13
+ import { run } from "./activateBillOfMaterial";
14
+ import type { CommandContext } from "@tailor-platform/erp-kit/module";
15
+
16
+ describe("activateBillOfMaterial", () => {
17
+ const ctx: CommandContext = {
18
+ actorId: "test-actor",
19
+ permissions: ["manufacturing:activateBillOfMaterial"],
20
+ };
21
+
22
+ it("activates a valid draft BOM", async () => {
23
+ const { db, spies } = createMockDb<Transaction>();
24
+ const activatedBom = { ...baseDraftBom, status: "ACTIVE" as const };
25
+
26
+ // BOM exists and is DRAFT
27
+ spies.select.mockReturnValueOnce(baseDraftBom);
28
+ // lines exist
29
+ spies.select.mockReturnValueOnce([baseBomLine]);
30
+ // component item exists (active)
31
+ spies.select.mockReturnValueOnce({ id: "item-2" });
32
+ // no conflicting active BOM
33
+ spies.select.mockReturnValueOnce(undefined);
34
+ // update to ACTIVE
35
+ spies.update.mockReturnValue(activatedBom);
36
+
37
+ const result = await run(db, { id: "bom-1" }, ctx);
38
+
39
+ expect(result.ok).toBe(true);
40
+ if (result.ok) {
41
+ expect(result.value.billOfMaterial.status).toBe("ACTIVE");
42
+ }
43
+ expect(spies.update).toHaveBeenCalled();
44
+ });
45
+
46
+ it("returns error when the BOM does not exist", async () => {
47
+ const { db, spies } = createMockDb<Transaction>();
48
+
49
+ spies.select.mockReturnValueOnce(undefined);
50
+
51
+ const result = await run(db, { id: "bom-nonexistent" }, ctx);
52
+
53
+ expect(result.ok).toBe(false);
54
+ if (!result.ok) {
55
+ expect(result.error).toBeInstanceOf(BomNotFoundError);
56
+ }
57
+ });
58
+
59
+ it("returns error when the BOM is not in DRAFT", async () => {
60
+ const { db, spies } = createMockDb<Transaction>();
61
+
62
+ spies.select.mockReturnValueOnce(baseActiveBom);
63
+
64
+ const result = await run(db, { id: "bom-2" }, ctx);
65
+
66
+ expect(result.ok).toBe(false);
67
+ if (!result.ok) {
68
+ expect(result.error).toBeInstanceOf(BomNotActivatableError);
69
+ }
70
+ });
71
+
72
+ it("returns error when the BOM has no component lines", async () => {
73
+ const { db, spies } = createMockDb<Transaction>();
74
+
75
+ // BOM exists and is DRAFT
76
+ spies.select.mockReturnValueOnce(baseDraftBom);
77
+ // no lines
78
+ spies.select.mockReturnValueOnce([]);
79
+
80
+ const result = await run(db, { id: "bom-1" }, ctx);
81
+
82
+ expect(result.ok).toBe(false);
83
+ if (!result.ok) {
84
+ expect(result.error).toBeInstanceOf(ComponentLineRequiredError);
85
+ }
86
+ });
87
+
88
+ it("returns error when a component item is inactive", async () => {
89
+ const { db, spies } = createMockDb<Transaction>();
90
+
91
+ // BOM exists and is DRAFT
92
+ spies.select.mockReturnValueOnce(baseDraftBom);
93
+ // lines exist
94
+ spies.select.mockReturnValueOnce([baseBomLine]);
95
+ // component item not found (inactive)
96
+ spies.select.mockReturnValueOnce(undefined);
97
+
98
+ const result = await run(db, { id: "bom-1" }, ctx);
99
+
100
+ expect(result.ok).toBe(false);
101
+ if (!result.ok) {
102
+ expect(result.error).toBeInstanceOf(ComponentItemInactiveError);
103
+ }
104
+ });
105
+
106
+ it("returns error when a circular manufactured-item structure is detected", async () => {
107
+ const { db, spies } = createMockDb<Transaction>();
108
+ const bomWithSubassembly = {
109
+ ...baseDraftBom,
110
+ };
111
+ const subassemblyLine = {
112
+ ...baseBomLine,
113
+ isSubassembly: true,
114
+ itemId: "item-sub",
115
+ };
116
+
117
+ // BOM exists and is DRAFT
118
+ spies.select.mockReturnValueOnce(bomWithSubassembly);
119
+ // lines with subassembly
120
+ spies.select.mockReturnValueOnce([subassemblyLine]);
121
+ // component item exists
122
+ spies.select.mockReturnValueOnce({ id: "item-sub" });
123
+ // child BOMs exist for subassembly item (execute returns array)
124
+ spies.select.mockReturnValueOnce([
125
+ {
126
+ ...baseDraftBom,
127
+ id: "child-bom",
128
+ parentItemId: "item-sub",
129
+ },
130
+ ]);
131
+ // child BOM lines reference back to parent item (circular)
132
+ spies.select.mockReturnValueOnce([{ ...baseBomLine, itemId: "item-1" }]);
133
+
134
+ const result = await run(db, { id: "bom-1" }, ctx);
135
+
136
+ expect(result.ok).toBe(false);
137
+ if (!result.ok) {
138
+ expect(result.error).toBeInstanceOf(CircularBomReferenceError);
139
+ }
140
+ });
141
+
142
+ it("returns error when activation would create an effectivity conflict", async () => {
143
+ const { db, spies } = createMockDb<Transaction>();
144
+
145
+ // BOM exists and is DRAFT with default selection
146
+ spies.select.mockReturnValueOnce(baseDraftBom);
147
+ // lines exist
148
+ spies.select.mockReturnValueOnce([baseBomLine]);
149
+ // component item exists
150
+ spies.select.mockReturnValueOnce({ id: "item-2" });
151
+ // conflicting active BOM with same parent + default selection + no date bounds
152
+ spies.select.mockReturnValueOnce({
153
+ ...baseActiveBom,
154
+ defaultSelection: true,
155
+ effectivityStartDate: null,
156
+ effectivityEndDate: null,
157
+ });
158
+
159
+ const result = await run(db, { id: "bom-1" }, ctx);
160
+
161
+ expect(result.ok).toBe(false);
162
+ if (!result.ok) {
163
+ expect(result.error).toBeInstanceOf(EffectivityConflictError);
164
+ }
165
+ });
166
+ });