@happyvertical/smrt-manufacturing 0.53.5 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/AGENTS.md +18 -7
  2. package/README.md +70 -5
  3. package/agents/multilevel-and-runs.md +28 -0
  4. package/dist/collections/ProductionRunCollection.d.ts +15 -0
  5. package/dist/collections/ProductionRunCollection.d.ts.map +1 -0
  6. package/dist/collections/ProductionRunCompletionCollection.d.ts +14 -0
  7. package/dist/collections/ProductionRunCompletionCollection.d.ts.map +1 -0
  8. package/dist/collections/index.d.ts +2 -0
  9. package/dist/collections/index.d.ts.map +1 -1
  10. package/dist/index.d.ts +13 -5
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +972 -81
  13. package/dist/index.js.map +1 -1
  14. package/dist/manifest.json +630 -2
  15. package/dist/models/ProductionRun.d.ts +67 -0
  16. package/dist/models/ProductionRun.d.ts.map +1 -0
  17. package/dist/models/ProductionRunCompletion.d.ts +36 -0
  18. package/dist/models/ProductionRunCompletion.d.ts.map +1 -0
  19. package/dist/models/index.d.ts +2 -0
  20. package/dist/models/index.d.ts.map +1 -1
  21. package/dist/operation-types.d.ts +9 -0
  22. package/dist/operation-types.d.ts.map +1 -1
  23. package/dist/production-run-types.d.ts +100 -0
  24. package/dist/production-run-types.d.ts.map +1 -0
  25. package/dist/quantity.d.ts +36 -0
  26. package/dist/quantity.d.ts.map +1 -0
  27. package/dist/services/AssemblyService.d.ts +7 -2
  28. package/dist/services/AssemblyService.d.ts.map +1 -1
  29. package/dist/services/BomService.d.ts +84 -5
  30. package/dist/services/BomService.d.ts.map +1 -1
  31. package/dist/services/ProductionRunService.d.ts +123 -0
  32. package/dist/services/ProductionRunService.d.ts.map +1 -0
  33. package/dist/services/ProductionService.d.ts +11 -0
  34. package/dist/services/ProductionService.d.ts.map +1 -1
  35. package/dist/services/index.d.ts +2 -0
  36. package/dist/services/index.d.ts.map +1 -1
  37. package/dist/services/structure-walk.d.ts +77 -0
  38. package/dist/services/structure-walk.d.ts.map +1 -0
  39. package/dist/smrt-knowledge.json +514 -9
  40. package/dist/svelte/components/AssemblyForm.svelte +423 -52
  41. package/dist/svelte/components/AssemblyForm.svelte.d.ts +17 -32
  42. package/dist/svelte/components/AssemblyForm.svelte.d.ts.map +1 -1
  43. package/dist/svelte/components/AssemblyList.svelte +18 -12
  44. package/dist/svelte/components/AssemblyList.svelte.d.ts +2 -13
  45. package/dist/svelte/components/AssemblyList.svelte.d.ts.map +1 -1
  46. package/dist/svelte/components/ProductionRunList.svelte +213 -0
  47. package/dist/svelte/components/ProductionRunList.svelte.d.ts +18 -0
  48. package/dist/svelte/components/ProductionRunList.svelte.d.ts.map +1 -0
  49. package/dist/svelte/components/RequirementsTree.svelte +125 -0
  50. package/dist/svelte/components/RequirementsTree.svelte.d.ts +13 -0
  51. package/dist/svelte/components/RequirementsTree.svelte.d.ts.map +1 -0
  52. package/dist/svelte/components/RequirementsTreeLevel.svelte +120 -0
  53. package/dist/svelte/components/RequirementsTreeLevel.svelte.d.ts +12 -0
  54. package/dist/svelte/components/RequirementsTreeLevel.svelte.d.ts.map +1 -0
  55. package/dist/svelte/i18n.d.ts +55 -0
  56. package/dist/svelte/i18n.d.ts.map +1 -1
  57. package/dist/svelte/i18n.js +55 -0
  58. package/dist/svelte/index.d.ts +11 -6
  59. package/dist/svelte/index.d.ts.map +1 -1
  60. package/dist/svelte/index.js +11 -6
  61. package/dist/svelte/types.d.ts +182 -0
  62. package/dist/svelte/types.d.ts.map +1 -1
  63. package/dist/svelte/types.js +250 -0
  64. package/dist/tenant-scope.d.ts +25 -0
  65. package/dist/tenant-scope.d.ts.map +1 -0
  66. package/dist/types.d.ts +207 -3
  67. package/dist/types.d.ts.map +1 -1
  68. package/dist/types.js +13 -1
  69. package/dist/types.js.map +1 -1
  70. package/dist/ui.d.ts.map +1 -1
  71. package/dist/ui.js +23 -1
  72. package/dist/ui.js.map +1 -1
  73. package/package.json +10 -10
package/dist/index.js CHANGED
@@ -1,32 +1,32 @@
1
- import { BomNotFoundError, NoActiveBomForProductError } from "./types.js";
1
+ import { BomExplosionLimitError, BomNotFoundError, MAX_EXPLOSION_DEPTH, MAX_EXPLOSION_LINES, NoActiveBomForProductError } from "./types.js";
2
2
  import { ObjectRegistry, SmrtCollection, SmrtObject, ValidationError, crossPackageRef, field, foreignKey, resolveDatabase, smrt } from "@happyvertical/smrt-core";
3
3
  import { ProductCollection, SkuCollection } from "@happyvertical/smrt-products/collections";
4
4
  import { Material, Product, ProductType } from "@happyvertical/smrt-products/models";
5
- import { TenantScoped, getCurrentTenant, tenantId } from "@happyvertical/smrt-tenancy";
5
+ import { TenantScoped, getCurrentTenant, tenantId, withTenant, withTenantGlobalRead } from "@happyvertical/smrt-tenancy";
6
6
  import { createStockService } from "@happyvertical/smrt-inventory";
7
7
  import { createLogger } from "@happyvertical/logger";
8
8
  //#region \0rolldown/runtime.js
9
- var __defProp$5 = Object.defineProperty;
9
+ var __defProp$7 = Object.defineProperty;
10
10
  var __exportAll = (all, no_symbols) => {
11
11
  let target = {};
12
- for (var name in all) __defProp$5(target, name, {
12
+ for (var name in all) __defProp$7(target, name, {
13
13
  get: all[name],
14
14
  enumerable: true
15
15
  });
16
- if (!no_symbols) __defProp$5(target, Symbol.toStringTag, { value: "Module" });
16
+ if (!no_symbols) __defProp$7(target, Symbol.toStringTag, { value: "Module" });
17
17
  return target;
18
18
  };
19
19
  //#endregion
20
20
  //#region src/__smrt-register__.ts
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\"]}"));
21
+ ObjectRegistry.registerPackageManifest(JSON.parse("{\"version\":\"1.0.0\",\"timestamp\":0,\"packageName\":\"@happyvertical/smrt-manufacturing\",\"packageVersion\":\"0.54.0\",\"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:ProductionRunCollection\":{\"name\":\"productionruncollection\",\"className\":\"ProductionRunCollection\",\"qualifiedName\":\"@happyvertical/smrt-manufacturing:ProductionRunCollection\",\"collection\":\"productionruns\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/manufacturing/src/collections/ProductionRunCollection.ts\",\"packageName\":\"@happyvertical/smrt-manufacturing\",\"fields\":{},\"methods\":{\"findByBom\":{\"name\":\"findByBom\",\"async\":true,\"parameters\":[{\"name\":\"bomId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ProductionRun[]>\",\"isStatic\":false,\"isPublic\":true},\"findByStatus\":{\"name\":\"findByStatus\",\"async\":true,\"parameters\":[{\"name\":\"status\",\"type\":\"ProductionRunStatus\",\"optional\":false}],\"returnType\":\"Promise<ProductionRun[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"manufacturing_production_runs\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"ProductionRun\",\"exportName\":\"ProductionRunCollection\",\"collectionExportName\":\"ProductionRunCollectionCollection\",\"schema\":{\"tableName\":\"manufacturing_production_runs\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"manufacturing_production_runs\\\" (\\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 \\\"target_qty\\\" REAL,\\n \\\"completed_qty\\\" REAL,\\n \\\"status\\\" TEXT NOT NULL DEFAULT 'planned'\\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\":\"RESTRICT\",\"onUpdate\":\"CASCADE\"}},\"target_qty\":{\"type\":\"REAL\",\"notNull\":false,\"unique\":false},\"completed_qty\":{\"type\":\"REAL\",\"notNull\":false,\"unique\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"planned\"}},\"indexes\":[{\"name\":\"manufacturing_production_runs_slug_context_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"manufacturing_production_runs_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"manufacturing_production_runs_bom_id_idx\",\"columns\":[\"bom_id\"]}],\"version\":\"2ca8147d\"},\"importPath\":\"@happyvertical/smrt-manufacturing\"},\"@happyvertical/smrt-manufacturing:ProductionRunCompletionCollection\":{\"name\":\"productionruncompletioncollection\",\"className\":\"ProductionRunCompletionCollection\",\"qualifiedName\":\"@happyvertical/smrt-manufacturing:ProductionRunCompletionCollection\",\"collection\":\"productionruncompletions\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/manufacturing/src/collections/ProductionRunCompletionCollection.ts\",\"packageName\":\"@happyvertical/smrt-manufacturing\",\"fields\":{},\"methods\":{\"findByRun\":{\"name\":\"findByRun\",\"async\":true,\"parameters\":[{\"name\":\"runId\",\"type\":\"string\",\"optional\":false}],\"returnType\":\"Promise<ProductionRunCompletion[]>\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"manufacturing_production_run_completions\"},\"extends\":\"SmrtCollection\",\"extendsTypeArg\":\"ProductionRunCompletion\",\"exportName\":\"ProductionRunCompletionCollection\",\"collectionExportName\":\"ProductionRunCompletionCollectionCollection\",\"schema\":{\"tableName\":\"manufacturing_production_run_completions\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"manufacturing_production_run_completions\\\" (\\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 \\\"run_id\\\" UUID NOT NULL,\\n \\\"qty\\\" REAL,\\n \\\"completed_at\\\" TIMESTAMP\\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},\"run_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false,\"foreignKey\":{\"table\":\"manufacturing_production_runs\",\"column\":\"id\",\"onDelete\":\"CASCADE\",\"onUpdate\":\"CASCADE\"}},\"qty\":{\"type\":\"REAL\",\"notNull\":false,\"unique\":false},\"completed_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"manufacturing_production_run_completions_slug_context_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"manufacturing_production_run_completions_tenant_ea7a0d0125_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"manufacturing_production_run_completions_run_id_idx\",\"columns\":[\"run_id\"]}],\"version\":\"c940de6c\"},\"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:ProductionRun\":{\"name\":\"productionrun\",\"className\":\"ProductionRun\",\"qualifiedName\":\"@happyvertical/smrt-manufacturing:ProductionRun\",\"collection\":\"productionruns\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/manufacturing/src/models/ProductionRun.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\":\"RESTRICT\"}},\"targetQty\":{\"type\":\"decimal\",\"required\":false,\"_meta\":{}},\"completedQty\":{\"type\":\"decimal\",\"required\":false,\"_meta\":{}},\"status\":{\"type\":\"text\",\"required\":true,\"default\":\"planned\",\"_meta\":{\"required\":true}}},\"methods\":{\"remainingQty\":{\"name\":\"remainingQty\",\"async\":false,\"parameters\":[],\"returnType\":\"number\",\"isStatic\":false,\"isPublic\":true}},\"decoratorConfig\":{\"tableName\":\"manufacturing_production_runs\",\"api\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":{\"include\":[\"list\",\"get\"]},\"tenantScoped\":{\"mode\":\"optional\"},\"conflictColumns\":[\"tenant_id\",\"slug\",\"context\"]},\"extends\":\"SmrtObject\",\"exportName\":\"ProductionRun\",\"collectionExportName\":\"ProductionRunCollection\",\"validationRules\":[{\"field\":\"bomId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"},{\"field\":\"status\",\"rule\":\"required\",\"fieldType\":\"text\"}],\"schema\":{\"tableName\":\"manufacturing_production_runs\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"manufacturing_production_runs\\\" (\\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 \\\"target_qty\\\" REAL,\\n \\\"completed_qty\\\" REAL,\\n \\\"status\\\" TEXT NOT NULL DEFAULT 'planned'\\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\":\"RESTRICT\",\"onUpdate\":\"CASCADE\"}},\"target_qty\":{\"type\":\"REAL\",\"notNull\":false,\"unique\":false},\"completed_qty\":{\"type\":\"REAL\",\"notNull\":false,\"unique\":false},\"status\":{\"type\":\"TEXT\",\"notNull\":true,\"unique\":false,\"default\":\"planned\"}},\"indexes\":[{\"name\":\"manufacturing_production_runs_slug_context_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"manufacturing_production_runs_tenant_id_created_at_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"manufacturing_production_runs_bom_id_idx\",\"columns\":[\"bom_id\"]}],\"version\":\"2ca8147d\"},\"importPath\":\"@happyvertical/smrt-manufacturing\"},\"@happyvertical/smrt-manufacturing:ProductionRunCompletion\":{\"name\":\"productionruncompletion\",\"className\":\"ProductionRunCompletion\",\"qualifiedName\":\"@happyvertical/smrt-manufacturing:ProductionRunCompletion\",\"collection\":\"productionruncompletions\",\"filePath\":\"/home/runner/work/smrt/smrt/packages/manufacturing/src/models/ProductionRunCompletion.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}}},\"runId\":{\"type\":\"foreignKey\",\"required\":true,\"related\":\"ProductionRun\",\"_meta\":{\"required\":true,\"onDelete\":\"CASCADE\"}},\"qty\":{\"type\":\"decimal\",\"required\":false,\"_meta\":{}},\"completedAt\":{\"type\":\"datetime\",\"required\":false}},\"methods\":{},\"decoratorConfig\":{\"tableName\":\"manufacturing_production_run_completions\",\"api\":{\"include\":[\"list\",\"get\"]},\"mcp\":{\"include\":[\"list\",\"get\"]},\"cli\":{\"include\":[\"list\",\"get\"]},\"tenantScoped\":{\"mode\":\"optional\"},\"conflictColumns\":[\"tenant_id\",\"slug\",\"context\"]},\"extends\":\"SmrtObject\",\"exportName\":\"ProductionRunCompletion\",\"collectionExportName\":\"ProductionRunCompletionCollection\",\"validationRules\":[{\"field\":\"runId\",\"rule\":\"required\",\"fieldType\":\"foreignKey\"}],\"schema\":{\"tableName\":\"manufacturing_production_run_completions\",\"ddl\":\"CREATE TABLE IF NOT EXISTS \\\"manufacturing_production_run_completions\\\" (\\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 \\\"run_id\\\" UUID NOT NULL,\\n \\\"qty\\\" REAL,\\n \\\"completed_at\\\" TIMESTAMP\\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},\"run_id\":{\"type\":\"UUID\",\"referenceKind\":\"foreignKey\",\"notNull\":true,\"unique\":false,\"foreignKey\":{\"table\":\"manufacturing_production_runs\",\"column\":\"id\",\"onDelete\":\"CASCADE\",\"onUpdate\":\"CASCADE\"}},\"qty\":{\"type\":\"REAL\",\"notNull\":false,\"unique\":false},\"completed_at\":{\"type\":\"TIMESTAMP\",\"notNull\":false,\"unique\":false}},\"indexes\":[{\"name\":\"manufacturing_production_run_completions_slug_context_idx\",\"columns\":[\"tenant_id\",\"slug\",\"context\"],\"unique\":true,\"nullsNotDistinct\":true},{\"name\":\"manufacturing_production_run_completions_tenant_ea7a0d0125_idx\",\"columns\":[\"tenant_id\",\"created_at\"]},{\"name\":\"manufacturing_production_run_completions_run_id_idx\",\"columns\":[\"run_id\"]}],\"version\":\"c940de6c\"},\"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-inventory\",\"@happyvertical/smrt-products\",\"@happyvertical/smrt-tenancy\"]}"));
22
22
  //#endregion
23
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;
24
+ var __defProp$6 = Object.defineProperty;
25
+ var __getOwnPropDesc$6 = Object.getOwnPropertyDescriptor;
26
+ var __decorateClass$6 = (decorators, target, key, kind) => {
27
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$6(target, key) : target;
28
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);
29
+ if (kind && result) __defProp$6(target, key, result);
30
30
  return result;
31
31
  };
32
32
  var Operation = class extends SmrtObject {
@@ -58,11 +58,11 @@ var Operation = class extends SmrtObject {
58
58
  throw new Error(`Operation ${this.code || this.id} cannot be deleted; retire it instead.`);
59
59
  }
60
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({
61
+ __decorateClass$6([tenantId({ nullable: true })], Operation.prototype, "tenantId", 2);
62
+ __decorateClass$6([field({ required: true })], Operation.prototype, "code", 2);
63
+ __decorateClass$6([field({ required: true })], Operation.prototype, "name", 2);
64
+ __decorateClass$6([crossPackageRef("@happyvertical/smrt-human-resources:Qualification", { nullable: true })], Operation.prototype, "requiredQualificationId", 2);
65
+ Operation = __decorateClass$6([TenantScoped({ mode: "optional" }), smrt({
66
66
  tableName: "manufacturing_operations",
67
67
  conflictColumns: ["code", "tenant_id"],
68
68
  api: { include: ["list", "get"] },
@@ -71,12 +71,12 @@ Operation = __decorateClass$4([TenantScoped({ mode: "optional" }), smrt({
71
71
  })], Operation);
72
72
  //#endregion
73
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;
74
+ var __defProp$5 = Object.defineProperty;
75
+ var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor;
76
+ var __decorateClass$5 = (decorators, target, key, kind) => {
77
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$5(target, key) : target;
78
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);
79
+ if (kind && result) __defProp$5(target, key, result);
80
80
  return result;
81
81
  };
82
82
  var Assembly = class extends Product {
@@ -100,13 +100,13 @@ var Assembly = class extends Product {
100
100
  if (!Number.isSafeInteger(minutes) || minutes < 0) throw ValidationError.invalidValue("estimatedLabourMinutes", minutes, "a whole number of minutes, zero or more");
101
101
  }
102
102
  };
103
- __decorateClass$3([field()], Assembly.prototype, "estimatedLabourMinutes", 2);
104
- __decorateClass$3([foreignKey(Operation, {
103
+ __decorateClass$5([field()], Assembly.prototype, "estimatedLabourMinutes", 2);
104
+ __decorateClass$5([foreignKey(Operation, {
105
105
  nullable: true,
106
106
  onDelete: "RESTRICT"
107
107
  })], Assembly.prototype, "defaultOperationId", 2);
108
- __decorateClass$3([field()], Assembly.prototype, "partReference", 2);
109
- Assembly = __decorateClass$3([TenantScoped({ mode: "optional" }), smrt({
108
+ __decorateClass$5([field()], Assembly.prototype, "partReference", 2);
109
+ Assembly = __decorateClass$5([TenantScoped({ mode: "optional" }), smrt({
110
110
  api: { include: [
111
111
  "list",
112
112
  "get",
@@ -123,12 +123,12 @@ var AssemblyCollection = class extends ProductCollection {
123
123
  };
124
124
  //#endregion
125
125
  //#region src/models/BillOfMaterials.ts
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;
126
+ var __defProp$4 = Object.defineProperty;
127
+ var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
128
+ var __decorateClass$4 = (decorators, target, key, kind) => {
129
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
130
130
  for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
131
- if (kind && result) __defProp$2(target, key, result);
131
+ if (kind && result) __defProp$4(target, key, result);
132
132
  return result;
133
133
  };
134
134
  var BillOfMaterials = class extends SmrtObject {
@@ -177,11 +177,11 @@ var BillOfMaterials = class extends SmrtObject {
177
177
  await (await AssemblyService.create({ db: this.db })).assertBillAcyclic(this);
178
178
  }
179
179
  };
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({
180
+ __decorateClass$4([tenantId({ nullable: true })], BillOfMaterials.prototype, "tenantId", 2);
181
+ __decorateClass$4([field({ required: true })], BillOfMaterials.prototype, "productId", 2);
182
+ __decorateClass$4([field({ required: true })], BillOfMaterials.prototype, "version", 2);
183
+ __decorateClass$4([field({ required: true })], BillOfMaterials.prototype, "status", 2);
184
+ BillOfMaterials = __decorateClass$4([TenantScoped({ mode: "optional" }), smrt({
185
185
  tableName: "manufacturing_boms",
186
186
  conflictColumns: [
187
187
  "product_id",
@@ -241,12 +241,12 @@ var BillOfMaterialsCollection = class extends SmrtCollection {
241
241
  };
242
242
  //#endregion
243
243
  //#region src/models/BomLine.ts
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;
244
+ var __defProp$3 = Object.defineProperty;
245
+ var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
246
+ var __decorateClass$3 = (decorators, target, key, kind) => {
247
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
248
248
  for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
249
- if (kind && result) __defProp$1(target, key, result);
249
+ if (kind && result) __defProp$3(target, key, result);
250
250
  return result;
251
251
  };
252
252
  var BomLine = class extends SmrtObject {
@@ -299,12 +299,12 @@ var BomLine = class extends SmrtObject {
299
299
  await (await AssemblyService.create({ db: this.db })).assertLineAcyclic(this);
300
300
  }
301
301
  };
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({
302
+ __decorateClass$3([tenantId({ nullable: true })], BomLine.prototype, "tenantId", 2);
303
+ __decorateClass$3([field({ required: true })], BomLine.prototype, "bomId", 2);
304
+ __decorateClass$3([field({ required: true })], BomLine.prototype, "componentSkuId", 2);
305
+ __decorateClass$3([field({ type: "decimal" })], BomLine.prototype, "qtyPerUnit", 2);
306
+ __decorateClass$3([field({ type: "decimal" })], BomLine.prototype, "wastePercent", 2);
307
+ BomLine = __decorateClass$3([TenantScoped({ mode: "optional" }), smrt({
308
308
  tableName: "manufacturing_bom_lines",
309
309
  conflictColumns: [
310
310
  "bom_id",
@@ -370,6 +370,154 @@ var OperationCollection = class extends SmrtCollection {
370
370
  }
371
371
  };
372
372
  //#endregion
373
+ //#region src/quantity.ts
374
+ var QUANTITY_DECIMALS = 6;
375
+ var SCALE = 10 ** 6;
376
+ var MAX_QUANTITY = 999999999;
377
+ function roundQuantity(value) {
378
+ return Number(value.toFixed(6));
379
+ }
380
+ function toMillionths(value) {
381
+ return Number(value.toFixed(6).replace(".", ""));
382
+ }
383
+ function fromMillionths(millionths) {
384
+ return millionths / SCALE;
385
+ }
386
+ //#endregion
387
+ //#region src/models/ProductionRun.ts
388
+ var __defProp$2 = Object.defineProperty;
389
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
390
+ var __decorateClass$2 = (decorators, target, key, kind) => {
391
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
392
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
393
+ if (kind && result) __defProp$2(target, key, result);
394
+ return result;
395
+ };
396
+ var PRODUCTION_RUN_STATUSES = [
397
+ "planned",
398
+ "in_progress",
399
+ "done",
400
+ "cancelled"
401
+ ];
402
+ var ProductionRun = class extends SmrtObject {
403
+ tenantId = null;
404
+ bomId = "";
405
+ targetQty = 0;
406
+ completedQty = 0;
407
+ status = "planned";
408
+ constructor(options = {}) {
409
+ super(options);
410
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
411
+ if (options.bomId !== void 0) this.bomId = options.bomId;
412
+ if (options.targetQty !== void 0) this.targetQty = options.targetQty;
413
+ if (options.completedQty !== void 0) this.completedQty = options.completedQty;
414
+ if (options.status !== void 0) this.status = options.status;
415
+ }
416
+ /**
417
+ * Units still to build, at the six-decimal quantity precision; `0` once
418
+ * the target is reached.
419
+ */
420
+ remainingQty() {
421
+ return Math.max(0, roundQuantity(Number(this.targetQty) - Number(this.completedQty)));
422
+ }
423
+ /** Refuses a status outside the lifecycle and impossible quantities. */
424
+ async validateBeforeSave() {
425
+ await super.validateBeforeSave();
426
+ if (!PRODUCTION_RUN_STATUSES.includes(this.status)) throw new ValidationError(`Unknown production run status: ${String(this.status)}`, "MANUFACTURING_PRODUCTION_RUN_INVALID", { field: "status" });
427
+ const target = Number(this.targetQty);
428
+ const completed = Number(this.completedQty);
429
+ if (!Number.isFinite(target) || target <= 0) throw new ValidationError("A production run needs a target quantity greater than zero.", "MANUFACTURING_PRODUCTION_RUN_INVALID", { field: "targetQty" });
430
+ if (!Number.isFinite(completed) || completed < 0) throw new ValidationError("A production run cannot have a negative completed quantity.", "MANUFACTURING_PRODUCTION_RUN_INVALID", { field: "completedQty" });
431
+ }
432
+ };
433
+ __decorateClass$2([tenantId({ nullable: true })], ProductionRun.prototype, "tenantId", 2);
434
+ __decorateClass$2([foreignKey(BillOfMaterials, {
435
+ required: true,
436
+ onDelete: "RESTRICT"
437
+ })], ProductionRun.prototype, "bomId", 2);
438
+ __decorateClass$2([field({ type: "decimal" })], ProductionRun.prototype, "targetQty", 2);
439
+ __decorateClass$2([field({ type: "decimal" })], ProductionRun.prototype, "completedQty", 2);
440
+ __decorateClass$2([field({ required: true })], ProductionRun.prototype, "status", 2);
441
+ ProductionRun = __decorateClass$2([TenantScoped({ mode: "optional" }), smrt({
442
+ tableName: "manufacturing_production_runs",
443
+ api: { include: ["list", "get"] },
444
+ mcp: { include: ["list", "get"] },
445
+ cli: { include: ["list", "get"] }
446
+ })], ProductionRun);
447
+ //#endregion
448
+ //#region src/collections/ProductionRunCollection.ts
449
+ var ProductionRunCollection = class extends SmrtCollection {
450
+ static _itemClass = ProductionRun;
451
+ /** Runs against one bill, newest first. */
452
+ async findByBom(bomId) {
453
+ return this.list({
454
+ where: { bomId },
455
+ orderBy: "created_at DESC"
456
+ });
457
+ }
458
+ /** Runs in one status, oldest first (a work queue). */
459
+ async findByStatus(status) {
460
+ return this.list({
461
+ where: { status },
462
+ orderBy: "created_at ASC"
463
+ });
464
+ }
465
+ };
466
+ //#endregion
467
+ //#region src/models/ProductionRunCompletion.ts
468
+ var __defProp$1 = Object.defineProperty;
469
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
470
+ var __decorateClass$1 = (decorators, target, key, kind) => {
471
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
472
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
473
+ if (kind && result) __defProp$1(target, key, result);
474
+ return result;
475
+ };
476
+ var ProductionRunCompletion = class extends SmrtObject {
477
+ tenantId = null;
478
+ runId = "";
479
+ qty = 0;
480
+ /** When the units were finished (defaults to when the report was made). */
481
+ completedAt = /* @__PURE__ */ new Date();
482
+ constructor(options = {}) {
483
+ super(options);
484
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
485
+ if (options.runId !== void 0) this.runId = options.runId;
486
+ if (options.qty !== void 0) this.qty = options.qty;
487
+ if (options.completedAt !== void 0) this.completedAt = options.completedAt instanceof Date ? options.completedAt : new Date(options.completedAt);
488
+ }
489
+ /** Refuses a report of zero, negative or non-finite units. */
490
+ async validateBeforeSave() {
491
+ await super.validateBeforeSave();
492
+ const qty = Number(this.qty);
493
+ if (!Number.isFinite(qty) || qty <= 0) throw new ValidationError("A completion needs a quantity greater than zero.", "MANUFACTURING_PRODUCTION_RUN_INVALID", { field: "qty" });
494
+ }
495
+ };
496
+ __decorateClass$1([tenantId({ nullable: true })], ProductionRunCompletion.prototype, "tenantId", 2);
497
+ __decorateClass$1([foreignKey(ProductionRun, {
498
+ required: true,
499
+ onDelete: "CASCADE"
500
+ })], ProductionRunCompletion.prototype, "runId", 2);
501
+ __decorateClass$1([field({ type: "decimal" })], ProductionRunCompletion.prototype, "qty", 2);
502
+ ProductionRunCompletion = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({
503
+ tableName: "manufacturing_production_run_completions",
504
+ api: { include: ["list", "get"] },
505
+ mcp: { include: ["list", "get"] },
506
+ cli: { include: ["list", "get"] }
507
+ })], ProductionRunCompletion);
508
+ //#endregion
509
+ //#region src/collections/ProductionRunCompletionCollection.ts
510
+ var ProductionRunCompletionCollection = class extends SmrtCollection {
511
+ static _itemClass = ProductionRunCompletion;
512
+ /** A run's completions in the order they happened. */
513
+ async findByRun(runId) {
514
+ return this.list({
515
+ where: { runId },
516
+ orderBy: ["completedAt ASC", "created_at ASC"]
517
+ });
518
+ }
519
+ };
520
+ //#endregion
373
521
  //#region src/models/RoutingStep.ts
374
522
  var __defProp = Object.defineProperty;
375
523
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -468,6 +616,53 @@ var InvalidOperationInputError = class extends Error {
468
616
  name = "InvalidOperationInputError";
469
617
  };
470
618
  //#endregion
619
+ //#region src/production-run-types.ts
620
+ var ProductionRunNotFoundError = class extends Error {
621
+ constructor(runId) {
622
+ super(`Production run not found: ${runId}`);
623
+ this.runId = runId;
624
+ }
625
+ runId;
626
+ name = "ProductionRunNotFoundError";
627
+ };
628
+ var ProductionRunStateError = class extends Error {
629
+ constructor(runId, status, action) {
630
+ super(`Production run ${runId} is ${status}; cannot ${action} it.`);
631
+ this.runId = runId;
632
+ this.status = status;
633
+ this.action = action;
634
+ }
635
+ runId;
636
+ status;
637
+ action;
638
+ name = "ProductionRunStateError";
639
+ };
640
+ var ProductionRunOverCompletionError = class extends Error {
641
+ constructor(runId, qty, remaining) {
642
+ super(`Production run ${runId} has ${remaining} left to build; cannot report ${qty}.`);
643
+ this.runId = runId;
644
+ this.qty = qty;
645
+ this.remaining = remaining;
646
+ }
647
+ runId;
648
+ qty;
649
+ remaining;
650
+ name = "ProductionRunOverCompletionError";
651
+ };
652
+ var InvalidProductionRunInputError = class extends Error {
653
+ name = "InvalidProductionRunInputError";
654
+ };
655
+ //#endregion
656
+ //#region src/tenant-scope.ts
657
+ function readOwnAndGlobal(owner, read) {
658
+ const scope = owner ?? getCurrentTenant()?.tenantId ?? null;
659
+ return scope ? withTenantGlobalRead(scope, read) : read();
660
+ }
661
+ function isOwnOrGlobal(row, owner) {
662
+ const rowTenant = row.tenantId ?? null;
663
+ return rowTenant === null || rowTenant === owner;
664
+ }
665
+ //#endregion
471
666
  //#region src/services/AssemblyService.ts
472
667
  var AssemblyService_exports = /* @__PURE__ */ __exportAll({
473
668
  AssemblyService: () => AssemblyService,
@@ -488,14 +683,14 @@ var BomCycleError = class extends ValidationError {
488
683
  path;
489
684
  name = "BomCycleError";
490
685
  };
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;
686
+ var UUID_PATTERN$2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
492
687
  function visibleTo(row, options) {
493
688
  if (options.tenantId === void 0) return true;
494
689
  const rowTenant = row.tenantId ?? null;
495
690
  return rowTenant === null || rowTenant === options.tenantId;
496
691
  }
497
692
  function isUuid(value) {
498
- return typeof value === "string" && UUID_PATTERN.test(value);
693
+ return typeof value === "string" && UUID_PATTERN$2.test(value);
499
694
  }
500
695
  var AssemblyService = class AssemblyService {
501
696
  constructor(products, skus, boms, lines) {
@@ -540,10 +735,10 @@ var AssemblyService = class AssemblyService {
540
735
  buildable: false
541
736
  });
542
737
  if (!isUuid(skuId)) return missing(null);
543
- const sku = await this.skus.get({ id: skuId });
738
+ const sku = await this.byId(this.skus, skuId, options);
544
739
  if (!sku || !visibleTo(sku, options)) return missing(null);
545
740
  if (!isUuid(sku.productId)) return missing(sku);
546
- const product = await this.products.get({ id: sku.productId });
741
+ const product = await this.byId(this.products, sku.productId, options);
547
742
  if (!product || !visibleTo(product, options)) return missing(sku);
548
743
  if (product instanceof Assembly) {
549
744
  const activeBom = await this.findActiveBom(product.id, options);
@@ -568,18 +763,32 @@ var AssemblyService = class AssemblyService {
568
763
  };
569
764
  }
570
765
  /**
571
- * The highest-version active bill for a product, limited to one tenant's
572
- * bills when `options.tenantId` is given.
766
+ * The highest-version active bill for a product. With `options.tenantId`,
767
+ * that tenant's own bill, or else a global one; another tenant's never.
573
768
  */
574
769
  async findActiveBom(productId, options = {}) {
575
- if (options.tenantId === void 0) return this.boms.findActiveForProduct(productId);
576
- return (await this.boms.list({
770
+ const owner = options.tenantId;
771
+ if (owner === void 0) return this.boms.findActiveForProduct(productId);
772
+ const active = await readOwnAndGlobal(owner, () => this.boms.list({
577
773
  where: {
578
774
  productId,
579
775
  status: "active"
580
776
  },
581
777
  orderBy: "version DESC"
582
- })).find((bom) => (bom.tenantId ?? null) === options.tenantId) ?? null;
778
+ }));
779
+ return active.find((bom) => (bom.tenantId ?? null) === owner) ?? active.find((bom) => (bom.tenantId ?? null) === null) ?? null;
780
+ }
781
+ /**
782
+ * One row by id: through the collection's `get` without an explicit
783
+ * tenant, else as a list read of that tenant's and global rows.
784
+ */
785
+ async byId(collection, id, options) {
786
+ const owner = options.tenantId;
787
+ if (owner === void 0) return collection.get({ id });
788
+ return (await readOwnAndGlobal(owner, () => collection.list({
789
+ where: { id },
790
+ limit: 1
791
+ })))[0] ?? null;
583
792
  }
584
793
  /** `true` when the SKU belongs to an {@link Assembly}. */
585
794
  async isAssembly(skuId) {
@@ -597,7 +806,7 @@ var AssemblyService = class AssemblyService {
597
806
  if (!bom) throw new BomNotFoundError(bomId);
598
807
  const cache = /* @__PURE__ */ new Map();
599
808
  const scope = { tenantId: bom.tenantId ?? null };
600
- const lines = (await this.lines.findByBom(bomId)).filter((line) => visibleTo(line, scope));
809
+ const lines = (await readOwnAndGlobal(scope.tenantId, () => this.lines.findByBom(bomId))).filter((line) => visibleTo(line, scope));
601
810
  const resolved = [];
602
811
  for (const line of lines) resolved.push({
603
812
  line,
@@ -626,7 +835,8 @@ var AssemblyService = class AssemblyService {
626
835
  if (product.id === productId) return [...trail2, product];
627
836
  if (!component.activeBom || visited.has(product.id)) continue;
628
837
  visited.add(product.id);
629
- const lines = (await this.lines.findByBom(component.activeBom.id)).filter((line) => visibleTo(line, options));
838
+ const billId = component.activeBom.id;
839
+ const lines = (options.tenantId === void 0 ? await this.lines.findByBom(billId) : await readOwnAndGlobal(options.tenantId, () => this.lines.findByBom(billId))).filter((line) => visibleTo(line, options));
630
840
  const found = await walk(lines.map((line) => line.componentSkuId), [...trail2, product]);
631
841
  if (found) return found;
632
842
  }
@@ -672,13 +882,13 @@ var AssemblyService = class AssemblyService {
672
882
  const billIds = /* @__PURE__ */ new Set();
673
883
  if (isUuid(bom.id)) billIds.add(bom.id);
674
884
  const tenantId = bom.tenantId ?? getCurrentTenant()?.tenantId ?? null;
675
- const sameKey = await this.boms.list({ where: {
885
+ const sameKey = await readOwnAndGlobal(tenantId, () => this.boms.list({ where: {
676
886
  productId: bom.productId,
677
887
  version: bom.version
678
- } });
888
+ } }));
679
889
  for (const stored of sameKey) if (stored.id && (stored.tenantId ?? null) === tenantId) billIds.add(stored.id);
680
890
  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);
891
+ for (const billId of billIds) for (const line of await readOwnAndGlobal(tenantId, () => this.lines.findByBom(billId))) if (visibleTo(line, { tenantId })) componentSkuIds.push(line.componentSkuId);
682
892
  if (componentSkuIds.length === 0) return;
683
893
  const path = await this.findCycle(bom.productId, componentSkuIds, { tenantId });
684
894
  if (path) throw new BomCycleError(bom.id || [...billIds][0] || "", path);
@@ -696,9 +906,123 @@ function createAssemblyService(options) {
696
906
  return AssemblyService.create(options);
697
907
  }
698
908
  //#endregion
909
+ //#region src/services/structure-walk.ts
910
+ var BomStructureCycleError = class extends BomCycleError {
911
+ name = "BomStructureCycleError";
912
+ constructor(bomId, path) {
913
+ super(bomId, path);
914
+ this.message = `The bill structure contains a cycle: ${path.map((entry) => entry.name).join(" → ")}`;
915
+ }
916
+ };
917
+ var UUID_PATTERN$1 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
918
+ function maxLevelFor(levels) {
919
+ if (levels === void 0) return 1;
920
+ if (levels === "all") return 32;
921
+ if (!Number.isInteger(levels) || levels < 1 || levels > 32) throw new Error(`levels must be a whole number from 1 to 32, or 'all' (got ${String(levels)})`);
922
+ return levels;
923
+ }
924
+ function isMultiLevel(levels) {
925
+ return maxLevelFor(levels) > 1;
926
+ }
927
+ var StructureWalk = class {
928
+ constructor(assemblies, top, levels) {
929
+ this.assemblies = assemblies;
930
+ this.top = top;
931
+ this.levels = levels;
932
+ this.maxLevel = maxLevelFor(levels);
933
+ }
934
+ assemblies;
935
+ top;
936
+ levels;
937
+ components = /* @__PURE__ */ new Map();
938
+ bills = /* @__PURE__ */ new Map();
939
+ lineCount = 0;
940
+ /** The deepest level whose lines the walk reads. */
941
+ maxLevel;
942
+ /**
943
+ * Resolve a component SKU, reading assemblies' active bills in the top
944
+ * bill's tenant so a shared assembly opens that tenant's bill.
945
+ */
946
+ resolve(skuId) {
947
+ let pending = this.components.get(skuId);
948
+ if (!pending) {
949
+ pending = this.assemblies.resolveComponent(skuId, { tenantId: this.top.tenantId ?? null });
950
+ this.components.set(skuId, pending);
951
+ }
952
+ return pending;
953
+ }
954
+ /**
955
+ * A bill's lines in the top bill's tenant (and global lines), counted
956
+ * against {@link MAX_EXPLOSION_LINES} every time the walk visits them (a
957
+ * shared sub-assembly counts once per use).
958
+ */
959
+ async linesOf(bomId) {
960
+ let pending = this.bills.get(bomId);
961
+ if (!pending) {
962
+ const tenantId = this.top.tenantId ?? null;
963
+ pending = readOwnAndGlobal(tenantId, () => this.assemblies.lines.findByBom(bomId)).then((lines2) => lines2.filter((line) => isOwnOrGlobal(line, tenantId)));
964
+ this.bills.set(bomId, pending);
965
+ }
966
+ const lines = await pending;
967
+ this.lineCount += lines.length;
968
+ if (this.lineCount > 1e4) throw new BomExplosionLimitError(this.top.id, "lines");
969
+ return lines;
970
+ }
971
+ /** The path entry for the top bill. */
972
+ async root() {
973
+ const productId = this.top.productId;
974
+ let name = productId;
975
+ if (UUID_PATTERN$1.test(productId)) {
976
+ const owner = this.top.tenantId ?? null;
977
+ const [product] = await readOwnAndGlobal(owner, () => this.assemblies.products.list({
978
+ where: { id: productId },
979
+ limit: 1
980
+ }));
981
+ if (product?.name && isOwnOrGlobal(product, owner)) name = product.name;
982
+ }
983
+ return {
984
+ bomId: this.top.id,
985
+ productId,
986
+ name
987
+ };
988
+ }
989
+ /**
990
+ * The bill to open below a line at `level`, or `null` when the component
991
+ * is not a sub-assembly with an active bill or `levels` stops here.
992
+ *
993
+ * @throws {BomStructureCycleError} when the sub-assembly is already on
994
+ * `path` (it would contain itself).
995
+ * @throws {BomExplosionLimitError} when `levels: 'all'` would go deeper
996
+ * than {@link MAX_EXPLOSION_DEPTH}.
997
+ */
998
+ open(component, level, path) {
999
+ const bom = component.activeBom;
1000
+ const product = component.product;
1001
+ if (!component.buildable || !bom?.id || !product?.id) return null;
1002
+ const name = product.name || product.id;
1003
+ const at = path.findIndex((entry) => entry.productId === product.id);
1004
+ if (at >= 0) throw new BomStructureCycleError(this.top.id, [...path.slice(at).map((entry) => ({
1005
+ productId: entry.productId,
1006
+ name: entry.name
1007
+ })), {
1008
+ productId: product.id,
1009
+ name
1010
+ }]);
1011
+ if (level >= this.maxLevel) {
1012
+ if (this.levels === "all") throw new BomExplosionLimitError(this.top.id, "depth");
1013
+ return null;
1014
+ }
1015
+ return {
1016
+ bomId: bom.id,
1017
+ productId: product.id,
1018
+ name
1019
+ };
1020
+ }
1021
+ };
1022
+ //#endregion
699
1023
  //#region src/services/BomService.ts
700
1024
  var BomService = class BomService {
701
- constructor(boms, lines, stockService, costResolver, routingSteps, operations, rateResolver = void 0) {
1025
+ constructor(boms, lines, stockService, costResolver, routingSteps, operations, rateResolver, assemblies) {
702
1026
  this.boms = boms;
703
1027
  this.lines = lines;
704
1028
  this.stockService = stockService;
@@ -706,6 +1030,7 @@ var BomService = class BomService {
706
1030
  this.routingSteps = routingSteps;
707
1031
  this.operations = operations;
708
1032
  this.rateResolver = rateResolver;
1033
+ this.assemblies = assemblies;
709
1034
  }
710
1035
  boms;
711
1036
  lines;
@@ -714,17 +1039,19 @@ var BomService = class BomService {
714
1039
  routingSteps;
715
1040
  operations;
716
1041
  rateResolver;
1042
+ assemblies;
717
1043
  /** Factory — prefer {@link createBomService}. */
718
1044
  static async create(options) {
719
1045
  const stockService = options.stockService ?? await buildStockService$1(options);
720
1046
  const sharedDb = await resolveDatabase(options.stockService ? options.db ?? stockService.db : stockService.db);
721
- const [boms, lines, routingSteps, operations] = await Promise.all([
1047
+ const [boms, lines, routingSteps, operations, assemblies] = await Promise.all([
722
1048
  BillOfMaterialsCollection.create({ db: sharedDb }),
723
1049
  BomLineCollection.create({ db: sharedDb }),
724
1050
  RoutingStepCollection.create({ db: sharedDb }),
725
- OperationCollection.create({ db: sharedDb })
1051
+ OperationCollection.create({ db: sharedDb }),
1052
+ AssemblyService.create({ db: sharedDb })
726
1053
  ]);
727
- return new BomService(boms, lines, stockService, options.costResolver, routingSteps, operations, options.rateResolver);
1054
+ return new BomService(boms, lines, stockService, options.costResolver, routingSteps, operations, options.rateResolver, assemblies);
728
1055
  }
729
1056
  /**
730
1057
  * Walk every {@link BomLine} for the given BOM, resolve each line's
@@ -737,10 +1064,19 @@ var BomService = class BomService {
737
1064
  * set `costUnavailable: true` on their breakdown row; the aggregate
738
1065
  * `hasMissingCosts` flag mirrors this so callers can surface a UI
739
1066
  * warning.
1067
+ *
1068
+ * With `{ levels }` above 1, a sub-assembly line whose bill the walk opens
1069
+ * is costed from that bill instead of the resolver: its `unitCost` is the
1070
+ * bill's rolled-up material cost per unit, its `components` hold that
1071
+ * bill's lines, and a missing cost anywhere below marks it and
1072
+ * `hasMissingCosts` (its known part still counts, so the total is a lower
1073
+ * bound). Other lines, including sub-assemblies with no active bill, use
1074
+ * the resolver as before.
740
1075
  */
741
- async computeMaterialCost(bomId) {
1076
+ async computeMaterialCost(bomId, options = {}) {
1077
+ if (isMultiLevel(options.levels)) return this.computeMultiLevelCost(bomId, options);
742
1078
  const bom = await this.requireBom(bomId);
743
- const lines = await this.lines.findByBom(bomId);
1079
+ const lines = await this.linesOf(bom);
744
1080
  const lineBreakdown = [];
745
1081
  let totalCost = 0;
746
1082
  let hasMissingCosts = false;
@@ -778,17 +1114,30 @@ var BomService = class BomService {
778
1114
  * estimate with zero totals, so callers can add it to the material cost
779
1115
  * unconditionally. Retired operations still count: the routing names them.
780
1116
  *
1117
+ * With `{ levels }` above 1, every sub-assembly the walk opens adds its own
1118
+ * bill's routing, multiplied by the units of it one unit needs (waste
1119
+ * included); see {@link LabourEstimate.subAssemblies}.
1120
+ *
781
1121
  * Throws {@link BomNotFoundError} when the BOM does not exist.
782
1122
  */
783
- async computeLabourEstimate(bomId) {
784
- const bom = await this.requireBom(bomId);
785
- const routing = await this.routingSteps.findByBom(bomId);
1123
+ async computeLabourEstimate(bomId, options = {}) {
1124
+ if (isMultiLevel(options.levels)) return this.computeMultiLevelLabour(bomId, options);
1125
+ return this.labourFor(await this.requireBom(bomId));
1126
+ }
1127
+ /** One bill's own routing estimate, per unit of its product. */
1128
+ async labourFor(bom) {
1129
+ const bomId = bom.id;
1130
+ const owner = bom.tenantId ?? null;
1131
+ const routing = (await readOwnAndGlobal(owner, () => this.routingSteps.findByBom(bomId))).filter((step) => isOwnOrGlobal(step, owner));
786
1132
  const steps = [];
787
1133
  let totalMinutes = 0;
788
1134
  let totalCost = 0;
789
1135
  let hasMissingRates = false;
790
1136
  for (const step of routing) {
791
- const operation = await this.operations.get(step.operationId);
1137
+ const [operation] = (await readOwnAndGlobal(owner, () => this.operations.list({
1138
+ where: { id: step.operationId },
1139
+ limit: 1
1140
+ }))).filter((row) => isOwnOrGlobal(row, owner));
792
1141
  const minutes = Number(step.estimatedMinutes ?? 0);
793
1142
  const rate = operation ? await this.resolveRate(operation) : null;
794
1143
  const rateUnavailable = rate === null;
@@ -828,13 +1177,17 @@ var BomService = class BomService {
828
1177
  * {@link ProductionService.consumeMaterials} when you're ready to write
829
1178
  * stock movements.
830
1179
  *
1180
+ * With `{ levels }` above 1, sub-assemblies with an active bill are
1181
+ * replaced by what their bills need, waste compounding down the levels;
1182
+ * this is {@link explode}'s `totals`.
1183
+ *
831
1184
  * Throws {@link BomNotFoundError} when the BOM does not exist; throws a
832
1185
  * plain `Error` when `qty` is not a positive finite number.
833
1186
  */
834
- async explodeRequirements(bomId, qty) {
1187
+ async explodeRequirements(bomId, qty, options = {}) {
1188
+ if (isMultiLevel(options.levels)) return (await this.explode(bomId, qty, options)).totals;
835
1189
  assertPositiveQty$1(qty, "explodeRequirements");
836
- await this.requireBom(bomId);
837
- const lines = await this.lines.findByBom(bomId);
1190
+ const lines = await this.linesOf(await this.requireBom(bomId));
838
1191
  const aggregate = /* @__PURE__ */ new Map();
839
1192
  for (const line of lines) {
840
1193
  const effectiveQty = line.effectiveQtyPerUnit() * qty;
@@ -860,9 +1213,25 @@ var BomService = class BomService {
860
1213
  * "where do we pull from?" is left to the caller of
861
1214
  * {@link ProductionService.consumeMaterials}).
862
1215
  *
1216
+ * With `{ levels }` above 1 the check is {@link planRequirements}: a
1217
+ * sub-assembly that is short and has an active bill is opened for its
1218
+ * shortfall, and each shortage is reported on the line where it occurs,
1219
+ * with its `level` and `path`. Stock is allocated as the walk goes, so a
1220
+ * component used in two places is not counted twice.
1221
+ *
863
1222
  * Throws {@link BomNotFoundError} when the BOM does not exist.
864
1223
  */
865
- async canProduce(bomId, qty) {
1224
+ async canProduce(bomId, qty, options = {}) {
1225
+ if (isMultiLevel(options.levels)) {
1226
+ const plan = await this.planRequirements(bomId, qty, options);
1227
+ return plan.ok ? {
1228
+ ok: true,
1229
+ shortages: []
1230
+ } : {
1231
+ ok: false,
1232
+ shortages: plan.shortages
1233
+ };
1234
+ }
866
1235
  const requirements = await this.explodeRequirements(bomId, qty);
867
1236
  const availabilities = await Promise.all(requirements.map(async (requirement) => ({
868
1237
  requirement,
@@ -884,15 +1253,254 @@ var BomService = class BomService {
884
1253
  };
885
1254
  }
886
1255
  /**
1256
+ * Walk the bill for `qty` units and return every line, depth first, with
1257
+ * its level, the path of bills above it and its quantity for the run, plus
1258
+ * the rolled-up `totals`. Gross requirements: stock is not read.
1259
+ *
1260
+ * `levels` (default `1`) says how far to open sub-assemblies; see
1261
+ * {@link ExplosionLevels}. Waste compounds: a sub-assembly line's quantity
1262
+ * includes its own waste, and its bill's lines are multiplied by it.
1263
+ *
1264
+ * Throws {@link BomNotFoundError}, `BomStructureCycleError` when stored
1265
+ * data already has a product containing itself, `BomExplosionLimitError`
1266
+ * past the depth or line limits, and a plain `Error` for a bad `qty` or
1267
+ * `levels`.
1268
+ */
1269
+ async explode(bomId, qty, options = {}) {
1270
+ assertPositiveQty$1(qty, "explode");
1271
+ const levels = options.levels ?? 1;
1272
+ const top = await this.requireBom(bomId);
1273
+ const walk = new StructureWalk(this.assemblies, top, levels);
1274
+ const lines = [];
1275
+ const visit = async (billId, level, path, parentQty) => {
1276
+ for (const line of await walk.linesOf(billId)) {
1277
+ const component = await walk.resolve(line.componentSkuId);
1278
+ const sub = walk.open(component, level, path);
1279
+ const exploded = explodedLine(line, component, level, path, parentQty);
1280
+ exploded.expanded = sub !== null;
1281
+ lines.push(exploded);
1282
+ if (sub) await visit(sub.bomId, level + 1, [...path, sub], exploded.totalQty);
1283
+ }
1284
+ };
1285
+ await visit(bomId, 1, [await walk.root()], qty);
1286
+ return {
1287
+ bomId,
1288
+ productId: top.productId,
1289
+ qty,
1290
+ levels,
1291
+ lines,
1292
+ totals: sumRequirements(lines.filter((line) => !line.expanded))
1293
+ };
1294
+ }
1295
+ /**
1296
+ * Net the walk against stock: for each line, the available stock (all
1297
+ * locations) not yet allocated to an earlier line and how much is short.
1298
+ * A sub-assembly that is short and has an active bill is opened for the
1299
+ * shortfall only, so its lines say what building that shortfall takes;
1300
+ * one covered by stock is not opened. Lines that are short and cannot be
1301
+ * opened are the `shortages`, each at its own level.
1302
+ *
1303
+ * These are facts, not a decision: the application decides whether a
1304
+ * short sub-assembly is built or bought. Does not mutate stock.
1305
+ *
1306
+ * Throws as {@link explode} does.
1307
+ */
1308
+ async planRequirements(bomId, qty, options = {}) {
1309
+ assertPositiveQty$1(qty, "planRequirements");
1310
+ const levels = options.levels ?? 1;
1311
+ const top = await this.requireBom(bomId);
1312
+ const walk = new StructureWalk(this.assemblies, top, levels);
1313
+ const lines = [];
1314
+ const shortages = [];
1315
+ const unallocated = /* @__PURE__ */ new Map();
1316
+ const tenantId = top.tenantId ?? null;
1317
+ const availableFor = (skuId) => {
1318
+ let pending = unallocated.get(skuId);
1319
+ if (!pending) {
1320
+ pending = this.availableInTenant(skuId, tenantId);
1321
+ unallocated.set(skuId, pending);
1322
+ }
1323
+ return pending;
1324
+ };
1325
+ const visit = async (billId, level, path, parentQty) => {
1326
+ for (const line of await walk.linesOf(billId)) {
1327
+ const component = await walk.resolve(line.componentSkuId);
1328
+ const base = explodedLine(line, component, level, path, parentQty);
1329
+ const available = await availableFor(line.componentSkuId);
1330
+ const allocated = Math.min(available, base.totalQty);
1331
+ unallocated.set(line.componentSkuId, Promise.resolve(available - allocated));
1332
+ const short = Math.max(0, roundQuantity(base.totalQty - allocated));
1333
+ const sub = short > 0 ? walk.open(component, level, path) : null;
1334
+ lines.push({
1335
+ ...base,
1336
+ expanded: sub !== null,
1337
+ available,
1338
+ short
1339
+ });
1340
+ if (sub) await visit(sub.bomId, level + 1, [...path, sub], short);
1341
+ else if (short > 0) shortages.push({
1342
+ componentSkuId: line.componentSkuId,
1343
+ requested: base.totalQty,
1344
+ available,
1345
+ level,
1346
+ path,
1347
+ uom: base.uom,
1348
+ buildable: component.buildable
1349
+ });
1350
+ }
1351
+ };
1352
+ await visit(bomId, 1, [await walk.root()], qty);
1353
+ return {
1354
+ bomId,
1355
+ productId: top.productId,
1356
+ qty,
1357
+ levels,
1358
+ lines,
1359
+ shortages,
1360
+ ok: shortages.length === 0
1361
+ };
1362
+ }
1363
+ /**
887
1364
  * Fetch a BOM by id or throw {@link BomNotFoundError}. Centralises the
888
1365
  * "missing BOM" error path so callers don't have to repeat the check.
889
1366
  */
1367
+ /**
1368
+ * A bill's lines: its own tenant's and global ones, whatever tenant
1369
+ * context is active (a context alone would hide a global bill's lines).
1370
+ */
1371
+ async linesOf(bom) {
1372
+ const owner = bom.tenantId ?? null;
1373
+ return (await readOwnAndGlobal(owner, () => this.lines.findByBom(bom.id))).filter((line) => isOwnOrGlobal(line, owner));
1374
+ }
890
1375
  async requireBom(bomId) {
891
1376
  if (!bomId) throw new BomNotFoundError(bomId);
892
- const bom = await this.boms.get(bomId);
1377
+ const tenantId = getCurrentTenant()?.tenantId;
1378
+ const bom = tenantId ? (await readOwnAndGlobal(tenantId, () => this.boms.list({
1379
+ where: { id: bomId },
1380
+ limit: 1
1381
+ }))).find((row) => isOwnOrGlobal(row, tenantId)) ?? null : await this.boms.get(bomId);
893
1382
  if (!bom) throw new BomNotFoundError(bomId);
894
1383
  return bom;
895
1384
  }
1385
+ /**
1386
+ * Available stock of a SKU across locations, counting only the top bill's
1387
+ * tenant's rows and global ones, whatever tenant context is active (the
1388
+ * walk reads its structure the same way).
1389
+ */
1390
+ async availableInTenant(skuId, tenantId) {
1391
+ const rows = await readOwnAndGlobal(tenantId, () => this.stockService.levels.list({ where: {
1392
+ skuId,
1393
+ state: "available"
1394
+ } }));
1395
+ let total = 0;
1396
+ for (const row of rows) if (isOwnOrGlobal(row, tenantId)) total += Number(row.qty ?? 0);
1397
+ return Math.max(0, total);
1398
+ }
1399
+ /** {@link computeMaterialCost} with `levels` above 1. */
1400
+ async computeMultiLevelCost(bomId, options) {
1401
+ const top = await this.requireBom(bomId);
1402
+ const currency = top.currency || "USD";
1403
+ const walk = new StructureWalk(this.assemblies, top, options.levels ?? 1);
1404
+ const rollUp = async (billId, level, path) => {
1405
+ const lineBreakdown = [];
1406
+ let totalCost = 0;
1407
+ let hasMissingCosts = false;
1408
+ for (const line of await walk.linesOf(billId)) {
1409
+ const component = await walk.resolve(line.componentSkuId);
1410
+ const sub = walk.open(component, level, path);
1411
+ const effectiveQty = line.effectiveQtyPerUnit();
1412
+ const entry = {
1413
+ componentSkuId: line.componentSkuId,
1414
+ qtyPerUnit: Number(line.qtyPerUnit ?? 0),
1415
+ wastePercent: Number(line.wastePercent ?? 0),
1416
+ effectiveQty,
1417
+ unitCost: 0,
1418
+ lineCost: 0,
1419
+ uom: line.uom,
1420
+ costUnavailable: false
1421
+ };
1422
+ const sameCurrency = (component.activeBom?.currency || "USD") === currency;
1423
+ if (sub && sameCurrency) {
1424
+ const inner = await rollUp(sub.bomId, level + 1, [...path, sub]);
1425
+ entry.unitCost = inner.totalCost;
1426
+ entry.lineCost = inner.totalCost * effectiveQty;
1427
+ entry.costUnavailable = inner.hasMissingCosts;
1428
+ entry.subBomId = sub.bomId;
1429
+ entry.components = inner.lineBreakdown;
1430
+ totalCost += entry.lineCost;
1431
+ } else {
1432
+ const resolved = await this.resolveCost(line.componentSkuId);
1433
+ entry.unitCost = resolved ?? 0;
1434
+ entry.lineCost = entry.unitCost * effectiveQty;
1435
+ entry.costUnavailable = resolved === null;
1436
+ if (!entry.costUnavailable) totalCost += entry.lineCost;
1437
+ }
1438
+ if (entry.costUnavailable) hasMissingCosts = true;
1439
+ lineBreakdown.push(entry);
1440
+ }
1441
+ return {
1442
+ totalCost,
1443
+ lineBreakdown,
1444
+ hasMissingCosts
1445
+ };
1446
+ };
1447
+ return {
1448
+ bomId,
1449
+ currency,
1450
+ ...await rollUp(bomId, 1, [await walk.root()])
1451
+ };
1452
+ }
1453
+ /** {@link computeLabourEstimate} with `levels` above 1. */
1454
+ async computeMultiLevelLabour(bomId, options) {
1455
+ const top = await this.requireBom(bomId);
1456
+ const walk = new StructureWalk(this.assemblies, top, options.levels ?? 1);
1457
+ const own = await this.labourFor(top);
1458
+ const estimates = /* @__PURE__ */ new Map();
1459
+ const subAssemblies = [];
1460
+ const visit = async (billId, level, path, units) => {
1461
+ for (const line of await walk.linesOf(billId)) {
1462
+ const component = await walk.resolve(line.componentSkuId);
1463
+ const sub = walk.open(component, level, path);
1464
+ if (!sub || !component.activeBom) continue;
1465
+ const subBill = component.activeBom;
1466
+ let estimate = estimates.get(sub.bomId);
1467
+ if (!estimate) {
1468
+ estimate = this.labourFor(subBill);
1469
+ estimates.set(sub.bomId, estimate);
1470
+ }
1471
+ const unitsPerUnit = units * line.effectiveQtyPerUnit();
1472
+ subAssemblies.push({
1473
+ lineId: line.id,
1474
+ componentSkuId: line.componentSkuId,
1475
+ name: sub.name,
1476
+ level,
1477
+ path,
1478
+ unitsPerUnit,
1479
+ estimate: await estimate
1480
+ });
1481
+ await visit(sub.bomId, level + 1, [...path, sub], unitsPerUnit);
1482
+ }
1483
+ };
1484
+ await visit(bomId, 1, [await walk.root()], 1);
1485
+ let totalMinutes = own.totalMinutes;
1486
+ let totalCost = own.totalCost;
1487
+ let hasMissingRates = own.hasMissingRates;
1488
+ for (const sub of subAssemblies) {
1489
+ totalMinutes += sub.unitsPerUnit * sub.estimate.totalMinutes;
1490
+ if (sub.estimate.currency === own.currency) {
1491
+ totalCost += sub.unitsPerUnit * sub.estimate.totalCost;
1492
+ if (sub.estimate.hasMissingRates) hasMissingRates = true;
1493
+ } else if (sub.estimate.hasRouting) hasMissingRates = true;
1494
+ }
1495
+ return {
1496
+ ...own,
1497
+ hasRouting: own.hasRouting || subAssemblies.some((sub) => sub.estimate.hasRouting),
1498
+ totalMinutes,
1499
+ totalCost,
1500
+ hasMissingRates,
1501
+ subAssemblies
1502
+ };
1503
+ }
896
1504
  /** Run the {@link OperationRateResolver}; `null` when there is no rate. */
897
1505
  async resolveRate(operation) {
898
1506
  if (!this.rateResolver) return null;
@@ -913,6 +1521,40 @@ var BomService = class BomService {
913
1521
  return Number(value);
914
1522
  }
915
1523
  };
1524
+ function explodedLine(line, component, level, path, parentQty) {
1525
+ const effectiveQtyPerUnit = line.effectiveQtyPerUnit();
1526
+ return {
1527
+ lineId: line.id,
1528
+ bomId: line.bomId,
1529
+ level,
1530
+ path,
1531
+ componentSkuId: line.componentSkuId,
1532
+ kind: component.kind,
1533
+ name: component.product?.name ?? "",
1534
+ skuCode: component.sku?.code ?? "",
1535
+ qtyPerUnit: Number(line.qtyPerUnit ?? 0),
1536
+ wastePercent: Number(line.wastePercent ?? 0),
1537
+ effectiveQtyPerUnit,
1538
+ uom: line.uom,
1539
+ totalQty: parentQty * effectiveQtyPerUnit,
1540
+ buildable: component.buildable,
1541
+ subBomId: component.activeBom?.id ?? null,
1542
+ expanded: false
1543
+ };
1544
+ }
1545
+ function sumRequirements(lines) {
1546
+ const totals = /* @__PURE__ */ new Map();
1547
+ for (const line of lines) {
1548
+ const existing = totals.get(line.componentSkuId);
1549
+ if (existing) existing.totalQty += line.totalQty;
1550
+ else totals.set(line.componentSkuId, {
1551
+ componentSkuId: line.componentSkuId,
1552
+ totalQty: line.totalQty,
1553
+ uom: line.uom
1554
+ });
1555
+ }
1556
+ return Array.from(totals.values());
1557
+ }
916
1558
  function assertPositiveQty$1(qty, op) {
917
1559
  if (!Number.isFinite(qty) || qty <= 0) throw new Error(`${op}: qty must be a positive finite number (got ${qty})`);
918
1560
  }
@@ -1031,13 +1673,14 @@ var ProductionService = class ProductionService {
1031
1673
  const orderId = order.id ?? "";
1032
1674
  if (!orderId) throw new Error("consumeMaterials: order.id is required for source attribution");
1033
1675
  const bom = await this.resolveBom(order);
1676
+ const owner = bom.tenantId ?? null;
1034
1677
  return {
1035
1678
  orderId,
1036
- lines: await this.lines.findByBom(bom.id),
1679
+ lines: (await readOwnAndGlobal(owner, () => this.lines.findByBom(bom.id))).filter((line) => isOwnOrGlobal(line, owner)),
1037
1680
  runQty: options.qty,
1038
1681
  locationId: options.locationId,
1039
1682
  mutationOptions: {
1040
- sourceType: "ProductionOrder",
1683
+ sourceType: options.sourceType ?? "ProductionOrder",
1041
1684
  sourceId: orderId,
1042
1685
  reasonCode: options.reasonCode ?? "production_consume",
1043
1686
  note: options.note
@@ -1101,8 +1744,13 @@ var ProductionService = class ProductionService {
1101
1744
  */
1102
1745
  async resolveBom(order) {
1103
1746
  if (order.bomId) {
1104
- const bom = await this.boms.get(order.bomId);
1105
- if (!bom) throw new BomNotFoundError(order.bomId);
1747
+ const bomId = order.bomId;
1748
+ const tenantId = getCurrentTenant()?.tenantId;
1749
+ const bom = tenantId ? (await readOwnAndGlobal(tenantId, () => this.boms.list({
1750
+ where: { id: bomId },
1751
+ limit: 1
1752
+ }))).find((row) => isOwnOrGlobal(row, tenantId)) ?? null : await this.boms.get(bomId);
1753
+ if (!bom) throw new BomNotFoundError(bomId);
1106
1754
  return bom;
1107
1755
  }
1108
1756
  const productId = order.productId ?? "";
@@ -1124,7 +1772,7 @@ function prepareProduce(order, options) {
1124
1772
  qty: options.qty,
1125
1773
  locationId: options.locationId,
1126
1774
  mutationOptions: {
1127
- sourceType: "ProductionOrder",
1775
+ sourceType: options.sourceType ?? "ProductionOrder",
1128
1776
  sourceId: orderId,
1129
1777
  reasonCode: options.reasonCode ?? "production_produce",
1130
1778
  note: options.note
@@ -1329,6 +1977,249 @@ async function createOperationService(options) {
1329
1977
  return OperationService.create(options);
1330
1978
  }
1331
1979
  //#endregion
1980
+ //#region src/services/ProductionRunService.ts
1981
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1982
+ var OPEN_STATUSES = /* @__PURE__ */ new Set(["planned", "in_progress"]);
1983
+ var ProductionRunService = class ProductionRunService {
1984
+ constructor(stockService, runs, completions, boms) {
1985
+ this.stockService = stockService;
1986
+ this.runs = runs;
1987
+ this.completions = completions;
1988
+ this.boms = boms;
1989
+ }
1990
+ stockService;
1991
+ runs;
1992
+ completions;
1993
+ boms;
1994
+ /** Factory — prefer {@link createProductionRunService}. */
1995
+ static async create(options) {
1996
+ let stockService = options.stockService;
1997
+ if (!stockService) {
1998
+ if (!options.db) throw new Error("ProductionRunService.create: either `db` or `stockService` is required");
1999
+ stockService = await createStockService({ db: await resolveDatabase(options.db) });
2000
+ }
2001
+ const db = stockService.db;
2002
+ if (typeof db.transaction !== "function") throw new Error("ProductionRunService requires a database adapter with transaction() support.");
2003
+ const [runs, completions, boms] = await Promise.all([
2004
+ ProductionRunCollection.create({ db }),
2005
+ ProductionRunCompletionCollection.create({ db }),
2006
+ BillOfMaterialsCollection.create({ db })
2007
+ ]);
2008
+ return new ProductionRunService(stockService, runs, completions, boms);
2009
+ }
2010
+ /**
2011
+ * Create a `planned` run of `targetQty` units, pinned to `bomId`, or to the
2012
+ * active bill of `productId`.
2013
+ *
2014
+ * @throws {InvalidProductionRunInputError} for a target that is not a
2015
+ * positive number, or when neither `bomId` nor `productId` is given.
2016
+ * @throws {BomNotFoundError} when `bomId` does not exist.
2017
+ * @throws {NoActiveBomForProductError} when `productId` has no active bill.
2018
+ */
2019
+ async createRun(input) {
2020
+ const targetQty = positive(input.targetQty, "targetQty");
2021
+ const tenantId = getCurrentTenant()?.tenantId ?? null;
2022
+ let bom = null;
2023
+ if (input.bomId) {
2024
+ const bomId = input.bomId;
2025
+ bom = (UUID_PATTERN.test(bomId) ? await readOwnAndGlobal(tenantId, () => this.boms.list({
2026
+ where: { id: bomId },
2027
+ limit: 1
2028
+ })) : []).find((row) => isOwnOrGlobal(row, tenantId)) ?? null;
2029
+ if (!bom?.id) throw new BomNotFoundError(bomId);
2030
+ } else if (input.productId) {
2031
+ const productId = input.productId;
2032
+ const active = (await readOwnAndGlobal(tenantId, () => this.boms.list({
2033
+ where: {
2034
+ productId,
2035
+ status: "active"
2036
+ },
2037
+ orderBy: "version DESC"
2038
+ }))).filter((candidate) => isOwnOrGlobal(candidate, tenantId));
2039
+ bom = active.find((candidate) => (candidate.tenantId ?? null) === tenantId) ?? active[0] ?? null;
2040
+ if (!bom?.id) throw new NoActiveBomForProductError(input.productId);
2041
+ } else throw new InvalidProductionRunInputError("A production run needs a bomId or a productId.");
2042
+ return this.runs.create({
2043
+ tenantId: bom.tenantId ?? tenantId,
2044
+ bomId: bom.id,
2045
+ targetQty,
2046
+ completedQty: 0,
2047
+ status: "planned"
2048
+ });
2049
+ }
2050
+ /** The run, or `null` when no run has this id. */
2051
+ async get(runId) {
2052
+ if (!UUID_PATTERN.test(runId)) return null;
2053
+ return this.runs.get({ id: runId });
2054
+ }
2055
+ /** A run's completions in the order they happened. */
2056
+ async listCompletions(runId) {
2057
+ if (!UUID_PATTERN.test(runId)) return [];
2058
+ return this.completions.findByRun(runId);
2059
+ }
2060
+ /** Mark a `planned` run `in_progress`. */
2061
+ async start(runId) {
2062
+ return this.change(runId, "start", (run) => {
2063
+ if (run.status !== "planned") throw new ProductionRunStateError(runId, run.status, "start");
2064
+ run.status = "in_progress";
2065
+ });
2066
+ }
2067
+ /** Mark an open run `done`, short of its target if work stopped early. */
2068
+ async finish(runId) {
2069
+ return this.change(runId, "finish", (run) => {
2070
+ run.status = "done";
2071
+ });
2072
+ }
2073
+ /** Cancel an open run. Completions already reported stay. */
2074
+ async cancel(runId) {
2075
+ return this.change(runId, "cancel", (run) => {
2076
+ run.status = "cancelled";
2077
+ });
2078
+ }
2079
+ /**
2080
+ * Change an open run's target. It may not go below what is already done;
2081
+ * setting it to exactly that finishes the run.
2082
+ *
2083
+ * @throws {InvalidProductionRunInputError} for a target that is not a
2084
+ * positive number or is below the completed quantity.
2085
+ */
2086
+ async setTarget(runId, targetQty) {
2087
+ const target = positive(targetQty, "targetQty");
2088
+ return this.change(runId, "retarget", (run) => {
2089
+ const completed = toMillionths(Number(run.completedQty));
2090
+ const goal = toMillionths(target);
2091
+ if (goal < completed) throw new InvalidProductionRunInputError(`The target cannot be below the ${fromMillionths(completed)} already done.`);
2092
+ run.targetQty = target;
2093
+ if (completed > 0 && completed >= goal) run.status = "done";
2094
+ });
2095
+ }
2096
+ /**
2097
+ * Report `qty` finished units on an open run: records a dated completion,
2098
+ * adds it to `completedQty`, moves a `planned` run to `in_progress`, and
2099
+ * marks the run `done` when the target is reached. With `consume` and/or
2100
+ * `produce`, also writes the stock movements through
2101
+ * {@link ProductionService}, stamped `sourceType: 'ProductionRunCompletion'`
2102
+ * with the completion id. Everything runs in one transaction.
2103
+ *
2104
+ * Consumption takes the run's bill's own lines for `qty` units: a
2105
+ * sub-assembly on the bill is taken from stock as a component. Building
2106
+ * that sub-assembly is its own run.
2107
+ *
2108
+ * @throws {ProductionRunNotFoundError}
2109
+ * @throws {ProductionRunStateError} when the run is `done` or `cancelled`.
2110
+ * @throws {ProductionRunOverCompletionError} when `qty` is more than the
2111
+ * run has left.
2112
+ * @throws {InvalidProductionRunInputError} for a bad `qty` or date.
2113
+ * @throws `InsufficientStockError` from inventory when consuming would
2114
+ * drive stock below zero; nothing is recorded then.
2115
+ */
2116
+ async recordCompletion(runId, input) {
2117
+ const qty = positive(input.qty, "qty");
2118
+ const completedAt = input.completedAt === void 0 ? /* @__PURE__ */ new Date() : new Date(input.completedAt);
2119
+ if (Number.isNaN(completedAt.getTime())) throw new InvalidProductionRunInputError("completedAt is not a valid date.");
2120
+ return this.inTransaction(async (tx) => {
2121
+ const run = await this.load(tx, runId);
2122
+ if (!OPEN_STATUSES.has(run.status)) throw new ProductionRunStateError(runId, run.status, "complete");
2123
+ const target = toMillionths(Number(run.targetQty));
2124
+ const done = toMillionths(Number(run.completedQty));
2125
+ const reported = toMillionths(qty);
2126
+ const remaining = Math.max(0, target - done);
2127
+ if (reported > remaining) throw new ProductionRunOverCompletionError(runId, qty, fromMillionths(remaining));
2128
+ const total = done + reported;
2129
+ run.completedQty = fromMillionths(total);
2130
+ run.status = total >= target ? "done" : "in_progress";
2131
+ await run.save();
2132
+ const completion = await tx.completions.create({
2133
+ tenantId: run.tenantId ?? null,
2134
+ runId,
2135
+ qty,
2136
+ completedAt
2137
+ });
2138
+ let consumed = [];
2139
+ let produced = null;
2140
+ const inRunTenant = (work) => run.tenantId ? withTenant({ tenantId: run.tenantId }, work) : work();
2141
+ if (input.consume || input.produce) {
2142
+ const production = await ProductionService.create({ stockService: tx.stock });
2143
+ const ref = {
2144
+ id: completion.id,
2145
+ bomId: run.bomId
2146
+ };
2147
+ const sourceType = "ProductionRunCompletion";
2148
+ const consume = input.consume && {
2149
+ ...input.consume,
2150
+ qty,
2151
+ sourceType
2152
+ };
2153
+ const produce = input.produce && {
2154
+ ...input.produce,
2155
+ qty,
2156
+ sourceType
2157
+ };
2158
+ if (consume && produce) ({consumed, produced} = await inRunTenant(() => production.runProduction(ref, {
2159
+ consume,
2160
+ produce
2161
+ })));
2162
+ else if (consume) consumed = await inRunTenant(() => production.consumeMaterials(ref, consume));
2163
+ else if (produce) produced = await inRunTenant(() => production.produceFinishedGoods(ref, produce));
2164
+ }
2165
+ return {
2166
+ run,
2167
+ completion,
2168
+ consumed,
2169
+ produced
2170
+ };
2171
+ });
2172
+ }
2173
+ /** Apply `apply` to an open run and save it, in one transaction. */
2174
+ change(runId, action, apply) {
2175
+ return this.inTransaction(async (tx) => {
2176
+ const run = await this.load(tx, runId);
2177
+ if (!OPEN_STATUSES.has(run.status)) throw new ProductionRunStateError(runId, run.status, action);
2178
+ apply(run);
2179
+ await run.save();
2180
+ return run;
2181
+ });
2182
+ }
2183
+ async load(tx, runId) {
2184
+ const run = UUID_PATTERN.test(runId) ? await tx.runs.get({ id: runId }) : null;
2185
+ if (!run) throw new ProductionRunNotFoundError(runId);
2186
+ return run;
2187
+ }
2188
+ /**
2189
+ * Run `work` in one transaction on collections bound to it, rerunning the
2190
+ * whole transaction when it lost a revision race on the run row.
2191
+ */
2192
+ inTransaction(work) {
2193
+ return withRevisionRetry$1(() => this.stockService.withTransaction(async (stock) => {
2194
+ const db = stock.db;
2195
+ const [runs, completions] = await Promise.all([ProductionRunCollection.create({ db }), ProductionRunCompletionCollection.create({ db })]);
2196
+ return work({
2197
+ stock,
2198
+ runs,
2199
+ completions
2200
+ });
2201
+ }));
2202
+ }
2203
+ };
2204
+ var MAX_REVISION_ATTEMPTS = 10;
2205
+ async function withRevisionRetry$1(run) {
2206
+ for (let attempt = 1;; attempt++) try {
2207
+ return await run();
2208
+ } catch (error) {
2209
+ if (error?.code !== "RUNTIME_REVISION_CONFLICT" || attempt >= MAX_REVISION_ATTEMPTS) throw error;
2210
+ }
2211
+ }
2212
+ function positive(value, field) {
2213
+ const raw = Number(value);
2214
+ const number = Number.isFinite(raw) ? roundQuantity(raw) : raw;
2215
+ if (!Number.isFinite(number) || number <= 0) throw new InvalidProductionRunInputError(`${field} must be a number greater than zero (got ${String(value)}).`);
2216
+ if (number > 999999999) throw new InvalidProductionRunInputError(`${field} must be at most ${MAX_QUANTITY} (got ${String(value)}).`);
2217
+ return number;
2218
+ }
2219
+ function createProductionRunService(options) {
2220
+ return ProductionRunService.create(options);
2221
+ }
2222
+ //#endregion
1332
2223
  //#region src/services/RoutingService.ts
1333
2224
  var RoutingService = class RoutingService {
1334
2225
  constructor(db, boms, operations, steps) {
@@ -1420,6 +2311,6 @@ async function createRoutingService(options) {
1420
2311
  return RoutingService.create(options);
1421
2312
  }
1422
2313
  //#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 };
2314
+ export { Assembly, AssemblyCollection, AssemblyService, BillOfMaterials, BillOfMaterialsCollection, BomCycleError, BomExplosionLimitError, BomLine, BomLineCollection, BomNotFoundError, BomService, BomStructureCycleError, DuplicateOperationCodeError, InvalidOperationInputError, InvalidProductionRunInputError, MAX_EXPLOSION_DEPTH, MAX_EXPLOSION_LINES, MAX_QUANTITY, NoActiveBomForProductError, Operation, OperationCollection, OperationNotFoundError, OperationRetiredError, OperationService, PRODUCTION_RUN_STATUSES, ProductionRun, ProductionRunCollection, ProductionRunCompletion, ProductionRunCompletionCollection, ProductionRunNotFoundError, ProductionRunOverCompletionError, ProductionRunService, ProductionRunStateError, ProductionService, QUANTITY_DECIMALS, RoutingService, RoutingStep, RoutingStepCollection, createAssemblyService, createBomService, createOperationService, createProductionRunService, createProductionService, createRoutingService, installManufacturingDispatchHandlers, roundQuantity };
1424
2315
 
1425
2316
  //# sourceMappingURL=index.js.map