@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.
- package/CHANGELOG.md +15 -0
- package/dist/cli.mjs +103 -23
- package/package.json +1 -1
- package/skills/erp-kit-app-5-impl-backend/SKILL.md +7 -4
- package/skills/erp-kit-app-7-impl-review/SKILL.md +1 -1
- package/skills/erp-kit-module-6-impl-review/SKILL.md +39 -17
- package/src/commands/generate-doc.ts +1 -1
- package/src/commands/lib/discovery.test.ts +13 -3
- package/src/commands/lib/discovery.ts +10 -2
- package/src/commands/lib/paths.ts +4 -2
- package/src/commands/lib/sync-check-tests.test.ts +84 -6
- package/src/commands/lib/sync-check-tests.ts +63 -3
- package/src/commands/sync-check.ts +7 -3
- package/src/generator/generate-app-code.ts +51 -16
- package/src/generator/generate-stubs.ts +4 -0
- package/src/generator/stub-templates.test.ts +11 -0
- package/src/generator/stub-templates.ts +22 -1
- package/src/modules/inventory/docs/features/inventory-adjustment.md +2 -1
- package/src/modules/inventory/docs/features/scrap-management.md +39 -1
- package/src/modules/manufacturing/README.md +63 -0
- package/src/modules/manufacturing/command/.gitkeep +0 -0
- package/src/modules/manufacturing/command/activateBillOfMaterial.generated.ts +6 -0
- package/src/modules/manufacturing/command/activateBillOfMaterial.test.ts +166 -0
- package/src/modules/manufacturing/command/activateBillOfMaterial.ts +173 -0
- package/src/modules/manufacturing/command/activateRouting.generated.ts +6 -0
- package/src/modules/manufacturing/command/activateRouting.test.ts +152 -0
- package/src/modules/manufacturing/command/activateRouting.ts +92 -0
- package/src/modules/manufacturing/command/activateWorkCenter.generated.ts +6 -0
- package/src/modules/manufacturing/command/activateWorkCenter.test.ts +135 -0
- package/src/modules/manufacturing/command/activateWorkCenter.ts +91 -0
- package/src/modules/manufacturing/command/cancelProductionOrder.generated.ts +6 -0
- package/src/modules/manufacturing/command/cancelProductionOrder.test.ts +151 -0
- package/src/modules/manufacturing/command/cancelProductionOrder.ts +114 -0
- package/src/modules/manufacturing/command/closeProductionOrder.generated.ts +6 -0
- package/src/modules/manufacturing/command/closeProductionOrder.test.ts +126 -0
- package/src/modules/manufacturing/command/closeProductionOrder.ts +87 -0
- package/src/modules/manufacturing/command/completeProductionOrder.generated.ts +6 -0
- package/src/modules/manufacturing/command/completeProductionOrder.test.ts +132 -0
- package/src/modules/manufacturing/command/completeProductionOrder.ts +97 -0
- package/src/modules/manufacturing/command/completeWorkOrder.generated.ts +6 -0
- package/src/modules/manufacturing/command/completeWorkOrder.test.ts +369 -0
- package/src/modules/manufacturing/command/completeWorkOrder.ts +212 -0
- package/src/modules/manufacturing/command/createBillOfMaterial.generated.ts +6 -0
- package/src/modules/manufacturing/command/createBillOfMaterial.test.ts +210 -0
- package/src/modules/manufacturing/command/createBillOfMaterial.ts +176 -0
- package/src/modules/manufacturing/command/createProductionOrder.generated.ts +6 -0
- package/src/modules/manufacturing/command/createProductionOrder.test.ts +160 -0
- package/src/modules/manufacturing/command/createProductionOrder.ts +129 -0
- package/src/modules/manufacturing/command/createRouting.generated.ts +6 -0
- package/src/modules/manufacturing/command/createRouting.test.ts +168 -0
- package/src/modules/manufacturing/command/createRouting.ts +128 -0
- package/src/modules/manufacturing/command/createWorkCenter.generated.ts +6 -0
- package/src/modules/manufacturing/command/createWorkCenter.test.ts +148 -0
- package/src/modules/manufacturing/command/createWorkCenter.ts +131 -0
- package/src/modules/manufacturing/command/deactivateBillOfMaterial.generated.ts +6 -0
- package/src/modules/manufacturing/command/deactivateBillOfMaterial.test.ts +103 -0
- package/src/modules/manufacturing/command/deactivateBillOfMaterial.ts +78 -0
- package/src/modules/manufacturing/command/deactivateRouting.generated.ts +6 -0
- package/src/modules/manufacturing/command/deactivateRouting.test.ts +112 -0
- package/src/modules/manufacturing/command/deactivateRouting.ts +76 -0
- package/src/modules/manufacturing/command/deactivateWorkCenter.generated.ts +6 -0
- package/src/modules/manufacturing/command/deactivateWorkCenter.test.ts +113 -0
- package/src/modules/manufacturing/command/deactivateWorkCenter.ts +85 -0
- package/src/modules/manufacturing/command/pauseWorkOrder.generated.ts +6 -0
- package/src/modules/manufacturing/command/pauseWorkOrder.test.ts +118 -0
- package/src/modules/manufacturing/command/pauseWorkOrder.ts +82 -0
- package/src/modules/manufacturing/command/recordInventoryIssueOutcome.generated.ts +6 -0
- package/src/modules/manufacturing/command/recordInventoryIssueOutcome.test.ts +183 -0
- package/src/modules/manufacturing/command/recordInventoryIssueOutcome.ts +139 -0
- package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.generated.ts +6 -0
- package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.test.ts +120 -0
- package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.ts +110 -0
- package/src/modules/manufacturing/command/releaseProductionOrder.generated.ts +6 -0
- package/src/modules/manufacturing/command/releaseProductionOrder.test.ts +220 -0
- package/src/modules/manufacturing/command/releaseProductionOrder.ts +450 -0
- package/src/modules/manufacturing/command/reopenProductionOrder.generated.ts +6 -0
- package/src/modules/manufacturing/command/reopenProductionOrder.test.ts +196 -0
- package/src/modules/manufacturing/command/reopenProductionOrder.ts +98 -0
- package/src/modules/manufacturing/command/reportWorkOrderProgress.generated.ts +6 -0
- package/src/modules/manufacturing/command/reportWorkOrderProgress.test.ts +204 -0
- package/src/modules/manufacturing/command/reportWorkOrderProgress.ts +129 -0
- package/src/modules/manufacturing/command/rescheduleProductionOrder.generated.ts +6 -0
- package/src/modules/manufacturing/command/rescheduleProductionOrder.test.ts +185 -0
- package/src/modules/manufacturing/command/rescheduleProductionOrder.ts +95 -0
- package/src/modules/manufacturing/command/resumeWorkOrder.generated.ts +6 -0
- package/src/modules/manufacturing/command/resumeWorkOrder.test.ts +122 -0
- package/src/modules/manufacturing/command/resumeWorkOrder.ts +94 -0
- package/src/modules/manufacturing/command/reviewManufacturingCostSummary.generated.ts +6 -0
- package/src/modules/manufacturing/command/reviewManufacturingCostSummary.test.ts +231 -0
- package/src/modules/manufacturing/command/reviewManufacturingCostSummary.ts +137 -0
- package/src/modules/manufacturing/command/startWorkOrder.generated.ts +6 -0
- package/src/modules/manufacturing/command/startWorkOrder.test.ts +118 -0
- package/src/modules/manufacturing/command/startWorkOrder.ts +126 -0
- package/src/modules/manufacturing/command/technicallyCompleteProductionOrder.generated.ts +6 -0
- package/src/modules/manufacturing/command/technicallyCompleteProductionOrder.test.ts +153 -0
- package/src/modules/manufacturing/command/technicallyCompleteProductionOrder.ts +106 -0
- package/src/modules/manufacturing/command/unreleaseProductionOrder.generated.ts +6 -0
- package/src/modules/manufacturing/command/unreleaseProductionOrder.test.ts +140 -0
- package/src/modules/manufacturing/command/unreleaseProductionOrder.ts +131 -0
- package/src/modules/manufacturing/command/updateBillOfMaterial.generated.ts +6 -0
- package/src/modules/manufacturing/command/updateBillOfMaterial.test.ts +149 -0
- package/src/modules/manufacturing/command/updateBillOfMaterial.ts +174 -0
- package/src/modules/manufacturing/command/updateProductionOrder.generated.ts +6 -0
- package/src/modules/manufacturing/command/updateProductionOrder.test.ts +112 -0
- package/src/modules/manufacturing/command/updateProductionOrder.ts +145 -0
- package/src/modules/manufacturing/command/updateRouting.generated.ts +6 -0
- package/src/modules/manufacturing/command/updateRouting.test.ts +211 -0
- package/src/modules/manufacturing/command/updateRouting.ts +124 -0
- package/src/modules/manufacturing/command/updateWorkCenter.generated.ts +6 -0
- package/src/modules/manufacturing/command/updateWorkCenter.test.ts +152 -0
- package/src/modules/manufacturing/command/updateWorkCenter.ts +137 -0
- package/src/modules/manufacturing/db/.gitkeep +0 -0
- package/src/modules/manufacturing/db/billOfMaterial.ts +70 -0
- package/src/modules/manufacturing/db/billOfMaterialLine.ts +49 -0
- package/src/modules/manufacturing/db/costVarianceLine.ts +53 -0
- package/src/modules/manufacturing/db/manufacturingCostLine.ts +35 -0
- package/src/modules/manufacturing/db/manufacturingCostSettlementRecord.ts +39 -0
- package/src/modules/manufacturing/db/manufacturingCostSummary.ts +59 -0
- package/src/modules/manufacturing/db/productionOrder.ts +83 -0
- package/src/modules/manufacturing/db/productionOrderBomSnapshot.ts +44 -0
- package/src/modules/manufacturing/db/productionOrderCostBaseline.ts +44 -0
- package/src/modules/manufacturing/db/productionOrderMaterialRequirement.ts +57 -0
- package/src/modules/manufacturing/db/productionOrderRoutingSnapshot.ts +43 -0
- package/src/modules/manufacturing/db/routing.ts +63 -0
- package/src/modules/manufacturing/db/routingOperation.ts +57 -0
- package/src/modules/manufacturing/db/workCenter.ts +87 -0
- package/src/modules/manufacturing/db/workOrder.ts +65 -0
- package/src/modules/manufacturing/db/workOrderExecutionEvent.ts +54 -0
- package/src/modules/manufacturing/docs/commands/ActivateBillOfMaterial.md +50 -0
- package/src/modules/manufacturing/docs/commands/ActivateRouting.md +48 -0
- package/src/modules/manufacturing/docs/commands/ActivateWorkCenter.md +49 -0
- package/src/modules/manufacturing/docs/commands/CancelProductionOrder.md +48 -0
- package/src/modules/manufacturing/docs/commands/CloseProductionOrder.md +46 -0
- package/src/modules/manufacturing/docs/commands/CompleteProductionOrder.md +48 -0
- package/src/modules/manufacturing/docs/commands/CompleteWorkOrder.md +66 -0
- package/src/modules/manufacturing/docs/commands/CreateBillOfMaterial.md +54 -0
- package/src/modules/manufacturing/docs/commands/CreateProductionOrder.md +49 -0
- package/src/modules/manufacturing/docs/commands/CreateRouting.md +50 -0
- package/src/modules/manufacturing/docs/commands/CreateWorkCenter.md +51 -0
- package/src/modules/manufacturing/docs/commands/DeactivateBillOfMaterial.md +45 -0
- package/src/modules/manufacturing/docs/commands/DeactivateRouting.md +45 -0
- package/src/modules/manufacturing/docs/commands/DeactivateWorkCenter.md +45 -0
- package/src/modules/manufacturing/docs/commands/PauseWorkOrder.md +44 -0
- package/src/modules/manufacturing/docs/commands/RecordInventoryIssueOutcome.md +59 -0
- package/src/modules/manufacturing/docs/commands/RecordManufacturingCostSettlementAcknowledgment.md +49 -0
- package/src/modules/manufacturing/docs/commands/ReleaseProductionOrder.md +57 -0
- package/src/modules/manufacturing/docs/commands/ReopenProductionOrder.md +54 -0
- package/src/modules/manufacturing/docs/commands/ReportWorkOrderProgress.md +53 -0
- package/src/modules/manufacturing/docs/commands/RescheduleProductionOrder.md +45 -0
- package/src/modules/manufacturing/docs/commands/ResumeWorkOrder.md +44 -0
- package/src/modules/manufacturing/docs/commands/ReviewManufacturingCostSummary.md +52 -0
- package/src/modules/manufacturing/docs/commands/StartWorkOrder.md +46 -0
- package/src/modules/manufacturing/docs/commands/TechnicallyCompleteProductionOrder.md +51 -0
- package/src/modules/manufacturing/docs/commands/UnreleaseProductionOrder.md +46 -0
- package/src/modules/manufacturing/docs/commands/UpdateBillOfMaterial.md +48 -0
- package/src/modules/manufacturing/docs/commands/UpdateProductionOrder.md +48 -0
- package/src/modules/manufacturing/docs/commands/UpdateRouting.md +52 -0
- package/src/modules/manufacturing/docs/commands/UpdateWorkCenter.md +48 -0
- package/src/modules/manufacturing/docs/features/bill-of-material-management.md +83 -0
- package/src/modules/manufacturing/docs/features/manufacturing-cost-and-variance.md +191 -0
- package/src/modules/manufacturing/docs/features/production-order-lifecycle.md +103 -0
- package/src/modules/manufacturing/docs/features/routing-and-work-center-definition.md +63 -0
- package/src/modules/manufacturing/docs/features/work-order-execution.md +115 -0
- package/src/modules/manufacturing/docs/models/BillOfMaterial.md +60 -0
- package/src/modules/manufacturing/docs/models/ManufacturingCostSummary.md +66 -0
- package/src/modules/manufacturing/docs/models/ProductionOrder.md +76 -0
- package/src/modules/manufacturing/docs/models/Routing.md +58 -0
- package/src/modules/manufacturing/docs/models/WorkCenter.md +56 -0
- package/src/modules/manufacturing/docs/models/WorkOrder.md +63 -0
- package/src/modules/manufacturing/docs/queries/DetectBillOfMaterialCircularReference.md +39 -0
- package/src/modules/manufacturing/docs/queries/ExplodeBillOfMaterial.md +56 -0
- package/src/modules/manufacturing/docs/queries/GetBillOfMaterial.md +37 -0
- package/src/modules/manufacturing/docs/queries/GetManufacturingCostSummary.md +39 -0
- package/src/modules/manufacturing/docs/queries/GetProductionOrder.md +37 -0
- package/src/modules/manufacturing/docs/queries/GetRouting.md +39 -0
- package/src/modules/manufacturing/docs/queries/GetWorkCenter.md +35 -0
- package/src/modules/manufacturing/docs/queries/GetWorkOrder.md +38 -0
- package/src/modules/manufacturing/docs/queries/ListBillOfMaterialsByItem.md +42 -0
- package/src/modules/manufacturing/docs/queries/ListManufacturingCostSummariesByStatus.md +41 -0
- package/src/modules/manufacturing/docs/queries/ListProductionOrdersByStatus.md +41 -0
- package/src/modules/manufacturing/docs/queries/ListRoutingsByItem.md +42 -0
- package/src/modules/manufacturing/docs/queries/ListWorkCentersBySite.md +38 -0
- package/src/modules/manufacturing/docs/queries/ListWorkOrdersByProductionOrder.md +39 -0
- package/src/modules/manufacturing/docs/queries/ListWorkOrdersByWorkCenter.md +43 -0
- package/src/modules/manufacturing/executor/.gitkeep +0 -0
- package/src/modules/manufacturing/generated/enums.ts +113 -0
- package/src/modules/manufacturing/generated/kysely-tailordb.ts +247 -0
- package/src/modules/manufacturing/index.ts +2 -0
- package/src/modules/manufacturing/lib/_db_deps.ts +22 -0
- package/src/modules/manufacturing/lib/errors.generated.ts +592 -0
- package/src/modules/manufacturing/lib/permissions.generated.ts +35 -0
- package/src/modules/manufacturing/lib/types.ts +111 -0
- package/src/modules/manufacturing/module.ts +226 -0
- package/src/modules/manufacturing/permissions.ts +3 -0
- package/src/modules/manufacturing/query/.gitkeep +0 -0
- package/src/modules/manufacturing/query/detectBillOfMaterialCircularReference.generated.ts +5 -0
- package/src/modules/manufacturing/query/detectBillOfMaterialCircularReference.test.ts +115 -0
- package/src/modules/manufacturing/query/detectBillOfMaterialCircularReference.ts +79 -0
- package/src/modules/manufacturing/query/explodeBillOfMaterial.generated.ts +5 -0
- package/src/modules/manufacturing/query/explodeBillOfMaterial.test.ts +445 -0
- package/src/modules/manufacturing/query/explodeBillOfMaterial.ts +306 -0
- package/src/modules/manufacturing/query/getBillOfMaterial.generated.ts +5 -0
- package/src/modules/manufacturing/query/getBillOfMaterial.test.ts +64 -0
- package/src/modules/manufacturing/query/getBillOfMaterial.ts +27 -0
- package/src/modules/manufacturing/query/getManufacturingCostSummary.generated.ts +5 -0
- package/src/modules/manufacturing/query/getManufacturingCostSummary.test.ts +147 -0
- package/src/modules/manufacturing/query/getManufacturingCostSummary.ts +46 -0
- package/src/modules/manufacturing/query/getProductionOrder.generated.ts +5 -0
- package/src/modules/manufacturing/query/getProductionOrder.test.ts +139 -0
- package/src/modules/manufacturing/query/getProductionOrder.ts +84 -0
- package/src/modules/manufacturing/query/getRouting.generated.ts +5 -0
- package/src/modules/manufacturing/query/getRouting.test.ts +71 -0
- package/src/modules/manufacturing/query/getRouting.ts +34 -0
- package/src/modules/manufacturing/query/getWorkCenter.generated.ts +5 -0
- package/src/modules/manufacturing/query/getWorkCenter.test.ts +37 -0
- package/src/modules/manufacturing/query/getWorkCenter.ts +21 -0
- package/src/modules/manufacturing/query/getWorkOrder.generated.ts +5 -0
- package/src/modules/manufacturing/query/getWorkOrder.test.ts +73 -0
- package/src/modules/manufacturing/query/getWorkOrder.ts +28 -0
- package/src/modules/manufacturing/query/listBillOfMaterialsByItem.generated.ts +5 -0
- package/src/modules/manufacturing/query/listBillOfMaterialsByItem.test.ts +107 -0
- package/src/modules/manufacturing/query/listBillOfMaterialsByItem.ts +58 -0
- package/src/modules/manufacturing/query/listManufacturingCostSummariesByStatus.generated.ts +5 -0
- package/src/modules/manufacturing/query/listManufacturingCostSummariesByStatus.test.ts +96 -0
- package/src/modules/manufacturing/query/listManufacturingCostSummariesByStatus.ts +77 -0
- package/src/modules/manufacturing/query/listProductionOrdersByStatus.generated.ts +5 -0
- package/src/modules/manufacturing/query/listProductionOrdersByStatus.test.ts +121 -0
- package/src/modules/manufacturing/query/listProductionOrdersByStatus.ts +83 -0
- package/src/modules/manufacturing/query/listRoutingsByItem.generated.ts +5 -0
- package/src/modules/manufacturing/query/listRoutingsByItem.test.ts +110 -0
- package/src/modules/manufacturing/query/listRoutingsByItem.ts +54 -0
- package/src/modules/manufacturing/query/listWorkCentersBySite.generated.ts +5 -0
- package/src/modules/manufacturing/query/listWorkCentersBySite.test.ts +81 -0
- package/src/modules/manufacturing/query/listWorkCentersBySite.ts +70 -0
- package/src/modules/manufacturing/query/listWorkOrdersByProductionOrder.generated.ts +5 -0
- package/src/modules/manufacturing/query/listWorkOrdersByProductionOrder.test.ts +102 -0
- package/src/modules/manufacturing/query/listWorkOrdersByProductionOrder.ts +53 -0
- package/src/modules/manufacturing/query/listWorkOrdersByWorkCenter.generated.ts +5 -0
- package/src/modules/manufacturing/query/listWorkOrdersByWorkCenter.test.ts +143 -0
- package/src/modules/manufacturing/query/listWorkOrdersByWorkCenter.ts +56 -0
- package/src/modules/manufacturing/seed/index.ts +19 -0
- package/src/modules/manufacturing/tailor.config.ts +13 -0
- package/src/modules/manufacturing/tailor.d.ts +13 -0
- package/src/modules/manufacturing/testing/commandTestUtils.ts +29 -0
- package/src/modules/manufacturing/testing/fixtures.ts +402 -0
- package/templates/scaffold/app/backend/package.json +9 -2
- package/templates/scaffold/app/backend/src/tests/utils/graphql-client.ts +66 -0
- package/templates/scaffold/app/backend/src/tests/utils/setup.ts +21 -0
- package/templates/scaffold/app/backend/tsconfig.json +9 -2
- package/templates/scaffold/app/backend/vitest.config.ts +35 -0
- 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 {
|
|
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
|
|
12
|
+
export async function runModuleTestCaseSyncCheck(root: string, cwd: string): Promise<CheckError[]> {
|
|
13
13
|
const errors: CheckError[] = [];
|
|
14
|
-
const categories =
|
|
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 {
|
|
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
|
|
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 {
|
|
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
|
-
|
|
12
|
-
|
|
17
|
+
generateResolverStubs(appPath, appName);
|
|
18
|
+
generateStoryTestStubs(appPath, appName);
|
|
13
19
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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:
|
|
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
|
|
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
|
+
});
|