@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.
Files changed (72) hide show
  1. package/AGENTS.md +45 -6
  2. package/README.md +161 -3
  3. package/dist/collections/AssemblyCollection.d.ts +15 -0
  4. package/dist/collections/AssemblyCollection.d.ts.map +1 -0
  5. package/dist/collections/OperationCollection.d.ts +20 -0
  6. package/dist/collections/OperationCollection.d.ts.map +1 -0
  7. package/dist/collections/RoutingStepCollection.d.ts +15 -0
  8. package/dist/collections/RoutingStepCollection.d.ts.map +1 -0
  9. package/dist/collections/index.d.ts +3 -0
  10. package/dist/collections/index.d.ts.map +1 -1
  11. package/dist/index.d.ts +13 -3
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +756 -29
  14. package/dist/index.js.map +1 -1
  15. package/dist/manifest.json +1248 -128
  16. package/dist/models/Assembly.d.ts +62 -0
  17. package/dist/models/Assembly.d.ts.map +1 -0
  18. package/dist/models/BillOfMaterials.d.ts +12 -4
  19. package/dist/models/BillOfMaterials.d.ts.map +1 -1
  20. package/dist/models/BomLine.d.ts +11 -5
  21. package/dist/models/BomLine.d.ts.map +1 -1
  22. package/dist/models/Operation.d.ts +59 -0
  23. package/dist/models/Operation.d.ts.map +1 -0
  24. package/dist/models/RoutingStep.d.ts +46 -0
  25. package/dist/models/RoutingStep.d.ts.map +1 -0
  26. package/dist/models/index.d.ts +3 -0
  27. package/dist/models/index.d.ts.map +1 -1
  28. package/dist/operation-types.d.ts +99 -0
  29. package/dist/operation-types.d.ts.map +1 -0
  30. package/dist/services/AssemblyService.d.ts +217 -0
  31. package/dist/services/AssemblyService.d.ts.map +1 -0
  32. package/dist/services/BomService.d.ts +25 -1
  33. package/dist/services/BomService.d.ts.map +1 -1
  34. package/dist/services/OperationService.d.ts +48 -0
  35. package/dist/services/OperationService.d.ts.map +1 -0
  36. package/dist/services/RoutingService.d.ts +50 -0
  37. package/dist/services/RoutingService.d.ts.map +1 -0
  38. package/dist/services/index.d.ts +3 -0
  39. package/dist/services/index.d.ts.map +1 -1
  40. package/dist/smrt-knowledge.json +1138 -81
  41. package/dist/svelte/components/AssemblyForm.svelte +69 -0
  42. package/dist/svelte/components/AssemblyForm.svelte.d.ts +38 -0
  43. package/dist/svelte/components/AssemblyForm.svelte.d.ts.map +1 -0
  44. package/dist/svelte/components/AssemblyList.svelte +229 -0
  45. package/dist/svelte/components/AssemblyList.svelte.d.ts +28 -0
  46. package/dist/svelte/components/AssemblyList.svelte.d.ts.map +1 -0
  47. package/dist/svelte/components/BomEditor.svelte +575 -0
  48. package/dist/svelte/components/BomEditor.svelte.d.ts +29 -0
  49. package/dist/svelte/components/BomEditor.svelte.d.ts.map +1 -0
  50. package/dist/svelte/components/BomStructureTree.svelte +164 -0
  51. package/dist/svelte/components/BomStructureTree.svelte.d.ts +16 -0
  52. package/dist/svelte/components/BomStructureTree.svelte.d.ts.map +1 -0
  53. package/dist/svelte/components/OperationForm.svelte +258 -0
  54. package/dist/svelte/components/OperationForm.svelte.d.ts +17 -0
  55. package/dist/svelte/components/OperationForm.svelte.d.ts.map +1 -0
  56. package/dist/svelte/components/OperationList.svelte +170 -0
  57. package/dist/svelte/components/OperationList.svelte.d.ts +17 -0
  58. package/dist/svelte/components/OperationList.svelte.d.ts.map +1 -0
  59. package/dist/svelte/i18n.d.ts +87 -0
  60. package/dist/svelte/i18n.d.ts.map +1 -0
  61. package/dist/svelte/i18n.js +87 -0
  62. package/dist/svelte/index.d.ts +28 -0
  63. package/dist/svelte/index.d.ts.map +1 -0
  64. package/dist/svelte/index.js +29 -0
  65. package/dist/svelte/types.d.ts +170 -0
  66. package/dist/svelte/types.d.ts.map +1 -0
  67. package/dist/svelte/types.js +126 -0
  68. package/dist/ui.d.ts +17 -0
  69. package/dist/ui.d.ts.map +1 -0
  70. package/dist/ui.js +72 -0
  71. package/dist/ui.js.map +1 -0
  72. 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 { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
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$1 = Object.defineProperty;
11
- var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
12
- var __decorateClass$1 = (decorators, target, key, kind) => {
13
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
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$1(target, key, result);
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$1([tenantId({ nullable: true })], BillOfMaterials.prototype, "tenantId", 2);
52
- __decorateClass$1([field({ required: true })], BillOfMaterials.prototype, "productId", 2);
53
- __decorateClass$1([field({ required: true })], BillOfMaterials.prototype, "version", 2);
54
- __decorateClass$1([field({ required: true })], BillOfMaterials.prototype, "status", 2);
55
- BillOfMaterials = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({
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([BillOfMaterialsCollection.create({ db: sharedDb }), BomLineCollection.create({ db: sharedDb })]);
228
- return new BomService(boms, lines, stockService, options.costResolver);
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
- export { BillOfMaterials, BillOfMaterialsCollection, BomLine, BomLineCollection, BomNotFoundError, BomService, NoActiveBomForProductError, ProductionService, createBomService, createProductionService, installManufacturingDispatchHandlers };
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