@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,139 @@
|
|
|
1
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
2
|
+
import {
|
|
3
|
+
ProductionOrderNotFoundError,
|
|
4
|
+
CostSummaryNotFoundError,
|
|
5
|
+
CostSummaryNotCollectingError,
|
|
6
|
+
MissingInventoryIssueReferenceError,
|
|
7
|
+
MissingActualUnitCostError,
|
|
8
|
+
MissingActualExtendedCostError,
|
|
9
|
+
MissingPostingDateError,
|
|
10
|
+
IssueOutcomeNotFinalError,
|
|
11
|
+
DuplicateIssueOutcomeError,
|
|
12
|
+
} from "../lib/errors.generated";
|
|
13
|
+
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
|
|
14
|
+
|
|
15
|
+
export interface RecordInventoryIssueOutcomeInput {
|
|
16
|
+
productionOrderId: string;
|
|
17
|
+
inventoryIssueReference: string;
|
|
18
|
+
itemReference: string;
|
|
19
|
+
issuedQuantity: number;
|
|
20
|
+
unitOfMeasure: string;
|
|
21
|
+
actualUnitCost: number;
|
|
22
|
+
actualExtendedCost: number;
|
|
23
|
+
currency: string;
|
|
24
|
+
valuationMethod: string;
|
|
25
|
+
postingDate: string;
|
|
26
|
+
siteReference: string;
|
|
27
|
+
isFinalValuation: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Function: recordInventoryIssueOutcome
|
|
32
|
+
*
|
|
33
|
+
* Applies one inventory-owned valuation outcome to the manufacturing cost
|
|
34
|
+
* summary for a production order. It is the only valid path for actual
|
|
35
|
+
* material cost to enter manufacturing.
|
|
36
|
+
*/
|
|
37
|
+
export async function run<CF extends Record<string, unknown>>(
|
|
38
|
+
db: Transaction,
|
|
39
|
+
input: RecordInventoryIssueOutcomeInput & CF,
|
|
40
|
+
_ctx: CommandContext,
|
|
41
|
+
) {
|
|
42
|
+
const {
|
|
43
|
+
productionOrderId,
|
|
44
|
+
inventoryIssueReference,
|
|
45
|
+
actualUnitCost,
|
|
46
|
+
actualExtendedCost,
|
|
47
|
+
postingDate,
|
|
48
|
+
isFinalValuation,
|
|
49
|
+
...customFields
|
|
50
|
+
} = input;
|
|
51
|
+
void customFields;
|
|
52
|
+
|
|
53
|
+
// 1. Validate required payload fields
|
|
54
|
+
if (!inventoryIssueReference) {
|
|
55
|
+
return err(new MissingInventoryIssueReferenceError(productionOrderId));
|
|
56
|
+
}
|
|
57
|
+
if (actualUnitCost == null) {
|
|
58
|
+
return err(new MissingActualUnitCostError(productionOrderId));
|
|
59
|
+
}
|
|
60
|
+
if (actualExtendedCost == null) {
|
|
61
|
+
return err(new MissingActualExtendedCostError(productionOrderId));
|
|
62
|
+
}
|
|
63
|
+
if (!postingDate) {
|
|
64
|
+
return err(new MissingPostingDateError(productionOrderId));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 2. Resolve production order
|
|
68
|
+
const productionOrder = await db
|
|
69
|
+
.selectFrom("ProductionOrder")
|
|
70
|
+
.selectAll()
|
|
71
|
+
.where("id", "=", productionOrderId)
|
|
72
|
+
.forUpdate()
|
|
73
|
+
.executeTakeFirst();
|
|
74
|
+
|
|
75
|
+
if (!productionOrder) {
|
|
76
|
+
return err(new ProductionOrderNotFoundError(productionOrderId));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 3. Resolve cost summary
|
|
80
|
+
const costSummary = await db
|
|
81
|
+
.selectFrom("ManufacturingCostSummary")
|
|
82
|
+
.selectAll()
|
|
83
|
+
.where("productionOrderId", "=", productionOrderId)
|
|
84
|
+
.forUpdate()
|
|
85
|
+
.executeTakeFirst();
|
|
86
|
+
|
|
87
|
+
if (!costSummary) {
|
|
88
|
+
return err(new CostSummaryNotFoundError(productionOrderId));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 4. Summary must be COLLECTING
|
|
92
|
+
if (costSummary.status !== "COLLECTING") {
|
|
93
|
+
return err(new CostSummaryNotCollectingError(productionOrderId));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 5. Inventory valuation must be final
|
|
97
|
+
if (!isFinalValuation) {
|
|
98
|
+
return err(new IssueOutcomeNotFinalError(productionOrderId));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 6. Check for duplicate application
|
|
102
|
+
const existingCostLine = await db
|
|
103
|
+
.selectFrom("ManufacturingCostLine")
|
|
104
|
+
.selectAll()
|
|
105
|
+
.where("costSummaryId", "=", costSummary.id)
|
|
106
|
+
.where("costType", "=", `MATERIAL:${inventoryIssueReference}`)
|
|
107
|
+
.executeTakeFirst();
|
|
108
|
+
|
|
109
|
+
if (existingCostLine) {
|
|
110
|
+
return err(new DuplicateIssueOutcomeError(productionOrderId));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 7. Create a manufacturing cost line for this issue outcome
|
|
114
|
+
await db
|
|
115
|
+
.insertInto("ManufacturingCostLine")
|
|
116
|
+
.values({
|
|
117
|
+
costSummaryId: costSummary.id,
|
|
118
|
+
costType: `MATERIAL:${inventoryIssueReference}`,
|
|
119
|
+
plannedAmount: 0,
|
|
120
|
+
actualAmount: actualExtendedCost,
|
|
121
|
+
createdAt: new Date(),
|
|
122
|
+
updatedAt: null,
|
|
123
|
+
})
|
|
124
|
+
.returningAll()
|
|
125
|
+
.executeTakeFirst();
|
|
126
|
+
|
|
127
|
+
// 8. Update the cost summary's actual material cost
|
|
128
|
+
const updatedSummary = await db
|
|
129
|
+
.updateTable("ManufacturingCostSummary")
|
|
130
|
+
.set({
|
|
131
|
+
actualMaterialCost: costSummary.actualMaterialCost + actualExtendedCost,
|
|
132
|
+
updatedAt: new Date(),
|
|
133
|
+
})
|
|
134
|
+
.where("id", "=", costSummary.id)
|
|
135
|
+
.returningAll()
|
|
136
|
+
.executeTakeFirst();
|
|
137
|
+
|
|
138
|
+
return ok({ costSummary: updatedSummary! });
|
|
139
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @generated — do not edit
|
|
2
|
+
import { permissions } from "../lib/permissions.generated";
|
|
3
|
+
import { run } from "./recordManufacturingCostSettlementAcknowledgment";
|
|
4
|
+
import { defineCommand } from "@tailor-platform/erp-kit/module";
|
|
5
|
+
|
|
6
|
+
export const recordManufacturingCostSettlementAcknowledgment = defineCommand(permissions.recordManufacturingCostSettlementAcknowledgment, run);
|
package/src/modules/manufacturing/command/recordManufacturingCostSettlementAcknowledgment.test.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { createMockDb } from "../../../testing/index";
|
|
3
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
4
|
+
import {
|
|
5
|
+
CostSummaryNotFoundError,
|
|
6
|
+
CostSummaryNotSettlableError,
|
|
7
|
+
AcknowledgmentMismatchError,
|
|
8
|
+
AcknowledgmentRejectedOrSupersededError,
|
|
9
|
+
AcknowledgmentSourceInvalidError,
|
|
10
|
+
} from "../lib/errors.generated";
|
|
11
|
+
import { baseReviewedCostSummary, basePendingReviewCostSummary } from "../testing/fixtures";
|
|
12
|
+
import { run } from "./recordManufacturingCostSettlementAcknowledgment";
|
|
13
|
+
import type { CommandContext } from "@tailor-platform/erp-kit/module";
|
|
14
|
+
|
|
15
|
+
describe("recordManufacturingCostSettlementAcknowledgment", () => {
|
|
16
|
+
const ctx: CommandContext = {
|
|
17
|
+
actorId: "test-actor",
|
|
18
|
+
permissions: ["manufacturing:recordManufacturingCostSettlementAcknowledgment"],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const validInput = {
|
|
22
|
+
costSummaryId: "cost-summary-3",
|
|
23
|
+
productionOrderId: "production-order-2",
|
|
24
|
+
currencyCode: "USD",
|
|
25
|
+
settlementReference: "SETTLE-2024-001",
|
|
26
|
+
settlementDate: "2024-04-15",
|
|
27
|
+
acknowledgedDate: "2024-04-15",
|
|
28
|
+
acknowledgmentStatus: "ACCEPTED",
|
|
29
|
+
acknowledgmentSource: "downstream-accounting-system",
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
it("records a valid downstream settlement acknowledgment and settles the summary", async () => {
|
|
33
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
34
|
+
|
|
35
|
+
spies.select.mockReturnValueOnce(baseReviewedCostSummary);
|
|
36
|
+
|
|
37
|
+
const settledSummary = {
|
|
38
|
+
...baseReviewedCostSummary,
|
|
39
|
+
status: "SETTLED",
|
|
40
|
+
};
|
|
41
|
+
spies.insert.mockReturnValue({});
|
|
42
|
+
spies.update.mockReturnValue(settledSummary);
|
|
43
|
+
|
|
44
|
+
const result = await run(db, validInput, ctx);
|
|
45
|
+
|
|
46
|
+
expect(result.ok).toBe(true);
|
|
47
|
+
if (result.ok) {
|
|
48
|
+
expect(result.value.costSummary.status).toBe("SETTLED");
|
|
49
|
+
}
|
|
50
|
+
expect(spies.insert).toHaveBeenCalled();
|
|
51
|
+
expect(spies.update).toHaveBeenCalled();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("returns error when the summary does not exist", async () => {
|
|
55
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
56
|
+
|
|
57
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
58
|
+
|
|
59
|
+
const result = await run(db, validInput, ctx);
|
|
60
|
+
|
|
61
|
+
expect(result.ok).toBe(false);
|
|
62
|
+
if (!result.ok) {
|
|
63
|
+
expect(result.error).toBeInstanceOf(CostSummaryNotFoundError);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("returns error when the summary is not variance reviewed", async () => {
|
|
68
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
69
|
+
|
|
70
|
+
spies.select.mockReturnValueOnce(basePendingReviewCostSummary); // PENDING_VARIANCE_REVIEW
|
|
71
|
+
|
|
72
|
+
const result = await run(db, validInput, ctx);
|
|
73
|
+
|
|
74
|
+
expect(result.ok).toBe(false);
|
|
75
|
+
if (!result.ok) {
|
|
76
|
+
expect(result.error).toBeInstanceOf(CostSummaryNotSettlableError);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("returns error when the payload does not match the reviewed handoff", async () => {
|
|
81
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
82
|
+
|
|
83
|
+
spies.select.mockReturnValueOnce(baseReviewedCostSummary);
|
|
84
|
+
|
|
85
|
+
const result = await run(
|
|
86
|
+
db,
|
|
87
|
+
{ ...validInput, currencyCode: "EUR" }, // mismatch
|
|
88
|
+
ctx,
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(result.ok).toBe(false);
|
|
92
|
+
if (!result.ok) {
|
|
93
|
+
expect(result.error).toBeInstanceOf(AcknowledgmentMismatchError);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("returns error when downstream accounting rejects, reverses, or supersedes the reviewed handoff", async () => {
|
|
98
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
99
|
+
|
|
100
|
+
spies.select.mockReturnValueOnce(baseReviewedCostSummary);
|
|
101
|
+
|
|
102
|
+
const result = await run(db, { ...validInput, acknowledgmentStatus: "REJECTED" }, ctx);
|
|
103
|
+
|
|
104
|
+
expect(result.ok).toBe(false);
|
|
105
|
+
if (!result.ok) {
|
|
106
|
+
expect(result.error).toBeInstanceOf(AcknowledgmentRejectedOrSupersededError);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("returns error when the acknowledgment source is invalid", async () => {
|
|
111
|
+
const { db } = createMockDb<Transaction>();
|
|
112
|
+
|
|
113
|
+
const result = await run(db, { ...validInput, acknowledgmentSource: null }, ctx);
|
|
114
|
+
|
|
115
|
+
expect(result.ok).toBe(false);
|
|
116
|
+
if (!result.ok) {
|
|
117
|
+
expect(result.error).toBeInstanceOf(AcknowledgmentSourceInvalidError);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { Transaction } from "../generated/kysely-tailordb";
|
|
2
|
+
import {
|
|
3
|
+
CostSummaryNotFoundError,
|
|
4
|
+
CostSummaryNotSettlableError,
|
|
5
|
+
AcknowledgmentMismatchError,
|
|
6
|
+
AcknowledgmentRejectedOrSupersededError,
|
|
7
|
+
AcknowledgmentSourceInvalidError,
|
|
8
|
+
} from "../lib/errors.generated";
|
|
9
|
+
import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/module";
|
|
10
|
+
|
|
11
|
+
export interface RecordManufacturingCostSettlementAcknowledgmentInput {
|
|
12
|
+
costSummaryId: string;
|
|
13
|
+
productionOrderId: string;
|
|
14
|
+
currencyCode: string;
|
|
15
|
+
settlementReference: string;
|
|
16
|
+
settlementDate: string;
|
|
17
|
+
acknowledgedDate?: string | null;
|
|
18
|
+
acknowledgmentStatus?: string | null;
|
|
19
|
+
acknowledgmentSource?: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Function: recordManufacturingCostSettlementAcknowledgment
|
|
24
|
+
*
|
|
25
|
+
* Persists the downstream accounting acknowledgment that a reviewed
|
|
26
|
+
* manufacturing cost handoff has been consumed by settlement processing.
|
|
27
|
+
* It is the only path that moves a reviewed summary to SETTLED.
|
|
28
|
+
*/
|
|
29
|
+
export async function run<CF extends Record<string, unknown>>(
|
|
30
|
+
db: Transaction,
|
|
31
|
+
input: RecordManufacturingCostSettlementAcknowledgmentInput & CF,
|
|
32
|
+
_ctx: CommandContext,
|
|
33
|
+
) {
|
|
34
|
+
const {
|
|
35
|
+
costSummaryId,
|
|
36
|
+
productionOrderId,
|
|
37
|
+
currencyCode,
|
|
38
|
+
settlementReference,
|
|
39
|
+
settlementDate,
|
|
40
|
+
acknowledgedDate,
|
|
41
|
+
acknowledgmentStatus,
|
|
42
|
+
acknowledgmentSource,
|
|
43
|
+
} = input;
|
|
44
|
+
|
|
45
|
+
// 1. Validate acknowledgment source
|
|
46
|
+
if (!acknowledgmentSource) {
|
|
47
|
+
return err(new AcknowledgmentSourceInvalidError(costSummaryId));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 2. Resolve cost summary
|
|
51
|
+
const costSummary = await db
|
|
52
|
+
.selectFrom("ManufacturingCostSummary")
|
|
53
|
+
.selectAll()
|
|
54
|
+
.where("id", "=", costSummaryId)
|
|
55
|
+
.forUpdate()
|
|
56
|
+
.executeTakeFirst();
|
|
57
|
+
|
|
58
|
+
if (!costSummary) {
|
|
59
|
+
return err(new CostSummaryNotFoundError(costSummaryId));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 3. Summary must be VARIANCE_REVIEWED
|
|
63
|
+
if (costSummary.status !== "VARIANCE_REVIEWED") {
|
|
64
|
+
return err(new CostSummaryNotSettlableError(costSummaryId));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 4. Validate payload matches the reviewed summary's identity and currency
|
|
68
|
+
if (
|
|
69
|
+
costSummary.productionOrderId !== productionOrderId ||
|
|
70
|
+
costSummary.currencyCode !== currencyCode
|
|
71
|
+
) {
|
|
72
|
+
return err(new AcknowledgmentMismatchError(costSummaryId));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 5. Check that downstream accounting accepted (not rejected/reversed/superseded)
|
|
76
|
+
if (
|
|
77
|
+
acknowledgmentStatus === "REJECTED" ||
|
|
78
|
+
acknowledgmentStatus === "REVERSED" ||
|
|
79
|
+
acknowledgmentStatus === "SUPERSEDED"
|
|
80
|
+
) {
|
|
81
|
+
return err(new AcknowledgmentRejectedOrSupersededError(costSummaryId));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 6. Create settlement record
|
|
85
|
+
await db
|
|
86
|
+
.insertInto("ManufacturingCostSettlementRecord")
|
|
87
|
+
.values({
|
|
88
|
+
costSummaryId: costSummary.id,
|
|
89
|
+
settlementDate: new Date(settlementDate),
|
|
90
|
+
settlementReference,
|
|
91
|
+
acknowledgedDate: acknowledgedDate ? new Date(acknowledgedDate) : new Date(),
|
|
92
|
+
createdAt: new Date(),
|
|
93
|
+
updatedAt: null,
|
|
94
|
+
})
|
|
95
|
+
.returningAll()
|
|
96
|
+
.executeTakeFirst();
|
|
97
|
+
|
|
98
|
+
// 7. Update summary to SETTLED
|
|
99
|
+
const updatedSummary = await db
|
|
100
|
+
.updateTable("ManufacturingCostSummary")
|
|
101
|
+
.set({
|
|
102
|
+
status: "SETTLED",
|
|
103
|
+
updatedAt: new Date(),
|
|
104
|
+
})
|
|
105
|
+
.where("id", "=", costSummary.id)
|
|
106
|
+
.returningAll()
|
|
107
|
+
.executeTakeFirst();
|
|
108
|
+
|
|
109
|
+
return ok({ costSummary: updatedSummary! });
|
|
110
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// @generated — do not edit
|
|
2
|
+
import { permissions } from "../lib/permissions.generated";
|
|
3
|
+
import { run } from "./releaseProductionOrder";
|
|
4
|
+
import { defineCommand } from "@tailor-platform/erp-kit/module";
|
|
5
|
+
|
|
6
|
+
export const releaseProductionOrder = defineCommand(permissions.releaseProductionOrder, run);
|
|
@@ -0,0 +1,220 @@
|
|
|
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
|
+
ProductionOrderNotReleasableError,
|
|
7
|
+
BomNotResolvedError,
|
|
8
|
+
RoutingNotResolvedError,
|
|
9
|
+
CrossCompanyMasterReferenceError,
|
|
10
|
+
CrossSiteMasterReferenceError,
|
|
11
|
+
PlannedMaterialCostUnavailableError,
|
|
12
|
+
} from "../lib/errors.generated";
|
|
13
|
+
import {
|
|
14
|
+
baseDraftProductionOrder,
|
|
15
|
+
baseReleasedProductionOrder,
|
|
16
|
+
baseActiveBom,
|
|
17
|
+
baseActiveRouting,
|
|
18
|
+
baseBomLine,
|
|
19
|
+
baseRoutingOperation,
|
|
20
|
+
baseActiveWorkCenter,
|
|
21
|
+
} from "../testing/fixtures";
|
|
22
|
+
import { run } from "./releaseProductionOrder";
|
|
23
|
+
import type { CommandContext } from "@tailor-platform/erp-kit/module";
|
|
24
|
+
|
|
25
|
+
const baseReleaseValuation = {
|
|
26
|
+
id: "item-2",
|
|
27
|
+
valuationMethod: "AVCO",
|
|
28
|
+
costPerUnit: 10,
|
|
29
|
+
currencyCode: "USD",
|
|
30
|
+
valuationReference: "valuation-1",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
describe("releaseProductionOrder", () => {
|
|
34
|
+
const ctx: CommandContext = {
|
|
35
|
+
actorId: "test-actor",
|
|
36
|
+
permissions: ["manufacturing:releaseProductionOrder"],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
it("releases a draft order with valid BOM and routing snapshots", async () => {
|
|
40
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
41
|
+
const released = { ...baseDraftProductionOrder, status: "RELEASED" as const };
|
|
42
|
+
|
|
43
|
+
// order lookup
|
|
44
|
+
spies.select.mockReturnValueOnce(baseDraftProductionOrder);
|
|
45
|
+
// bom resolution (no selectedBomVersionId, so default resolution)
|
|
46
|
+
spies.select.mockReturnValueOnce(baseActiveBom);
|
|
47
|
+
// routing resolution (no selectedRoutingRevisionId, so default resolution)
|
|
48
|
+
spies.select.mockReturnValueOnce(baseActiveRouting);
|
|
49
|
+
// BOM lines query
|
|
50
|
+
spies.select.mockReturnValueOnce([baseBomLine]);
|
|
51
|
+
// material cost valuation for bomLine itemId
|
|
52
|
+
spies.select.mockReturnValueOnce(baseReleaseValuation);
|
|
53
|
+
// routing operations query
|
|
54
|
+
spies.select.mockReturnValueOnce([baseRoutingOperation]);
|
|
55
|
+
// work center lookup for operation
|
|
56
|
+
spies.select.mockReturnValueOnce(baseActiveWorkCenter);
|
|
57
|
+
// inserts: bom snapshot, routing snapshot, cost baseline, material requirement, work order, cost summary
|
|
58
|
+
spies.insert.mockReturnValue({});
|
|
59
|
+
// update: order status
|
|
60
|
+
spies.update.mockReturnValue(released);
|
|
61
|
+
|
|
62
|
+
const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
|
|
63
|
+
|
|
64
|
+
expect(result.ok).toBe(true);
|
|
65
|
+
if (result.ok) {
|
|
66
|
+
expect(result.value.productionOrder.status).toBe("RELEASED");
|
|
67
|
+
}
|
|
68
|
+
const snapshotValues = spies.values.mock.calls.find(
|
|
69
|
+
([values]) =>
|
|
70
|
+
typeof values === "object" &&
|
|
71
|
+
values !== null &&
|
|
72
|
+
"snapshotData" in (values as Record<string, unknown>),
|
|
73
|
+
)?.[0] as Record<string, unknown> | undefined;
|
|
74
|
+
expect(snapshotValues?.snapshotData).not.toBeNull();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("returns error when the order does not exist", async () => {
|
|
78
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
79
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
80
|
+
|
|
81
|
+
const result = await run(db, { id: "nonexistent" }, ctx);
|
|
82
|
+
|
|
83
|
+
expect(result.ok).toBe(false);
|
|
84
|
+
if (!result.ok) {
|
|
85
|
+
expect(result.error).toBeInstanceOf(ProductionOrderNotFoundError);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("returns error when the order is not in DRAFT", async () => {
|
|
90
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
91
|
+
spies.select.mockReturnValueOnce(baseReleasedProductionOrder);
|
|
92
|
+
|
|
93
|
+
const result = await run(db, { id: baseReleasedProductionOrder.id }, ctx);
|
|
94
|
+
|
|
95
|
+
expect(result.ok).toBe(false);
|
|
96
|
+
if (!result.ok) {
|
|
97
|
+
expect(result.error).toBeInstanceOf(ProductionOrderNotReleasableError);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("returns error when no active BOM can be resolved", async () => {
|
|
102
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
103
|
+
spies.select.mockReturnValueOnce(baseDraftProductionOrder);
|
|
104
|
+
// bom resolution returns nothing
|
|
105
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
106
|
+
|
|
107
|
+
const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
|
|
108
|
+
|
|
109
|
+
expect(result.ok).toBe(false);
|
|
110
|
+
if (!result.ok) {
|
|
111
|
+
expect(result.error).toBeInstanceOf(BomNotResolvedError);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("returns error when no active routing can be resolved", async () => {
|
|
116
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
117
|
+
spies.select.mockReturnValueOnce(baseDraftProductionOrder);
|
|
118
|
+
// bom resolution
|
|
119
|
+
spies.select.mockReturnValueOnce(baseActiveBom);
|
|
120
|
+
// routing resolution returns nothing
|
|
121
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
122
|
+
|
|
123
|
+
const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
|
|
124
|
+
|
|
125
|
+
expect(result.ok).toBe(false);
|
|
126
|
+
if (!result.ok) {
|
|
127
|
+
expect(result.error).toBeInstanceOf(RoutingNotResolvedError);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("returns error when BOM or routing belongs to another company", async () => {
|
|
132
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
133
|
+
const crossCompanyBom = { ...baseActiveBom, companyId: "other-company" };
|
|
134
|
+
|
|
135
|
+
spies.select.mockReturnValueOnce(baseDraftProductionOrder);
|
|
136
|
+
// bom resolution
|
|
137
|
+
spies.select.mockReturnValueOnce(crossCompanyBom);
|
|
138
|
+
|
|
139
|
+
const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
|
|
140
|
+
|
|
141
|
+
expect(result.ok).toBe(false);
|
|
142
|
+
if (!result.ok) {
|
|
143
|
+
expect(result.error).toBeInstanceOf(CrossCompanyMasterReferenceError);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("returns error when BOM or routing belongs to another site in the same company", async () => {
|
|
148
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
149
|
+
const crossSiteBom = { ...baseActiveBom, companyId: "company-1", siteId: "other-site" };
|
|
150
|
+
|
|
151
|
+
spies.select.mockReturnValueOnce(baseDraftProductionOrder);
|
|
152
|
+
// bom resolution
|
|
153
|
+
spies.select.mockReturnValueOnce(crossSiteBom);
|
|
154
|
+
|
|
155
|
+
const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
|
|
156
|
+
|
|
157
|
+
expect(result.ok).toBe(false);
|
|
158
|
+
if (!result.ok) {
|
|
159
|
+
expect(result.error).toBeInstanceOf(CrossSiteMasterReferenceError);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("returns error when inventory valuation for a required component is unavailable", async () => {
|
|
164
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
165
|
+
|
|
166
|
+
spies.select.mockReturnValueOnce(baseDraftProductionOrder);
|
|
167
|
+
// bom resolution
|
|
168
|
+
spies.select.mockReturnValueOnce(baseActiveBom);
|
|
169
|
+
// routing resolution
|
|
170
|
+
spies.select.mockReturnValueOnce(baseActiveRouting);
|
|
171
|
+
// BOM lines query
|
|
172
|
+
spies.select.mockReturnValueOnce([baseBomLine]);
|
|
173
|
+
// material cost valuation returns nothing
|
|
174
|
+
spies.select.mockReturnValueOnce(undefined);
|
|
175
|
+
|
|
176
|
+
const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
|
|
177
|
+
|
|
178
|
+
expect(result.ok).toBe(false);
|
|
179
|
+
if (!result.ok) {
|
|
180
|
+
expect(result.error).toBeInstanceOf(PlannedMaterialCostUnavailableError);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("creates work orders and a collecting cost summary at release", async () => {
|
|
185
|
+
const { db, spies } = createMockDb<Transaction>();
|
|
186
|
+
const released = {
|
|
187
|
+
...baseDraftProductionOrder,
|
|
188
|
+
status: "RELEASED" as const,
|
|
189
|
+
selectedBomVersionId: baseActiveBom.id,
|
|
190
|
+
selectedRoutingRevisionId: baseActiveRouting.id,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// order lookup
|
|
194
|
+
spies.select.mockReturnValueOnce(baseDraftProductionOrder);
|
|
195
|
+
// bom resolution
|
|
196
|
+
spies.select.mockReturnValueOnce(baseActiveBom);
|
|
197
|
+
// routing resolution
|
|
198
|
+
spies.select.mockReturnValueOnce(baseActiveRouting);
|
|
199
|
+
// BOM lines query
|
|
200
|
+
spies.select.mockReturnValueOnce([baseBomLine]);
|
|
201
|
+
// material cost valuation
|
|
202
|
+
spies.select.mockReturnValueOnce(baseReleaseValuation);
|
|
203
|
+
// routing operations query
|
|
204
|
+
spies.select.mockReturnValueOnce([baseRoutingOperation]);
|
|
205
|
+
// work center lookup
|
|
206
|
+
spies.select.mockReturnValueOnce(baseActiveWorkCenter);
|
|
207
|
+
// inserts
|
|
208
|
+
spies.insert.mockReturnValue({});
|
|
209
|
+
// update
|
|
210
|
+
spies.update.mockReturnValue(released);
|
|
211
|
+
|
|
212
|
+
const result = await run(db, { id: baseDraftProductionOrder.id }, ctx);
|
|
213
|
+
|
|
214
|
+
expect(result.ok).toBe(true);
|
|
215
|
+
// Verify inserts were called: bom snapshot + routing snapshot + cost baseline +
|
|
216
|
+
// material requirements + work orders + cost summary
|
|
217
|
+
expect(spies.insert).toHaveBeenCalled();
|
|
218
|
+
expect(spies.update).toHaveBeenCalled();
|
|
219
|
+
});
|
|
220
|
+
});
|