@tailor-platform/erp-kit 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/cli.mjs +139 -35
- package/package.json +1 -1
- package/skills/erp-kit-app-5-impl-backend/SKILL.md +10 -5
- 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/init-module.test.ts +17 -3
- package/src/commands/init-module.ts +0 -12
- 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-code-boilerplate.test.ts +9 -1
- package/src/generator/generate-stubs.ts +4 -0
- package/src/generator/scaffold.ts +6 -2
- package/src/generator/stub-templates.test.ts +11 -0
- package/src/generator/stub-templates.ts +22 -1
- package/src/mdschema.ts +39 -3
- 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/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
- package/templates/scaffold/module/__dot__gitignore +3 -0
- package/templates/scaffold/module/eslint.config.js +31 -0
- package/templates/scaffold/module/generated/kysely-tailordb.ts +3 -0
- package/templates/scaffold/module/lib/types.ts +1 -6
- package/templates/scaffold/module/package.json +26 -0
- package/templates/scaffold/module/tsconfig.json +16 -0
- /package/{templates/scaffold/module/generated → src/modules/manufacturing/command}/.gitkeep +0 -0
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
2
|
+
import {
|
|
3
|
+
BomNotFoundError,
|
|
4
|
+
BomNotActivatableError,
|
|
5
|
+
ComponentLineRequiredError,
|
|
6
|
+
ComponentItemInactiveError,
|
|
7
|
+
CircularBomReferenceError,
|
|
8
|
+
EffectivityConflictError,
|
|
9
|
+
} from "../lib/errors.generated";
|
|
10
|
+
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
|
|
11
|
+
|
|
12
|
+
export interface ActivateBillOfMaterialInput {
|
|
13
|
+
id: string;
|
|
14
|
+
from?: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Function: activateBillOfMaterial
|
|
19
|
+
*
|
|
20
|
+
* Validates a draft BOM version and makes it selectable for production-order
|
|
21
|
+
* release. Activation enforces circular-reference checks, effectivity
|
|
22
|
+
* conflicts, and component readiness.
|
|
23
|
+
*/
|
|
24
|
+
export async function run<CF extends Record<string, unknown>>(
|
|
25
|
+
db: Transaction,
|
|
26
|
+
input: ActivateBillOfMaterialInput & CF,
|
|
27
|
+
_ctx: CommandContext,
|
|
28
|
+
) {
|
|
29
|
+
const { id, from, ...customFields } = input;
|
|
30
|
+
void customFields;
|
|
31
|
+
|
|
32
|
+
const allowedStatuses = from ?? ["DRAFT"];
|
|
33
|
+
|
|
34
|
+
// 1. Fetch BOM with lock
|
|
35
|
+
const bom = await db
|
|
36
|
+
.selectFrom("BillOfMaterial")
|
|
37
|
+
.selectAll()
|
|
38
|
+
.where("id", "=", id)
|
|
39
|
+
.forUpdate()
|
|
40
|
+
.executeTakeFirst();
|
|
41
|
+
|
|
42
|
+
if (!bom) {
|
|
43
|
+
return err(new BomNotFoundError(id));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 2. Validate status is activatable
|
|
47
|
+
if (!allowedStatuses.includes(bom.status)) {
|
|
48
|
+
return err(new BomNotActivatableError(id));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 3. Validate at least one component line exists
|
|
52
|
+
const lines = await db
|
|
53
|
+
.selectFrom("BillOfMaterialLine")
|
|
54
|
+
.selectAll()
|
|
55
|
+
.where("billOfMaterialId", "=", id)
|
|
56
|
+
.execute();
|
|
57
|
+
|
|
58
|
+
if (!lines || lines.length === 0) {
|
|
59
|
+
return err(new ComponentLineRequiredError(id));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 4. Validate all component items are active
|
|
63
|
+
for (const line of lines) {
|
|
64
|
+
const componentItem = await db
|
|
65
|
+
.selectFrom("Item")
|
|
66
|
+
.selectAll()
|
|
67
|
+
.where("id", "=", line.itemId)
|
|
68
|
+
.executeTakeFirst();
|
|
69
|
+
|
|
70
|
+
if (!componentItem) {
|
|
71
|
+
return err(new ComponentItemInactiveError(line.itemId));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 5. Check for circular references using DFS traversal
|
|
76
|
+
const circularResult = await detectCircularReference(
|
|
77
|
+
db,
|
|
78
|
+
bom.parentItemId,
|
|
79
|
+
lines,
|
|
80
|
+
new Set<string>([bom.parentItemId]),
|
|
81
|
+
);
|
|
82
|
+
if (circularResult) {
|
|
83
|
+
return err(new CircularBomReferenceError(id));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 6. Check effectivity conflicts — overlapping active effectivity windows
|
|
87
|
+
// for the same parent item and scope
|
|
88
|
+
const conflictingBom = await db
|
|
89
|
+
.selectFrom("BillOfMaterial")
|
|
90
|
+
.selectAll()
|
|
91
|
+
.where("parentItemId", "=", bom.parentItemId)
|
|
92
|
+
.where("companyId", "=", bom.companyId)
|
|
93
|
+
.where("id", "!=", id)
|
|
94
|
+
.where("status", "=", "ACTIVE")
|
|
95
|
+
.executeTakeFirst();
|
|
96
|
+
|
|
97
|
+
if (conflictingBom) {
|
|
98
|
+
// Check date overlap
|
|
99
|
+
const bomStart = bom.effectivityStartDate;
|
|
100
|
+
const bomEnd = bom.effectivityEndDate;
|
|
101
|
+
const conflictStart = conflictingBom.effectivityStartDate;
|
|
102
|
+
const conflictEnd = conflictingBom.effectivityEndDate;
|
|
103
|
+
|
|
104
|
+
const hasOverlap =
|
|
105
|
+
(!bomStart || !conflictEnd || bomStart <= conflictEnd) &&
|
|
106
|
+
(!bomEnd || !conflictStart || bomEnd >= conflictStart);
|
|
107
|
+
|
|
108
|
+
if (hasOverlap && bom.defaultSelection && conflictingBom.defaultSelection) {
|
|
109
|
+
return err(new EffectivityConflictError(id));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 7. Set status to ACTIVE
|
|
114
|
+
const updatedBom = await db
|
|
115
|
+
.updateTable("BillOfMaterial")
|
|
116
|
+
.set({
|
|
117
|
+
status: "ACTIVE",
|
|
118
|
+
updatedAt: new Date(),
|
|
119
|
+
})
|
|
120
|
+
.where("id", "=", id)
|
|
121
|
+
.returningAll()
|
|
122
|
+
.executeTakeFirst();
|
|
123
|
+
|
|
124
|
+
return ok({ billOfMaterial: updatedBom! });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function detectCircularReference(
|
|
128
|
+
db: Transaction,
|
|
129
|
+
rootParentItemId: string,
|
|
130
|
+
lines: { itemId: string; isSubassembly: boolean | null }[],
|
|
131
|
+
visitedItems: Set<string>,
|
|
132
|
+
): Promise<boolean> {
|
|
133
|
+
for (const line of lines) {
|
|
134
|
+
// If this item has already been visited in the path, we have a cycle
|
|
135
|
+
if (visitedItems.has(line.itemId)) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!line.isSubassembly) continue;
|
|
140
|
+
|
|
141
|
+
// Find ALL child BOMs for this item (multiple versions may exist)
|
|
142
|
+
const childBoms = await db
|
|
143
|
+
.selectFrom("BillOfMaterial")
|
|
144
|
+
.selectAll()
|
|
145
|
+
.where("parentItemId", "=", line.itemId)
|
|
146
|
+
.where("status", "in", ["DRAFT", "ACTIVE"])
|
|
147
|
+
.execute();
|
|
148
|
+
|
|
149
|
+
if (childBoms.length > 0) {
|
|
150
|
+
visitedItems.add(line.itemId);
|
|
151
|
+
|
|
152
|
+
for (const childBom of childBoms) {
|
|
153
|
+
const childLines = await db
|
|
154
|
+
.selectFrom("BillOfMaterialLine")
|
|
155
|
+
.selectAll()
|
|
156
|
+
.where("billOfMaterialId", "=", childBom.id)
|
|
157
|
+
.execute();
|
|
158
|
+
|
|
159
|
+
const hasCycle = await detectCircularReference(
|
|
160
|
+
db,
|
|
161
|
+
rootParentItemId,
|
|
162
|
+
childLines,
|
|
163
|
+
visitedItems,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if (hasCycle) return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
visitedItems.delete(line.itemId);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @generated — do not edit
|
|
2
|
+
import { permissions } from "../lib/permissions.generated";
|
|
3
|
+
import { run } from "./activateRouting";
|
|
4
|
+
import { defineCommand } from "@tailor-platform/erp-kit/module";
|
|
5
|
+
|
|
6
|
+
export const activateRouting = defineCommand(permissions.activateRouting, run);
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMockDb } from "../../../testing/index";
|
|
3
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
4
|
+
import {
|
|
5
|
+
RoutingNotFoundError,
|
|
6
|
+
RoutingNotActivatableError,
|
|
7
|
+
OperationRequiredError,
|
|
8
|
+
DuplicateOperationSequenceError,
|
|
9
|
+
WorkCenterNotActiveError,
|
|
10
|
+
CrossCompanyWorkCenterError,
|
|
11
|
+
} from "../lib/errors.generated";
|
|
12
|
+
import {
|
|
13
|
+
baseDraftRouting,
|
|
14
|
+
baseActiveRouting,
|
|
15
|
+
baseRoutingOperation,
|
|
16
|
+
baseActiveWorkCenter,
|
|
17
|
+
baseInactiveWorkCenter,
|
|
18
|
+
} from "../testing/fixtures";
|
|
19
|
+
import { run } from "./activateRouting";
|
|
20
|
+
import type { CommandContext } from "@tailor-platform/erp-kit/module";
|
|
21
|
+
|
|
22
|
+
describe("activateRouting", () => {
|
|
23
|
+
const ctx: CommandContext = {
|
|
24
|
+
actorId: "test-actor",
|
|
25
|
+
permissions: ["manufacturing:activateRouting"],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const validInput = { id: "routing-1" };
|
|
29
|
+
|
|
30
|
+
it("activates a valid draft routing", async () => {
|
|
31
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
32
|
+
|
|
33
|
+
// select: Routing lookup
|
|
34
|
+
spies.select.mockReturnValueOnce(baseDraftRouting);
|
|
35
|
+
// select: RoutingOperation list
|
|
36
|
+
spies.select.mockReturnValueOnce([baseRoutingOperation]);
|
|
37
|
+
// select: WorkCenter lookup for operation
|
|
38
|
+
spies.select.mockReturnValueOnce(baseActiveWorkCenter);
|
|
39
|
+
// update: status to ACTIVE
|
|
40
|
+
spies.update.mockReturnValue({ ...baseDraftRouting, status: "ACTIVE" });
|
|
41
|
+
|
|
42
|
+
const result = await run(db, validInput, ctx);
|
|
43
|
+
|
|
44
|
+
expect(result.ok).toBe(true);
|
|
45
|
+
if (result.ok) {
|
|
46
|
+
expect(result.value.routing.status).toBe("ACTIVE");
|
|
47
|
+
}
|
|
48
|
+
expect(spies.update).toHaveBeenCalled();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("returns error when the routing does not exist", async () => {
|
|
52
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
53
|
+
|
|
54
|
+
// select: Routing lookup - not found
|
|
55
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
56
|
+
|
|
57
|
+
const result = await run(db, validInput, ctx);
|
|
58
|
+
|
|
59
|
+
expect(result.ok).toBe(false);
|
|
60
|
+
if (!result.ok) {
|
|
61
|
+
expect(result.error).toBeInstanceOf(RoutingNotFoundError);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("returns error when the routing is not in DRAFT", async () => {
|
|
66
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
67
|
+
|
|
68
|
+
// select: Routing lookup - ACTIVE
|
|
69
|
+
spies.select.mockReturnValueOnce(baseActiveRouting);
|
|
70
|
+
|
|
71
|
+
const result = await run(db, { id: "routing-2" }, ctx);
|
|
72
|
+
|
|
73
|
+
expect(result.ok).toBe(false);
|
|
74
|
+
if (!result.ok) {
|
|
75
|
+
expect(result.error).toBeInstanceOf(RoutingNotActivatableError);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("returns error when the routing has no operations", async () => {
|
|
80
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
81
|
+
|
|
82
|
+
// select: Routing lookup
|
|
83
|
+
spies.select.mockReturnValueOnce(baseDraftRouting);
|
|
84
|
+
// select: RoutingOperation list - empty
|
|
85
|
+
spies.select.mockReturnValueOnce([]);
|
|
86
|
+
|
|
87
|
+
const result = await run(db, validInput, ctx);
|
|
88
|
+
|
|
89
|
+
expect(result.ok).toBe(false);
|
|
90
|
+
if (!result.ok) {
|
|
91
|
+
expect(result.error).toBeInstanceOf(OperationRequiredError);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("returns error when operation sequence numbers are duplicated", async () => {
|
|
96
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
97
|
+
|
|
98
|
+
// select: Routing lookup
|
|
99
|
+
spies.select.mockReturnValueOnce(baseDraftRouting);
|
|
100
|
+
// select: RoutingOperation list with duplicate sequences
|
|
101
|
+
spies.select.mockReturnValueOnce([
|
|
102
|
+
baseRoutingOperation,
|
|
103
|
+
{ ...baseRoutingOperation, id: "operation-dup", sequenceNumber: 10 },
|
|
104
|
+
]);
|
|
105
|
+
|
|
106
|
+
const result = await run(db, validInput, ctx);
|
|
107
|
+
|
|
108
|
+
expect(result.ok).toBe(false);
|
|
109
|
+
if (!result.ok) {
|
|
110
|
+
expect(result.error).toBeInstanceOf(DuplicateOperationSequenceError);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("returns error when a referenced work center is inactive", async () => {
|
|
115
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
116
|
+
|
|
117
|
+
// select: Routing lookup
|
|
118
|
+
spies.select.mockReturnValueOnce(baseDraftRouting);
|
|
119
|
+
// select: RoutingOperation list
|
|
120
|
+
spies.select.mockReturnValueOnce([baseRoutingOperation]);
|
|
121
|
+
// select: WorkCenter lookup - INACTIVE
|
|
122
|
+
spies.select.mockReturnValueOnce(baseInactiveWorkCenter);
|
|
123
|
+
|
|
124
|
+
const result = await run(db, validInput, ctx);
|
|
125
|
+
|
|
126
|
+
expect(result.ok).toBe(false);
|
|
127
|
+
if (!result.ok) {
|
|
128
|
+
expect(result.error).toBeInstanceOf(WorkCenterNotActiveError);
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("returns error when a referenced work center is outside the routing company", async () => {
|
|
133
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
134
|
+
|
|
135
|
+
// select: Routing lookup
|
|
136
|
+
spies.select.mockReturnValueOnce(baseDraftRouting);
|
|
137
|
+
// select: RoutingOperation list
|
|
138
|
+
spies.select.mockReturnValueOnce([baseRoutingOperation]);
|
|
139
|
+
// select: WorkCenter lookup - different company
|
|
140
|
+
spies.select.mockReturnValueOnce({
|
|
141
|
+
...baseActiveWorkCenter,
|
|
142
|
+
companyId: "other-company",
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const result = await run(db, validInput, ctx);
|
|
146
|
+
|
|
147
|
+
expect(result.ok).toBe(false);
|
|
148
|
+
if (!result.ok) {
|
|
149
|
+
expect(result.error).toBeInstanceOf(CrossCompanyWorkCenterError);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
2
|
+
import {
|
|
3
|
+
RoutingNotFoundError,
|
|
4
|
+
RoutingNotActivatableError,
|
|
5
|
+
OperationRequiredError,
|
|
6
|
+
DuplicateOperationSequenceError,
|
|
7
|
+
WorkCenterNotActiveError,
|
|
8
|
+
CrossCompanyWorkCenterError,
|
|
9
|
+
} from "../lib/errors.generated";
|
|
10
|
+
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
|
|
11
|
+
|
|
12
|
+
export interface ActivateRoutingInput {
|
|
13
|
+
id: string;
|
|
14
|
+
from?: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Function: activateRouting
|
|
19
|
+
*
|
|
20
|
+
* Validates a draft routing and transitions it to ACTIVE status.
|
|
21
|
+
* Enforces operation completeness, sequence integrity, and active
|
|
22
|
+
* work-center readiness.
|
|
23
|
+
*/
|
|
24
|
+
export async function run(db: Transaction, input: ActivateRoutingInput, _ctx: CommandContext) {
|
|
25
|
+
const { id, from = ["DRAFT"] } = input;
|
|
26
|
+
|
|
27
|
+
// 1. Fetch routing
|
|
28
|
+
const routing = await db
|
|
29
|
+
.selectFrom("Routing")
|
|
30
|
+
.selectAll()
|
|
31
|
+
.where("id", "=", id)
|
|
32
|
+
.forUpdate()
|
|
33
|
+
.executeTakeFirst();
|
|
34
|
+
|
|
35
|
+
if (!routing) {
|
|
36
|
+
return err(new RoutingNotFoundError(id));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 2. Verify allowed source status
|
|
40
|
+
if (!from.includes(routing.status)) {
|
|
41
|
+
return err(new RoutingNotActivatableError(id));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 3. Fetch operations
|
|
45
|
+
const operations = await db
|
|
46
|
+
.selectFrom("RoutingOperation")
|
|
47
|
+
.selectAll()
|
|
48
|
+
.where("routingId", "=", id)
|
|
49
|
+
.execute();
|
|
50
|
+
|
|
51
|
+
// 4. At least one operation required
|
|
52
|
+
if (operations.length === 0) {
|
|
53
|
+
return err(new OperationRequiredError(id));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 5. Validate unique sequence numbers
|
|
57
|
+
const seqNumbers = operations.map((op) => op.sequenceNumber);
|
|
58
|
+
const uniqueSeqs = new Set(seqNumbers);
|
|
59
|
+
if (uniqueSeqs.size !== seqNumbers.length) {
|
|
60
|
+
return err(new DuplicateOperationSequenceError(id));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 6. Validate work centers are active and in same company
|
|
64
|
+
for (const op of operations) {
|
|
65
|
+
const workCenter = await db
|
|
66
|
+
.selectFrom("WorkCenter")
|
|
67
|
+
.selectAll()
|
|
68
|
+
.where("id", "=", op.workCenterId)
|
|
69
|
+
.executeTakeFirst();
|
|
70
|
+
|
|
71
|
+
if (workCenter?.status !== "ACTIVE") {
|
|
72
|
+
return err(new WorkCenterNotActiveError(op.workCenterId));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (workCenter.companyId !== routing.companyId) {
|
|
76
|
+
return err(new CrossCompanyWorkCenterError(op.workCenterId));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 7. Set status to ACTIVE
|
|
81
|
+
const updatedRouting = await db
|
|
82
|
+
.updateTable("Routing")
|
|
83
|
+
.set({
|
|
84
|
+
status: "ACTIVE",
|
|
85
|
+
updatedAt: new Date(),
|
|
86
|
+
})
|
|
87
|
+
.where("id", "=", id)
|
|
88
|
+
.returningAll()
|
|
89
|
+
.executeTakeFirst();
|
|
90
|
+
|
|
91
|
+
return ok({ routing: updatedRouting! });
|
|
92
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @generated — do not edit
|
|
2
|
+
import { permissions } from "../lib/permissions.generated";
|
|
3
|
+
import { run } from "./activateWorkCenter";
|
|
4
|
+
import { defineCommand } from "@tailor-platform/erp-kit/module";
|
|
5
|
+
|
|
6
|
+
export const activateWorkCenter = defineCommand(permissions.activateWorkCenter, run);
|