@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
|
@@ -0,0 +1,196 @@
|
|
|
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
|
+
ProductionOrderNotReopenableError,
|
|
7
|
+
ReopenReasonRequiredError,
|
|
8
|
+
CostSummaryNotReopenableError,
|
|
9
|
+
OrderAlreadyClosedError,
|
|
10
|
+
} from "../lib/errors.generated";
|
|
11
|
+
import {
|
|
12
|
+
baseTechCompleteProductionOrder,
|
|
13
|
+
baseCompletedProductionOrder,
|
|
14
|
+
baseClosedProductionOrder,
|
|
15
|
+
basePendingReviewCostSummary,
|
|
16
|
+
baseReviewedCostSummary,
|
|
17
|
+
baseSettledCostSummary,
|
|
18
|
+
} from "../testing/fixtures";
|
|
19
|
+
import { run } from "./reopenProductionOrder";
|
|
20
|
+
import type { CommandContext } from "@tailor-platform/erp-kit/module";
|
|
21
|
+
|
|
22
|
+
describe("reopenProductionOrder", () => {
|
|
23
|
+
const ctx: CommandContext = {
|
|
24
|
+
actorId: "test-actor",
|
|
25
|
+
permissions: ["manufacturing:reopenProductionOrder"],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const validInput = {
|
|
29
|
+
id: baseTechCompleteProductionOrder.id,
|
|
30
|
+
reason: "Additional rework needed for quality defects",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
it("reopens a technically complete order for additional execution", async () => {
|
|
34
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
35
|
+
const reopened = { ...baseTechCompleteProductionOrder, status: "IN_PROGRESS" as const };
|
|
36
|
+
|
|
37
|
+
// order lookup
|
|
38
|
+
spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
|
|
39
|
+
// cost summary in PENDING_VARIANCE_REVIEW
|
|
40
|
+
spies.select.mockReturnValueOnce({
|
|
41
|
+
...basePendingReviewCostSummary,
|
|
42
|
+
productionOrderId: baseTechCompleteProductionOrder.id,
|
|
43
|
+
});
|
|
44
|
+
// update cost summary
|
|
45
|
+
spies.update.mockReturnValueOnce(undefined);
|
|
46
|
+
// update order status
|
|
47
|
+
spies.update.mockReturnValue(reopened);
|
|
48
|
+
|
|
49
|
+
const result = await run(db, validInput, ctx);
|
|
50
|
+
|
|
51
|
+
expect(result.ok).toBe(true);
|
|
52
|
+
if (result.ok) {
|
|
53
|
+
expect(result.value.productionOrder.status).toBe("IN_PROGRESS");
|
|
54
|
+
}
|
|
55
|
+
expect(spies.update).toHaveBeenCalled();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("returns error when the order does not exist", async () => {
|
|
59
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
60
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
61
|
+
|
|
62
|
+
const result = await run(db, { id: "nonexistent", reason: "rework" }, ctx);
|
|
63
|
+
|
|
64
|
+
expect(result.ok).toBe(false);
|
|
65
|
+
if (!result.ok) {
|
|
66
|
+
expect(result.error).toBeInstanceOf(ProductionOrderNotFoundError);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("returns error when the order is not technically complete", async () => {
|
|
71
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
72
|
+
spies.select.mockReturnValueOnce(baseCompletedProductionOrder);
|
|
73
|
+
|
|
74
|
+
const result = await run(db, { id: baseCompletedProductionOrder.id, reason: "rework" }, ctx);
|
|
75
|
+
|
|
76
|
+
expect(result.ok).toBe(false);
|
|
77
|
+
if (!result.ok) {
|
|
78
|
+
expect(result.error).toBeInstanceOf(ProductionOrderNotReopenableError);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("returns error when no reopen reason is provided", async () => {
|
|
83
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
84
|
+
spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
|
|
85
|
+
|
|
86
|
+
const result = await run(db, { id: baseTechCompleteProductionOrder.id, reason: "" }, ctx);
|
|
87
|
+
|
|
88
|
+
expect(result.ok).toBe(false);
|
|
89
|
+
if (!result.ok) {
|
|
90
|
+
expect(result.error).toBeInstanceOf(ReopenReasonRequiredError);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("reopens an order when the linked cost summary is PENDING_VARIANCE_REVIEW", async () => {
|
|
95
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
96
|
+
const reopened = { ...baseTechCompleteProductionOrder, status: "IN_PROGRESS" as const };
|
|
97
|
+
|
|
98
|
+
// order lookup
|
|
99
|
+
spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
|
|
100
|
+
// cost summary in PENDING_VARIANCE_REVIEW
|
|
101
|
+
spies.select.mockReturnValueOnce({
|
|
102
|
+
...basePendingReviewCostSummary,
|
|
103
|
+
productionOrderId: baseTechCompleteProductionOrder.id,
|
|
104
|
+
});
|
|
105
|
+
// update cost summary
|
|
106
|
+
spies.update.mockReturnValueOnce(undefined);
|
|
107
|
+
// update order status
|
|
108
|
+
spies.update.mockReturnValue(reopened);
|
|
109
|
+
|
|
110
|
+
const result = await run(db, validInput, ctx);
|
|
111
|
+
|
|
112
|
+
expect(result.ok).toBe(true);
|
|
113
|
+
if (result.ok) {
|
|
114
|
+
expect(result.value.productionOrder.status).toBe("IN_PROGRESS");
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("reopens an order when the linked cost summary is VARIANCE_REVIEWED", async () => {
|
|
119
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
120
|
+
const reopened = { ...baseTechCompleteProductionOrder, status: "IN_PROGRESS" as const };
|
|
121
|
+
|
|
122
|
+
// order lookup
|
|
123
|
+
spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
|
|
124
|
+
// cost summary in VARIANCE_REVIEWED
|
|
125
|
+
spies.select.mockReturnValueOnce({
|
|
126
|
+
...baseReviewedCostSummary,
|
|
127
|
+
productionOrderId: baseTechCompleteProductionOrder.id,
|
|
128
|
+
});
|
|
129
|
+
// update cost summary
|
|
130
|
+
spies.update.mockReturnValueOnce(undefined);
|
|
131
|
+
// update order status
|
|
132
|
+
spies.update.mockReturnValue(reopened);
|
|
133
|
+
|
|
134
|
+
const result = await run(db, validInput, ctx);
|
|
135
|
+
|
|
136
|
+
expect(result.ok).toBe(true);
|
|
137
|
+
if (result.ok) {
|
|
138
|
+
expect(result.value.productionOrder.status).toBe("IN_PROGRESS");
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("returns error when the linked cost summary is already SETTLED or otherwise not reopenable", async () => {
|
|
143
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
144
|
+
|
|
145
|
+
// order lookup
|
|
146
|
+
spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
|
|
147
|
+
// cost summary already SETTLED
|
|
148
|
+
spies.select.mockReturnValueOnce({
|
|
149
|
+
...baseSettledCostSummary,
|
|
150
|
+
productionOrderId: baseTechCompleteProductionOrder.id,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const result = await run(db, validInput, ctx);
|
|
154
|
+
|
|
155
|
+
expect(result.ok).toBe(false);
|
|
156
|
+
if (!result.ok) {
|
|
157
|
+
expect(result.error).toBeInstanceOf(CostSummaryNotReopenableError);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("returns error when the order is already closed", async () => {
|
|
162
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
163
|
+
spies.select.mockReturnValueOnce(baseClosedProductionOrder);
|
|
164
|
+
|
|
165
|
+
const result = await run(db, { id: baseClosedProductionOrder.id, reason: "rework" }, ctx);
|
|
166
|
+
|
|
167
|
+
expect(result.ok).toBe(false);
|
|
168
|
+
if (!result.ok) {
|
|
169
|
+
expect(result.error).toBeInstanceOf(OrderAlreadyClosedError);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("returns the linked cost summary to collecting on reopen", async () => {
|
|
174
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
175
|
+
const reopened = { ...baseTechCompleteProductionOrder, status: "IN_PROGRESS" as const };
|
|
176
|
+
|
|
177
|
+
// order lookup
|
|
178
|
+
spies.select.mockReturnValueOnce(baseTechCompleteProductionOrder);
|
|
179
|
+
// cost summary in PENDING_VARIANCE_REVIEW
|
|
180
|
+
spies.select.mockReturnValueOnce({
|
|
181
|
+
...basePendingReviewCostSummary,
|
|
182
|
+
productionOrderId: baseTechCompleteProductionOrder.id,
|
|
183
|
+
});
|
|
184
|
+
// update cost summary to COLLECTING
|
|
185
|
+
spies.update.mockReturnValueOnce(undefined);
|
|
186
|
+
// update order status
|
|
187
|
+
spies.update.mockReturnValue(reopened);
|
|
188
|
+
|
|
189
|
+
const result = await run(db, validInput, ctx);
|
|
190
|
+
|
|
191
|
+
expect(result.ok).toBe(true);
|
|
192
|
+
// The first update call should be for cost summary (back to COLLECTING)
|
|
193
|
+
// The second update call should be for order status (to IN_PROGRESS)
|
|
194
|
+
expect(spies.update).toHaveBeenCalledTimes(2);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
2
|
+
import {
|
|
3
|
+
ProductionOrderNotFoundError,
|
|
4
|
+
ProductionOrderNotReopenableError,
|
|
5
|
+
ReopenReasonRequiredError,
|
|
6
|
+
CostSummaryNotReopenableError,
|
|
7
|
+
OrderAlreadyClosedError,
|
|
8
|
+
} from "../lib/errors.generated";
|
|
9
|
+
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
|
|
10
|
+
|
|
11
|
+
export interface ReopenProductionOrderInput {
|
|
12
|
+
id: string;
|
|
13
|
+
reason: string;
|
|
14
|
+
from?: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Function: reopenProductionOrder
|
|
19
|
+
*
|
|
20
|
+
* Re-enables execution after a technically complete order needs more
|
|
21
|
+
* shop-floor work. It reverses the execution freeze and returns the linked
|
|
22
|
+
* cost summary to active collection.
|
|
23
|
+
*/
|
|
24
|
+
export async function run<CF extends Record<string, unknown>>(
|
|
25
|
+
db: Transaction,
|
|
26
|
+
input: ReopenProductionOrderInput & CF,
|
|
27
|
+
_ctx: CommandContext,
|
|
28
|
+
) {
|
|
29
|
+
const { id, reason, from, ...customFields } = input;
|
|
30
|
+
void customFields;
|
|
31
|
+
|
|
32
|
+
const allowedStatuses = from ?? ["TECHNICALLY_COMPLETE"];
|
|
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. Check if order is already closed
|
|
47
|
+
if (order.status === "CLOSED") {
|
|
48
|
+
return err(new OrderAlreadyClosedError(id));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 3. Validate status is reopenable
|
|
52
|
+
if (!allowedStatuses.includes(order.status)) {
|
|
53
|
+
return err(new ProductionOrderNotReopenableError(id));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 4. Validate reopen reason
|
|
57
|
+
if (!reason || reason.trim() === "") {
|
|
58
|
+
return err(new ReopenReasonRequiredError(id));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 5. Find and validate cost summary
|
|
62
|
+
const costSummary = await db
|
|
63
|
+
.selectFrom("ManufacturingCostSummary")
|
|
64
|
+
.selectAll()
|
|
65
|
+
.where("productionOrderId", "=", id)
|
|
66
|
+
.forUpdate()
|
|
67
|
+
.executeTakeFirst();
|
|
68
|
+
|
|
69
|
+
if (
|
|
70
|
+
!costSummary ||
|
|
71
|
+
(costSummary.status !== "PENDING_VARIANCE_REVIEW" && costSummary.status !== "VARIANCE_REVIEWED")
|
|
72
|
+
) {
|
|
73
|
+
return err(new CostSummaryNotReopenableError(id));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 6. Return cost summary to COLLECTING
|
|
77
|
+
await db
|
|
78
|
+
.updateTable("ManufacturingCostSummary")
|
|
79
|
+
.set({
|
|
80
|
+
status: "COLLECTING",
|
|
81
|
+
updatedAt: new Date(),
|
|
82
|
+
})
|
|
83
|
+
.where("id", "=", costSummary.id)
|
|
84
|
+
.execute();
|
|
85
|
+
|
|
86
|
+
// 7. Set order status to IN_PROGRESS
|
|
87
|
+
const reopenedOrder = await db
|
|
88
|
+
.updateTable("ProductionOrder")
|
|
89
|
+
.set({
|
|
90
|
+
status: "IN_PROGRESS",
|
|
91
|
+
updatedAt: new Date(),
|
|
92
|
+
})
|
|
93
|
+
.where("id", "=", id)
|
|
94
|
+
.returningAll()
|
|
95
|
+
.executeTakeFirstOrThrow();
|
|
96
|
+
|
|
97
|
+
return ok({ productionOrder: reopenedOrder });
|
|
98
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @generated — do not edit
|
|
2
|
+
import { permissions } from "../lib/permissions.generated";
|
|
3
|
+
import { run } from "./reportWorkOrderProgress";
|
|
4
|
+
import { defineCommand } from "@tailor-platform/erp-kit/module";
|
|
5
|
+
|
|
6
|
+
export const reportWorkOrderProgress = defineCommand(permissions.reportWorkOrderProgress, run);
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMockDb } from "../../../testing/index";
|
|
3
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
4
|
+
import {
|
|
5
|
+
WorkOrderNotFoundError,
|
|
6
|
+
WorkOrderNotReportableError,
|
|
7
|
+
InvalidReportedQuantityError,
|
|
8
|
+
EmptyProgressTransactionError,
|
|
9
|
+
ScrapHandoffRequiredError,
|
|
10
|
+
} from "../lib/errors.generated";
|
|
11
|
+
import { baseInProgressWorkOrder, basePendingWorkOrder } from "../testing/fixtures";
|
|
12
|
+
import { run } from "./reportWorkOrderProgress";
|
|
13
|
+
import type { CommandContext } from "@tailor-platform/erp-kit/module";
|
|
14
|
+
|
|
15
|
+
describe("reportWorkOrderProgress", () => {
|
|
16
|
+
const ctx: CommandContext = {
|
|
17
|
+
actorId: "test-actor",
|
|
18
|
+
permissions: ["manufacturing:reportWorkOrderProgress"],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
it("records partial completed quantity and time on an in-progress work order", async () => {
|
|
22
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
23
|
+
const updatedWorkOrder = {
|
|
24
|
+
...baseInProgressWorkOrder,
|
|
25
|
+
completedQuantity: baseInProgressWorkOrder.completedQuantity + 10,
|
|
26
|
+
actualRunTime: baseInProgressWorkOrder.actualRunTime + 30,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// work order lookup
|
|
30
|
+
spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
|
|
31
|
+
// update work order
|
|
32
|
+
spies.update.mockReturnValueOnce(updatedWorkOrder);
|
|
33
|
+
// insert execution event
|
|
34
|
+
spies.insert.mockReturnValueOnce(undefined);
|
|
35
|
+
// select: parent order lookup for rollup
|
|
36
|
+
spies.select.mockReturnValueOnce({ id: "production-order-2", status: "IN_PROGRESS" });
|
|
37
|
+
|
|
38
|
+
const result = await run(
|
|
39
|
+
db,
|
|
40
|
+
{ id: baseInProgressWorkOrder.id, completedQuantity: 10, actualRunTime: 30 },
|
|
41
|
+
ctx,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
expect(result.ok).toBe(true);
|
|
45
|
+
if (result.ok) {
|
|
46
|
+
expect(result.value.workOrder.completedQuantity).toBe(
|
|
47
|
+
baseInProgressWorkOrder.completedQuantity + 10,
|
|
48
|
+
);
|
|
49
|
+
expect(result.value.workOrder.actualRunTime).toBe(baseInProgressWorkOrder.actualRunTime + 30);
|
|
50
|
+
}
|
|
51
|
+
expect(spies.update).toHaveBeenCalled();
|
|
52
|
+
expect(spies.insert).toHaveBeenCalled();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("returns error when the work order does not exist", async () => {
|
|
56
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
57
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
58
|
+
|
|
59
|
+
const result = await run(db, { id: "nonexistent", completedQuantity: 5 }, ctx);
|
|
60
|
+
|
|
61
|
+
expect(result.ok).toBe(false);
|
|
62
|
+
if (!result.ok) {
|
|
63
|
+
expect(result.error).toBeInstanceOf(WorkOrderNotFoundError);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("returns error when the work order is not in progress", async () => {
|
|
68
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
69
|
+
spies.select.mockReturnValueOnce(basePendingWorkOrder);
|
|
70
|
+
|
|
71
|
+
const result = await run(db, { id: basePendingWorkOrder.id, completedQuantity: 5 }, ctx);
|
|
72
|
+
|
|
73
|
+
expect(result.ok).toBe(false);
|
|
74
|
+
if (!result.ok) {
|
|
75
|
+
expect(result.error).toBeInstanceOf(WorkOrderNotReportableError);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("returns error when completed or scrap quantity is negative", async () => {
|
|
80
|
+
const { db } = createMockDb<Transaction>();
|
|
81
|
+
|
|
82
|
+
const result = await run(db, { id: baseInProgressWorkOrder.id, completedQuantity: -5 }, ctx);
|
|
83
|
+
|
|
84
|
+
expect(result.ok).toBe(false);
|
|
85
|
+
if (!result.ok) {
|
|
86
|
+
expect(result.error).toBeInstanceOf(InvalidReportedQuantityError);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("returns error when no positive quantity or time is provided", async () => {
|
|
91
|
+
const { db } = createMockDb<Transaction>();
|
|
92
|
+
|
|
93
|
+
const result = await run(
|
|
94
|
+
db,
|
|
95
|
+
{ id: baseInProgressWorkOrder.id, completedQuantity: 0, scrapQuantity: 0 },
|
|
96
|
+
ctx,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(result.ok).toBe(false);
|
|
100
|
+
if (!result.ok) {
|
|
101
|
+
expect(result.error).toBeInstanceOf(EmptyProgressTransactionError);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("emits manufacturing scrap handoff when positive scrap is reported", async () => {
|
|
106
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
107
|
+
const updatedWorkOrder = {
|
|
108
|
+
...baseInProgressWorkOrder,
|
|
109
|
+
scrapQuantity: baseInProgressWorkOrder.scrapQuantity + 3,
|
|
110
|
+
completedQuantity: baseInProgressWorkOrder.completedQuantity + 10,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// work order lookup
|
|
114
|
+
spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
|
|
115
|
+
// update work order
|
|
116
|
+
spies.update.mockReturnValueOnce(updatedWorkOrder);
|
|
117
|
+
// insert execution event
|
|
118
|
+
spies.insert.mockReturnValueOnce(undefined);
|
|
119
|
+
// select: parent order lookup for rollup
|
|
120
|
+
spies.select.mockReturnValueOnce({ id: "production-order-2", status: "IN_PROGRESS" });
|
|
121
|
+
|
|
122
|
+
const result = await run(
|
|
123
|
+
db,
|
|
124
|
+
{
|
|
125
|
+
id: baseInProgressWorkOrder.id,
|
|
126
|
+
completedQuantity: 10,
|
|
127
|
+
scrapQuantity: 3,
|
|
128
|
+
scrapHandoffData: { reason: "defective material" },
|
|
129
|
+
},
|
|
130
|
+
ctx,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
expect(result.ok).toBe(true);
|
|
134
|
+
if (result.ok) {
|
|
135
|
+
expect(result.value.workOrder.scrapQuantity).toBe(baseInProgressWorkOrder.scrapQuantity + 3);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("returns error when positive scrap is reported without scrap handoff data", async () => {
|
|
140
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
141
|
+
|
|
142
|
+
// work order lookup
|
|
143
|
+
spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
|
|
144
|
+
|
|
145
|
+
const result = await run(
|
|
146
|
+
db,
|
|
147
|
+
{
|
|
148
|
+
id: baseInProgressWorkOrder.id,
|
|
149
|
+
completedQuantity: 10,
|
|
150
|
+
scrapQuantity: 3,
|
|
151
|
+
},
|
|
152
|
+
ctx,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
expect(result.ok).toBe(false);
|
|
156
|
+
if (!result.ok) {
|
|
157
|
+
expect(result.error).toBeInstanceOf(ScrapHandoffRequiredError);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("rolls up execution progress to the parent order", async () => {
|
|
162
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
163
|
+
const updatedWorkOrder = {
|
|
164
|
+
...baseInProgressWorkOrder,
|
|
165
|
+
completedQuantity: baseInProgressWorkOrder.completedQuantity + 20,
|
|
166
|
+
actualSetupTime: baseInProgressWorkOrder.actualSetupTime + 5,
|
|
167
|
+
actualRunTime: baseInProgressWorkOrder.actualRunTime + 45,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// work order lookup
|
|
171
|
+
spies.select.mockReturnValueOnce(baseInProgressWorkOrder);
|
|
172
|
+
// update work order
|
|
173
|
+
spies.update.mockReturnValueOnce(updatedWorkOrder);
|
|
174
|
+
// insert execution event
|
|
175
|
+
spies.insert.mockReturnValueOnce(undefined);
|
|
176
|
+
// select: parent order lookup for rollup
|
|
177
|
+
spies.select.mockReturnValueOnce({ id: "production-order-2", status: "IN_PROGRESS" });
|
|
178
|
+
|
|
179
|
+
const result = await run(
|
|
180
|
+
db,
|
|
181
|
+
{
|
|
182
|
+
id: baseInProgressWorkOrder.id,
|
|
183
|
+
completedQuantity: 20,
|
|
184
|
+
actualSetupTime: 5,
|
|
185
|
+
actualRunTime: 45,
|
|
186
|
+
},
|
|
187
|
+
ctx,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
expect(result.ok).toBe(true);
|
|
191
|
+
if (result.ok) {
|
|
192
|
+
expect(result.value.workOrder.completedQuantity).toBe(
|
|
193
|
+
baseInProgressWorkOrder.completedQuantity + 20,
|
|
194
|
+
);
|
|
195
|
+
expect(result.value.workOrder.actualSetupTime).toBe(
|
|
196
|
+
baseInProgressWorkOrder.actualSetupTime + 5,
|
|
197
|
+
);
|
|
198
|
+
expect(result.value.workOrder.actualRunTime).toBe(baseInProgressWorkOrder.actualRunTime + 45);
|
|
199
|
+
}
|
|
200
|
+
// Verify parent production order was updated (rollup)
|
|
201
|
+
expect(spies.update).toHaveBeenCalledTimes(2);
|
|
202
|
+
expect(spies.select).toHaveBeenCalledTimes(2);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
2
|
+
import {
|
|
3
|
+
WorkOrderNotFoundError,
|
|
4
|
+
WorkOrderNotReportableError,
|
|
5
|
+
InvalidReportedQuantityError,
|
|
6
|
+
EmptyProgressTransactionError,
|
|
7
|
+
ScrapHandoffRequiredError,
|
|
8
|
+
} from "../lib/errors.generated";
|
|
9
|
+
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
|
|
10
|
+
|
|
11
|
+
export interface ReportWorkOrderProgressInput {
|
|
12
|
+
id: string;
|
|
13
|
+
completedQuantity?: number;
|
|
14
|
+
scrapQuantity?: number;
|
|
15
|
+
actualSetupTime?: number;
|
|
16
|
+
actualRunTime?: number;
|
|
17
|
+
notes?: string | null;
|
|
18
|
+
scrapHandoffData?: Record<string, unknown> | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Function: reportWorkOrderProgress
|
|
23
|
+
*
|
|
24
|
+
* Records partial execution evidence such as completed quantity, scrap quantity,
|
|
25
|
+
* actual time, and exception notes. Emits ManufacturingScrapHandoff when the
|
|
26
|
+
* report contains scrapped quantity.
|
|
27
|
+
*/
|
|
28
|
+
export async function run<CF extends Record<string, unknown>>(
|
|
29
|
+
db: Transaction,
|
|
30
|
+
input: ReportWorkOrderProgressInput & CF,
|
|
31
|
+
_ctx: CommandContext,
|
|
32
|
+
) {
|
|
33
|
+
const {
|
|
34
|
+
id,
|
|
35
|
+
completedQuantity = 0,
|
|
36
|
+
scrapQuantity = 0,
|
|
37
|
+
actualSetupTime = 0,
|
|
38
|
+
actualRunTime = 0,
|
|
39
|
+
notes = null,
|
|
40
|
+
scrapHandoffData = null,
|
|
41
|
+
...customFields
|
|
42
|
+
} = input;
|
|
43
|
+
void customFields;
|
|
44
|
+
|
|
45
|
+
// 1. Validate quantities are non-negative
|
|
46
|
+
if (completedQuantity < 0 || scrapQuantity < 0) {
|
|
47
|
+
return err(new InvalidReportedQuantityError(id));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 2. Validate at least one positive value was reported
|
|
51
|
+
if (completedQuantity <= 0 && scrapQuantity <= 0 && actualSetupTime <= 0 && actualRunTime <= 0) {
|
|
52
|
+
return err(new EmptyProgressTransactionError(id));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 3. Fetch work order with lock
|
|
56
|
+
const workOrder = await db
|
|
57
|
+
.selectFrom("WorkOrder")
|
|
58
|
+
.selectAll()
|
|
59
|
+
.where("id", "=", id)
|
|
60
|
+
.forUpdate()
|
|
61
|
+
.executeTakeFirst();
|
|
62
|
+
|
|
63
|
+
if (!workOrder) {
|
|
64
|
+
return err(new WorkOrderNotFoundError(id));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 4. Validate status is IN_PROGRESS
|
|
68
|
+
if (workOrder.status !== "IN_PROGRESS") {
|
|
69
|
+
return err(new WorkOrderNotReportableError(id));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 5. Validate scrap handoff when positive scrap is reported
|
|
73
|
+
if (scrapQuantity > 0 && !scrapHandoffData) {
|
|
74
|
+
return err(new ScrapHandoffRequiredError(id));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 6. Accumulate quantities and time
|
|
78
|
+
const now = new Date();
|
|
79
|
+
|
|
80
|
+
const updatedWorkOrder = await db
|
|
81
|
+
.updateTable("WorkOrder")
|
|
82
|
+
.set({
|
|
83
|
+
completedQuantity: workOrder.completedQuantity + completedQuantity,
|
|
84
|
+
scrapQuantity: workOrder.scrapQuantity + scrapQuantity,
|
|
85
|
+
actualSetupTime: workOrder.actualSetupTime + actualSetupTime,
|
|
86
|
+
actualRunTime: workOrder.actualRunTime + actualRunTime,
|
|
87
|
+
executionNotes: notes ?? workOrder.executionNotes,
|
|
88
|
+
updatedAt: now,
|
|
89
|
+
})
|
|
90
|
+
.where("id", "=", id)
|
|
91
|
+
.returningAll()
|
|
92
|
+
.executeTakeFirstOrThrow();
|
|
93
|
+
|
|
94
|
+
// 7. Create PROGRESS_REPORTED execution event
|
|
95
|
+
await db
|
|
96
|
+
.insertInto("WorkOrderExecutionEvent")
|
|
97
|
+
.values({
|
|
98
|
+
workOrderId: id,
|
|
99
|
+
eventType: "PROGRESS_REPORTED",
|
|
100
|
+
timestamp: now,
|
|
101
|
+
quantity: completedQuantity,
|
|
102
|
+
timeValue: actualSetupTime + actualRunTime,
|
|
103
|
+
scrapValue: scrapQuantity > 0 ? scrapQuantity : null,
|
|
104
|
+
notes,
|
|
105
|
+
createdAt: now,
|
|
106
|
+
updatedAt: null,
|
|
107
|
+
})
|
|
108
|
+
.execute();
|
|
109
|
+
|
|
110
|
+
// 8. Roll up progress to parent production order
|
|
111
|
+
const parentOrder = await db
|
|
112
|
+
.selectFrom("ProductionOrder")
|
|
113
|
+
.selectAll()
|
|
114
|
+
.where("id", "=", workOrder.productionOrderId)
|
|
115
|
+
.forUpdate()
|
|
116
|
+
.executeTakeFirst();
|
|
117
|
+
|
|
118
|
+
if (parentOrder) {
|
|
119
|
+
await db
|
|
120
|
+
.updateTable("ProductionOrder")
|
|
121
|
+
.set({
|
|
122
|
+
updatedAt: now,
|
|
123
|
+
})
|
|
124
|
+
.where("id", "=", parentOrder.id)
|
|
125
|
+
.execute();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return ok({ workOrder: updatedWorkOrder });
|
|
129
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @generated — do not edit
|
|
2
|
+
import { permissions } from "../lib/permissions.generated";
|
|
3
|
+
import { run } from "./rescheduleProductionOrder";
|
|
4
|
+
import { defineCommand } from "@tailor-platform/erp-kit/module";
|
|
5
|
+
|
|
6
|
+
export const rescheduleProductionOrder = defineCommand(permissions.rescheduleProductionOrder, run);
|