@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,135 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMockDb } from "../../../testing/index";
|
|
3
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
4
|
+
import {
|
|
5
|
+
WorkCenterNotFoundError,
|
|
6
|
+
WorkCenterNotActivatableError,
|
|
7
|
+
MissingCalendarContextError,
|
|
8
|
+
InvalidCapacityError,
|
|
9
|
+
InvalidRateError,
|
|
10
|
+
OverheadCurrencyRequiredError,
|
|
11
|
+
} from "../lib/errors.generated";
|
|
12
|
+
import {
|
|
13
|
+
baseDraftWorkCenter,
|
|
14
|
+
baseActiveWorkCenter,
|
|
15
|
+
baseInactiveWorkCenter,
|
|
16
|
+
} from "../testing/fixtures";
|
|
17
|
+
import { run } from "./activateWorkCenter";
|
|
18
|
+
import type { CommandContext } from "@tailor-platform/erp-kit/module";
|
|
19
|
+
|
|
20
|
+
describe("activateWorkCenter", () => {
|
|
21
|
+
const ctx: CommandContext = {
|
|
22
|
+
actorId: "test-actor",
|
|
23
|
+
permissions: ["manufacturing:activateWorkCenter"],
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
it("activates a valid draft work center", async () => {
|
|
27
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
28
|
+
const activated = { ...baseDraftWorkCenter, status: "ACTIVE" as const };
|
|
29
|
+
|
|
30
|
+
spies.select.mockReturnValueOnce(baseDraftWorkCenter);
|
|
31
|
+
spies.update.mockReturnValue(activated);
|
|
32
|
+
|
|
33
|
+
const result = await run(db, { id: baseDraftWorkCenter.id }, ctx);
|
|
34
|
+
|
|
35
|
+
expect(result.ok).toBe(true);
|
|
36
|
+
if (result.ok) {
|
|
37
|
+
expect(result.value.workCenter.status).toBe("ACTIVE");
|
|
38
|
+
}
|
|
39
|
+
expect(spies.update).toHaveBeenCalled();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("reactivates a valid inactive work center", async () => {
|
|
43
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
44
|
+
const reactivated = { ...baseInactiveWorkCenter, status: "ACTIVE" as const };
|
|
45
|
+
|
|
46
|
+
spies.select.mockReturnValueOnce(baseInactiveWorkCenter);
|
|
47
|
+
spies.update.mockReturnValue(reactivated);
|
|
48
|
+
|
|
49
|
+
const result = await run(db, { id: baseInactiveWorkCenter.id }, ctx);
|
|
50
|
+
|
|
51
|
+
expect(result.ok).toBe(true);
|
|
52
|
+
if (result.ok) {
|
|
53
|
+
expect(result.value.workCenter.status).toBe("ACTIVE");
|
|
54
|
+
}
|
|
55
|
+
expect(spies.update).toHaveBeenCalled();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("returns error when the work center does not exist", async () => {
|
|
59
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
60
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
61
|
+
|
|
62
|
+
const result = await run(db, { id: "nonexistent" }, ctx);
|
|
63
|
+
|
|
64
|
+
expect(result.ok).toBe(false);
|
|
65
|
+
if (!result.ok) {
|
|
66
|
+
expect(result.error).toBeInstanceOf(WorkCenterNotFoundError);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("returns error when the status does not allow activation", async () => {
|
|
71
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
72
|
+
spies.select.mockReturnValueOnce(baseActiveWorkCenter);
|
|
73
|
+
|
|
74
|
+
const result = await run(db, { id: baseActiveWorkCenter.id }, ctx);
|
|
75
|
+
|
|
76
|
+
expect(result.ok).toBe(false);
|
|
77
|
+
if (!result.ok) {
|
|
78
|
+
expect(result.error).toBeInstanceOf(WorkCenterNotActivatableError);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("returns error when required calendar context is missing", async () => {
|
|
83
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
84
|
+
const noCalendar = { ...baseDraftWorkCenter, calendarReference: null };
|
|
85
|
+
spies.select.mockReturnValueOnce(noCalendar);
|
|
86
|
+
|
|
87
|
+
const result = await run(db, { id: baseDraftWorkCenter.id }, ctx);
|
|
88
|
+
|
|
89
|
+
expect(result.ok).toBe(false);
|
|
90
|
+
if (!result.ok) {
|
|
91
|
+
expect(result.error).toBeInstanceOf(MissingCalendarContextError);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("returns error when capacity is not positive", async () => {
|
|
96
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
97
|
+
const zeroCapacity = { ...baseDraftWorkCenter, capacityAssumptions: 0 };
|
|
98
|
+
spies.select.mockReturnValueOnce(zeroCapacity);
|
|
99
|
+
|
|
100
|
+
const result = await run(db, { id: baseDraftWorkCenter.id }, ctx);
|
|
101
|
+
|
|
102
|
+
expect(result.ok).toBe(false);
|
|
103
|
+
if (!result.ok) {
|
|
104
|
+
expect(result.error).toBeInstanceOf(InvalidCapacityError);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("returns error when a configured rate is negative", async () => {
|
|
109
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
110
|
+
spies.select.mockReturnValueOnce({ ...baseDraftWorkCenter, laborRate: -1 });
|
|
111
|
+
|
|
112
|
+
const result = await run(db, { id: baseDraftWorkCenter.id }, ctx);
|
|
113
|
+
|
|
114
|
+
expect(result.ok).toBe(false);
|
|
115
|
+
if (!result.ok) {
|
|
116
|
+
expect(result.error).toBeInstanceOf(InvalidRateError);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("returns error when fixed overhead is missing its currency", async () => {
|
|
121
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
122
|
+
spies.select.mockReturnValueOnce({
|
|
123
|
+
...baseDraftWorkCenter,
|
|
124
|
+
overheadAbsorptionMethod: "FIXED_AMOUNT_PER_GOOD_UNIT",
|
|
125
|
+
overheadAbsorptionCurrency: null,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const result = await run(db, { id: baseDraftWorkCenter.id }, ctx);
|
|
129
|
+
|
|
130
|
+
expect(result.ok).toBe(false);
|
|
131
|
+
if (!result.ok) {
|
|
132
|
+
expect(result.error).toBeInstanceOf(OverheadCurrencyRequiredError);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
2
|
+
import {
|
|
3
|
+
WorkCenterNotFoundError,
|
|
4
|
+
WorkCenterNotActivatableError,
|
|
5
|
+
MissingCalendarContextError,
|
|
6
|
+
InvalidCapacityError,
|
|
7
|
+
InvalidRateError,
|
|
8
|
+
OverheadCurrencyRequiredError,
|
|
9
|
+
} from "../lib/errors.generated";
|
|
10
|
+
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
|
|
11
|
+
|
|
12
|
+
const ACTIVATABLE_STATUSES = ["DRAFT", "INACTIVE"] as const;
|
|
13
|
+
|
|
14
|
+
export interface ActivateWorkCenterInput {
|
|
15
|
+
id: string;
|
|
16
|
+
from?: string[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Function: activateWorkCenter
|
|
21
|
+
*
|
|
22
|
+
* Validates that a draft or inactive work center has the execution data
|
|
23
|
+
* required by planning and costing, then marks it available for routing
|
|
24
|
+
* and work-order use.
|
|
25
|
+
*/
|
|
26
|
+
export async function run<CF extends Record<string, unknown>>(
|
|
27
|
+
db: Transaction,
|
|
28
|
+
input: ActivateWorkCenterInput & CF,
|
|
29
|
+
_ctx: CommandContext,
|
|
30
|
+
) {
|
|
31
|
+
const { id, from, ...customFields } = input;
|
|
32
|
+
void customFields;
|
|
33
|
+
|
|
34
|
+
const allowedStatuses = from ?? [...ACTIVATABLE_STATUSES];
|
|
35
|
+
|
|
36
|
+
// 1. Fetch work center with lock
|
|
37
|
+
const workCenter = await db
|
|
38
|
+
.selectFrom("WorkCenter")
|
|
39
|
+
.selectAll()
|
|
40
|
+
.where("id", "=", id)
|
|
41
|
+
.forUpdate()
|
|
42
|
+
.executeTakeFirst();
|
|
43
|
+
|
|
44
|
+
if (!workCenter) {
|
|
45
|
+
return err(new WorkCenterNotFoundError(id));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 2. Validate status is activatable
|
|
49
|
+
if (!allowedStatuses.includes(workCenter.status)) {
|
|
50
|
+
return err(new WorkCenterNotActivatableError(workCenter.code));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 3. Validate calendar context
|
|
54
|
+
if (!workCenter.calendarReference) {
|
|
55
|
+
return err(new MissingCalendarContextError(workCenter.code));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 4. Validate capacity > 0
|
|
59
|
+
if (workCenter.capacityAssumptions <= 0) {
|
|
60
|
+
return err(new InvalidCapacityError(workCenter.code));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 5. Validate rates >= 0
|
|
64
|
+
if (workCenter.laborRate != null && workCenter.laborRate < 0) {
|
|
65
|
+
return err(new InvalidRateError(workCenter.code));
|
|
66
|
+
}
|
|
67
|
+
if (workCenter.machineRate != null && workCenter.machineRate < 0) {
|
|
68
|
+
return err(new InvalidRateError(workCenter.code));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 6. Validate overhead currency for FIXED_AMOUNT_PER_GOOD_UNIT
|
|
72
|
+
if (
|
|
73
|
+
workCenter.overheadAbsorptionMethod === "FIXED_AMOUNT_PER_GOOD_UNIT" &&
|
|
74
|
+
!workCenter.overheadAbsorptionCurrency
|
|
75
|
+
) {
|
|
76
|
+
return err(new OverheadCurrencyRequiredError(workCenter.code));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 7. Set status to ACTIVE
|
|
80
|
+
const updatedWorkCenter = await db
|
|
81
|
+
.updateTable("WorkCenter")
|
|
82
|
+
.set({
|
|
83
|
+
status: "ACTIVE",
|
|
84
|
+
updatedAt: new Date(),
|
|
85
|
+
})
|
|
86
|
+
.where("id", "=", id)
|
|
87
|
+
.returningAll()
|
|
88
|
+
.executeTakeFirst();
|
|
89
|
+
|
|
90
|
+
return ok({ workCenter: updatedWorkCenter! });
|
|
91
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @generated — do not edit
|
|
2
|
+
import { permissions } from "../lib/permissions.generated";
|
|
3
|
+
import { run } from "./cancelProductionOrder";
|
|
4
|
+
import { defineCommand } from "@tailor-platform/erp-kit/module";
|
|
5
|
+
|
|
6
|
+
export const cancelProductionOrder = defineCommand(permissions.cancelProductionOrder, run);
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMockDb } from "../../../testing/index";
|
|
3
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
4
|
+
import {
|
|
5
|
+
ProductionOrderNotFoundError,
|
|
6
|
+
ProductionOrderNotCancellableError,
|
|
7
|
+
ExecutionAlreadyStartedError,
|
|
8
|
+
InventoryHandoffExistsError,
|
|
9
|
+
} from "../lib/errors.generated";
|
|
10
|
+
import {
|
|
11
|
+
baseDraftProductionOrder,
|
|
12
|
+
baseReleasedProductionOrder,
|
|
13
|
+
baseInProgressProductionOrder,
|
|
14
|
+
basePendingWorkOrder,
|
|
15
|
+
baseInProgressWorkOrder,
|
|
16
|
+
baseCollectingCostSummary,
|
|
17
|
+
} from "../testing/fixtures";
|
|
18
|
+
import { run } from "./cancelProductionOrder";
|
|
19
|
+
import type { CommandContext } from "@tailor-platform/erp-kit/module";
|
|
20
|
+
|
|
21
|
+
describe("cancelProductionOrder", () => {
|
|
22
|
+
const ctx: CommandContext = {
|
|
23
|
+
actorId: "test-actor",
|
|
24
|
+
permissions: ["manufacturing:cancelProductionOrder"],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
it("cancels a draft production order", async () => {
|
|
28
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
29
|
+
const cancelled = { ...baseDraftProductionOrder, status: "CANCELLED" as const };
|
|
30
|
+
|
|
31
|
+
// order lookup
|
|
32
|
+
spies.select.mockReturnValueOnce(baseDraftProductionOrder);
|
|
33
|
+
// update order status
|
|
34
|
+
spies.update.mockReturnValue(cancelled);
|
|
35
|
+
|
|
36
|
+
const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
|
|
37
|
+
|
|
38
|
+
expect(result.ok).toBe(true);
|
|
39
|
+
if (result.ok) {
|
|
40
|
+
expect(result.value.productionOrder.status).toBe("CANCELLED");
|
|
41
|
+
}
|
|
42
|
+
expect(spies.update).toHaveBeenCalled();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("cancels a released order before execution starts", async () => {
|
|
46
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
47
|
+
const cancelled = { ...baseReleasedProductionOrder, status: "CANCELLED" as const };
|
|
48
|
+
|
|
49
|
+
// order lookup
|
|
50
|
+
spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
|
|
51
|
+
// work orders - all pending
|
|
52
|
+
spies.select.mockReturnValueOnce([basePendingWorkOrder]);
|
|
53
|
+
// cost summary - no actuals
|
|
54
|
+
spies.select.mockReturnValueOnce(baseCollectingCostSummary);
|
|
55
|
+
// cascade cancel work orders
|
|
56
|
+
spies.update.mockReturnValueOnce(undefined);
|
|
57
|
+
// update order status
|
|
58
|
+
spies.update.mockReturnValue(cancelled);
|
|
59
|
+
|
|
60
|
+
const result = await run(db, { id: baseReleasedProductionOrder.id }, ctx);
|
|
61
|
+
|
|
62
|
+
expect(result.ok).toBe(true);
|
|
63
|
+
if (result.ok) {
|
|
64
|
+
expect(result.value.productionOrder.status).toBe("CANCELLED");
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("returns error when the order does not exist", async () => {
|
|
69
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
70
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
71
|
+
|
|
72
|
+
const result = await run(db, { id: "nonexistent" }, ctx);
|
|
73
|
+
|
|
74
|
+
expect(result.ok).toBe(false);
|
|
75
|
+
if (!result.ok) {
|
|
76
|
+
expect(result.error).toBeInstanceOf(ProductionOrderNotFoundError);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("returns error when the status does not allow cancellation", async () => {
|
|
81
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
82
|
+
spies.select.mockReturnValueOnce(baseInProgressProductionOrder);
|
|
83
|
+
|
|
84
|
+
const result = await run(db, { id: baseInProgressProductionOrder.id }, ctx);
|
|
85
|
+
|
|
86
|
+
expect(result.ok).toBe(false);
|
|
87
|
+
if (!result.ok) {
|
|
88
|
+
expect(result.error).toBeInstanceOf(ProductionOrderNotCancellableError);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("returns error when execution evidence already exists", async () => {
|
|
93
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
94
|
+
|
|
95
|
+
// order lookup
|
|
96
|
+
spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
|
|
97
|
+
// work orders - one in progress
|
|
98
|
+
spies.select.mockReturnValueOnce([baseInProgressWorkOrder]);
|
|
99
|
+
|
|
100
|
+
const result = await run(db, { id: baseReleasedProductionOrder.id }, ctx);
|
|
101
|
+
|
|
102
|
+
expect(result.ok).toBe(false);
|
|
103
|
+
if (!result.ok) {
|
|
104
|
+
expect(result.error).toBeInstanceOf(ExecutionAlreadyStartedError);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("returns error when inventory handoff evidence already exists", async () => {
|
|
109
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
110
|
+
const costSummaryWithActuals = {
|
|
111
|
+
...baseCollectingCostSummary,
|
|
112
|
+
actualMaterialCost: 500,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// order lookup
|
|
116
|
+
spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
|
|
117
|
+
// work orders - all pending
|
|
118
|
+
spies.select.mockReturnValueOnce([basePendingWorkOrder]);
|
|
119
|
+
// cost summary with actual costs
|
|
120
|
+
spies.select.mockReturnValueOnce(costSummaryWithActuals);
|
|
121
|
+
|
|
122
|
+
const result = await run(db, { id: baseReleasedProductionOrder.id }, ctx);
|
|
123
|
+
|
|
124
|
+
expect(result.ok).toBe(false);
|
|
125
|
+
if (!result.ok) {
|
|
126
|
+
expect(result.error).toBeInstanceOf(InventoryHandoffExistsError);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("cascades cancellation to pending work orders", async () => {
|
|
131
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
132
|
+
const cancelled = { ...baseReleasedProductionOrder, status: "CANCELLED" as const };
|
|
133
|
+
|
|
134
|
+
// order lookup
|
|
135
|
+
spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
|
|
136
|
+
// work orders - all pending
|
|
137
|
+
spies.select.mockReturnValueOnce([basePendingWorkOrder]);
|
|
138
|
+
// cost summary - no actuals
|
|
139
|
+
spies.select.mockReturnValueOnce(baseCollectingCostSummary);
|
|
140
|
+
// cascade cancel work orders (first update call)
|
|
141
|
+
spies.update.mockReturnValueOnce(undefined);
|
|
142
|
+
// update order status (second update call)
|
|
143
|
+
spies.update.mockReturnValue(cancelled);
|
|
144
|
+
|
|
145
|
+
const result = await run(db, { id: baseReleasedProductionOrder.id }, ctx);
|
|
146
|
+
|
|
147
|
+
expect(result.ok).toBe(true);
|
|
148
|
+
// update should be called at least twice: once for work orders, once for order
|
|
149
|
+
expect(spies.update).toHaveBeenCalled();
|
|
150
|
+
});
|
|
151
|
+
});
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
2
|
+
import {
|
|
3
|
+
ProductionOrderNotFoundError,
|
|
4
|
+
ProductionOrderNotCancellableError,
|
|
5
|
+
ExecutionAlreadyStartedError,
|
|
6
|
+
InventoryHandoffExistsError,
|
|
7
|
+
} from "../lib/errors.generated";
|
|
8
|
+
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
|
|
9
|
+
|
|
10
|
+
const CANCELLABLE_STATUSES = ["DRAFT", "RELEASED"] as const;
|
|
11
|
+
|
|
12
|
+
export interface CancelProductionOrderInput {
|
|
13
|
+
id: string;
|
|
14
|
+
from?: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Function: cancelProductionOrder
|
|
19
|
+
*
|
|
20
|
+
* Abandons a draft or not-yet-started released order. Preserves audit
|
|
21
|
+
* history while blocking further execution updates and cascading
|
|
22
|
+
* cancellation to pending work orders.
|
|
23
|
+
*/
|
|
24
|
+
export async function run<CF extends Record<string, unknown>>(
|
|
25
|
+
db: Transaction,
|
|
26
|
+
input: CancelProductionOrderInput & CF,
|
|
27
|
+
_ctx: CommandContext,
|
|
28
|
+
) {
|
|
29
|
+
const { id, from, ...customFields } = input;
|
|
30
|
+
void customFields;
|
|
31
|
+
|
|
32
|
+
const allowedStatuses = from ?? [...CANCELLABLE_STATUSES];
|
|
33
|
+
|
|
34
|
+
// 1. Fetch production order with lock
|
|
35
|
+
const order = await db
|
|
36
|
+
.selectFrom("ProductionOrder")
|
|
37
|
+
.selectAll()
|
|
38
|
+
.where("id", "=", id)
|
|
39
|
+
.forUpdate()
|
|
40
|
+
.executeTakeFirst();
|
|
41
|
+
|
|
42
|
+
if (!order) {
|
|
43
|
+
return err(new ProductionOrderNotFoundError(id));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 2. Validate status is cancellable
|
|
47
|
+
if (!allowedStatuses.includes(order.status)) {
|
|
48
|
+
return err(new ProductionOrderNotCancellableError(id));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 3. For released orders, check execution and inventory evidence
|
|
52
|
+
if (order.status === "RELEASED") {
|
|
53
|
+
// Check work orders for execution evidence
|
|
54
|
+
const workOrders = await db
|
|
55
|
+
.selectFrom("WorkOrder")
|
|
56
|
+
.selectAll()
|
|
57
|
+
.where("productionOrderId", "=", id)
|
|
58
|
+
.execute();
|
|
59
|
+
|
|
60
|
+
const hasExecution = workOrders.some(
|
|
61
|
+
(wo) =>
|
|
62
|
+
wo.status === "IN_PROGRESS" ||
|
|
63
|
+
wo.status === "PAUSED" ||
|
|
64
|
+
wo.status === "COMPLETE" ||
|
|
65
|
+
wo.completedQuantity > 0 ||
|
|
66
|
+
wo.actualStartDate != null,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (hasExecution) {
|
|
70
|
+
return err(new ExecutionAlreadyStartedError(id));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check for inventory handoff evidence
|
|
74
|
+
const costSummary = await db
|
|
75
|
+
.selectFrom("ManufacturingCostSummary")
|
|
76
|
+
.selectAll()
|
|
77
|
+
.where("productionOrderId", "=", id)
|
|
78
|
+
.executeTakeFirst();
|
|
79
|
+
|
|
80
|
+
if (
|
|
81
|
+
costSummary &&
|
|
82
|
+
(costSummary.actualMaterialCost > 0 ||
|
|
83
|
+
costSummary.actualLaborCost > 0 ||
|
|
84
|
+
costSummary.actualMachineCost > 0 ||
|
|
85
|
+
costSummary.actualOverheadCost > 0)
|
|
86
|
+
) {
|
|
87
|
+
return err(new InventoryHandoffExistsError(id));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 4. Cascade cancellation to pending work orders
|
|
91
|
+
await db
|
|
92
|
+
.updateTable("WorkOrder")
|
|
93
|
+
.set({
|
|
94
|
+
status: "CANCELLED",
|
|
95
|
+
updatedAt: new Date(),
|
|
96
|
+
})
|
|
97
|
+
.where("productionOrderId", "=", id)
|
|
98
|
+
.where("status", "=", "PENDING")
|
|
99
|
+
.execute();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 5. Set order status to CANCELLED
|
|
103
|
+
const cancelledOrder = await db
|
|
104
|
+
.updateTable("ProductionOrder")
|
|
105
|
+
.set({
|
|
106
|
+
status: "CANCELLED",
|
|
107
|
+
updatedAt: new Date(),
|
|
108
|
+
})
|
|
109
|
+
.where("id", "=", id)
|
|
110
|
+
.returningAll()
|
|
111
|
+
.executeTakeFirstOrThrow();
|
|
112
|
+
|
|
113
|
+
return ok({ productionOrder: cancelledOrder });
|
|
114
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @generated — do not edit
|
|
2
|
+
import { permissions } from "../lib/permissions.generated";
|
|
3
|
+
import { run } from "./closeProductionOrder";
|
|
4
|
+
import { defineCommand } from "@tailor-platform/erp-kit/module";
|
|
5
|
+
|
|
6
|
+
export const closeProductionOrder = defineCommand(permissions.closeProductionOrder, run);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMockDb } from "../../../testing/index";
|
|
3
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
4
|
+
import {
|
|
5
|
+
ProductionOrderNotFoundError,
|
|
6
|
+
ProductionOrderNotClosableError,
|
|
7
|
+
OpenWorkRemainsError,
|
|
8
|
+
CostSummaryNotSettledError,
|
|
9
|
+
} from "../lib/errors.generated";
|
|
10
|
+
import {
|
|
11
|
+
baseTechCompleteProductionOrder,
|
|
12
|
+
baseCompletedProductionOrder,
|
|
13
|
+
baseClosedProductionOrder,
|
|
14
|
+
baseCompleteWorkOrder,
|
|
15
|
+
baseCancelledWorkOrder,
|
|
16
|
+
basePendingWorkOrder,
|
|
17
|
+
baseSettledCostSummary,
|
|
18
|
+
basePendingReviewCostSummary,
|
|
19
|
+
} from "../testing/fixtures";
|
|
20
|
+
import { run } from "./closeProductionOrder";
|
|
21
|
+
import type { CommandContext } from "@tailor-platform/erp-kit/module";
|
|
22
|
+
|
|
23
|
+
describe("closeProductionOrder", () => {
|
|
24
|
+
const ctx: CommandContext = {
|
|
25
|
+
actorId: "test-actor",
|
|
26
|
+
permissions: ["manufacturing:closeProductionOrder"],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
it("closes a technically complete order with a settled cost summary", async () => {
|
|
30
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
31
|
+
const closed = { ...baseTechCompleteProductionOrder, status: "CLOSED" as const };
|
|
32
|
+
|
|
33
|
+
// order lookup
|
|
34
|
+
spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
|
|
35
|
+
// work orders - all complete or cancelled
|
|
36
|
+
spies.select.mockReturnValueOnce([baseCompleteWorkOrder, baseCancelledWorkOrder]);
|
|
37
|
+
// cost summary in SETTLED
|
|
38
|
+
spies.select.mockReturnValueOnce({
|
|
39
|
+
...baseSettledCostSummary,
|
|
40
|
+
productionOrderId: baseTechCompleteProductionOrder.id,
|
|
41
|
+
});
|
|
42
|
+
// update order status
|
|
43
|
+
spies.update.mockReturnValue(closed);
|
|
44
|
+
|
|
45
|
+
const result = await run(db, { id: baseTechCompleteProductionOrder.id }, ctx);
|
|
46
|
+
|
|
47
|
+
expect(result.ok).toBe(true);
|
|
48
|
+
if (result.ok) {
|
|
49
|
+
expect(result.value.productionOrder.status).toBe("CLOSED");
|
|
50
|
+
}
|
|
51
|
+
expect(spies.update).toHaveBeenCalled();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("returns error when the order does not exist", async () => {
|
|
55
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
56
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
57
|
+
|
|
58
|
+
const result = await run(db, { id: "nonexistent" }, ctx);
|
|
59
|
+
|
|
60
|
+
expect(result.ok).toBe(false);
|
|
61
|
+
if (!result.ok) {
|
|
62
|
+
expect(result.error).toBeInstanceOf(ProductionOrderNotFoundError);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("returns error when the order is not technically complete", async () => {
|
|
67
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
68
|
+
spies.select.mockReturnValueOnce(baseCompletedProductionOrder);
|
|
69
|
+
|
|
70
|
+
const result = await run(db, { id: baseCompletedProductionOrder.id }, ctx);
|
|
71
|
+
|
|
72
|
+
expect(result.ok).toBe(false);
|
|
73
|
+
if (!result.ok) {
|
|
74
|
+
expect(result.error).toBeInstanceOf(ProductionOrderNotClosableError);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("returns error when open work or execution exceptions remain", async () => {
|
|
79
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
80
|
+
|
|
81
|
+
// order lookup
|
|
82
|
+
spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
|
|
83
|
+
// work orders - one still pending
|
|
84
|
+
spies.select.mockReturnValueOnce([baseCompleteWorkOrder, basePendingWorkOrder]);
|
|
85
|
+
|
|
86
|
+
const result = await run(db, { id: baseTechCompleteProductionOrder.id }, ctx);
|
|
87
|
+
|
|
88
|
+
expect(result.ok).toBe(false);
|
|
89
|
+
if (!result.ok) {
|
|
90
|
+
expect(result.error).toBeInstanceOf(OpenWorkRemainsError);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("returns error when the linked cost summary is not settled", async () => {
|
|
95
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
96
|
+
|
|
97
|
+
// order lookup
|
|
98
|
+
spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
|
|
99
|
+
// work orders - all complete
|
|
100
|
+
spies.select.mockReturnValueOnce([baseCompleteWorkOrder]);
|
|
101
|
+
// cost summary not settled
|
|
102
|
+
spies.select.mockReturnValueOnce({
|
|
103
|
+
...basePendingReviewCostSummary,
|
|
104
|
+
productionOrderId: baseTechCompleteProductionOrder.id,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const result = await run(db, { id: baseTechCompleteProductionOrder.id }, ctx);
|
|
108
|
+
|
|
109
|
+
expect(result.ok).toBe(false);
|
|
110
|
+
if (!result.ok) {
|
|
111
|
+
expect(result.error).toBeInstanceOf(CostSummaryNotSettledError);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("blocks further execution commands after close", async () => {
|
|
116
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
117
|
+
spies.select.mockReturnValueOnce(baseClosedProductionOrder);
|
|
118
|
+
|
|
119
|
+
const result = await run(db, { id: baseClosedProductionOrder.id }, ctx);
|
|
120
|
+
|
|
121
|
+
expect(result.ok).toBe(false);
|
|
122
|
+
if (!result.ok) {
|
|
123
|
+
expect(result.error).toBeInstanceOf(ProductionOrderNotClosableError);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
});
|