@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,212 @@
|
|
|
1
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
2
|
+
import {
|
|
3
|
+
WorkOrderNotFoundError,
|
|
4
|
+
WorkOrderNotCompletableError,
|
|
5
|
+
WorkOrderNotStartedError,
|
|
6
|
+
InvalidCompletionQuantityError,
|
|
7
|
+
DuplicateBackflushRiskError,
|
|
8
|
+
ReceiptHandoffRequiredError,
|
|
9
|
+
LotReferenceRequiredError,
|
|
10
|
+
SerialReferenceRequiredError,
|
|
11
|
+
} from "../lib/errors.generated";
|
|
12
|
+
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
|
|
13
|
+
|
|
14
|
+
export interface ReceiptData {
|
|
15
|
+
itemReference?: string | null;
|
|
16
|
+
unitOfMeasure?: string | null;
|
|
17
|
+
siteReference?: string | null;
|
|
18
|
+
postingDate?: Date | null;
|
|
19
|
+
storageLocationReference?: string | null;
|
|
20
|
+
lotTracked: boolean;
|
|
21
|
+
serialTracked: boolean;
|
|
22
|
+
finishedGoodLotReference?: string | null;
|
|
23
|
+
serialReferences?: string[] | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface ZeroQuantityBypassPolicy {
|
|
27
|
+
allowZeroCompletion: boolean;
|
|
28
|
+
reasonCode?: string | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CompleteWorkOrderInput {
|
|
32
|
+
id: string;
|
|
33
|
+
completedQuantity: number;
|
|
34
|
+
zeroQuantityBypassPolicy?: ZeroQuantityBypassPolicy | null;
|
|
35
|
+
backflushRequired: boolean;
|
|
36
|
+
manuallyIssuedQuantity?: number;
|
|
37
|
+
receiptRequired: boolean;
|
|
38
|
+
receiptData?: ReceiptData | null;
|
|
39
|
+
notes?: string | null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Function: completeWorkOrder
|
|
44
|
+
*
|
|
45
|
+
* Finishes execution on an in-progress work order. Records the final completed
|
|
46
|
+
* quantity, validates backflush and receipt-handoff obligations, creates a
|
|
47
|
+
* COMPLETED execution event, and rolls up completion to the parent production
|
|
48
|
+
* order when all sibling work orders are finished.
|
|
49
|
+
*/
|
|
50
|
+
export async function run<CF extends Record<string, unknown>>(
|
|
51
|
+
db: Transaction,
|
|
52
|
+
input: CompleteWorkOrderInput & CF,
|
|
53
|
+
_ctx: CommandContext,
|
|
54
|
+
) {
|
|
55
|
+
const {
|
|
56
|
+
id,
|
|
57
|
+
completedQuantity,
|
|
58
|
+
zeroQuantityBypassPolicy,
|
|
59
|
+
backflushRequired,
|
|
60
|
+
manuallyIssuedQuantity,
|
|
61
|
+
receiptRequired,
|
|
62
|
+
receiptData,
|
|
63
|
+
notes,
|
|
64
|
+
...customFields
|
|
65
|
+
} = input;
|
|
66
|
+
void customFields;
|
|
67
|
+
|
|
68
|
+
// 1. Fetch work order with lock
|
|
69
|
+
const workOrder = await db
|
|
70
|
+
.selectFrom("WorkOrder")
|
|
71
|
+
.selectAll()
|
|
72
|
+
.where("id", "=", id)
|
|
73
|
+
.forUpdate()
|
|
74
|
+
.executeTakeFirst();
|
|
75
|
+
|
|
76
|
+
if (!workOrder) {
|
|
77
|
+
return err(new WorkOrderNotFoundError(id));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 2. Validate status is IN_PROGRESS
|
|
81
|
+
if (workOrder.status !== "IN_PROGRESS") {
|
|
82
|
+
return err(new WorkOrderNotCompletableError(id));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 3. Validate actual start evidence exists
|
|
86
|
+
if (!workOrder.actualStartDate) {
|
|
87
|
+
return err(new WorkOrderNotStartedError(id));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 4. Validate completed quantity, allowing explicit zero-quantity bypass.
|
|
91
|
+
const zeroQuantityBypassAllowed =
|
|
92
|
+
completedQuantity === 0 && zeroQuantityBypassPolicy?.allowZeroCompletion === true;
|
|
93
|
+
if (completedQuantity < 0 || (completedQuantity === 0 && !zeroQuantityBypassAllowed)) {
|
|
94
|
+
return err(new InvalidCompletionQuantityError(id));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 5. Validate backflush does not duplicate manual issue
|
|
98
|
+
if (backflushRequired && manuallyIssuedQuantity != null && manuallyIssuedQuantity > 0) {
|
|
99
|
+
return err(new DuplicateBackflushRiskError(id));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 6. Validate receipt handoff data when receipt is required
|
|
103
|
+
if (receiptRequired) {
|
|
104
|
+
if (!receiptData) {
|
|
105
|
+
return err(new ReceiptHandoffRequiredError(id));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (
|
|
109
|
+
!receiptData.itemReference ||
|
|
110
|
+
!receiptData.unitOfMeasure ||
|
|
111
|
+
!receiptData.siteReference ||
|
|
112
|
+
!receiptData.postingDate
|
|
113
|
+
) {
|
|
114
|
+
return err(new ReceiptHandoffRequiredError(id));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (receiptData.lotTracked && !receiptData.finishedGoodLotReference) {
|
|
118
|
+
return err(new LotReferenceRequiredError(id));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (
|
|
122
|
+
receiptData.serialTracked &&
|
|
123
|
+
(!receiptData.serialReferences || receiptData.serialReferences.length === 0)
|
|
124
|
+
) {
|
|
125
|
+
return err(new SerialReferenceRequiredError(id));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 7. Update work order to COMPLETE
|
|
130
|
+
const now = new Date();
|
|
131
|
+
const updatedWorkOrder = await db
|
|
132
|
+
.updateTable("WorkOrder")
|
|
133
|
+
.set({
|
|
134
|
+
status: "COMPLETE",
|
|
135
|
+
completedQuantity: workOrder.completedQuantity + completedQuantity,
|
|
136
|
+
executionNotes: notes ?? workOrder.executionNotes,
|
|
137
|
+
updatedAt: now,
|
|
138
|
+
})
|
|
139
|
+
.where("id", "=", id)
|
|
140
|
+
.returningAll()
|
|
141
|
+
.executeTakeFirstOrThrow();
|
|
142
|
+
|
|
143
|
+
// 8. Create COMPLETED execution event
|
|
144
|
+
await db
|
|
145
|
+
.insertInto("WorkOrderExecutionEvent")
|
|
146
|
+
.values({
|
|
147
|
+
workOrderId: id,
|
|
148
|
+
eventType: "COMPLETED",
|
|
149
|
+
timestamp: now,
|
|
150
|
+
quantity: completedQuantity,
|
|
151
|
+
timeValue: null,
|
|
152
|
+
scrapValue: null,
|
|
153
|
+
notes: notes ?? null,
|
|
154
|
+
createdAt: now,
|
|
155
|
+
updatedAt: null,
|
|
156
|
+
})
|
|
157
|
+
.execute();
|
|
158
|
+
|
|
159
|
+
const backflushHandoff = backflushRequired
|
|
160
|
+
? {
|
|
161
|
+
productionOrderReference: workOrder.productionOrderId,
|
|
162
|
+
workOrderReference: id,
|
|
163
|
+
completedQuantity,
|
|
164
|
+
manuallyIssuedQuantity: manuallyIssuedQuantity ?? 0,
|
|
165
|
+
postingDate: receiptData?.postingDate ?? now,
|
|
166
|
+
bypassReason: zeroQuantityBypassAllowed
|
|
167
|
+
? (zeroQuantityBypassPolicy?.reasonCode ?? null)
|
|
168
|
+
: null,
|
|
169
|
+
}
|
|
170
|
+
: null;
|
|
171
|
+
|
|
172
|
+
const receiptHandoff =
|
|
173
|
+
receiptRequired && receiptData
|
|
174
|
+
? {
|
|
175
|
+
productionOrderReference: workOrder.productionOrderId,
|
|
176
|
+
workOrderReference: id,
|
|
177
|
+
itemReference: receiptData.itemReference,
|
|
178
|
+
quantity: completedQuantity,
|
|
179
|
+
unitOfMeasure: receiptData.unitOfMeasure,
|
|
180
|
+
siteReference: receiptData.siteReference,
|
|
181
|
+
postingDate: receiptData.postingDate,
|
|
182
|
+
storageLocationReference: receiptData.storageLocationReference ?? null,
|
|
183
|
+
finishedGoodLotReference: receiptData.finishedGoodLotReference ?? null,
|
|
184
|
+
serialReferences: receiptData.serialReferences ?? null,
|
|
185
|
+
}
|
|
186
|
+
: null;
|
|
187
|
+
|
|
188
|
+
// 9. Roll up to parent production order
|
|
189
|
+
const siblingWorkOrders = await db
|
|
190
|
+
.selectFrom("WorkOrder")
|
|
191
|
+
.selectAll()
|
|
192
|
+
.where("productionOrderId", "=", workOrder.productionOrderId)
|
|
193
|
+
.execute();
|
|
194
|
+
|
|
195
|
+
const allComplete = (siblingWorkOrders as { id: string; status: string }[]).every((wo) => {
|
|
196
|
+
if (wo.id === id) return true; // this one was just completed
|
|
197
|
+
return wo.status === "COMPLETE" || wo.status === "CANCELLED";
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (allComplete) {
|
|
201
|
+
await db
|
|
202
|
+
.updateTable("ProductionOrder")
|
|
203
|
+
.set({
|
|
204
|
+
status: "COMPLETED",
|
|
205
|
+
updatedAt: now,
|
|
206
|
+
})
|
|
207
|
+
.where("id", "=", workOrder.productionOrderId)
|
|
208
|
+
.execute();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return ok({ workOrder: updatedWorkOrder, backflushHandoff, receiptHandoff });
|
|
212
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @generated — do not edit
|
|
2
|
+
import { permissions } from "../lib/permissions.generated";
|
|
3
|
+
import { run } from "./createBillOfMaterial";
|
|
4
|
+
import { defineCommand } from "@tailor-platform/erp-kit/module";
|
|
5
|
+
|
|
6
|
+
export const createBillOfMaterial = defineCommand(permissions.createBillOfMaterial, run);
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMockDb } from "../../../testing/index";
|
|
3
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
4
|
+
import {
|
|
5
|
+
ParentItemNotFoundError,
|
|
6
|
+
ParentItemNotManufacturedError,
|
|
7
|
+
InvalidBomTypeError,
|
|
8
|
+
ComponentLineRequiredError,
|
|
9
|
+
InvalidComponentQuantityError,
|
|
10
|
+
ComponentItemInactiveError,
|
|
11
|
+
BomVersionConflictError,
|
|
12
|
+
} from "../lib/errors.generated";
|
|
13
|
+
import { baseDraftBom, baseBomLine } from "../testing/fixtures";
|
|
14
|
+
import { run } from "./createBillOfMaterial";
|
|
15
|
+
import type { CommandContext } from "@tailor-platform/erp-kit/module";
|
|
16
|
+
|
|
17
|
+
describe("createBillOfMaterial", () => {
|
|
18
|
+
const ctx: CommandContext = {
|
|
19
|
+
actorId: "test-actor",
|
|
20
|
+
permissions: ["manufacturing:createBillOfMaterial"],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const validInput = {
|
|
24
|
+
parentItemId: "item-1",
|
|
25
|
+
companyId: "company-1",
|
|
26
|
+
siteId: null,
|
|
27
|
+
bomType: "MANUFACTURE",
|
|
28
|
+
effectivityStartDate: null,
|
|
29
|
+
effectivityEndDate: null,
|
|
30
|
+
defaultSelection: true,
|
|
31
|
+
revisionNumber: "V1",
|
|
32
|
+
lines: [
|
|
33
|
+
{
|
|
34
|
+
itemId: "item-2",
|
|
35
|
+
requiredQuantity: 2.0,
|
|
36
|
+
unitOfMeasure: "EA",
|
|
37
|
+
scrapAssumption: 5.0,
|
|
38
|
+
isSubassembly: false,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
it("creates a draft BOM for an active manufactured item", async () => {
|
|
44
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
45
|
+
const createdBom = {
|
|
46
|
+
...baseDraftBom,
|
|
47
|
+
id: "new-bom-id",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// parent item exists
|
|
51
|
+
spies.select.mockReturnValueOnce({ id: "item-1", status: "ACTIVE", isManufacturable: true });
|
|
52
|
+
// component item exists
|
|
53
|
+
spies.select.mockReturnValueOnce({ id: "item-2", status: "ACTIVE" });
|
|
54
|
+
// no existing BOM with same version identity
|
|
55
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
56
|
+
// insert BOM
|
|
57
|
+
spies.insert.mockReturnValueOnce(createdBom);
|
|
58
|
+
// insert BOM line
|
|
59
|
+
spies.insert.mockReturnValueOnce({ ...baseBomLine, billOfMaterialId: "new-bom-id" });
|
|
60
|
+
|
|
61
|
+
const result = await run(db, validInput, ctx);
|
|
62
|
+
|
|
63
|
+
expect(result.ok).toBe(true);
|
|
64
|
+
if (result.ok) {
|
|
65
|
+
expect(result.value.billOfMaterial.status).toBe("DRAFT");
|
|
66
|
+
expect(result.value.billOfMaterial.id).toBe("new-bom-id");
|
|
67
|
+
}
|
|
68
|
+
expect(spies.insert).toHaveBeenCalled();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("returns error when the parent item does not exist", async () => {
|
|
72
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
73
|
+
|
|
74
|
+
// parent item not found
|
|
75
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
76
|
+
|
|
77
|
+
const result = await run(db, validInput, ctx);
|
|
78
|
+
|
|
79
|
+
expect(result.ok).toBe(false);
|
|
80
|
+
if (!result.ok) {
|
|
81
|
+
expect(result.error).toBeInstanceOf(ParentItemNotFoundError);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("returns error when the parent item is not manufacturable", async () => {
|
|
86
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
87
|
+
|
|
88
|
+
// parent item exists but not manufacturable
|
|
89
|
+
spies.select.mockReturnValueOnce({ id: "item-1", status: "ACTIVE", isManufacturable: false });
|
|
90
|
+
|
|
91
|
+
const result = await run(db, validInput, ctx);
|
|
92
|
+
|
|
93
|
+
expect(result.ok).toBe(false);
|
|
94
|
+
if (!result.ok) {
|
|
95
|
+
expect(result.error).toBeInstanceOf(ParentItemNotManufacturedError);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("returns error when bomType is invalid", async () => {
|
|
100
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
101
|
+
|
|
102
|
+
// parent item exists
|
|
103
|
+
spies.select.mockReturnValueOnce({ id: "item-1", status: "ACTIVE", isManufacturable: true });
|
|
104
|
+
|
|
105
|
+
const result = await run(db, { ...validInput, bomType: "UNSUPPORTED" }, ctx);
|
|
106
|
+
|
|
107
|
+
expect(result.ok).toBe(false);
|
|
108
|
+
if (!result.ok) {
|
|
109
|
+
expect(result.error).toBeInstanceOf(InvalidBomTypeError);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("returns error when no component lines are provided", async () => {
|
|
114
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
115
|
+
|
|
116
|
+
// parent item exists
|
|
117
|
+
spies.select.mockReturnValueOnce({ id: "item-1", status: "ACTIVE", isManufacturable: true });
|
|
118
|
+
|
|
119
|
+
const result = await run(db, { ...validInput, lines: [] }, ctx);
|
|
120
|
+
|
|
121
|
+
expect(result.ok).toBe(false);
|
|
122
|
+
if (!result.ok) {
|
|
123
|
+
expect(result.error).toBeInstanceOf(ComponentLineRequiredError);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("returns error when a component quantity is not positive", async () => {
|
|
128
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
129
|
+
|
|
130
|
+
// parent item exists
|
|
131
|
+
spies.select.mockReturnValueOnce({ id: "item-1", status: "ACTIVE", isManufacturable: true });
|
|
132
|
+
|
|
133
|
+
const result = await run(
|
|
134
|
+
db,
|
|
135
|
+
{
|
|
136
|
+
...validInput,
|
|
137
|
+
lines: [{ itemId: "item-2", requiredQuantity: 0, unitOfMeasure: "EA" }],
|
|
138
|
+
},
|
|
139
|
+
ctx,
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
expect(result.ok).toBe(false);
|
|
143
|
+
if (!result.ok) {
|
|
144
|
+
expect(result.error).toBeInstanceOf(InvalidComponentQuantityError);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("returns error when a component item is inactive", async () => {
|
|
149
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
150
|
+
|
|
151
|
+
// parent item exists
|
|
152
|
+
spies.select.mockReturnValueOnce({ id: "item-1", status: "ACTIVE", isManufacturable: true });
|
|
153
|
+
// component item not found (treated as inactive)
|
|
154
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
155
|
+
|
|
156
|
+
const result = await run(db, validInput, ctx);
|
|
157
|
+
|
|
158
|
+
expect(result.ok).toBe(false);
|
|
159
|
+
if (!result.ok) {
|
|
160
|
+
expect(result.error).toBeInstanceOf(ComponentItemInactiveError);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("returns error when a component item exists but is inactive", async () => {
|
|
165
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
166
|
+
|
|
167
|
+
// parent item exists
|
|
168
|
+
spies.select.mockReturnValueOnce({ id: "item-1", status: "ACTIVE", isManufacturable: true });
|
|
169
|
+
// component item exists but is inactive
|
|
170
|
+
spies.select.mockReturnValueOnce({ id: "item-2", isActive: false });
|
|
171
|
+
|
|
172
|
+
const result = await run(db, validInput, ctx);
|
|
173
|
+
|
|
174
|
+
expect(result.ok).toBe(false);
|
|
175
|
+
if (!result.ok) {
|
|
176
|
+
expect(result.error).toBeInstanceOf(ComponentItemInactiveError);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("returns error when the version identity already exists in scope", async () => {
|
|
181
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
182
|
+
|
|
183
|
+
// parent item exists
|
|
184
|
+
spies.select.mockReturnValueOnce({ id: "item-1", status: "ACTIVE", isManufacturable: true });
|
|
185
|
+
// component item exists
|
|
186
|
+
spies.select.mockReturnValueOnce({ id: "item-2", status: "ACTIVE" });
|
|
187
|
+
// existing BOM with same version identity
|
|
188
|
+
spies.select.mockReturnValueOnce(baseDraftBom);
|
|
189
|
+
|
|
190
|
+
const result = await run(db, validInput, ctx);
|
|
191
|
+
|
|
192
|
+
expect(result.ok).toBe(false);
|
|
193
|
+
if (!result.ok) {
|
|
194
|
+
expect(result.error).toBeInstanceOf(BomVersionConflictError);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("returns error when the parent item is inactive", async () => {
|
|
199
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
200
|
+
|
|
201
|
+
spies.select.mockReturnValueOnce({ id: "item-1", status: "INACTIVE", isManufacturable: true });
|
|
202
|
+
|
|
203
|
+
const result = await run(db, validInput, ctx);
|
|
204
|
+
|
|
205
|
+
expect(result.ok).toBe(false);
|
|
206
|
+
if (!result.ok) {
|
|
207
|
+
expect(result.error).toBeInstanceOf(ParentItemNotManufacturedError);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
});
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
2
|
+
import {
|
|
3
|
+
ParentItemNotFoundError,
|
|
4
|
+
ParentItemNotManufacturedError,
|
|
5
|
+
InvalidBomTypeError,
|
|
6
|
+
ComponentLineRequiredError,
|
|
7
|
+
InvalidComponentQuantityError,
|
|
8
|
+
ComponentItemInactiveError,
|
|
9
|
+
BomVersionConflictError,
|
|
10
|
+
} from "../lib/errors.generated";
|
|
11
|
+
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
|
|
12
|
+
|
|
13
|
+
const VALID_BOM_TYPES = ["MANUFACTURE", "PHANTOM", "KIT"] as const;
|
|
14
|
+
|
|
15
|
+
export interface CreateBillOfMaterialLineInput {
|
|
16
|
+
itemId: string;
|
|
17
|
+
requiredQuantity: number;
|
|
18
|
+
unitOfMeasure?: string | null;
|
|
19
|
+
scrapAssumption?: number | null;
|
|
20
|
+
isSubassembly?: boolean | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CreateBillOfMaterialInput {
|
|
24
|
+
parentItemId: string;
|
|
25
|
+
companyId: string;
|
|
26
|
+
siteId?: string | null;
|
|
27
|
+
bomType: string;
|
|
28
|
+
effectivityStartDate?: Date | null;
|
|
29
|
+
effectivityEndDate?: Date | null;
|
|
30
|
+
defaultSelection?: boolean | null;
|
|
31
|
+
revisionNumber?: string | null;
|
|
32
|
+
lines: CreateBillOfMaterialLineInput[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Function: createBillOfMaterial
|
|
37
|
+
*
|
|
38
|
+
* Creates a draft BOM version for one manufactured parent item at company or
|
|
39
|
+
* site scope. Establishes the initial bomType, effectivity window,
|
|
40
|
+
* default-selection intent, and component-line payload.
|
|
41
|
+
*/
|
|
42
|
+
export async function run<CF extends Record<string, unknown>>(
|
|
43
|
+
db: Transaction,
|
|
44
|
+
input: CreateBillOfMaterialInput & CF,
|
|
45
|
+
_ctx: CommandContext,
|
|
46
|
+
) {
|
|
47
|
+
const {
|
|
48
|
+
parentItemId,
|
|
49
|
+
companyId,
|
|
50
|
+
siteId,
|
|
51
|
+
bomType,
|
|
52
|
+
effectivityStartDate,
|
|
53
|
+
effectivityEndDate,
|
|
54
|
+
defaultSelection,
|
|
55
|
+
revisionNumber,
|
|
56
|
+
lines,
|
|
57
|
+
...customFields
|
|
58
|
+
} = input;
|
|
59
|
+
|
|
60
|
+
// 1. Validate parent item exists
|
|
61
|
+
const parentItem = await db
|
|
62
|
+
.selectFrom("Item")
|
|
63
|
+
.selectAll()
|
|
64
|
+
.where("id", "=", parentItemId)
|
|
65
|
+
.executeTakeFirst();
|
|
66
|
+
|
|
67
|
+
if (!parentItem) {
|
|
68
|
+
return err(new ParentItemNotFoundError(parentItemId));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 2. Validate parent item is active and manufacturable.
|
|
72
|
+
const parentItemRecord = parentItem as Record<string, unknown>;
|
|
73
|
+
const isInactive =
|
|
74
|
+
("status" in parentItemRecord && parentItemRecord.status !== "ACTIVE") ||
|
|
75
|
+
("isActive" in parentItemRecord && parentItemRecord.isActive === false);
|
|
76
|
+
const isNotManufacturable =
|
|
77
|
+
"isManufacturable" in parentItemRecord && parentItemRecord.isManufacturable === false;
|
|
78
|
+
|
|
79
|
+
if (isInactive || isNotManufacturable) {
|
|
80
|
+
return err(new ParentItemNotManufacturedError(parentItemId));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 3. Validate bomType
|
|
84
|
+
if (!VALID_BOM_TYPES.includes(bomType as (typeof VALID_BOM_TYPES)[number])) {
|
|
85
|
+
return err(new InvalidBomTypeError(parentItemId));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 4. Validate at least one component line
|
|
89
|
+
if (!lines || lines.length === 0) {
|
|
90
|
+
return err(new ComponentLineRequiredError(parentItemId));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 5. Validate each component line
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
if (line.requiredQuantity <= 0) {
|
|
96
|
+
return err(new InvalidComponentQuantityError(line.itemId));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check component item is not inactive (exists in Item table)
|
|
100
|
+
const componentItem = await db
|
|
101
|
+
.selectFrom("Item")
|
|
102
|
+
.selectAll()
|
|
103
|
+
.where("id", "=", line.itemId)
|
|
104
|
+
.executeTakeFirst();
|
|
105
|
+
|
|
106
|
+
if (!componentItem) {
|
|
107
|
+
return err(new ComponentItemInactiveError(line.itemId));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Check component item is active
|
|
111
|
+
if ("isActive" in componentItem && componentItem.isActive === false) {
|
|
112
|
+
return err(new ComponentItemInactiveError(line.itemId));
|
|
113
|
+
}
|
|
114
|
+
if ("status" in componentItem && componentItem.status === "INACTIVE") {
|
|
115
|
+
return err(new ComponentItemInactiveError(line.itemId));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 6. Check version identity uniqueness within same parent item and scope
|
|
120
|
+
const existingBomQuery = db
|
|
121
|
+
.selectFrom("BillOfMaterial")
|
|
122
|
+
.selectAll()
|
|
123
|
+
.where("parentItemId", "=", parentItemId)
|
|
124
|
+
.where("companyId", "=", companyId);
|
|
125
|
+
|
|
126
|
+
const existingBom = await (
|
|
127
|
+
siteId ? existingBomQuery.where("siteId", "=", siteId) : existingBomQuery
|
|
128
|
+
)
|
|
129
|
+
.where("revisionNumber", "=", revisionNumber ?? null)
|
|
130
|
+
.forUpdate()
|
|
131
|
+
.executeTakeFirst();
|
|
132
|
+
|
|
133
|
+
if (existingBom) {
|
|
134
|
+
return err(new BomVersionConflictError(parentItemId));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 7. Create draft BOM
|
|
138
|
+
const billOfMaterial = await db
|
|
139
|
+
.insertInto("BillOfMaterial")
|
|
140
|
+
.values({
|
|
141
|
+
...(customFields as Record<string, unknown>),
|
|
142
|
+
parentItemId,
|
|
143
|
+
companyId,
|
|
144
|
+
siteId: siteId ?? null,
|
|
145
|
+
bomType: bomType as "MANUFACTURE" | "PHANTOM" | "KIT",
|
|
146
|
+
effectivityStartDate: effectivityStartDate ?? null,
|
|
147
|
+
effectivityEndDate: effectivityEndDate ?? null,
|
|
148
|
+
defaultSelection: defaultSelection ?? null,
|
|
149
|
+
revisionNumber: revisionNumber ?? null,
|
|
150
|
+
status: "DRAFT",
|
|
151
|
+
createdAt: new Date(),
|
|
152
|
+
updatedAt: null,
|
|
153
|
+
})
|
|
154
|
+
.returningAll()
|
|
155
|
+
.executeTakeFirst();
|
|
156
|
+
|
|
157
|
+
// 8. Create component lines
|
|
158
|
+
for (const line of lines) {
|
|
159
|
+
await db
|
|
160
|
+
.insertInto("BillOfMaterialLine")
|
|
161
|
+
.values({
|
|
162
|
+
billOfMaterialId: billOfMaterial!.id,
|
|
163
|
+
itemId: line.itemId,
|
|
164
|
+
requiredQuantity: line.requiredQuantity,
|
|
165
|
+
unitOfMeasure: line.unitOfMeasure ?? null,
|
|
166
|
+
scrapAssumption: line.scrapAssumption ?? null,
|
|
167
|
+
isSubassembly: line.isSubassembly ?? null,
|
|
168
|
+
createdAt: new Date(),
|
|
169
|
+
updatedAt: null,
|
|
170
|
+
})
|
|
171
|
+
.returningAll()
|
|
172
|
+
.executeTakeFirst();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return ok({ billOfMaterial: billOfMaterial! });
|
|
176
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @generated — do not edit
|
|
2
|
+
import { permissions } from "../lib/permissions.generated";
|
|
3
|
+
import { run } from "./createProductionOrder";
|
|
4
|
+
import { defineCommand } from "@tailor-platform/erp-kit/module";
|
|
5
|
+
|
|
6
|
+
export const createProductionOrder = defineCommand(permissions.createProductionOrder, run);
|