@happyvertical/smrt-manufacturing 0.53.3 → 0.53.5
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/AGENTS.md +45 -6
- package/README.md +161 -3
- package/dist/collections/AssemblyCollection.d.ts +15 -0
- package/dist/collections/AssemblyCollection.d.ts.map +1 -0
- package/dist/collections/OperationCollection.d.ts +20 -0
- package/dist/collections/OperationCollection.d.ts.map +1 -0
- package/dist/collections/RoutingStepCollection.d.ts +15 -0
- package/dist/collections/RoutingStepCollection.d.ts.map +1 -0
- package/dist/collections/index.d.ts +3 -0
- package/dist/collections/index.d.ts.map +1 -1
- package/dist/index.d.ts +13 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +756 -29
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +1248 -128
- package/dist/models/Assembly.d.ts +62 -0
- package/dist/models/Assembly.d.ts.map +1 -0
- package/dist/models/BillOfMaterials.d.ts +12 -4
- package/dist/models/BillOfMaterials.d.ts.map +1 -1
- package/dist/models/BomLine.d.ts +11 -5
- package/dist/models/BomLine.d.ts.map +1 -1
- package/dist/models/Operation.d.ts +59 -0
- package/dist/models/Operation.d.ts.map +1 -0
- package/dist/models/RoutingStep.d.ts +46 -0
- package/dist/models/RoutingStep.d.ts.map +1 -0
- package/dist/models/index.d.ts +3 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/operation-types.d.ts +99 -0
- package/dist/operation-types.d.ts.map +1 -0
- package/dist/services/AssemblyService.d.ts +217 -0
- package/dist/services/AssemblyService.d.ts.map +1 -0
- package/dist/services/BomService.d.ts +25 -1
- package/dist/services/BomService.d.ts.map +1 -1
- package/dist/services/OperationService.d.ts +48 -0
- package/dist/services/OperationService.d.ts.map +1 -0
- package/dist/services/RoutingService.d.ts +50 -0
- package/dist/services/RoutingService.d.ts.map +1 -0
- package/dist/services/index.d.ts +3 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +1138 -81
- package/dist/svelte/components/AssemblyForm.svelte +69 -0
- package/dist/svelte/components/AssemblyForm.svelte.d.ts +38 -0
- package/dist/svelte/components/AssemblyForm.svelte.d.ts.map +1 -0
- package/dist/svelte/components/AssemblyList.svelte +229 -0
- package/dist/svelte/components/AssemblyList.svelte.d.ts +28 -0
- package/dist/svelte/components/AssemblyList.svelte.d.ts.map +1 -0
- package/dist/svelte/components/BomEditor.svelte +575 -0
- package/dist/svelte/components/BomEditor.svelte.d.ts +29 -0
- package/dist/svelte/components/BomEditor.svelte.d.ts.map +1 -0
- package/dist/svelte/components/BomStructureTree.svelte +164 -0
- package/dist/svelte/components/BomStructureTree.svelte.d.ts +16 -0
- package/dist/svelte/components/BomStructureTree.svelte.d.ts.map +1 -0
- package/dist/svelte/components/OperationForm.svelte +258 -0
- package/dist/svelte/components/OperationForm.svelte.d.ts +17 -0
- package/dist/svelte/components/OperationForm.svelte.d.ts.map +1 -0
- package/dist/svelte/components/OperationList.svelte +170 -0
- package/dist/svelte/components/OperationList.svelte.d.ts +17 -0
- package/dist/svelte/components/OperationList.svelte.d.ts.map +1 -0
- package/dist/svelte/i18n.d.ts +87 -0
- package/dist/svelte/i18n.d.ts.map +1 -0
- package/dist/svelte/i18n.js +87 -0
- package/dist/svelte/index.d.ts +28 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +29 -0
- package/dist/svelte/types.d.ts +170 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +126 -0
- package/dist/ui.d.ts +17 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +72 -0
- package/dist/ui.js.map +1 -0
- package/package.json +39 -10
package/dist/index.js
CHANGED
|
@@ -1,18 +1,134 @@
|
|
|
1
1
|
import { BomNotFoundError, NoActiveBomForProductError } from "./types.js";
|
|
2
|
-
import { ObjectRegistry, SmrtCollection, SmrtObject, field, smrt } from "@happyvertical/smrt-core";
|
|
3
|
-
import {
|
|
2
|
+
import { ObjectRegistry, SmrtCollection, SmrtObject, ValidationError, crossPackageRef, field, foreignKey, resolveDatabase, smrt } from "@happyvertical/smrt-core";
|
|
3
|
+
import { ProductCollection, SkuCollection } from "@happyvertical/smrt-products/collections";
|
|
4
|
+
import { Material, Product, ProductType } from "@happyvertical/smrt-products/models";
|
|
5
|
+
import { TenantScoped, getCurrentTenant, tenantId } from "@happyvertical/smrt-tenancy";
|
|
4
6
|
import { createStockService } from "@happyvertical/smrt-inventory";
|
|
5
7
|
import { createLogger } from "@happyvertical/logger";
|
|
8
|
+
//#region \0rolldown/runtime.js
|
|
9
|
+
var __defProp$5 = Object.defineProperty;
|
|
10
|
+
var __exportAll = (all, no_symbols) => {
|
|
11
|
+
let target = {};
|
|
12
|
+
for (var name in all) __defProp$5(target, name, {
|
|
13
|
+
get: all[name],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
if (!no_symbols) __defProp$5(target, Symbol.toStringTag, { value: "Module" });
|
|
17
|
+
return target;
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
6
20
|
//#region src/__smrt-register__.ts
|
|
7
|
-
ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-manufacturing\",\"packageVersion\":\"0.53.3\",\"objects\":{\"@happyvertical/smrt-manufacturing:BillOfMaterialsCollection\":{\"name\":\"billofmaterialscollection\",\"className\":\"BillOfMaterialsCollection\",\"qualifiedName\":\"@happyvertical/smrt-manufacturing:BillOfMaterialsCollection\",\"collection\":\"billofmaterialses\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/manufacturing/src/collections/BillOfMaterialsCollection.ts\",\"packageName\":\"@happyvertical/smrt-manufacturing\",\"fields\":{},\"methods\":{\"findByProduct\":{\"name\":\"findByProduct\",\"async\":true,\"parameters\":[{\"name\":\"productId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<BillOfMaterials[]>\",\"isStatic\":false,\"isPublic\":true},\"findActiveForProduct\":{\"name\":\"findActiveForProduct\",\"async\":true,\"parameters\":[{\"name\":\"productId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<BillOfMaterials | null>\",\"isStatic\":false,\"isPublic\":true},\"findByStatus\":{\"name\":\"findByStatus\",\"async\":true,\"parameters\":[{\"name\":\"status\",\"type\":\"BomStatus\",\"optional\":false}],\"returnType\":\"Promise<BillOfMaterials[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"manufacturing_boms\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"BillOfMaterials\",\"exportName\":\"BillOfMaterialsCollection\",\"collectionExportName\":\"BillOfMaterialsCollectionCollection\",\"schema\":{\"tableName\":\"manufacturing_boms\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"manufacturing_boms\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"product_id\\\" TEXT NOT NULL DEFAULT '',\\n \\\"version\\\" INTEGER NOT NULL DEFAULT 1,\\n \\\"effective_date\\\" TIMESTAMP,\\n \\\"status\\\" TEXT NOT NULL DEFAULT 'draft',\\n \\\"notes\\\" TEXT DEFAULT '',\\n \\\"currency\\\" TEXT DEFAULT 'USD'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"product_id\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"version\":{\"type\":\"INTEGER\",\"notNull\":true,\"unique\":false,\"default\":1},\"effective_date\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"draft\"},\"notes\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"currency\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"USD\"}},\"indexes\":[{\"name\":\"manufacturing_boms_product_id_version_idx\",\"columns\":[\"product_id\",\"version\",\"tenant_id\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"manufacturing_boms_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"manufacturing_boms_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"b2691adc\"},\"importPath\":\"@happyvertical/smrt-manufacturing\"},\"@happyvertical/smrt-manufacturing:BomLineCollection\":{\"name\":\"bomlinecollection\",\"className\":\"BomLineCollection\",\"qualifiedName\":\"@happyvertical/smrt-manufacturing:BomLineCollection\",\"collection\":\"bomlines\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/manufacturing/src/collections/BomLineCollection.ts\",\"packageName\":\"@happyvertical/smrt-manufacturing\",\"fields\":{},\"methods\":{\"findByBom\":{\"name\":\"findByBom\",\"async\":true,\"parameters\":[{\"name\":\"bomId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<BomLine[]>\",\"isStatic\":false,\"isPublic\":true},\"findByComponent\":{\"name\":\"findByComponent\",\"async\":true,\"parameters\":[{\"name\":\"componentSkuId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<BomLine[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"manufacturing_bom_lines\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"BomLine\",\"exportName\":\"BomLineCollection\",\"collectionExportName\":\"BomLineCollectionCollection\",\"schema\":{\"tableName\":\"manufacturing_bom_lines\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"manufacturing_bom_lines\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"bom_id\\\" TEXT NOT NULL DEFAULT '',\\n \\\"component_sku_id\\\" TEXT NOT NULL DEFAULT '',\\n \\\"qty_per_unit\\\" REAL,\\n \\\"uom\\\" TEXT DEFAULT 'each',\\n \\\"waste_percent\\\" REAL,\\n \\\"notes\\\" TEXT DEFAULT ''\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"bom_id\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"component_sku_id\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"qty_per_unit\":{\"type\":\"REAL\",\"notNull\":false,\"unique\":false},\"uom\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"each\"},\"waste_percent\":{\"type\":\"REAL\",\"notNull\":false,\"unique\":false},\"notes\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"}},\"indexes\":[{\"name\":\"manufacturing_bom_lines_bom_id_component_sku_id_idx\",\"columns\":[\"bom_id\",\"component_sku_id\",\"tenant_id\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"manufacturing_bom_lines_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"manufacturing_bom_lines_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"cfbae7e8\"},\"importPath\":\"@happyvertical/smrt-manufacturing\"},\"@happyvertical/smrt-manufacturing:BillOfMaterials\":{\"name\":\"billofmaterials\",\"className\":\"BillOfMaterials\",\"qualifiedName\":\"@happyvertical/smrt-manufacturing:BillOfMaterials\",\"collection\":\"billofmaterialses\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/manufacturing/src/models/BillOfMaterials.ts\",\"packageName\":\"@happyvertical/smrt-manufacturing\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"productId\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"version\":{\"type\":\"integer\",\"required\":true,\"default\":1,\"_meta\":{\"required\":true}},\"effectiveDate\":{\"type\":\"datetime\",\"required\":false},\"status\":{\"type\":\"text\",\"required\":true,\"default\":\"draft\",\"_meta\":{\"required\":true}},\"notes\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"currency\":{\"type\":\"text\",\"required\":false,\"default\":\"USD\"}},\"methods\":{},\"decoratorConfig\":{\"tableName\":\"manufacturing_boms\",\"conflictColumns\":[\"product_id\",\"version\",\"tenant_id\"],\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":true,\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"BillOfMaterials\",\"collectionExportName\":\"BillOfMaterialsCollection\",\"validationRules\":[{\"field\":\"productId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"version\",\"rule\":\"required\",\"fieldType\":\"integer\"},{\"field\":\"status\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"manufacturing_boms\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"manufacturing_boms\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"product_id\\\" TEXT NOT NULL DEFAULT '',\\n \\\"version\\\" INTEGER NOT NULL DEFAULT 1,\\n \\\"effective_date\\\" TIMESTAMP,\\n \\\"status\\\" TEXT NOT NULL DEFAULT 'draft',\\n \\\"notes\\\" TEXT DEFAULT '',\\n \\\"currency\\\" TEXT DEFAULT 'USD'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"product_id\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"version\":{\"type\":\"INTEGER\",\"notNull\":true,\"unique\":false,\"default\":1},\"effective_date\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"draft\"},\"notes\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"currency\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"USD\"}},\"indexes\":[{\"name\":\"manufacturing_boms_product_id_version_idx\",\"columns\":[\"product_id\",\"version\",\"tenant_id\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"manufacturing_boms_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"manufacturing_boms_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"b2691adc\"},\"importPath\":\"@happyvertical/smrt-manufacturing\"},\"@happyvertical/smrt-manufacturing:BomLine\":{\"name\":\"bomline\",\"className\":\"BomLine\",\"qualifiedName\":\"@happyvertical/smrt-manufacturing:BomLine\",\"collection\":\"bomlines\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/manufacturing/src/models/BomLine.ts\",\"packageName\":\"@happyvertical/smrt-manufacturing\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"bomId\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"componentSkuId\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"qtyPerUnit\":{\"type\":\"decimal\",\"required\":false,\"_meta\":{}},\"uom\":{\"type\":\"text\",\"required\":false,\"default\":\"each\"},\"wastePercent\":{\"type\":\"decimal\",\"required\":false,\"_meta\":{}},\"notes\":{\"type\":\"text\",\"required\":false,\"default\":\"\"}},\"methods\":{\"effectiveQtyPerUnit\":{\"name\":\"effectiveQtyPerUnit\",\"async\":false,\"parameters\":[],\"returnType\":\"number\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"manufacturing_bom_lines\",\"conflictColumns\":[\"bom_id\",\"component_sku_id\",\"tenant_id\"],\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":{\"skipApiCheck\":true},\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"BomLine\",\"collectionExportName\":\"BomLineCollection\",\"validationRules\":[{\"field\":\"bomId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"componentSkuId\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"manufacturing_bom_lines\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"manufacturing_bom_lines\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"bom_id\\\" TEXT NOT NULL DEFAULT '',\\n \\\"component_sku_id\\\" TEXT NOT NULL DEFAULT '',\\n \\\"qty_per_unit\\\" REAL,\\n \\\"uom\\\" TEXT DEFAULT 'each',\\n \\\"waste_percent\\\" REAL,\\n \\\"notes\\\" TEXT DEFAULT ''\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"bom_id\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"component_sku_id\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"qty_per_unit\":{\"type\":\"REAL\",\"notNull\":false,\"unique\":false},\"uom\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"each\"},\"waste_percent\":{\"type\":\"REAL\",\"notNull\":false,\"unique\":false},\"notes\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"}},\"indexes\":[{\"name\":\"manufacturing_bom_lines_bom_id_component_sku_id_idx\",\"columns\":[\"bom_id\",\"component_sku_id\",\"tenant_id\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"manufacturing_bom_lines_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"manufacturing_bom_lines_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"cfbae7e8\"},\"importPath\":\"@happyvertical/smrt-manufacturing\"}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-core\",\"@happyvertical/smrt-inventory\",\"@happyvertical/smrt-tenancy\"]}"));
|
|
21
|
+
ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-manufacturing\",\"packageVersion\":\"0.53.5\",\"objects\":{\"@happyvertical/smrt-manufacturing:BillOfMaterialsCollection\":{\"name\":\"billofmaterialscollection\",\"className\":\"BillOfMaterialsCollection\",\"qualifiedName\":\"@happyvertical/smrt-manufacturing:BillOfMaterialsCollection\",\"collection\":\"billofmaterialses\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/manufacturing/src/collections/BillOfMaterialsCollection.ts\",\"packageName\":\"@happyvertical/smrt-manufacturing\",\"fields\":{},\"methods\":{\"findByProduct\":{\"name\":\"findByProduct\",\"async\":true,\"parameters\":[{\"name\":\"productId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<BillOfMaterials[]>\",\"isStatic\":false,\"isPublic\":true},\"findActiveForProduct\":{\"name\":\"findActiveForProduct\",\"async\":true,\"parameters\":[{\"name\":\"productId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<BillOfMaterials | null>\",\"isStatic\":false,\"isPublic\":true},\"findByStatus\":{\"name\":\"findByStatus\",\"async\":true,\"parameters\":[{\"name\":\"status\",\"type\":\"BomStatus\",\"optional\":false}],\"returnType\":\"Promise<BillOfMaterials[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"manufacturing_boms\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"BillOfMaterials\",\"exportName\":\"BillOfMaterialsCollection\",\"collectionExportName\":\"BillOfMaterialsCollectionCollection\",\"schema\":{\"tableName\":\"manufacturing_boms\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"manufacturing_boms\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"product_id\\\" TEXT NOT NULL DEFAULT '',\\n \\\"version\\\" INTEGER NOT NULL DEFAULT 1,\\n \\\"effective_date\\\" TIMESTAMP,\\n \\\"status\\\" TEXT NOT NULL DEFAULT 'draft',\\n \\\"notes\\\" TEXT DEFAULT '',\\n \\\"currency\\\" TEXT DEFAULT 'USD'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"product_id\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"version\":{\"type\":\"INTEGER\",\"notNull\":true,\"unique\":false,\"default\":1},\"effective_date\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"draft\"},\"notes\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"currency\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"USD\"}},\"indexes\":[{\"name\":\"manufacturing_boms_product_id_version_idx\",\"columns\":[\"product_id\",\"version\",\"tenant_id\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"manufacturing_boms_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"manufacturing_boms_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"b2691adc\"},\"importPath\":\"@happyvertical/smrt-manufacturing\"},\"@happyvertical/smrt-manufacturing:BomLineCollection\":{\"name\":\"bomlinecollection\",\"className\":\"BomLineCollection\",\"qualifiedName\":\"@happyvertical/smrt-manufacturing:BomLineCollection\",\"collection\":\"bomlines\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/manufacturing/src/collections/BomLineCollection.ts\",\"packageName\":\"@happyvertical/smrt-manufacturing\",\"fields\":{},\"methods\":{\"findByBom\":{\"name\":\"findByBom\",\"async\":true,\"parameters\":[{\"name\":\"bomId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<BomLine[]>\",\"isStatic\":false,\"isPublic\":true},\"findByComponent\":{\"name\":\"findByComponent\",\"async\":true,\"parameters\":[{\"name\":\"componentSkuId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<BomLine[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"manufacturing_bom_lines\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"BomLine\",\"exportName\":\"BomLineCollection\",\"collectionExportName\":\"BomLineCollectionCollection\",\"schema\":{\"tableName\":\"manufacturing_bom_lines\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"manufacturing_bom_lines\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"bom_id\\\" TEXT NOT NULL DEFAULT '',\\n \\\"component_sku_id\\\" TEXT NOT NULL DEFAULT '',\\n \\\"qty_per_unit\\\" REAL,\\n \\\"uom\\\" TEXT DEFAULT 'each',\\n \\\"waste_percent\\\" REAL,\\n \\\"notes\\\" TEXT DEFAULT ''\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"bom_id\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"component_sku_id\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"qty_per_unit\":{\"type\":\"REAL\",\"notNull\":false,\"unique\":false},\"uom\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"each\"},\"waste_percent\":{\"type\":\"REAL\",\"notNull\":false,\"unique\":false},\"notes\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"}},\"indexes\":[{\"name\":\"manufacturing_bom_lines_bom_id_component_sku_id_idx\",\"columns\":[\"bom_id\",\"component_sku_id\",\"tenant_id\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"manufacturing_bom_lines_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"manufacturing_bom_lines_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"cfbae7e8\"},\"importPath\":\"@happyvertical/smrt-manufacturing\"},\"@happyvertical/smrt-manufacturing:OperationCollection\":{\"name\":\"operationcollection\",\"className\":\"OperationCollection\",\"qualifiedName\":\"@happyvertical/smrt-manufacturing:OperationCollection\",\"collection\":\"operations\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/manufacturing/src/collections/OperationCollection.ts\",\"packageName\":\"@happyvertical/smrt-manufacturing\",\"fields\":{},\"methods\":{\"findByCode\":{\"name\":\"findByCode\",\"async\":true,\"parameters\":[{\"name\":\"code\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<Operation | null>\",\"isStatic\":false,\"isPublic\":true},\"listOperations\":{\"name\":\"listOperations\",\"async\":true,\"parameters\":[{\"name\":\"options\",\"type\":\"object\",\"optional\":true,\"memberTypes\":[\"boolean\"]}],\"returnType\":\"Promise<Operation[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"manufacturing_operations\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"Operation\",\"exportName\":\"OperationCollection\",\"collectionExportName\":\"OperationCollectionCollection\",\"schema\":{\"tableName\":\"manufacturing_operations\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"manufacturing_operations\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"code\\\" TEXT NOT NULL DEFAULT '',\\n \\\"name\\\" TEXT NOT NULL DEFAULT '',\\n \\\"category\\\" TEXT DEFAULT '',\\n \\\"is_active\\\" BOOLEAN DEFAULT TRUE,\\n \\\"required_qualification_id\\\" UUID\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"code\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"name\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"category\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"is_active\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false,\"default\":true},\"required_qualification_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"manufacturing_operations_code_tenant_id_idx\",\"columns\":[\"code\",\"tenant_id\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"manufacturing_operations_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"manufacturing_operations_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"manufacturing_operations_required_qualification_id_idx\",\"columns\":[\"required_qualification_id\"]}],\"version\":\"0b3134d6\"},\"importPath\":\"@happyvertical/smrt-manufacturing\"},\"@happyvertical/smrt-manufacturing:RoutingStepCollection\":{\"name\":\"routingstepcollection\",\"className\":\"RoutingStepCollection\",\"qualifiedName\":\"@happyvertical/smrt-manufacturing:RoutingStepCollection\",\"collection\":\"routingsteps\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/manufacturing/src/collections/RoutingStepCollection.ts\",\"packageName\":\"@happyvertical/smrt-manufacturing\",\"fields\":{},\"methods\":{\"findByBom\":{\"name\":\"findByBom\",\"async\":true,\"parameters\":[{\"name\":\"bomId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<RoutingStep[]>\",\"isStatic\":false,\"isPublic\":true},\"findByOperation\":{\"name\":\"findByOperation\",\"async\":true,\"parameters\":[{\"name\":\"operationId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<RoutingStep[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"manufacturing_routing_steps\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"RoutingStep\",\"exportName\":\"RoutingStepCollection\",\"collectionExportName\":\"RoutingStepCollectionCollection\",\"schema\":{\"tableName\":\"manufacturing_routing_steps\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"manufacturing_routing_steps\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"bom_id\\\" UUID NOT NULL,\\n \\\"operation_id\\\" UUID NOT NULL,\\n \\\"sequence\\\" INTEGER NOT NULL DEFAULT 1,\\n \\\"estimated_minutes\\\" REAL,\\n \\\"notes\\\" TEXT DEFAULT ''\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"bom_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false,\"foreignKey\":{\"table\":\"manufacturing_boms\",\"column\":\"id\",\"onDelete\":\"CASCADE\",\"onUpdate\":\"CASCADE\"}},\"operation_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false,\"foreignKey\":{\"table\":\"manufacturing_operations\",\"column\":\"id\",\"onDelete\":\"RESTRICT\",\"onUpdate\":\"CASCADE\"}},\"sequence\":{\"type\":\"INTEGER\",\"notNull\":true,\"unique\":false,\"default\":1},\"estimated_minutes\":{\"type\":\"REAL\",\"notNull\":false,\"unique\":false},\"notes\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"}},\"indexes\":[{\"name\":\"manufacturing_routing_steps_bom_id_sequence_idx\",\"columns\":[\"bom_id\",\"sequence\",\"tenant_id\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"manufacturing_routing_steps_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"manufacturing_routing_steps_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"manufacturing_routing_steps_operation_id_idx\",\"columns\":[\"operation_id\"]}],\"version\":\"41ac3c66\"},\"importPath\":\"@happyvertical/smrt-manufacturing\"},\"@happyvertical/smrt-manufacturing:Assembly\":{\"name\":\"assembly\",\"className\":\"Assembly\",\"qualifiedName\":\"@happyvertical/smrt-manufacturing:Assembly\",\"collection\":\"products\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/manufacturing/src/models/Assembly.ts\",\"packageName\":\"@happyvertical/smrt-manufacturing\",\"fields\":{\"created_at\":{\"type\":\"datetime\",\"required\":false},\"updated_at\":{\"type\":\"datetime\",\"required\":false},\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"generated\":true,\"source\":\"tenantScoped_decorator\",\"sqlType\":\"UUID\",\"__tenancy\":{\"isTenantIdField\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"autoFilter\":true,\"autoPopulate\":true,\"allowSuperAdminBypass\":false}}},\"productType\":{\"type\":\"json\",\"required\":false},\"name\":{\"type\":\"text\",\"required\":false},\"description\":{\"type\":\"text\",\"required\":false},\"category\":{\"type\":\"text\",\"required\":false},\"manufacturer\":{\"type\":\"text\",\"required\":false},\"model\":{\"type\":\"text\",\"required\":false},\"price\":{\"type\":\"integer\",\"required\":true,\"default\":0},\"inStock\":{\"type\":\"boolean\",\"required\":true,\"default\":true},\"specifications\":{\"type\":\"json\",\"required\":false,\"default\":{}},\"tags\":{\"type\":\"json\",\"required\":false,\"default\":[]},\"estimatedLabourMinutes\":{\"type\":\"integer\",\"required\":false,\"default\":0},\"defaultOperationId\":{\"type\":\"foreignKey\",\"required\":false,\"related\":\"Operation\",\"_meta\":{\"nullable\":true,\"onDelete\":\"RESTRICT\"}},\"partReference\":{\"type\":\"text\",\"required\":false,\"default\":\"\"}},\"methods\":{\"getSpecification\":{\"name\":\"getSpecification\",\"async\":true,\"parameters\":[{\"name\":\"key\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<unknown>\",\"isStatic\":false,\"isPublic\":true},\"updateSpecification\":{\"name\":\"updateSpecification\",\"async\":true,\"parameters\":[{\"name\":\"key\",\"type\":\"string\",\"optional\":false},{\"name\":\"value\",\"type\":\"unknown\",\"optional\":false}],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"getAssets\":{\"name\":\"getAssets\",\"async\":true,\"parameters\":[{\"name\":\"relationship\",\"type\":\"string\",\"optional\":true}],\"returnType\":\"Promise<Asset[]>\",\"isStatic\":false,\"isPublic\":true},\"addAsset\":{\"name\":\"addAsset\",\"async\":true,\"parameters\":[{\"name\":\"asset\",\"type\":\"Asset\",\"optional\":false},{\"name\":\"relationship\",\"type\":\"any\",\"optional\":true,\"default\":\"attachment\"},{\"name\":\"sortOrder\",\"type\":\"any\",\"optional\":true}],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true},\"removeAsset\":{\"name\":\"removeAsset\",\"async\":true,\"parameters\":[{\"name\":\"assetId\",\"type\":\"string\",\"optional\":false},{\"name\":\"relationship\",\"type\":\"string\",\"optional\":true}],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":{\"skipApiCheck\":true},\"tenantScoped\":{\"mode\":\"optional\"},\"tableName\":\"products\",\"tableStrategy\":\"sti\"},\"extends\":\"Product\",\"exportName\":\"Assembly\",\"collectionExportName\":\"AssemblyCollection\",\"validationRules\":[{\"field\":\"price\",\"rule\":\"required\",\"fieldType\":\"integer\"},{\"field\":\"inStock\",\"rule\":\"required\",\"fieldType\":\"boolean\"}],\"schema\":{\"tableName\":\"products\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"products\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"_meta_type\\\" TEXT NOT NULL,\\n \\\"_meta_data\\\" JSON,\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"product_type\\\" TEXT,\\n \\\"name\\\" TEXT,\\n \\\"description\\\" TEXT,\\n \\\"category\\\" TEXT,\\n \\\"manufacturer\\\" TEXT,\\n \\\"model\\\" TEXT,\\n \\\"price\\\" INTEGER DEFAULT 0,\\n \\\"in_stock\\\" BOOLEAN DEFAULT TRUE,\\n \\\"specifications\\\" JSON DEFAULT '{}',\\n \\\"tags\\\" JSON DEFAULT '[]',\\n \\\"estimated_labour_minutes\\\" INTEGER DEFAULT 0,\\n \\\"default_operation_id\\\" UUID,\\n \\\"part_reference\\\" TEXT DEFAULT ''\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"_meta_type\":{\"type\":\"TEXT\",\"notNull\":true},\"_meta_data\":{\"type\":\"JSON\",\"notNull\":false},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false},\"product_type\":{\"type\":\"TEXT\",\"notNull\":false},\"name\":{\"type\":\"TEXT\",\"notNull\":false},\"description\":{\"type\":\"TEXT\",\"notNull\":false},\"category\":{\"type\":\"TEXT\",\"notNull\":false},\"manufacturer\":{\"type\":\"TEXT\",\"notNull\":false},\"model\":{\"type\":\"TEXT\",\"notNull\":false},\"price\":{\"type\":\"INTEGER\",\"notNull\":false,\"default\":0},\"in_stock\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"default\":true},\"specifications\":{\"type\":\"JSON\",\"notNull\":false,\"default\":{}},\"tags\":{\"type\":\"JSON\",\"notNull\":false,\"default\":[]},\"estimated_labour_minutes\":{\"type\":\"INTEGER\",\"notNull\":false,\"default\":0},\"default_operation_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":false,\"foreignKey\":{\"table\":\"manufacturing_operations\",\"column\":\"id\",\"onDelete\":\"RESTRICT\",\"onUpdate\":\"CASCADE\"}},\"part_reference\":{\"type\":\"TEXT\",\"notNull\":false,\"default\":\"\"}},\"indexes\":[{\"name\":\"products_slug_context_meta_type_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\",\"_meta_type\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"products_meta_type_idx\",\"columns\":[\"_meta_type\"]},{\"name\":\"products_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"products_default_operation_id_idx\",\"columns\":[\"default_operation_id\"]}],\"version\":\"f2f2bf95\"},\"importPath\":\"@happyvertical/smrt-manufacturing\"},\"@happyvertical/smrt-manufacturing:BillOfMaterials\":{\"name\":\"billofmaterials\",\"className\":\"BillOfMaterials\",\"qualifiedName\":\"@happyvertical/smrt-manufacturing:BillOfMaterials\",\"collection\":\"billofmaterialses\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/manufacturing/src/models/BillOfMaterials.ts\",\"packageName\":\"@happyvertical/smrt-manufacturing\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"productId\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"version\":{\"type\":\"integer\",\"required\":true,\"default\":1,\"_meta\":{\"required\":true}},\"effectiveDate\":{\"type\":\"datetime\",\"required\":false},\"status\":{\"type\":\"text\",\"required\":true,\"default\":\"draft\",\"_meta\":{\"required\":true}},\"notes\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"currency\":{\"type\":\"text\",\"required\":false,\"default\":\"USD\"}},\"methods\":{},\"decoratorConfig\":{\"tableName\":\"manufacturing_boms\",\"conflictColumns\":[\"product_id\",\"version\",\"tenant_id\"],\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":true,\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"BillOfMaterials\",\"collectionExportName\":\"BillOfMaterialsCollection\",\"validationRules\":[{\"field\":\"productId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"version\",\"rule\":\"required\",\"fieldType\":\"integer\"},{\"field\":\"status\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"manufacturing_boms\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"manufacturing_boms\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"product_id\\\" TEXT NOT NULL DEFAULT '',\\n \\\"version\\\" INTEGER NOT NULL DEFAULT 1,\\n \\\"effective_date\\\" TIMESTAMP,\\n \\\"status\\\" TEXT NOT NULL DEFAULT 'draft',\\n \\\"notes\\\" TEXT DEFAULT '',\\n \\\"currency\\\" TEXT DEFAULT 'USD'\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"product_id\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"version\":{\"type\":\"INTEGER\",\"notNull\":true,\"unique\":false,\"default\":1},\"effective_date\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"draft\"},\"notes\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"currency\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"USD\"}},\"indexes\":[{\"name\":\"manufacturing_boms_product_id_version_idx\",\"columns\":[\"product_id\",\"version\",\"tenant_id\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"manufacturing_boms_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"manufacturing_boms_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"b2691adc\"},\"importPath\":\"@happyvertical/smrt-manufacturing\"},\"@happyvertical/smrt-manufacturing:BomLine\":{\"name\":\"bomline\",\"className\":\"BomLine\",\"qualifiedName\":\"@happyvertical/smrt-manufacturing:BomLine\",\"collection\":\"bomlines\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/manufacturing/src/models/BomLine.ts\",\"packageName\":\"@happyvertical/smrt-manufacturing\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"bomId\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"componentSkuId\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"qtyPerUnit\":{\"type\":\"decimal\",\"required\":false,\"_meta\":{}},\"uom\":{\"type\":\"text\",\"required\":false,\"default\":\"each\"},\"wastePercent\":{\"type\":\"decimal\",\"required\":false,\"_meta\":{}},\"notes\":{\"type\":\"text\",\"required\":false,\"default\":\"\"}},\"methods\":{\"effectiveQtyPerUnit\":{\"name\":\"effectiveQtyPerUnit\",\"async\":false,\"parameters\":[],\"returnType\":\"number\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"manufacturing_bom_lines\",\"conflictColumns\":[\"bom_id\",\"component_sku_id\",\"tenant_id\"],\"api\":{\"include\":[\"list\",\"get\",\"create\",\"update\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":{\"skipApiCheck\":true},\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"BomLine\",\"collectionExportName\":\"BomLineCollection\",\"validationRules\":[{\"field\":\"bomId\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"componentSkuId\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"manufacturing_bom_lines\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"manufacturing_bom_lines\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"bom_id\\\" TEXT NOT NULL DEFAULT '',\\n \\\"component_sku_id\\\" TEXT NOT NULL DEFAULT '',\\n \\\"qty_per_unit\\\" REAL,\\n \\\"uom\\\" TEXT DEFAULT 'each',\\n \\\"waste_percent\\\" REAL,\\n \\\"notes\\\" TEXT DEFAULT ''\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"bom_id\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"component_sku_id\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"qty_per_unit\":{\"type\":\"REAL\",\"notNull\":false,\"unique\":false},\"uom\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"each\"},\"waste_percent\":{\"type\":\"REAL\",\"notNull\":false,\"unique\":false},\"notes\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"}},\"indexes\":[{\"name\":\"manufacturing_bom_lines_bom_id_component_sku_id_idx\",\"columns\":[\"bom_id\",\"component_sku_id\",\"tenant_id\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"manufacturing_bom_lines_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"manufacturing_bom_lines_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]}],\"version\":\"cfbae7e8\"},\"importPath\":\"@happyvertical/smrt-manufacturing\"},\"@happyvertical/smrt-manufacturing:Operation\":{\"name\":\"operation\",\"className\":\"Operation\",\"qualifiedName\":\"@happyvertical/smrt-manufacturing:Operation\",\"collection\":\"operations\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/manufacturing/src/models/Operation.ts\",\"packageName\":\"@happyvertical/smrt-manufacturing\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"code\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"name\":{\"type\":\"text\",\"required\":true,\"default\":\"\",\"_meta\":{\"required\":true}},\"category\":{\"type\":\"text\",\"required\":false,\"default\":\"\"},\"isActive\":{\"type\":\"boolean\",\"required\":false,\"default\":true},\"requiredQualificationId\":{\"type\":\"crossPackageRef\",\"required\":false,\"related\":\"@happyvertical/smrt-human-resources:Qualification\",\"_meta\":{\"nullable\":true}}},\"methods\":{\"delete\":{\"name\":\"delete\",\"async\":true,\"parameters\":[],\"returnType\":\"Promise<void>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"manufacturing_operations\",\"conflictColumns\":[\"code\",\"tenant_id\"],\"api\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":{\"include\":[\"list\",\"get\"]},\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"Operation\",\"collectionExportName\":\"OperationCollection\",\"validationRules\":[{\"field\":\"code\",\"rule\":\"required\",\"fieldType\":\"text\"},{\"field\":\"name\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"manufacturing_operations\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"manufacturing_operations\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"code\\\" TEXT NOT NULL DEFAULT '',\\n \\\"name\\\" TEXT NOT NULL DEFAULT '',\\n \\\"category\\\" TEXT DEFAULT '',\\n \\\"is_active\\\" BOOLEAN DEFAULT TRUE,\\n \\\"required_qualification_id\\\" UUID\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"code\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"name\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"\"},\"category\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"},\"is_active\":{\"type\":\"BOOLEAN\",\"notNull\":false,\"unique\":false,\"default\":true},\"required_qualification_id\":{\"type\":\"UUID\",\"referenceKind\":\"crossPackageRef\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"manufacturing_operations_code_tenant_id_idx\",\"columns\":[\"code\",\"tenant_id\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"manufacturing_operations_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"manufacturing_operations_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"manufacturing_operations_required_qualification_id_idx\",\"columns\":[\"required_qualification_id\"]}],\"version\":\"0b3134d6\"},\"importPath\":\"@happyvertical/smrt-manufacturing\"},\"@happyvertical/smrt-manufacturing:RoutingStep\":{\"name\":\"routingstep\",\"className\":\"RoutingStep\",\"qualifiedName\":\"@happyvertical/smrt-manufacturing:RoutingStep\",\"collection\":\"routingsteps\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/manufacturing/src/models/RoutingStep.ts\",\"packageName\":\"@happyvertical/smrt-manufacturing\",\"fields\":{\"tenantId\":{\"type\":\"text\",\"required\":false,\"_meta\":{\"sqlType\":\"UUID\",\"nullable\":true,\"__tenancy\":{\"isTenantIdField\":true,\"autoFilter\":true,\"required\":false,\"autoPopulate\":true,\"nullable\":true,\"mode\":\"optional\",\"field\":\"tenantId\",\"allowSuperAdminBypass\":false}}},\"bomId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"BillOfMaterials\",\"_meta\":{\"required\":true,\"onDelete\":\"CASCADE\"}},\"operationId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"Operation\",\"_meta\":{\"required\":true,\"onDelete\":\"RESTRICT\"}},\"sequence\":{\"type\":\"integer\",\"required\":true,\"default\":1,\"_meta\":{\"required\":true}},\"estimatedMinutes\":{\"type\":\"decimal\",\"required\":false,\"_meta\":{}},\"notes\":{\"type\":\"text\",\"required\":false,\"default\":\"\"}},\"methods\":{},\"decoratorConfig\":{\"tableName\":\"manufacturing_routing_steps\",\"conflictColumns\":[\"bom_id\",\"sequence\",\"tenant_id\"],\"api\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":{\"skipApiCheck\":true},\"tenantScoped\":{\"mode\":\"optional\"}},\"extends\":\"SmrtObject\",\"exportName\":\"RoutingStep\",\"collectionExportName\":\"RoutingStepCollection\",\"validationRules\":[{\"field\":\"bomId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"},{\"field\":\"operationId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"},{\"field\":\"sequence\",\"rule\":\"required\",\"fieldType\":\"integer\"}],\"schema\":{\"tableName\":\"manufacturing_routing_steps\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"manufacturing_routing_steps\\\" (\\n \\\"id\\\" UUID PRIMARY KEY NOT NULL,\\n \\\"slug\\\" TEXT NOT NULL,\\n \\\"context\\\" TEXT NOT NULL DEFAULT '',\\n \\\"created_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"updated_at\\\" TIMESTAMP NOT NULL DEFAULT current_timestamp,\\n \\\"tenant_id\\\" UUID,\\n \\\"bom_id\\\" UUID NOT NULL,\\n \\\"operation_id\\\" UUID NOT NULL,\\n \\\"sequence\\\" INTEGER NOT NULL DEFAULT 1,\\n \\\"estimated_minutes\\\" REAL,\\n \\\"notes\\\" TEXT DEFAULT ''\\n);\",\"columns\":{\"id\":{\"type\":\"UUID\",\"primaryKey\":true,\"referenceKind\":\"id\",\"notNull\":true},\"slug\":{\"type\":\"TEXT\",\"notNull\":true},\"context\":{\"type\":\"TEXT\",\"notNull\":true,\"default\":\"\"},\"created_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"updated_at\":{\"type\":\"TIMESTAMP\",\"notNull\":true,\"default\":\"current_timestamp\"},\"tenant_id\":{\"type\":\"UUID\",\"referenceKind\":\"tenantId\",\"notNull\":false,\"unique\":false},\"bom_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false,\"foreignKey\":{\"table\":\"manufacturing_boms\",\"column\":\"id\",\"onDelete\":\"CASCADE\",\"onUpdate\":\"CASCADE\"}},\"operation_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false,\"foreignKey\":{\"table\":\"manufacturing_operations\",\"column\":\"id\",\"onDelete\":\"RESTRICT\",\"onUpdate\":\"CASCADE\"}},\"sequence\":{\"type\":\"INTEGER\",\"notNull\":true,\"unique\":false,\"default\":1},\"estimated_minutes\":{\"type\":\"REAL\",\"notNull\":false,\"unique\":false},\"notes\":{\"type\":\"TEXT\",\"notNull\":false,\"unique\":false,\"default\":\"\"}},\"indexes\":[{\"name\":\"manufacturing_routing_steps_bom_id_sequence_idx\",\"columns\":[\"bom_id\",\"sequence\",\"tenant_id\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"manufacturing_routing_steps_slug_context_idx\",\"columns\":[\"slug\",\"context\"]},{\"name\":\"manufacturing_routing_steps_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"manufacturing_routing_steps_operation_id_idx\",\"columns\":[\"operation_id\"]}],\"version\":\"41ac3c66\"},\"importPath\":\"@happyvertical/smrt-manufacturing\"}},\"moduleType\":\"smrt\",\"smrtDependencies\":[\"@happyvertical/smrt-core\",\"@happyvertical/smrt-fields\",\"@happyvertical/smrt-inventory\",\"@happyvertical/smrt-products\",\"@happyvertical/smrt-tenancy\"]}"));
|
|
22
|
+
//#endregion
|
|
23
|
+
//#region src/models/Operation.ts
|
|
24
|
+
var __defProp$4 = Object.defineProperty;
|
|
25
|
+
var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
|
|
26
|
+
var __decorateClass$4 = (decorators, target, key, kind) => {
|
|
27
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
|
|
28
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
29
|
+
if (kind && result) __defProp$4(target, key, result);
|
|
30
|
+
return result;
|
|
31
|
+
};
|
|
32
|
+
var Operation = class extends SmrtObject {
|
|
33
|
+
tenantId = null;
|
|
34
|
+
code = "";
|
|
35
|
+
name = "";
|
|
36
|
+
/**
|
|
37
|
+
* Free-text grouping for lists (`fabrication`, `finishing`, `quality`).
|
|
38
|
+
* Empty when the operation is not grouped.
|
|
39
|
+
*/
|
|
40
|
+
category = "";
|
|
41
|
+
/**
|
|
42
|
+
* `false` once the operation is retired. A retired operation stays on the
|
|
43
|
+
* routings and history that name it and is left out of pickers.
|
|
44
|
+
*/
|
|
45
|
+
isActive = true;
|
|
46
|
+
requiredQualificationId = null;
|
|
47
|
+
constructor(options = {}) {
|
|
48
|
+
super(options);
|
|
49
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
50
|
+
if (options.code !== void 0) this.code = options.code;
|
|
51
|
+
if (options.name !== void 0) this.name = options.name;
|
|
52
|
+
if (options.category !== void 0) this.category = options.category;
|
|
53
|
+
if (options.isActive !== void 0) this.isActive = options.isActive;
|
|
54
|
+
if (options.requiredQualificationId !== void 0) this.requiredQualificationId = options.requiredQualificationId;
|
|
55
|
+
}
|
|
56
|
+
/** Operations are retired, never deleted, so history keeps resolving. */
|
|
57
|
+
async delete() {
|
|
58
|
+
throw new Error(`Operation ${this.code || this.id} cannot be deleted; retire it instead.`);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
__decorateClass$4([tenantId({ nullable: true })], Operation.prototype, "tenantId", 2);
|
|
62
|
+
__decorateClass$4([field({ required: true })], Operation.prototype, "code", 2);
|
|
63
|
+
__decorateClass$4([field({ required: true })], Operation.prototype, "name", 2);
|
|
64
|
+
__decorateClass$4([crossPackageRef("@happyvertical/smrt-human-resources:Qualification", { nullable: true })], Operation.prototype, "requiredQualificationId", 2);
|
|
65
|
+
Operation = __decorateClass$4([TenantScoped({ mode: "optional" }), smrt({
|
|
66
|
+
tableName: "manufacturing_operations",
|
|
67
|
+
conflictColumns: ["code", "tenant_id"],
|
|
68
|
+
api: { include: ["list", "get"] },
|
|
69
|
+
mcp: { include: ["list", "get"] },
|
|
70
|
+
cli: { include: ["list", "get"] }
|
|
71
|
+
})], Operation);
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/models/Assembly.ts
|
|
74
|
+
var __defProp$3 = Object.defineProperty;
|
|
75
|
+
var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
|
|
76
|
+
var __decorateClass$3 = (decorators, target, key, kind) => {
|
|
77
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
|
|
78
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
79
|
+
if (kind && result) __defProp$3(target, key, result);
|
|
80
|
+
return result;
|
|
81
|
+
};
|
|
82
|
+
var Assembly = class extends Product {
|
|
83
|
+
productType = ProductType.ASSEMBLY;
|
|
84
|
+
estimatedLabourMinutes = 0;
|
|
85
|
+
defaultOperationId = null;
|
|
86
|
+
partReference = "";
|
|
87
|
+
constructor(options = {}) {
|
|
88
|
+
super(options);
|
|
89
|
+
if (options.estimatedLabourMinutes !== void 0) this.estimatedLabourMinutes = options.estimatedLabourMinutes;
|
|
90
|
+
if (options.defaultOperationId !== void 0) this.defaultOperationId = options.defaultOperationId;
|
|
91
|
+
if (options.partReference !== void 0) this.partReference = options.partReference;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Refuses a labour estimate that is not a whole number of minutes of zero
|
|
95
|
+
* or more, on every save (generated REST and collection writes included).
|
|
96
|
+
*/
|
|
97
|
+
async validateBeforeSave() {
|
|
98
|
+
await super.validateBeforeSave();
|
|
99
|
+
const minutes = this.estimatedLabourMinutes;
|
|
100
|
+
if (!Number.isSafeInteger(minutes) || minutes < 0) throw ValidationError.invalidValue("estimatedLabourMinutes", minutes, "a whole number of minutes, zero or more");
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
__decorateClass$3([field()], Assembly.prototype, "estimatedLabourMinutes", 2);
|
|
104
|
+
__decorateClass$3([foreignKey(Operation, {
|
|
105
|
+
nullable: true,
|
|
106
|
+
onDelete: "RESTRICT"
|
|
107
|
+
})], Assembly.prototype, "defaultOperationId", 2);
|
|
108
|
+
__decorateClass$3([field()], Assembly.prototype, "partReference", 2);
|
|
109
|
+
Assembly = __decorateClass$3([TenantScoped({ mode: "optional" }), smrt({
|
|
110
|
+
api: { include: [
|
|
111
|
+
"list",
|
|
112
|
+
"get",
|
|
113
|
+
"create",
|
|
114
|
+
"update"
|
|
115
|
+
] },
|
|
116
|
+
mcp: { include: ["list", "get"] },
|
|
117
|
+
cli: { skipApiCheck: true }
|
|
118
|
+
})], Assembly);
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region src/collections/AssemblyCollection.ts
|
|
121
|
+
var AssemblyCollection = class extends ProductCollection {
|
|
122
|
+
static _itemClass = Assembly;
|
|
123
|
+
};
|
|
8
124
|
//#endregion
|
|
9
125
|
//#region src/models/BillOfMaterials.ts
|
|
10
|
-
var __defProp$
|
|
11
|
-
var __getOwnPropDesc$
|
|
12
|
-
var __decorateClass$
|
|
13
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$
|
|
126
|
+
var __defProp$2 = Object.defineProperty;
|
|
127
|
+
var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
|
|
128
|
+
var __decorateClass$2 = (decorators, target, key, kind) => {
|
|
129
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
|
|
14
130
|
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
15
|
-
if (kind && result) __defProp$
|
|
131
|
+
if (kind && result) __defProp$2(target, key, result);
|
|
16
132
|
return result;
|
|
17
133
|
};
|
|
18
134
|
var BillOfMaterials = class extends SmrtObject {
|
|
@@ -47,12 +163,25 @@ var BillOfMaterials = class extends SmrtObject {
|
|
|
47
163
|
if (options.notes !== void 0) this.notes = options.notes;
|
|
48
164
|
if (options.currency !== void 0) this.currency = options.currency;
|
|
49
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Refuses saving the bill as `active` when one of its lines would make
|
|
168
|
+
* its product contain itself, directly or through sub-assemblies' active
|
|
169
|
+
* bills, with a `BomCycleError` naming the path. Draft and superseded
|
|
170
|
+
* bills are not part of the structure and are not checked here (their
|
|
171
|
+
* lines are checked as they are saved).
|
|
172
|
+
*/
|
|
173
|
+
async validateBeforeSave() {
|
|
174
|
+
await super.validateBeforeSave();
|
|
175
|
+
if (this.status !== "active") return;
|
|
176
|
+
const { AssemblyService } = await Promise.resolve().then(() => AssemblyService_exports);
|
|
177
|
+
await (await AssemblyService.create({ db: this.db })).assertBillAcyclic(this);
|
|
178
|
+
}
|
|
50
179
|
};
|
|
51
|
-
__decorateClass$
|
|
52
|
-
__decorateClass$
|
|
53
|
-
__decorateClass$
|
|
54
|
-
__decorateClass$
|
|
55
|
-
BillOfMaterials = __decorateClass$
|
|
180
|
+
__decorateClass$2([tenantId({ nullable: true })], BillOfMaterials.prototype, "tenantId", 2);
|
|
181
|
+
__decorateClass$2([field({ required: true })], BillOfMaterials.prototype, "productId", 2);
|
|
182
|
+
__decorateClass$2([field({ required: true })], BillOfMaterials.prototype, "version", 2);
|
|
183
|
+
__decorateClass$2([field({ required: true })], BillOfMaterials.prototype, "status", 2);
|
|
184
|
+
BillOfMaterials = __decorateClass$2([TenantScoped({ mode: "optional" }), smrt({
|
|
56
185
|
tableName: "manufacturing_boms",
|
|
57
186
|
conflictColumns: [
|
|
58
187
|
"product_id",
|
|
@@ -112,12 +241,12 @@ var BillOfMaterialsCollection = class extends SmrtCollection {
|
|
|
112
241
|
};
|
|
113
242
|
//#endregion
|
|
114
243
|
//#region src/models/BomLine.ts
|
|
115
|
-
var __defProp = Object.defineProperty;
|
|
116
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
117
|
-
var __decorateClass = (decorators, target, key, kind) => {
|
|
118
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
244
|
+
var __defProp$1 = Object.defineProperty;
|
|
245
|
+
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
|
|
246
|
+
var __decorateClass$1 = (decorators, target, key, kind) => {
|
|
247
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
|
|
119
248
|
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
120
|
-
if (kind && result) __defProp(target, key, result);
|
|
249
|
+
if (kind && result) __defProp$1(target, key, result);
|
|
121
250
|
return result;
|
|
122
251
|
};
|
|
123
252
|
var BomLine = class extends SmrtObject {
|
|
@@ -158,13 +287,24 @@ var BomLine = class extends SmrtObject {
|
|
|
158
287
|
effectiveQtyPerUnit() {
|
|
159
288
|
return Number(this.qtyPerUnit ?? 0) * (1 + Number(this.wastePercent ?? 0) / 100);
|
|
160
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* Refuses a line that would make its bill's product contain itself,
|
|
292
|
+
* directly or through sub-assemblies' active bills, with a
|
|
293
|
+
* `BomCycleError` naming the path. Runs on every save, so the generated
|
|
294
|
+
* REST `create`/`update` routes refuse it too.
|
|
295
|
+
*/
|
|
296
|
+
async validateBeforeSave() {
|
|
297
|
+
await super.validateBeforeSave();
|
|
298
|
+
const { AssemblyService } = await Promise.resolve().then(() => AssemblyService_exports);
|
|
299
|
+
await (await AssemblyService.create({ db: this.db })).assertLineAcyclic(this);
|
|
300
|
+
}
|
|
161
301
|
};
|
|
162
|
-
__decorateClass([tenantId({ nullable: true })], BomLine.prototype, "tenantId", 2);
|
|
163
|
-
__decorateClass([field({ required: true })], BomLine.prototype, "bomId", 2);
|
|
164
|
-
__decorateClass([field({ required: true })], BomLine.prototype, "componentSkuId", 2);
|
|
165
|
-
__decorateClass([field({ type: "decimal" })], BomLine.prototype, "qtyPerUnit", 2);
|
|
166
|
-
__decorateClass([field({ type: "decimal" })], BomLine.prototype, "wastePercent", 2);
|
|
167
|
-
BomLine = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
|
|
302
|
+
__decorateClass$1([tenantId({ nullable: true })], BomLine.prototype, "tenantId", 2);
|
|
303
|
+
__decorateClass$1([field({ required: true })], BomLine.prototype, "bomId", 2);
|
|
304
|
+
__decorateClass$1([field({ required: true })], BomLine.prototype, "componentSkuId", 2);
|
|
305
|
+
__decorateClass$1([field({ type: "decimal" })], BomLine.prototype, "qtyPerUnit", 2);
|
|
306
|
+
__decorateClass$1([field({ type: "decimal" })], BomLine.prototype, "wastePercent", 2);
|
|
307
|
+
BomLine = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({
|
|
168
308
|
tableName: "manufacturing_bom_lines",
|
|
169
309
|
conflictColumns: [
|
|
170
310
|
"bom_id",
|
|
@@ -208,24 +348,383 @@ var BomLineCollection = class extends SmrtCollection {
|
|
|
208
348
|
}
|
|
209
349
|
};
|
|
210
350
|
//#endregion
|
|
351
|
+
//#region src/collections/OperationCollection.ts
|
|
352
|
+
var OperationCollection = class extends SmrtCollection {
|
|
353
|
+
static _itemClass = Operation;
|
|
354
|
+
/** Return the operation with the given code, or `null`. */
|
|
355
|
+
async findByCode(code) {
|
|
356
|
+
return (await this.list({
|
|
357
|
+
where: { code },
|
|
358
|
+
limit: 1
|
|
359
|
+
}))[0] ?? null;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Return operations ordered by category then code. Retired operations are
|
|
363
|
+
* left out unless `includeRetired` is set, so the default is a picker list.
|
|
364
|
+
*/
|
|
365
|
+
async listOperations(options = {}) {
|
|
366
|
+
return this.list({
|
|
367
|
+
where: options.includeRetired ? {} : { isActive: true },
|
|
368
|
+
orderBy: ["category ASC", "code ASC"]
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
//#endregion
|
|
373
|
+
//#region src/models/RoutingStep.ts
|
|
374
|
+
var __defProp = Object.defineProperty;
|
|
375
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
376
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
377
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
378
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
379
|
+
if (kind && result) __defProp(target, key, result);
|
|
380
|
+
return result;
|
|
381
|
+
};
|
|
382
|
+
var RoutingStep = class extends SmrtObject {
|
|
383
|
+
tenantId = null;
|
|
384
|
+
bomId = "";
|
|
385
|
+
operationId = "";
|
|
386
|
+
sequence = 1;
|
|
387
|
+
estimatedMinutes = 0;
|
|
388
|
+
/** Optional free-form note for this step. */
|
|
389
|
+
notes = "";
|
|
390
|
+
constructor(options = {}) {
|
|
391
|
+
super(options);
|
|
392
|
+
if (options.tenantId !== void 0) this.tenantId = options.tenantId;
|
|
393
|
+
if (options.bomId !== void 0) this.bomId = options.bomId;
|
|
394
|
+
if (options.operationId !== void 0) this.operationId = options.operationId;
|
|
395
|
+
if (options.sequence !== void 0) this.sequence = options.sequence;
|
|
396
|
+
if (options.estimatedMinutes !== void 0) this.estimatedMinutes = options.estimatedMinutes;
|
|
397
|
+
if (options.notes !== void 0) this.notes = options.notes;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
__decorateClass([tenantId({ nullable: true })], RoutingStep.prototype, "tenantId", 2);
|
|
401
|
+
__decorateClass([foreignKey(BillOfMaterials, {
|
|
402
|
+
required: true,
|
|
403
|
+
onDelete: "CASCADE"
|
|
404
|
+
})], RoutingStep.prototype, "bomId", 2);
|
|
405
|
+
__decorateClass([foreignKey(Operation, {
|
|
406
|
+
required: true,
|
|
407
|
+
onDelete: "RESTRICT"
|
|
408
|
+
})], RoutingStep.prototype, "operationId", 2);
|
|
409
|
+
__decorateClass([field({ required: true })], RoutingStep.prototype, "sequence", 2);
|
|
410
|
+
__decorateClass([field({ type: "decimal" })], RoutingStep.prototype, "estimatedMinutes", 2);
|
|
411
|
+
RoutingStep = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
|
|
412
|
+
tableName: "manufacturing_routing_steps",
|
|
413
|
+
conflictColumns: [
|
|
414
|
+
"bom_id",
|
|
415
|
+
"sequence",
|
|
416
|
+
"tenant_id"
|
|
417
|
+
],
|
|
418
|
+
api: { include: ["list", "get"] },
|
|
419
|
+
mcp: { include: ["list", "get"] },
|
|
420
|
+
cli: { skipApiCheck: true }
|
|
421
|
+
})], RoutingStep);
|
|
422
|
+
//#endregion
|
|
423
|
+
//#region src/collections/RoutingStepCollection.ts
|
|
424
|
+
var RoutingStepCollection = class extends SmrtCollection {
|
|
425
|
+
static _itemClass = RoutingStep;
|
|
426
|
+
/** Return the routing of a bill in step order; empty when it has none. */
|
|
427
|
+
async findByBom(bomId) {
|
|
428
|
+
return this.list({
|
|
429
|
+
where: { bomId },
|
|
430
|
+
orderBy: "sequence ASC"
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
/** Return every step that uses the given operation, across bills. */
|
|
434
|
+
async findByOperation(operationId) {
|
|
435
|
+
return this.list({
|
|
436
|
+
where: { operationId },
|
|
437
|
+
orderBy: "bomId ASC"
|
|
438
|
+
});
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
//#endregion
|
|
442
|
+
//#region src/operation-types.ts
|
|
443
|
+
var OperationNotFoundError = class extends Error {
|
|
444
|
+
constructor(operationId) {
|
|
445
|
+
super(`Operation not found: ${operationId}`);
|
|
446
|
+
this.operationId = operationId;
|
|
447
|
+
}
|
|
448
|
+
operationId;
|
|
449
|
+
name = "OperationNotFoundError";
|
|
450
|
+
};
|
|
451
|
+
var DuplicateOperationCodeError = class extends Error {
|
|
452
|
+
constructor(code) {
|
|
453
|
+
super(`An operation with code "${code}" already exists.`);
|
|
454
|
+
this.code = code;
|
|
455
|
+
}
|
|
456
|
+
code;
|
|
457
|
+
name = "DuplicateOperationCodeError";
|
|
458
|
+
};
|
|
459
|
+
var OperationRetiredError = class extends Error {
|
|
460
|
+
constructor(operationId) {
|
|
461
|
+
super(`Operation ${operationId} is retired and cannot be added to a routing.`);
|
|
462
|
+
this.operationId = operationId;
|
|
463
|
+
}
|
|
464
|
+
operationId;
|
|
465
|
+
name = "OperationRetiredError";
|
|
466
|
+
};
|
|
467
|
+
var InvalidOperationInputError = class extends Error {
|
|
468
|
+
name = "InvalidOperationInputError";
|
|
469
|
+
};
|
|
470
|
+
//#endregion
|
|
471
|
+
//#region src/services/AssemblyService.ts
|
|
472
|
+
var AssemblyService_exports = /* @__PURE__ */ __exportAll({
|
|
473
|
+
AssemblyService: () => AssemblyService,
|
|
474
|
+
BomCycleError: () => BomCycleError,
|
|
475
|
+
createAssemblyService: () => createAssemblyService
|
|
476
|
+
});
|
|
477
|
+
var BomCycleError = class extends ValidationError {
|
|
478
|
+
constructor(bomId, path) {
|
|
479
|
+
const first = path[0]?.name ?? "";
|
|
480
|
+
super(`Refused: "${first}" would contain itself: ${path.map((entry) => entry.name).join(" → ")}`, "MANUFACTURING_BOM_CYCLE", {
|
|
481
|
+
bomId,
|
|
482
|
+
path
|
|
483
|
+
});
|
|
484
|
+
this.bomId = bomId;
|
|
485
|
+
this.path = path;
|
|
486
|
+
}
|
|
487
|
+
bomId;
|
|
488
|
+
path;
|
|
489
|
+
name = "BomCycleError";
|
|
490
|
+
};
|
|
491
|
+
var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
492
|
+
function visibleTo(row, options) {
|
|
493
|
+
if (options.tenantId === void 0) return true;
|
|
494
|
+
const rowTenant = row.tenantId ?? null;
|
|
495
|
+
return rowTenant === null || rowTenant === options.tenantId;
|
|
496
|
+
}
|
|
497
|
+
function isUuid(value) {
|
|
498
|
+
return typeof value === "string" && UUID_PATTERN.test(value);
|
|
499
|
+
}
|
|
500
|
+
var AssemblyService = class AssemblyService {
|
|
501
|
+
constructor(products, skus, boms, lines) {
|
|
502
|
+
this.products = products;
|
|
503
|
+
this.skus = skus;
|
|
504
|
+
this.boms = boms;
|
|
505
|
+
this.lines = lines;
|
|
506
|
+
}
|
|
507
|
+
products;
|
|
508
|
+
skus;
|
|
509
|
+
boms;
|
|
510
|
+
lines;
|
|
511
|
+
/** Factory — prefer {@link createAssemblyService}. */
|
|
512
|
+
static async create(options) {
|
|
513
|
+
const db = await resolveDatabase(options.db);
|
|
514
|
+
const [products, skus, boms, lines] = await Promise.all([
|
|
515
|
+
ProductCollection.create({ db }),
|
|
516
|
+
SkuCollection.create({ db }),
|
|
517
|
+
BillOfMaterialsCollection.create({ db }),
|
|
518
|
+
BomLineCollection.create({ db })
|
|
519
|
+
]);
|
|
520
|
+
return new AssemblyService(products, skus, boms, lines);
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Resolve a component SKU to what it is: an assembly (with its active bill,
|
|
524
|
+
* or `null` when it has none), a material, a bought item, or missing. Never
|
|
525
|
+
* throws for an unknown id; it reports `missing` instead.
|
|
526
|
+
*
|
|
527
|
+
* This is the documented link a multi-level explosion walks: recurse into
|
|
528
|
+
* `activeBom` while `kind === 'assembly'`, stop at every other kind. Pass
|
|
529
|
+
* the parent bill's `tenantId` so a shared assembly resolves to that
|
|
530
|
+
* tenant's bill, not another tenant's.
|
|
531
|
+
*/
|
|
532
|
+
async resolveComponent(skuId, options = {}) {
|
|
533
|
+
const missing = (sku2) => ({
|
|
534
|
+
skuId,
|
|
535
|
+
kind: "missing",
|
|
536
|
+
sku: sku2,
|
|
537
|
+
product: null,
|
|
538
|
+
assembly: null,
|
|
539
|
+
activeBom: null,
|
|
540
|
+
buildable: false
|
|
541
|
+
});
|
|
542
|
+
if (!isUuid(skuId)) return missing(null);
|
|
543
|
+
const sku = await this.skus.get({ id: skuId });
|
|
544
|
+
if (!sku || !visibleTo(sku, options)) return missing(null);
|
|
545
|
+
if (!isUuid(sku.productId)) return missing(sku);
|
|
546
|
+
const product = await this.products.get({ id: sku.productId });
|
|
547
|
+
if (!product || !visibleTo(product, options)) return missing(sku);
|
|
548
|
+
if (product instanceof Assembly) {
|
|
549
|
+
const activeBom = await this.findActiveBom(product.id, options);
|
|
550
|
+
return {
|
|
551
|
+
skuId,
|
|
552
|
+
kind: "assembly",
|
|
553
|
+
sku,
|
|
554
|
+
product,
|
|
555
|
+
assembly: product,
|
|
556
|
+
activeBom,
|
|
557
|
+
buildable: activeBom !== null
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
return {
|
|
561
|
+
skuId,
|
|
562
|
+
kind: product instanceof Material ? "material" : "bought",
|
|
563
|
+
sku,
|
|
564
|
+
product,
|
|
565
|
+
assembly: null,
|
|
566
|
+
activeBom: null,
|
|
567
|
+
buildable: false
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* The highest-version active bill for a product, limited to one tenant's
|
|
572
|
+
* bills when `options.tenantId` is given.
|
|
573
|
+
*/
|
|
574
|
+
async findActiveBom(productId, options = {}) {
|
|
575
|
+
if (options.tenantId === void 0) return this.boms.findActiveForProduct(productId);
|
|
576
|
+
return (await this.boms.list({
|
|
577
|
+
where: {
|
|
578
|
+
productId,
|
|
579
|
+
status: "active"
|
|
580
|
+
},
|
|
581
|
+
orderBy: "version DESC"
|
|
582
|
+
})).find((bom) => (bom.tenantId ?? null) === options.tenantId) ?? null;
|
|
583
|
+
}
|
|
584
|
+
/** `true` when the SKU belongs to an {@link Assembly}. */
|
|
585
|
+
async isAssembly(skuId) {
|
|
586
|
+
return (await this.resolveComponent(skuId)).kind === "assembly";
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Read one bill and resolve every line's component. Sub-assembly lines say
|
|
590
|
+
* whether they have an active bill of their own (`component.activeBom`);
|
|
591
|
+
* the caller opens the next level by passing that id back in.
|
|
592
|
+
*
|
|
593
|
+
* @throws {BomNotFoundError} when no bill has this id.
|
|
594
|
+
*/
|
|
595
|
+
async getBillStructure(bomId) {
|
|
596
|
+
const bom = isUuid(bomId) ? await this.boms.get({ id: bomId }) : null;
|
|
597
|
+
if (!bom) throw new BomNotFoundError(bomId);
|
|
598
|
+
const cache = /* @__PURE__ */ new Map();
|
|
599
|
+
const scope = { tenantId: bom.tenantId ?? null };
|
|
600
|
+
const lines = (await this.lines.findByBom(bomId)).filter((line) => visibleTo(line, scope));
|
|
601
|
+
const resolved = [];
|
|
602
|
+
for (const line of lines) resolved.push({
|
|
603
|
+
line,
|
|
604
|
+
component: await this.resolveCached(line.componentSkuId, cache, scope)
|
|
605
|
+
});
|
|
606
|
+
return {
|
|
607
|
+
bom,
|
|
608
|
+
lines: resolved
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Find the path by which adding `componentSkuIds` to a bill for
|
|
613
|
+
* `productId` would make that product contain itself, or `null` when none
|
|
614
|
+
* would. The walk follows each sub-assembly's **active** bill only; a
|
|
615
|
+
* draft or superseded bill is not part of the structure. Pass the bill's
|
|
616
|
+
* `tenantId` so the walk reads that tenant's structure.
|
|
617
|
+
*/
|
|
618
|
+
async findCycle(productId, componentSkuIds, options = {}) {
|
|
619
|
+
const cache = /* @__PURE__ */ new Map();
|
|
620
|
+
const visited = /* @__PURE__ */ new Set();
|
|
621
|
+
const walk = async (skuIds, trail2) => {
|
|
622
|
+
for (const skuId of skuIds) {
|
|
623
|
+
const component = await this.resolveCached(skuId, cache, options);
|
|
624
|
+
const product = component.product;
|
|
625
|
+
if (!product?.id) continue;
|
|
626
|
+
if (product.id === productId) return [...trail2, product];
|
|
627
|
+
if (!component.activeBom || visited.has(product.id)) continue;
|
|
628
|
+
visited.add(product.id);
|
|
629
|
+
const lines = (await this.lines.findByBom(component.activeBom.id)).filter((line) => visibleTo(line, options));
|
|
630
|
+
const found = await walk(lines.map((line) => line.componentSkuId), [...trail2, product]);
|
|
631
|
+
if (found) return found;
|
|
632
|
+
}
|
|
633
|
+
return null;
|
|
634
|
+
};
|
|
635
|
+
const trail = await walk(componentSkuIds, []);
|
|
636
|
+
if (!trail) return null;
|
|
637
|
+
return [trail[trail.length - 1], ...trail].map((product) => ({
|
|
638
|
+
productId: product.id,
|
|
639
|
+
name: product.name || product.id
|
|
640
|
+
}));
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Refuse a bill line that would make its bill's product contain itself.
|
|
644
|
+
* A line on a draft bill is checked too, so a cycle is refused when it is
|
|
645
|
+
* written rather than when the bill is activated. A line whose bill does
|
|
646
|
+
* not exist is left to the caller.
|
|
647
|
+
*
|
|
648
|
+
* @throws {BomCycleError} naming the path.
|
|
649
|
+
*/
|
|
650
|
+
async assertLineAcyclic(line) {
|
|
651
|
+
if (!isUuid(line.bomId)) return;
|
|
652
|
+
const bom = await this.boms.get({ id: line.bomId });
|
|
653
|
+
if (!bom) return;
|
|
654
|
+
const path = await this.findCycle(bom.productId, [line.componentSkuId], { tenantId: bom.tenantId ?? null });
|
|
655
|
+
if (path) throw new BomCycleError(line.bomId, path);
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Refuse saving a bill as `active` when any of its lines would make its
|
|
659
|
+
* product contain itself. Bills in any other status are not part of the
|
|
660
|
+
* structure and pass.
|
|
661
|
+
*
|
|
662
|
+
* The lines checked are the bill's own and those of any stored bill with
|
|
663
|
+
* the same natural key (`productId`, `version`, and the bill's tenant, or
|
|
664
|
+
* the active tenant when the bill has none yet): saving a new
|
|
665
|
+
* instance with that key upserts onto the stored bill and activates its
|
|
666
|
+
* lines, so they are part of what the save makes active.
|
|
667
|
+
*
|
|
668
|
+
* @throws {BomCycleError} naming the path.
|
|
669
|
+
*/
|
|
670
|
+
async assertBillAcyclic(bom) {
|
|
671
|
+
if (bom.status !== "active") return;
|
|
672
|
+
const billIds = /* @__PURE__ */ new Set();
|
|
673
|
+
if (isUuid(bom.id)) billIds.add(bom.id);
|
|
674
|
+
const tenantId = bom.tenantId ?? getCurrentTenant()?.tenantId ?? null;
|
|
675
|
+
const sameKey = await this.boms.list({ where: {
|
|
676
|
+
productId: bom.productId,
|
|
677
|
+
version: bom.version
|
|
678
|
+
} });
|
|
679
|
+
for (const stored of sameKey) if (stored.id && (stored.tenantId ?? null) === tenantId) billIds.add(stored.id);
|
|
680
|
+
const componentSkuIds = [];
|
|
681
|
+
for (const billId of billIds) for (const line of await this.lines.findByBom(billId)) if (visibleTo(line, { tenantId })) componentSkuIds.push(line.componentSkuId);
|
|
682
|
+
if (componentSkuIds.length === 0) return;
|
|
683
|
+
const path = await this.findCycle(bom.productId, componentSkuIds, { tenantId });
|
|
684
|
+
if (path) throw new BomCycleError(bom.id || [...billIds][0] || "", path);
|
|
685
|
+
}
|
|
686
|
+
resolveCached(skuId, cache, options) {
|
|
687
|
+
let pending = cache.get(skuId);
|
|
688
|
+
if (!pending) {
|
|
689
|
+
pending = this.resolveComponent(skuId, options);
|
|
690
|
+
cache.set(skuId, pending);
|
|
691
|
+
}
|
|
692
|
+
return pending;
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
function createAssemblyService(options) {
|
|
696
|
+
return AssemblyService.create(options);
|
|
697
|
+
}
|
|
698
|
+
//#endregion
|
|
211
699
|
//#region src/services/BomService.ts
|
|
212
700
|
var BomService = class BomService {
|
|
213
|
-
constructor(boms, lines, stockService, costResolver) {
|
|
701
|
+
constructor(boms, lines, stockService, costResolver, routingSteps, operations, rateResolver = void 0) {
|
|
214
702
|
this.boms = boms;
|
|
215
703
|
this.lines = lines;
|
|
216
704
|
this.stockService = stockService;
|
|
217
705
|
this.costResolver = costResolver;
|
|
706
|
+
this.routingSteps = routingSteps;
|
|
707
|
+
this.operations = operations;
|
|
708
|
+
this.rateResolver = rateResolver;
|
|
218
709
|
}
|
|
219
710
|
boms;
|
|
220
711
|
lines;
|
|
221
712
|
stockService;
|
|
222
713
|
costResolver;
|
|
714
|
+
routingSteps;
|
|
715
|
+
operations;
|
|
716
|
+
rateResolver;
|
|
223
717
|
/** Factory — prefer {@link createBomService}. */
|
|
224
718
|
static async create(options) {
|
|
225
719
|
const stockService = options.stockService ?? await buildStockService$1(options);
|
|
226
|
-
const sharedDb = options.db ?? stockService.db;
|
|
227
|
-
const [boms, lines] = await Promise.all([
|
|
228
|
-
|
|
720
|
+
const sharedDb = await resolveDatabase(options.stockService ? options.db ?? stockService.db : stockService.db);
|
|
721
|
+
const [boms, lines, routingSteps, operations] = await Promise.all([
|
|
722
|
+
BillOfMaterialsCollection.create({ db: sharedDb }),
|
|
723
|
+
BomLineCollection.create({ db: sharedDb }),
|
|
724
|
+
RoutingStepCollection.create({ db: sharedDb }),
|
|
725
|
+
OperationCollection.create({ db: sharedDb })
|
|
726
|
+
]);
|
|
727
|
+
return new BomService(boms, lines, stockService, options.costResolver, routingSteps, operations, options.rateResolver);
|
|
229
728
|
}
|
|
230
729
|
/**
|
|
231
730
|
* Walk every {@link BomLine} for the given BOM, resolve each line's
|
|
@@ -273,6 +772,53 @@ var BomService = class BomService {
|
|
|
273
772
|
};
|
|
274
773
|
}
|
|
275
774
|
/**
|
|
775
|
+
* Roll up the estimated labour to build one unit from the bill's routing:
|
|
776
|
+
* total minutes, plus cost when a `rateResolver` was supplied (hourly rate
|
|
777
|
+
* times minutes, per step). A bill with no routing returns an empty
|
|
778
|
+
* estimate with zero totals, so callers can add it to the material cost
|
|
779
|
+
* unconditionally. Retired operations still count: the routing names them.
|
|
780
|
+
*
|
|
781
|
+
* Throws {@link BomNotFoundError} when the BOM does not exist.
|
|
782
|
+
*/
|
|
783
|
+
async computeLabourEstimate(bomId) {
|
|
784
|
+
const bom = await this.requireBom(bomId);
|
|
785
|
+
const routing = await this.routingSteps.findByBom(bomId);
|
|
786
|
+
const steps = [];
|
|
787
|
+
let totalMinutes = 0;
|
|
788
|
+
let totalCost = 0;
|
|
789
|
+
let hasMissingRates = false;
|
|
790
|
+
for (const step of routing) {
|
|
791
|
+
const operation = await this.operations.get(step.operationId);
|
|
792
|
+
const minutes = Number(step.estimatedMinutes ?? 0);
|
|
793
|
+
const rate = operation ? await this.resolveRate(operation) : null;
|
|
794
|
+
const rateUnavailable = rate === null;
|
|
795
|
+
const stepCost = rate === null ? 0 : minutes / 60 * rate;
|
|
796
|
+
totalMinutes += minutes;
|
|
797
|
+
if (rateUnavailable) hasMissingRates = true;
|
|
798
|
+
else totalCost += stepCost;
|
|
799
|
+
steps.push({
|
|
800
|
+
stepId: step.id,
|
|
801
|
+
sequence: step.sequence,
|
|
802
|
+
operationId: step.operationId,
|
|
803
|
+
operationCode: operation?.code ?? "",
|
|
804
|
+
operationName: operation?.name ?? "",
|
|
805
|
+
estimatedMinutes: minutes,
|
|
806
|
+
hourlyRate: rate ?? 0,
|
|
807
|
+
stepCost,
|
|
808
|
+
rateUnavailable
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
return {
|
|
812
|
+
bomId,
|
|
813
|
+
hasRouting: steps.length > 0,
|
|
814
|
+
totalMinutes,
|
|
815
|
+
totalCost,
|
|
816
|
+
currency: bom.currency || "USD",
|
|
817
|
+
steps,
|
|
818
|
+
hasMissingRates
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
/**
|
|
276
822
|
* Return a "shopping list" of materials needed to produce `qty` units of
|
|
277
823
|
* the parent product against the given BOM. Lines that reference the
|
|
278
824
|
* same component SKU are summed so each `componentSkuId` appears once
|
|
@@ -347,6 +893,13 @@ var BomService = class BomService {
|
|
|
347
893
|
if (!bom) throw new BomNotFoundError(bomId);
|
|
348
894
|
return bom;
|
|
349
895
|
}
|
|
896
|
+
/** Run the {@link OperationRateResolver}; `null` when there is no rate. */
|
|
897
|
+
async resolveRate(operation) {
|
|
898
|
+
if (!this.rateResolver) return null;
|
|
899
|
+
const value = await this.rateResolver(operation);
|
|
900
|
+
if (value === null || value === void 0 || !Number.isFinite(value)) return null;
|
|
901
|
+
return Number(value);
|
|
902
|
+
}
|
|
350
903
|
/**
|
|
351
904
|
* Run the supplied {@link ComponentCostResolver} for the given component
|
|
352
905
|
* SKU. Returns `null` when no resolver is registered or when the
|
|
@@ -693,6 +1246,180 @@ async function buildProductionService(options) {
|
|
|
693
1246
|
return createProductionService({ db: options.db });
|
|
694
1247
|
}
|
|
695
1248
|
//#endregion
|
|
696
|
-
|
|
1249
|
+
//#region src/services/OperationService.ts
|
|
1250
|
+
var OperationService = class OperationService {
|
|
1251
|
+
constructor(operations) {
|
|
1252
|
+
this.operations = operations;
|
|
1253
|
+
}
|
|
1254
|
+
operations;
|
|
1255
|
+
/** Factory — prefer {@link createOperationService}. */
|
|
1256
|
+
static async create(options) {
|
|
1257
|
+
return new OperationService(await OperationCollection.create({ db: options.db }));
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Add an operation. The code is trimmed and must be unique per tenant;
|
|
1261
|
+
* throws {@link DuplicateOperationCodeError} when it is taken (retired
|
|
1262
|
+
* operations keep their code).
|
|
1263
|
+
*/
|
|
1264
|
+
async define(input) {
|
|
1265
|
+
const code = input.code?.trim() ?? "";
|
|
1266
|
+
const name = input.name?.trim() ?? "";
|
|
1267
|
+
if (!code) throw new InvalidOperationInputError("Operation code is required.");
|
|
1268
|
+
if (!name) throw new InvalidOperationInputError("Operation name is required.");
|
|
1269
|
+
if (await this.operations.findByCode(code)) throw new DuplicateOperationCodeError(code);
|
|
1270
|
+
try {
|
|
1271
|
+
return await this.operations.create({
|
|
1272
|
+
code,
|
|
1273
|
+
name,
|
|
1274
|
+
category: input.category?.trim() ?? "",
|
|
1275
|
+
requiredQualificationId: input.requiredQualificationId?.trim() || null,
|
|
1276
|
+
isActive: true,
|
|
1277
|
+
_insertOnly: true
|
|
1278
|
+
});
|
|
1279
|
+
} catch (error) {
|
|
1280
|
+
if (await this.operations.findByCode(code)) throw new DuplicateOperationCodeError(code);
|
|
1281
|
+
throw error;
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
/** Fetch an operation or throw {@link OperationNotFoundError}. */
|
|
1285
|
+
async get(id) {
|
|
1286
|
+
const operation = id ? await this.operations.get(id) : null;
|
|
1287
|
+
if (!operation) throw new OperationNotFoundError(id);
|
|
1288
|
+
return operation;
|
|
1289
|
+
}
|
|
1290
|
+
/** List operations; retired ones only with `includeRetired`. */
|
|
1291
|
+
async list(options = {}) {
|
|
1292
|
+
return this.operations.listOperations(options);
|
|
1293
|
+
}
|
|
1294
|
+
/** Change the display name. The code does not change. */
|
|
1295
|
+
async rename(id, name) {
|
|
1296
|
+
const next = name?.trim() ?? "";
|
|
1297
|
+
if (!next) throw new InvalidOperationInputError("Operation name is required.");
|
|
1298
|
+
const operation = await this.get(id);
|
|
1299
|
+
operation.name = next;
|
|
1300
|
+
await operation.save();
|
|
1301
|
+
return operation;
|
|
1302
|
+
}
|
|
1303
|
+
/** Change category and/or required qualification; an empty string clears. */
|
|
1304
|
+
async update(id, changes) {
|
|
1305
|
+
const operation = await this.get(id);
|
|
1306
|
+
if (changes.category !== void 0) operation.category = changes.category.trim();
|
|
1307
|
+
if (changes.requiredQualificationId !== void 0) operation.requiredQualificationId = changes.requiredQualificationId.trim() || null;
|
|
1308
|
+
await operation.save();
|
|
1309
|
+
return operation;
|
|
1310
|
+
}
|
|
1311
|
+
/** Retire: stays on history and routings, leaves pickers. Idempotent. */
|
|
1312
|
+
async retire(id) {
|
|
1313
|
+
return this.setActive(id, false);
|
|
1314
|
+
}
|
|
1315
|
+
/** Reinstate a retired operation. Idempotent. */
|
|
1316
|
+
async reinstate(id) {
|
|
1317
|
+
return this.setActive(id, true);
|
|
1318
|
+
}
|
|
1319
|
+
async setActive(id, isActive) {
|
|
1320
|
+
const operation = await this.get(id);
|
|
1321
|
+
if (operation.isActive !== isActive) {
|
|
1322
|
+
operation.isActive = isActive;
|
|
1323
|
+
await operation.save();
|
|
1324
|
+
}
|
|
1325
|
+
return operation;
|
|
1326
|
+
}
|
|
1327
|
+
};
|
|
1328
|
+
async function createOperationService(options) {
|
|
1329
|
+
return OperationService.create(options);
|
|
1330
|
+
}
|
|
1331
|
+
//#endregion
|
|
1332
|
+
//#region src/services/RoutingService.ts
|
|
1333
|
+
var RoutingService = class RoutingService {
|
|
1334
|
+
constructor(db, boms, operations, steps) {
|
|
1335
|
+
this.db = db;
|
|
1336
|
+
this.boms = boms;
|
|
1337
|
+
this.operations = operations;
|
|
1338
|
+
this.steps = steps;
|
|
1339
|
+
}
|
|
1340
|
+
db;
|
|
1341
|
+
boms;
|
|
1342
|
+
operations;
|
|
1343
|
+
steps;
|
|
1344
|
+
/** Factory — prefer {@link createRoutingService}. */
|
|
1345
|
+
static async create(options) {
|
|
1346
|
+
const db = await resolveDatabase(options.db);
|
|
1347
|
+
const [boms, operations, steps] = await Promise.all([
|
|
1348
|
+
BillOfMaterialsCollection.create({ db }),
|
|
1349
|
+
OperationCollection.create({ db }),
|
|
1350
|
+
RoutingStepCollection.create({ db })
|
|
1351
|
+
]);
|
|
1352
|
+
return new RoutingService(db, boms, operations, steps);
|
|
1353
|
+
}
|
|
1354
|
+
/** The routing of a bill in step order; empty when it has none. */
|
|
1355
|
+
async list(bomId) {
|
|
1356
|
+
return this.steps.findByBom(bomId);
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* Check `inputs` against the routing about to be replaced: durations,
|
|
1360
|
+
* operations, and that a retired operation is not added beyond the
|
|
1361
|
+
* occurrences `current` already has.
|
|
1362
|
+
*/
|
|
1363
|
+
async validate(current, inputs) {
|
|
1364
|
+
const retained = /* @__PURE__ */ new Map();
|
|
1365
|
+
for (const step of current) retained.set(step.operationId, (retained.get(step.operationId) ?? 0) + 1);
|
|
1366
|
+
for (const input of inputs) {
|
|
1367
|
+
const minutes = Number(input.estimatedMinutes);
|
|
1368
|
+
if (!Number.isFinite(minutes) || minutes < 0) throw new InvalidOperationInputError("Estimated minutes must be zero or more.");
|
|
1369
|
+
const operation = input.operationId ? await this.operations.get(input.operationId) : null;
|
|
1370
|
+
if (!operation) throw new OperationNotFoundError(input.operationId);
|
|
1371
|
+
const left = retained.get(operation.id) ?? 0;
|
|
1372
|
+
if (left > 0) retained.set(operation.id, left - 1);
|
|
1373
|
+
else if (!operation.isActive) throw new OperationRetiredError(operation.id);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Replace the bill's routing with `inputs`, numbered 1..n in the order given.
|
|
1378
|
+
* An empty list removes the routing.
|
|
1379
|
+
*
|
|
1380
|
+
* Throws {@link BomNotFoundError}, {@link OperationNotFoundError},
|
|
1381
|
+
* {@link OperationRetiredError} (a retired operation may stay on a routing
|
|
1382
|
+
* that already has it but cannot be newly added), or
|
|
1383
|
+
* {@link InvalidOperationInputError} for a negative or non-finite duration.
|
|
1384
|
+
*/
|
|
1385
|
+
async replaceRouting(bomId, inputs) {
|
|
1386
|
+
if (!bomId || !await this.boms.get(bomId)) throw new BomNotFoundError(bomId);
|
|
1387
|
+
const write = async (steps, boms) => {
|
|
1388
|
+
const bom = await boms.get(bomId);
|
|
1389
|
+
if (!bom) throw new BomNotFoundError(bomId);
|
|
1390
|
+
await bom.save();
|
|
1391
|
+
const current = await steps.findByBom(bomId);
|
|
1392
|
+
await this.validate(current, inputs);
|
|
1393
|
+
for (const step of current) await step.delete();
|
|
1394
|
+
const saved = [];
|
|
1395
|
+
for (const [index, input] of inputs.entries()) saved.push(await steps.create({
|
|
1396
|
+
bomId,
|
|
1397
|
+
operationId: input.operationId,
|
|
1398
|
+
sequence: index + 1,
|
|
1399
|
+
estimatedMinutes: Number(input.estimatedMinutes),
|
|
1400
|
+
notes: input.notes?.trim() ?? ""
|
|
1401
|
+
}));
|
|
1402
|
+
return saved;
|
|
1403
|
+
};
|
|
1404
|
+
const underlying = this.db;
|
|
1405
|
+
if (typeof underlying.transaction !== "function") throw new Error("RoutingService requires a database adapter with transaction() support.");
|
|
1406
|
+
return withRevisionRetry(() => underlying.transaction?.(async (txDb) => {
|
|
1407
|
+
const db = txDb;
|
|
1408
|
+
return write(await RoutingStepCollection.create({ db }), await BillOfMaterialsCollection.create({ db }));
|
|
1409
|
+
}));
|
|
1410
|
+
}
|
|
1411
|
+
};
|
|
1412
|
+
async function withRevisionRetry(run) {
|
|
1413
|
+
for (let attempt = 1;; attempt++) try {
|
|
1414
|
+
return await run();
|
|
1415
|
+
} catch (error) {
|
|
1416
|
+
if (error?.code !== "RUNTIME_REVISION_CONFLICT" || attempt >= 5) throw error;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
async function createRoutingService(options) {
|
|
1420
|
+
return RoutingService.create(options);
|
|
1421
|
+
}
|
|
1422
|
+
//#endregion
|
|
1423
|
+
export { Assembly, AssemblyCollection, AssemblyService, BillOfMaterials, BillOfMaterialsCollection, BomCycleError, BomLine, BomLineCollection, BomNotFoundError, BomService, DuplicateOperationCodeError, InvalidOperationInputError, NoActiveBomForProductError, Operation, OperationCollection, OperationNotFoundError, OperationRetiredError, OperationService, ProductionService, RoutingService, RoutingStep, RoutingStepCollection, createAssemblyService, createBomService, createOperationService, createProductionService, createRoutingService, installManufacturingDispatchHandlers };
|
|
697
1424
|
|
|
698
1425
|
//# sourceMappingURL=index.js.map
|