@happyvertical/smrt-inventory 0.57.16 → 0.57.18

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 (45) hide show
  1. package/AGENTS.md +19 -1
  2. package/README.md +32 -2
  3. package/dist/collections/LotCollection.d.ts +22 -0
  4. package/dist/collections/LotCollection.d.ts.map +1 -0
  5. package/dist/collections/LotCorrectionCollection.d.ts +12 -0
  6. package/dist/collections/LotCorrectionCollection.d.ts.map +1 -0
  7. package/dist/collections/LotDocumentCollection.d.ts +12 -0
  8. package/dist/collections/LotDocumentCollection.d.ts.map +1 -0
  9. package/dist/collections/LotStockLevelCollection.d.ts +21 -0
  10. package/dist/collections/LotStockLevelCollection.d.ts.map +1 -0
  11. package/dist/collections/LotUsageCollection.d.ts +18 -0
  12. package/dist/collections/LotUsageCollection.d.ts.map +1 -0
  13. package/dist/collections/index.d.ts +5 -0
  14. package/dist/collections/index.d.ts.map +1 -1
  15. package/dist/index.d.ts +4 -4
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +1168 -31
  18. package/dist/index.js.map +1 -1
  19. package/dist/manifest.json +2921 -288
  20. package/dist/models/Lot.d.ts +96 -0
  21. package/dist/models/Lot.d.ts.map +1 -0
  22. package/dist/models/LotCorrection.d.ts +55 -0
  23. package/dist/models/LotCorrection.d.ts.map +1 -0
  24. package/dist/models/LotDocument.d.ts +45 -0
  25. package/dist/models/LotDocument.d.ts.map +1 -0
  26. package/dist/models/LotStockLevel.d.ts +43 -0
  27. package/dist/models/LotStockLevel.d.ts.map +1 -0
  28. package/dist/models/LotUsage.d.ts +64 -0
  29. package/dist/models/LotUsage.d.ts.map +1 -0
  30. package/dist/models/StockMovement.d.ts +7 -0
  31. package/dist/models/StockMovement.d.ts.map +1 -1
  32. package/dist/models/index.d.ts +5 -0
  33. package/dist/models/index.d.ts.map +1 -1
  34. package/dist/services/LotService.d.ts +258 -0
  35. package/dist/services/LotService.d.ts.map +1 -0
  36. package/dist/services/StockService.d.ts +25 -2
  37. package/dist/services/StockService.d.ts.map +1 -1
  38. package/dist/services/index.d.ts +2 -0
  39. package/dist/services/index.d.ts.map +1 -1
  40. package/dist/services/lot-errors.d.ts +44 -0
  41. package/dist/services/lot-errors.d.ts.map +1 -0
  42. package/dist/smrt-knowledge.json +1333 -76
  43. package/dist/types.d.ts +6 -0
  44. package/dist/types.d.ts.map +1 -1
  45. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -1,25 +1,26 @@
1
- import { ObjectRegistry, SmrtCollection, SmrtObject, SmrtRecipe, crossPackageRef, field, resolveDatabase, smrt } from "@happyvertical/smrt-core";
2
- import { TenantScoped, tenantId } from "@happyvertical/smrt-tenancy";
1
+ import { ObjectRegistry, SmrtCollection, SmrtObject, SmrtRecipe, crossPackageRef, field, foreignKey, isUniqueViolationError, resolveDatabase, smrt } from "@happyvertical/smrt-core";
2
+ import { TenantScoped, getCurrentTenant, tenantId } from "@happyvertical/smrt-tenancy";
3
3
  import { createLogger } from "@happyvertical/logger";
4
+ import { NestedTransactionError } from "@happyvertical/sql/query";
4
5
  //#region src/__smrt-register__.ts
5
- ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-inventory","packageVersion":"0.57.16","objects":{"@happyvertical/smrt-inventory:InventoryLocationCollection":{"name":"inventorylocationcollection","className":"InventoryLocationCollection","qualifiedName":"@happyvertical/smrt-inventory:InventoryLocationCollection","collection":"inventorylocations","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/collections/InventoryLocationCollection.ts","packageName":"@happyvertical/smrt-inventory","fields":{},"methods":{"findByCode":{"name":"findByCode","async":true,"parameters":[{"name":"code","type":"string","optional":false}],"returnType":"Promise<InventoryLocation | null>","isStatic":false,"isPublic":true},"findByKind":{"name":"findByKind","async":true,"parameters":[{"name":"kind","type":"InventoryLocationKind","optional":false}],"returnType":"Promise<InventoryLocation[]>","isStatic":false,"isPublic":true},"findByPlace":{"name":"findByPlace","async":true,"parameters":[{"name":"placeId","type":"string","optional":false}],"returnType":"Promise<InventoryLocation[]>","isStatic":false,"isPublic":true},"findActive":{"name":"findActive","async":true,"parameters":[{"name":"kind","type":"InventoryLocationKind","optional":true}],"returnType":"Promise<InventoryLocation[]>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"inventory_locations","api":{"include":["list","get","create","update"]},"mcp":{"include":["list","get"]}},"extends":"SmrtCollection","extendsTypeArg":"InventoryLocation","exportName":"InventoryLocationCollection","collectionExportName":"InventoryLocationCollectionCollection","schema":{"tableName":"inventory_locations","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_locations\\" (\\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 DEFAULT '',\\n \\"kind\\" TEXT DEFAULT 'warehouse',\\n \\"place_id\\" TEXT DEFAULT '',\\n \\"active\\" BOOLEAN DEFAULT TRUE\\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":false,"unique":false,"default":""},"kind":{"type":"TEXT","notNull":false,"unique":false,"default":"warehouse"},"place_id":{"type":"TEXT","notNull":false,"unique":false,"default":""},"active":{"type":"BOOLEAN","notNull":false,"unique":false,"default":true}},"indexes":[{"name":"inventory_locations_code_tenant_id_idx","columns":["code","tenant_id"],"unique":true,"nullsNotDistinct":true},{"name":"inventory_locations_slug_context_idx","columns":["slug","context"]},{"name":"inventory_locations_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"401c28a1"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:StockLevelCollection":{"name":"stocklevelcollection","className":"StockLevelCollection","qualifiedName":"@happyvertical/smrt-inventory:StockLevelCollection","collection":"stocklevels","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/collections/StockLevelCollection.ts","packageName":"@happyvertical/smrt-inventory","fields":{},"methods":{"getLevel":{"name":"getLevel","async":true,"parameters":[{"name":"skuId","type":"string","optional":false},{"name":"locationId","type":"string","optional":false},{"name":"state","type":"StockState","optional":true,"default":"available"}],"returnType":"Promise<StockLevel | null>","isStatic":false,"isPublic":true},"findBySku":{"name":"findBySku","async":true,"parameters":[{"name":"skuId","type":"string","optional":false}],"returnType":"Promise<StockLevel[]>","isStatic":false,"isPublic":true},"findByLocation":{"name":"findByLocation","async":true,"parameters":[{"name":"locationId","type":"string","optional":false}],"returnType":"Promise<StockLevel[]>","isStatic":false,"isPublic":true},"findBelowReorderPoint":{"name":"findBelowReorderPoint","async":true,"parameters":[{"name":"locationId","type":"string","optional":true}],"returnType":"Promise<StockLevel[]>","isStatic":false,"isPublic":true},"totalForSku":{"name":"totalForSku","async":true,"parameters":[{"name":"skuId","type":"string","optional":false},{"name":"state","type":"StockState","optional":true}],"returnType":"Promise<number>","isStatic":false,"isPublic":true},"totalForLocation":{"name":"totalForLocation","async":true,"parameters":[{"name":"locationId","type":"string","optional":false},{"name":"state","type":"StockState","optional":true}],"returnType":"Promise<number>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"inventory_stock_levels","api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]}},"extends":"SmrtCollection","extendsTypeArg":"StockLevel","exportName":"StockLevelCollection","collectionExportName":"StockLevelCollectionCollection","schema":{"tableName":"inventory_stock_levels","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_stock_levels\\" (\\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 \\"sku_id\\" TEXT NOT NULL DEFAULT '',\\n \\"location_id\\" TEXT NOT NULL DEFAULT '',\\n \\"state\\" TEXT NOT NULL DEFAULT 'available',\\n \\"qty\\" REAL,\\n \\"reorder_point\\" REAL,\\n \\"reorder_quantity\\" REAL\\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},"sku_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"location_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"state":{"type":"TEXT","notNull":true,"unique":false,"default":"available"},"qty":{"type":"REAL","notNull":false,"unique":false},"reorder_point":{"type":"REAL","notNull":false,"unique":false},"reorder_quantity":{"type":"REAL","notNull":false,"unique":false}},"indexes":[{"name":"inventory_stock_levels_sku_id_location_id_idx","columns":["sku_id","location_id","state","tenant_id"],"unique":true,"nullsNotDistinct":true},{"name":"inventory_stock_levels_slug_context_idx","columns":["slug","context"]},{"name":"inventory_stock_levels_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"02387935"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:StockMovementCollection":{"name":"stockmovementcollection","className":"StockMovementCollection","qualifiedName":"@happyvertical/smrt-inventory:StockMovementCollection","collection":"stockmovements","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/collections/StockMovementCollection.ts","packageName":"@happyvertical/smrt-inventory","fields":{},"methods":{"findBySku":{"name":"findBySku","async":true,"parameters":[{"name":"skuId","type":"string","optional":false}],"returnType":"Promise<StockMovement[]>","isStatic":false,"isPublic":true},"findByLocation":{"name":"findByLocation","async":true,"parameters":[{"name":"locationId","type":"string","optional":false}],"returnType":"Promise<StockMovement[]>","isStatic":false,"isPublic":true},"findBySource":{"name":"findBySource","async":true,"parameters":[{"name":"sourceType","type":"string","optional":false},{"name":"sourceId","type":"string","optional":false}],"returnType":"Promise<StockMovement[]>","isStatic":false,"isPublic":true},"findByReason":{"name":"findByReason","async":true,"parameters":[{"name":"reasonCode","type":"StockMovementReason","optional":false}],"returnType":"Promise<StockMovement[]>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"inventory_stock_movements","api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]}},"extends":"SmrtCollection","extendsTypeArg":"StockMovement","exportName":"StockMovementCollection","collectionExportName":"StockMovementCollectionCollection","schema":{"tableName":"inventory_stock_movements","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_stock_movements\\" (\\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 \\"sku_id\\" TEXT NOT NULL DEFAULT '',\\n \\"location_id\\" TEXT NOT NULL DEFAULT '',\\n \\"from_state\\" TEXT,\\n \\"to_state\\" TEXT,\\n \\"qty\\" REAL,\\n \\"reason_code\\" TEXT NOT NULL DEFAULT 'adjustment',\\n \\"source_type\\" TEXT DEFAULT '',\\n \\"source_id\\" TEXT DEFAULT '',\\n \\"actor_profile_id\\" UUID,\\n \\"note\\" TEXT DEFAULT '',\\n \\"occurred_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},"sku_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"location_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"from_state":{"type":"TEXT","notNull":false,"unique":false},"to_state":{"type":"TEXT","notNull":false,"unique":false},"qty":{"type":"REAL","notNull":false,"unique":false},"reason_code":{"type":"TEXT","notNull":true,"unique":false,"default":"adjustment"},"source_type":{"type":"TEXT","notNull":false,"unique":false,"default":""},"source_id":{"type":"TEXT","notNull":false,"unique":false,"default":""},"actor_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"note":{"type":"TEXT","notNull":false,"unique":false,"default":""},"occurred_at":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"inventory_stock_movements_slug_context_idx","columns":["slug","context"]},{"name":"inventory_stock_movements_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"inventory_stock_movements_actor_profile_id_idx","columns":["actor_profile_id"]}],"version":"eff1027e"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:InventoryLocation":{"name":"inventorylocation","className":"InventoryLocation","qualifiedName":"@happyvertical/smrt-inventory:InventoryLocation","collection":"inventorylocations","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/models/InventoryLocation.ts","packageName":"@happyvertical/smrt-inventory","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":"","description":"A short code for the place stock is kept, for example MAIN.","_meta":{"required":true,"description":"A short code for the place stock is kept, for example MAIN."}},"name":{"type":"text","required":false,"default":"","description":"What you call the place, for example Main warehouse.","_meta":{"description":"What you call the place, for example Main warehouse."}},"kind":{"type":"text","required":false,"default":"warehouse","description":"What sort of place it is, for example a warehouse or a shop floor.","_meta":{"description":"What sort of place it is, for example a warehouse or a shop floor."}},"placeId":{"type":"text","required":false,"default":""},"active":{"type":"boolean","required":false,"default":true}},"methods":{},"decoratorConfig":{"tableName":"inventory_locations","conflictColumns":["code","tenant_id"],"api":{"include":["list","get","create","update"]},"mcp":{"include":["list","get"]},"cli":true,"tenantScoped":{"mode":"optional"}},"extends":"SmrtObject","exportName":"InventoryLocation","collectionExportName":"InventoryLocationCollection","displayLabelField":"name","validationRules":[{"field":"code","rule":"required","fieldType":"text"}],"schema":{"tableName":"inventory_locations","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_locations\\" (\\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 DEFAULT '',\\n \\"kind\\" TEXT DEFAULT 'warehouse',\\n \\"place_id\\" TEXT DEFAULT '',\\n \\"active\\" BOOLEAN DEFAULT TRUE\\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":false,"unique":false,"default":""},"kind":{"type":"TEXT","notNull":false,"unique":false,"default":"warehouse"},"place_id":{"type":"TEXT","notNull":false,"unique":false,"default":""},"active":{"type":"BOOLEAN","notNull":false,"unique":false,"default":true}},"indexes":[{"name":"inventory_locations_code_tenant_id_idx","columns":["code","tenant_id"],"unique":true,"nullsNotDistinct":true},{"name":"inventory_locations_slug_context_idx","columns":["slug","context"]},{"name":"inventory_locations_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"401c28a1"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:StockLevel":{"name":"stocklevel","className":"StockLevel","qualifiedName":"@happyvertical/smrt-inventory:StockLevel","collection":"stocklevels","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/models/StockLevel.ts","packageName":"@happyvertical/smrt-inventory","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}}},"skuId":{"type":"text","required":true,"default":"","description":"The item being counted.","_meta":{"required":true,"description":"The item being counted."}},"locationId":{"type":"text","required":true,"default":"","description":"Where the stock is kept.","_meta":{"required":true,"description":"Where the stock is kept."}},"state":{"type":"text","required":true,"default":"available","description":"The state of this stock: available, allocated, wip, qc_hold or damaged.","_meta":{"required":true,"description":"The state of this stock: available, allocated, wip, qc_hold or damaged."},"enum":["available","allocated","wip","qc_hold","damaged"]},"qty":{"type":"decimal","required":false,"description":"How many are on hand.","_meta":{"description":"How many are on hand."}},"reorderPoint":{"type":"decimal","required":false,"_meta":{"nullable":true}},"reorderQuantity":{"type":"decimal","required":false,"_meta":{"nullable":true}}},"methods":{},"decoratorConfig":{"tableName":"inventory_stock_levels","conflictColumns":["sku_id","location_id","state","tenant_id"],"api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]},"tenantScoped":{"mode":"optional"}},"extends":"SmrtObject","exportName":"StockLevel","collectionExportName":"StockLevelCollection","validationRules":[{"field":"skuId","rule":"required","fieldType":"text"},{"field":"locationId","rule":"required","fieldType":"text"},{"field":"state","rule":"required","fieldType":"text"}],"schema":{"tableName":"inventory_stock_levels","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_stock_levels\\" (\\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 \\"sku_id\\" TEXT NOT NULL DEFAULT '',\\n \\"location_id\\" TEXT NOT NULL DEFAULT '',\\n \\"state\\" TEXT NOT NULL DEFAULT 'available',\\n \\"qty\\" REAL,\\n \\"reorder_point\\" REAL,\\n \\"reorder_quantity\\" REAL\\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},"sku_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"location_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"state":{"type":"TEXT","notNull":true,"unique":false,"default":"available"},"qty":{"type":"REAL","notNull":false,"unique":false},"reorder_point":{"type":"REAL","notNull":false,"unique":false},"reorder_quantity":{"type":"REAL","notNull":false,"unique":false}},"indexes":[{"name":"inventory_stock_levels_sku_id_location_id_idx","columns":["sku_id","location_id","state","tenant_id"],"unique":true,"nullsNotDistinct":true},{"name":"inventory_stock_levels_slug_context_idx","columns":["slug","context"]},{"name":"inventory_stock_levels_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"02387935"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:StockMovement":{"name":"stockmovement","className":"StockMovement","qualifiedName":"@happyvertical/smrt-inventory:StockMovement","collection":"stockmovements","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/models/StockMovement.ts","packageName":"@happyvertical/smrt-inventory","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}}},"skuId":{"type":"text","required":true,"default":"","description":"The item that moved.","_meta":{"required":true,"description":"The item that moved."}},"locationId":{"type":"text","required":true,"default":"","description":"The place it moved at.","_meta":{"required":true,"description":"The place it moved at."}},"fromState":{"type":"text","required":false,"description":"What it was before, for example available or damaged.","_meta":{"description":"What it was before, for example available or damaged."},"enum":["available","allocated","wip","qc_hold","damaged"]},"toState":{"type":"text","required":false,"description":"What it became.","_meta":{"description":"What it became."},"enum":["available","allocated","wip","qc_hold","damaged"]},"qty":{"type":"decimal","required":false,"description":"How many moved.","_meta":{"description":"How many moved."}},"reasonCode":{"type":"text","required":true,"default":"adjustment","description":"Why it moved, for example adjustment, sale or return.","_meta":{"required":true,"description":"Why it moved, for example adjustment, sale or return."}},"sourceType":{"type":"text","required":false,"default":""},"sourceId":{"type":"text","required":false,"default":""},"actorProfileId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-profiles:Profile"},"note":{"type":"text","required":false,"default":"","description":"Anything worth remembering about this movement.","_meta":{"description":"Anything worth remembering about this movement."}},"occurredAt":{"type":"datetime","required":false,"description":"When it happened.","_meta":{"description":"When it happened."}}},"methods":{},"decoratorConfig":{"tableName":"inventory_stock_movements","conflictColumns":["id"],"api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]},"tenantScoped":{"mode":"optional"}},"extends":"SmrtObject","exportName":"StockMovement","collectionExportName":"StockMovementCollection","validationRules":[{"field":"skuId","rule":"required","fieldType":"text"},{"field":"locationId","rule":"required","fieldType":"text"},{"field":"reasonCode","rule":"required","fieldType":"text"}],"schema":{"tableName":"inventory_stock_movements","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_stock_movements\\" (\\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 \\"sku_id\\" TEXT NOT NULL DEFAULT '',\\n \\"location_id\\" TEXT NOT NULL DEFAULT '',\\n \\"from_state\\" TEXT,\\n \\"to_state\\" TEXT,\\n \\"qty\\" REAL,\\n \\"reason_code\\" TEXT NOT NULL DEFAULT 'adjustment',\\n \\"source_type\\" TEXT DEFAULT '',\\n \\"source_id\\" TEXT DEFAULT '',\\n \\"actor_profile_id\\" UUID,\\n \\"note\\" TEXT DEFAULT '',\\n \\"occurred_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},"sku_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"location_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"from_state":{"type":"TEXT","notNull":false,"unique":false},"to_state":{"type":"TEXT","notNull":false,"unique":false},"qty":{"type":"REAL","notNull":false,"unique":false},"reason_code":{"type":"TEXT","notNull":true,"unique":false,"default":"adjustment"},"source_type":{"type":"TEXT","notNull":false,"unique":false,"default":""},"source_id":{"type":"TEXT","notNull":false,"unique":false,"default":""},"actor_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"note":{"type":"TEXT","notNull":false,"unique":false,"default":""},"occurred_at":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"inventory_stock_movements_slug_context_idx","columns":["slug","context"]},{"name":"inventory_stock_movements_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"inventory_stock_movements_actor_profile_id_idx","columns":["actor_profile_id"]}],"version":"eff1027e"},"importPath":"@happyvertical/smrt-inventory"}},"moduleType":"smrt","recipes":[{"id":"inventory.stock","className":"StockRecipe","label":"Inventory","summary":"Keep count of how many of each product you have.","synonyms":["stock","quantity","on hand"],"models":["@happyvertical/smrt-inventory:StockLevel","@happyvertical/smrt-inventory:InventoryLocation","@happyvertical/smrt-inventory:StockMovement"],"nav":[{"label":"Stock levels","model":"@happyvertical/smrt-inventory:StockLevel","icon":"archive","description":"How much of each item you have on hand right now.","noun":"stock entry"},{"label":"Locations","model":"@happyvertical/smrt-inventory:InventoryLocation","icon":"home","description":"The places you keep stock, like shelves, rooms or vans."},{"label":"Stock movements","model":"@happyvertical/smrt-inventory:StockMovement","icon":"truck","description":"Every time stock came in, went out or moved."}],"requires":[],"requiresAny":[["products.simple","products.clothing"]],"section":{"id":"catalog","label":"Catalog","icon":"package","description":"Products, ingredients and the stock you hold."},"options":{"@happyvertical/smrt-inventory:StockLevel":{"fields":{"reorderPoint":{"visibility":"hidden"},"reorderQuantity":{"visibility":"hidden"}}},"@happyvertical/smrt-inventory:InventoryLocation":{"fields":{"placeId":{"visibility":"hidden"},"active":{"visibility":"hidden"}}},"@happyvertical/smrt-inventory:StockMovement":{"fields":{"sourceType":{"visibility":"hidden"},"sourceId":{"visibility":"hidden"},"actorProfileId":{"visibility":"hidden"}}}},"help":{"markdown":"## Overview\\n\\nInventory keeps count of how many of each product you have. Each count is a\\nstock level: an item, the place it is kept and how many are on hand. Stock can\\nsit in more than one place, and every change is recorded as a stock movement so\\nthere is a trail.\\n\\n## Tasks\\n\\n### Count what you have\\n\\n1. Create a stock level for the item and the place it is kept.\\n2. Set **{field:qty}** to how many you have on hand.\\n3. Set **{field:state}** if the stock is not available to sell, for example\\n allocated or on quality hold.\\n\\n### Correct a count\\n\\n1. Find the stock level for the item.\\n2. Set **{field:qty}** to what you really have. Record why as a stock movement.\\n\\n### Keep stock in more than one place\\n\\n1. Add a location with a **{field:code}** and a **{field:name}**, and say what\\n sort it is in **{field:kind}**.\\n2. Create stock levels against that location.\\n","fieldRefs":["code","kind","name","qty","state"]},"demo":{"mode":"server","reasons":["Needs a server because its package depends on @happyvertical/smrt-tenancy, which do not build for a browser (#3624)."]}}],"smrtDependencies":["@happyvertical/smrt-core","@happyvertical/smrt-tenancy"],"browser":{"status":"server-only","issues":["#3624"],"via":["@happyvertical/smrt-tenancy"]},"stampsConstructors":true}`));
6
+ ObjectRegistry.registerPackageManifest(JSON.parse(`{"version":"1.0.0","timestamp":0,"packageName":"@happyvertical/smrt-inventory","packageVersion":"0.57.18","objects":{"@happyvertical/smrt-inventory:InventoryLocationCollection":{"name":"inventorylocationcollection","className":"InventoryLocationCollection","qualifiedName":"@happyvertical/smrt-inventory:InventoryLocationCollection","collection":"inventorylocations","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/collections/InventoryLocationCollection.ts","packageName":"@happyvertical/smrt-inventory","fields":{},"methods":{"findByCode":{"name":"findByCode","async":true,"parameters":[{"name":"code","type":"string","optional":false}],"returnType":"Promise<InventoryLocation | null>","isStatic":false,"isPublic":true},"findByKind":{"name":"findByKind","async":true,"parameters":[{"name":"kind","type":"InventoryLocationKind","optional":false}],"returnType":"Promise<InventoryLocation[]>","isStatic":false,"isPublic":true},"findByPlace":{"name":"findByPlace","async":true,"parameters":[{"name":"placeId","type":"string","optional":false}],"returnType":"Promise<InventoryLocation[]>","isStatic":false,"isPublic":true},"findActive":{"name":"findActive","async":true,"parameters":[{"name":"kind","type":"InventoryLocationKind","optional":true}],"returnType":"Promise<InventoryLocation[]>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"inventory_locations","api":{"include":["list","get","create","update"]},"mcp":{"include":["list","get"]}},"extends":"SmrtCollection","extendsTypeArg":"InventoryLocation","exportName":"InventoryLocationCollection","collectionExportName":"InventoryLocationCollectionCollection","schema":{"tableName":"inventory_locations","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_locations\\" (\\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 DEFAULT '',\\n \\"kind\\" TEXT DEFAULT 'warehouse',\\n \\"place_id\\" TEXT DEFAULT '',\\n \\"active\\" BOOLEAN DEFAULT TRUE\\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":false,"unique":false,"default":""},"kind":{"type":"TEXT","notNull":false,"unique":false,"default":"warehouse"},"place_id":{"type":"TEXT","notNull":false,"unique":false,"default":""},"active":{"type":"BOOLEAN","notNull":false,"unique":false,"default":true}},"indexes":[{"name":"inventory_locations_code_tenant_id_idx","columns":["code","tenant_id"],"unique":true,"nullsNotDistinct":true},{"name":"inventory_locations_slug_context_idx","columns":["slug","context"]},{"name":"inventory_locations_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"401c28a1"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:LotCollection":{"name":"lotcollection","className":"LotCollection","qualifiedName":"@happyvertical/smrt-inventory:LotCollection","collection":"lots","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/collections/LotCollection.ts","packageName":"@happyvertical/smrt-inventory","fields":{},"methods":{"findByLotNumber":{"name":"findByLotNumber","async":true,"parameters":[{"name":"lotNumber","type":"string","optional":false},{"name":"options","type":"object","optional":true,"memberTypes":["string | null"]}],"returnType":"Promise<Lot[]>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"inventory_lots","api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]}},"extends":"SmrtCollection","extendsTypeArg":"Lot","exportName":"LotCollection","collectionExportName":"LotCollectionCollection","schema":{"tableName":"inventory_lots","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_lots\\" (\\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 \\"sku_id\\" TEXT,\\n \\"kind\\" TEXT DEFAULT 'lot',\\n \\"lot_number\\" TEXT NOT NULL DEFAULT '',\\n \\"search_key\\" TEXT DEFAULT '',\\n \\"identity_key\\" TEXT NOT NULL DEFAULT '',\\n \\"unique_number\\" BOOLEAN DEFAULT FALSE,\\n \\"received_qty\\" REAL,\\n \\"unit\\" TEXT DEFAULT '',\\n \\"source_type\\" TEXT DEFAULT '',\\n \\"source_id\\" TEXT DEFAULT '',\\n \\"attributes\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'active',\\n \\"received_at\\" TIMESTAMP,\\n \\"received_by_profile_id\\" UUID,\\n \\"last_used_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},"sku_id":{"type":"TEXT","notNull":false,"unique":false},"kind":{"type":"TEXT","notNull":false,"unique":false,"default":"lot"},"lot_number":{"type":"TEXT","notNull":true,"unique":false,"default":""},"search_key":{"type":"TEXT","notNull":false,"unique":false,"default":""},"identity_key":{"type":"TEXT","notNull":true,"unique":false,"default":""},"unique_number":{"type":"BOOLEAN","notNull":false,"unique":false,"default":false},"received_qty":{"type":"REAL","notNull":false,"unique":false},"unit":{"type":"TEXT","notNull":false,"unique":false,"default":""},"source_type":{"type":"TEXT","notNull":false,"unique":false,"default":""},"source_id":{"type":"TEXT","notNull":false,"unique":false,"default":""},"attributes":{"type":"TEXT","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"active"},"received_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"received_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"last_used_at":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"inventory_lots_identity_key_tenant_id_idx","columns":["identity_key","tenant_id"],"unique":true,"nullsNotDistinct":true},{"name":"inventory_lots_slug_context_idx","columns":["slug","context"]},{"name":"inventory_lots_search_key_idx","columns":["tenant_id","search_key"]},{"name":"inventory_lots_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"inventory_lots_received_by_profile_id_idx","columns":["received_by_profile_id"]}],"version":"eb959677"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:LotCorrectionCollection":{"name":"lotcorrectioncollection","className":"LotCorrectionCollection","qualifiedName":"@happyvertical/smrt-inventory:LotCorrectionCollection","collection":"lotcorrections","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/collections/LotCorrectionCollection.ts","packageName":"@happyvertical/smrt-inventory","fields":{},"methods":{},"decoratorConfig":{"tableName":"inventory_lot_corrections","api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]}},"extends":"SmrtCollection","extendsTypeArg":"LotCorrection","exportName":"LotCorrectionCollection","collectionExportName":"LotCorrectionCollectionCollection","schema":{"tableName":"inventory_lot_corrections","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_lot_corrections\\" (\\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 \\"lot_id\\" UUID NOT NULL,\\n \\"kind\\" TEXT NOT NULL DEFAULT 'attribute',\\n \\"field\\" TEXT DEFAULT '',\\n \\"old_value\\" TEXT,\\n \\"new_value\\" TEXT,\\n \\"reason\\" TEXT NOT NULL,\\n \\"usage_id\\" TEXT,\\n \\"document_id\\" TEXT,\\n \\"actor_profile_id\\" UUID,\\n \\"corrected_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},"lot_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"inventory_lots","column":"id","onDelete":"RESTRICT","onUpdate":"RESTRICT","engines":["postgres","sqlite"]}},"kind":{"type":"TEXT","notNull":true,"unique":false,"default":"attribute"},"field":{"type":"TEXT","notNull":false,"unique":false,"default":""},"old_value":{"type":"TEXT","notNull":false,"unique":false},"new_value":{"type":"TEXT","notNull":false,"unique":false},"reason":{"type":"TEXT","notNull":true,"unique":false},"usage_id":{"type":"TEXT","notNull":false,"unique":false},"document_id":{"type":"TEXT","notNull":false,"unique":false},"actor_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"corrected_at":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"inventory_lot_corrections_slug_context_idx","columns":["slug","context"]},{"name":"inventory_lot_corrections_lot_idx","columns":["lot_id"]},{"name":"inventory_lot_corrections_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"inventory_lot_corrections_actor_profile_id_idx","columns":["actor_profile_id"]}],"version":"d903252e"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:LotDocumentCollection":{"name":"lotdocumentcollection","className":"LotDocumentCollection","qualifiedName":"@happyvertical/smrt-inventory:LotDocumentCollection","collection":"lotdocuments","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/collections/LotDocumentCollection.ts","packageName":"@happyvertical/smrt-inventory","fields":{},"methods":{},"decoratorConfig":{"tableName":"inventory_lot_documents","api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]}},"extends":"SmrtCollection","extendsTypeArg":"LotDocument","exportName":"LotDocumentCollection","collectionExportName":"LotDocumentCollectionCollection","schema":{"tableName":"inventory_lot_documents","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_lot_documents\\" (\\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 \\"lot_id\\" UUID NOT NULL,\\n \\"asset_id\\" UUID,\\n \\"role\\" TEXT DEFAULT 'certificate',\\n \\"attached_at\\" TIMESTAMP,\\n \\"attached_by_profile_id\\" UUID,\\n \\"detached_at\\" TIMESTAMP,\\n \\"detached_by_profile_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},"lot_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"inventory_lots","column":"id","onDelete":"RESTRICT","onUpdate":"RESTRICT","engines":["postgres","sqlite"]}},"asset_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"role":{"type":"TEXT","notNull":false,"unique":false,"default":"certificate"},"attached_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"attached_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"detached_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"detached_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false}},"indexes":[{"name":"inventory_lot_documents_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"inventory_lot_documents_lot_idx","columns":["lot_id"]},{"name":"inventory_lot_documents_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"inventory_lot_documents_asset_id_idx","columns":["asset_id"]},{"name":"inventory_lot_documents_attached_by_profile_id_idx","columns":["attached_by_profile_id"]},{"name":"inventory_lot_documents_detached_by_profile_id_idx","columns":["detached_by_profile_id"]}],"version":"076940be"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:LotStockLevelCollection":{"name":"lotstocklevelcollection","className":"LotStockLevelCollection","qualifiedName":"@happyvertical/smrt-inventory:LotStockLevelCollection","collection":"lotstocklevels","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/collections/LotStockLevelCollection.ts","packageName":"@happyvertical/smrt-inventory","fields":{},"methods":{"getLevel":{"name":"getLevel","async":true,"parameters":[{"name":"lotId","type":"string","optional":false},{"name":"locationId","type":"string","optional":false},{"name":"state","type":"StockState","optional":true,"default":"available"}],"returnType":"Promise<LotStockLevel | null>","isStatic":false,"isPublic":true},"onHand":{"name":"onHand","async":true,"parameters":[{"name":"lotId","type":"string","optional":false},{"name":"state","type":"StockState","optional":true}],"returnType":"Promise<number>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"inventory_lot_stock_levels","api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]}},"extends":"SmrtCollection","extendsTypeArg":"LotStockLevel","exportName":"LotStockLevelCollection","collectionExportName":"LotStockLevelCollectionCollection","schema":{"tableName":"inventory_lot_stock_levels","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_lot_stock_levels\\" (\\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 \\"lot_id\\" UUID NOT NULL,\\n \\"sku_id\\" TEXT DEFAULT '',\\n \\"location_id\\" TEXT NOT NULL DEFAULT '',\\n \\"state\\" TEXT NOT NULL DEFAULT 'available',\\n \\"qty\\" REAL\\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},"lot_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"inventory_lots","column":"id","onDelete":"RESTRICT","onUpdate":"RESTRICT","engines":["postgres","sqlite"]}},"sku_id":{"type":"TEXT","notNull":false,"unique":false,"default":""},"location_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"state":{"type":"TEXT","notNull":true,"unique":false,"default":"available"},"qty":{"type":"REAL","notNull":false,"unique":false}},"indexes":[{"name":"inventory_lot_stock_levels_lot_id_location_id_idx","columns":["lot_id","location_id","state","tenant_id"],"unique":true,"nullsNotDistinct":true},{"name":"inventory_lot_stock_levels_slug_context_idx","columns":["slug","context"]},{"name":"inventory_lot_stock_levels_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"6f6becfa"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:LotUsageCollection":{"name":"lotusagecollection","className":"LotUsageCollection","qualifiedName":"@happyvertical/smrt-inventory:LotUsageCollection","collection":"lotusages","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/collections/LotUsageCollection.ts","packageName":"@happyvertical/smrt-inventory","fields":{},"methods":{"findActiveForLot":{"name":"findActiveForLot","async":true,"parameters":[{"name":"lotId","type":"string","optional":false}],"returnType":"Promise<LotUsage[]>","isStatic":false,"isPublic":true},"findForConsumer":{"name":"findForConsumer","async":true,"parameters":[{"name":"consumerType","type":"string","optional":false},{"name":"consumerId","type":"string","optional":false},{"name":"options","type":"object","optional":true,"memberTypes":["boolean"]}],"returnType":"Promise<LotUsage[]>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"inventory_lot_usages","api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]}},"extends":"SmrtCollection","extendsTypeArg":"LotUsage","exportName":"LotUsageCollection","collectionExportName":"LotUsageCollectionCollection","schema":{"tableName":"inventory_lot_usages","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_lot_usages\\" (\\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 \\"lot_id\\" UUID NOT NULL,\\n \\"consumer_type\\" TEXT NOT NULL DEFAULT '',\\n \\"consumer_id\\" TEXT NOT NULL DEFAULT '',\\n \\"qty\\" REAL,\\n \\"unit\\" TEXT DEFAULT '',\\n \\"status\\" TEXT DEFAULT 'active',\\n \\"linked_at\\" TIMESTAMP,\\n \\"linked_by_profile_id\\" UUID,\\n \\"unlinked_at\\" TIMESTAMP,\\n \\"unlinked_by_profile_id\\" UUID,\\n \\"unlink_reason\\" TEXT,\\n \\"movement_id\\" TEXT\\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},"lot_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"inventory_lots","column":"id","onDelete":"RESTRICT","onUpdate":"RESTRICT","engines":["postgres","sqlite"]}},"consumer_type":{"type":"TEXT","notNull":true,"unique":false,"default":""},"consumer_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"qty":{"type":"REAL","notNull":false,"unique":false},"unit":{"type":"TEXT","notNull":false,"unique":false,"default":""},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"active"},"linked_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"linked_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"unlinked_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"unlinked_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"unlink_reason":{"type":"TEXT","notNull":false,"unique":false},"movement_id":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"inventory_lot_usages_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"inventory_lot_usages_consumer_idx","columns":["consumer_type","consumer_id"]},{"name":"inventory_lot_usages_lot_status_idx","columns":["lot_id","status"]},{"name":"inventory_lot_usages_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"inventory_lot_usages_linked_by_profile_id_idx","columns":["linked_by_profile_id"]},{"name":"inventory_lot_usages_unlinked_by_profile_id_idx","columns":["unlinked_by_profile_id"]}],"version":"c106a419"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:StockLevelCollection":{"name":"stocklevelcollection","className":"StockLevelCollection","qualifiedName":"@happyvertical/smrt-inventory:StockLevelCollection","collection":"stocklevels","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/collections/StockLevelCollection.ts","packageName":"@happyvertical/smrt-inventory","fields":{},"methods":{"getLevel":{"name":"getLevel","async":true,"parameters":[{"name":"skuId","type":"string","optional":false},{"name":"locationId","type":"string","optional":false},{"name":"state","type":"StockState","optional":true,"default":"available"}],"returnType":"Promise<StockLevel | null>","isStatic":false,"isPublic":true},"findBySku":{"name":"findBySku","async":true,"parameters":[{"name":"skuId","type":"string","optional":false}],"returnType":"Promise<StockLevel[]>","isStatic":false,"isPublic":true},"findByLocation":{"name":"findByLocation","async":true,"parameters":[{"name":"locationId","type":"string","optional":false}],"returnType":"Promise<StockLevel[]>","isStatic":false,"isPublic":true},"findBelowReorderPoint":{"name":"findBelowReorderPoint","async":true,"parameters":[{"name":"locationId","type":"string","optional":true}],"returnType":"Promise<StockLevel[]>","isStatic":false,"isPublic":true},"totalForSku":{"name":"totalForSku","async":true,"parameters":[{"name":"skuId","type":"string","optional":false},{"name":"state","type":"StockState","optional":true}],"returnType":"Promise<number>","isStatic":false,"isPublic":true},"totalForLocation":{"name":"totalForLocation","async":true,"parameters":[{"name":"locationId","type":"string","optional":false},{"name":"state","type":"StockState","optional":true}],"returnType":"Promise<number>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"inventory_stock_levels","api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]}},"extends":"SmrtCollection","extendsTypeArg":"StockLevel","exportName":"StockLevelCollection","collectionExportName":"StockLevelCollectionCollection","schema":{"tableName":"inventory_stock_levels","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_stock_levels\\" (\\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 \\"sku_id\\" TEXT NOT NULL DEFAULT '',\\n \\"location_id\\" TEXT NOT NULL DEFAULT '',\\n \\"state\\" TEXT NOT NULL DEFAULT 'available',\\n \\"qty\\" REAL,\\n \\"reorder_point\\" REAL,\\n \\"reorder_quantity\\" REAL\\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},"sku_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"location_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"state":{"type":"TEXT","notNull":true,"unique":false,"default":"available"},"qty":{"type":"REAL","notNull":false,"unique":false},"reorder_point":{"type":"REAL","notNull":false,"unique":false},"reorder_quantity":{"type":"REAL","notNull":false,"unique":false}},"indexes":[{"name":"inventory_stock_levels_sku_id_location_id_idx","columns":["sku_id","location_id","state","tenant_id"],"unique":true,"nullsNotDistinct":true},{"name":"inventory_stock_levels_slug_context_idx","columns":["slug","context"]},{"name":"inventory_stock_levels_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"02387935"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:StockMovementCollection":{"name":"stockmovementcollection","className":"StockMovementCollection","qualifiedName":"@happyvertical/smrt-inventory:StockMovementCollection","collection":"stockmovements","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/collections/StockMovementCollection.ts","packageName":"@happyvertical/smrt-inventory","fields":{},"methods":{"findBySku":{"name":"findBySku","async":true,"parameters":[{"name":"skuId","type":"string","optional":false}],"returnType":"Promise<StockMovement[]>","isStatic":false,"isPublic":true},"findByLocation":{"name":"findByLocation","async":true,"parameters":[{"name":"locationId","type":"string","optional":false}],"returnType":"Promise<StockMovement[]>","isStatic":false,"isPublic":true},"findBySource":{"name":"findBySource","async":true,"parameters":[{"name":"sourceType","type":"string","optional":false},{"name":"sourceId","type":"string","optional":false}],"returnType":"Promise<StockMovement[]>","isStatic":false,"isPublic":true},"findByReason":{"name":"findByReason","async":true,"parameters":[{"name":"reasonCode","type":"StockMovementReason","optional":false}],"returnType":"Promise<StockMovement[]>","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"inventory_stock_movements","api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]}},"extends":"SmrtCollection","extendsTypeArg":"StockMovement","exportName":"StockMovementCollection","collectionExportName":"StockMovementCollectionCollection","schema":{"tableName":"inventory_stock_movements","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_stock_movements\\" (\\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 \\"sku_id\\" TEXT NOT NULL DEFAULT '',\\n \\"location_id\\" TEXT NOT NULL DEFAULT '',\\n \\"lot_id\\" TEXT,\\n \\"from_state\\" TEXT,\\n \\"to_state\\" TEXT,\\n \\"qty\\" REAL,\\n \\"reason_code\\" TEXT NOT NULL DEFAULT 'adjustment',\\n \\"source_type\\" TEXT DEFAULT '',\\n \\"source_id\\" TEXT DEFAULT '',\\n \\"actor_profile_id\\" UUID,\\n \\"note\\" TEXT DEFAULT '',\\n \\"occurred_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},"sku_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"location_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"lot_id":{"type":"TEXT","notNull":false,"unique":false},"from_state":{"type":"TEXT","notNull":false,"unique":false},"to_state":{"type":"TEXT","notNull":false,"unique":false},"qty":{"type":"REAL","notNull":false,"unique":false},"reason_code":{"type":"TEXT","notNull":true,"unique":false,"default":"adjustment"},"source_type":{"type":"TEXT","notNull":false,"unique":false,"default":""},"source_id":{"type":"TEXT","notNull":false,"unique":false,"default":""},"actor_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"note":{"type":"TEXT","notNull":false,"unique":false,"default":""},"occurred_at":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"inventory_stock_movements_slug_context_idx","columns":["slug","context"]},{"name":"inventory_stock_movements_lot_idx","columns":["lot_id"]},{"name":"inventory_stock_movements_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"inventory_stock_movements_actor_profile_id_idx","columns":["actor_profile_id"]}],"version":"b426c2c5"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:InventoryLocation":{"name":"inventorylocation","className":"InventoryLocation","qualifiedName":"@happyvertical/smrt-inventory:InventoryLocation","collection":"inventorylocations","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/models/InventoryLocation.ts","packageName":"@happyvertical/smrt-inventory","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":"","description":"A short code for the place stock is kept, for example MAIN.","_meta":{"required":true,"description":"A short code for the place stock is kept, for example MAIN."}},"name":{"type":"text","required":false,"default":"","description":"What you call the place, for example Main warehouse.","_meta":{"description":"What you call the place, for example Main warehouse."}},"kind":{"type":"text","required":false,"default":"warehouse","description":"What sort of place it is, for example a warehouse or a shop floor.","_meta":{"description":"What sort of place it is, for example a warehouse or a shop floor."}},"placeId":{"type":"text","required":false,"default":""},"active":{"type":"boolean","required":false,"default":true}},"methods":{},"decoratorConfig":{"tableName":"inventory_locations","conflictColumns":["code","tenant_id"],"api":{"include":["list","get","create","update"]},"mcp":{"include":["list","get"]},"cli":true,"tenantScoped":{"mode":"optional"}},"extends":"SmrtObject","exportName":"InventoryLocation","collectionExportName":"InventoryLocationCollection","displayLabelField":"name","validationRules":[{"field":"code","rule":"required","fieldType":"text"}],"schema":{"tableName":"inventory_locations","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_locations\\" (\\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 DEFAULT '',\\n \\"kind\\" TEXT DEFAULT 'warehouse',\\n \\"place_id\\" TEXT DEFAULT '',\\n \\"active\\" BOOLEAN DEFAULT TRUE\\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":false,"unique":false,"default":""},"kind":{"type":"TEXT","notNull":false,"unique":false,"default":"warehouse"},"place_id":{"type":"TEXT","notNull":false,"unique":false,"default":""},"active":{"type":"BOOLEAN","notNull":false,"unique":false,"default":true}},"indexes":[{"name":"inventory_locations_code_tenant_id_idx","columns":["code","tenant_id"],"unique":true,"nullsNotDistinct":true},{"name":"inventory_locations_slug_context_idx","columns":["slug","context"]},{"name":"inventory_locations_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"401c28a1"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:Lot":{"name":"lot","className":"Lot","qualifiedName":"@happyvertical/smrt-inventory:Lot","collection":"lots","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/models/Lot.ts","packageName":"@happyvertical/smrt-inventory","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}}},"skuId":{"type":"text","required":false,"description":"The item this lot is of, if catalogued.","_meta":{"nullable":true,"description":"The item this lot is of, if catalogued."}},"kind":{"type":"text","required":false,"default":"lot","description":"Whether this is a batch (lot) or a single unit (serial).","_meta":{"description":"Whether this is a batch (lot) or a single unit (serial)."},"enum":["lot","serial"]},"lotNumber":{"type":"text","required":true,"default":"","description":"The batch code, supplier lot or serial number.","maxLength":128,"_meta":{"required":true,"maxLength":128,"description":"The batch code, supplier lot or serial number."}},"searchKey":{"type":"text","required":false,"default":"","description":"The number in normal form, used for lookup.","maxLength":160,"_meta":{"maxLength":160,"description":"The number in normal form, used for lookup."}},"identityKey":{"type":"text","required":true,"default":"","description":"Internal identity that keeps lots from being created twice.","maxLength":160,"_meta":{"required":true,"maxLength":160,"description":"Internal identity that keeps lots from being created twice."}},"uniqueNumber":{"type":"boolean","required":false,"default":false,"description":"Whether the number must be unique for this item.","_meta":{"description":"Whether the number must be unique for this item."}},"receivedQty":{"type":"decimal","required":false,"description":"How many came in on the receipt.","_meta":{"description":"How many came in on the receipt."}},"unit":{"type":"text","required":false,"default":"","description":"The unit of measure.","_meta":{"description":"The unit of measure."}},"sourceType":{"type":"text","required":false,"default":"","description":"What kind of record received this lot.","_meta":{"description":"What kind of record received this lot."}},"sourceId":{"type":"text","required":false,"default":"","description":"The record that received this lot.","_meta":{"description":"The record that received this lot."}},"attributes":{"type":"text","required":false,"description":"Anything else worth knowing about this lot, stored as JSON.","_meta":{"description":"Anything else worth knowing about this lot, stored as JSON."}},"status":{"type":"text","required":false,"default":"active","description":"Where the lot stands: active, quarantined, consumed or void.","_meta":{"description":"Where the lot stands: active, quarantined, consumed or void."}},"receivedAt":{"type":"datetime","required":false,"description":"When the lot was received.","_meta":{"description":"When the lot was received."}},"receivedByProfileId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-profiles:Profile"},"lastUsedAt":{"type":"datetime","required":false,"_meta":{"nullable":true}}},"methods":{"getAttributes":{"name":"getAttributes","async":false,"parameters":[],"returnType":"Record<string, unknown>","isStatic":false,"isPublic":true},"setAttributes":{"name":"setAttributes","async":false,"parameters":[{"name":"value","type":"Record<string, unknown> | string","optional":false,"unionBranches":[{"type":"Record<string, unknown>"},{"type":"string"}]}],"returnType":"void","isStatic":false,"isPublic":true}},"decoratorConfig":{"tableName":"inventory_lots","conflictColumns":["identity_key","tenant_id"],"api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]},"indexes":[{"name":"inventory_lots_search_key_idx","columns":["tenantId","searchKey"]}],"tenantScoped":{"mode":"optional"}},"extends":"SmrtObject","exportName":"Lot","collectionExportName":"LotCollection","validationRules":[{"field":"lotNumber","rule":"required","fieldType":"text"},{"field":"lotNumber","rule":"maxLength","value":128,"fieldType":"text"},{"field":"searchKey","rule":"maxLength","value":160,"fieldType":"text"},{"field":"identityKey","rule":"required","fieldType":"text"},{"field":"identityKey","rule":"maxLength","value":160,"fieldType":"text"}],"schema":{"tableName":"inventory_lots","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_lots\\" (\\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 \\"sku_id\\" TEXT,\\n \\"kind\\" TEXT DEFAULT 'lot',\\n \\"lot_number\\" TEXT NOT NULL DEFAULT '',\\n \\"search_key\\" TEXT DEFAULT '',\\n \\"identity_key\\" TEXT NOT NULL DEFAULT '',\\n \\"unique_number\\" BOOLEAN DEFAULT FALSE,\\n \\"received_qty\\" REAL,\\n \\"unit\\" TEXT DEFAULT '',\\n \\"source_type\\" TEXT DEFAULT '',\\n \\"source_id\\" TEXT DEFAULT '',\\n \\"attributes\\" TEXT,\\n \\"status\\" TEXT DEFAULT 'active',\\n \\"received_at\\" TIMESTAMP,\\n \\"received_by_profile_id\\" UUID,\\n \\"last_used_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},"sku_id":{"type":"TEXT","notNull":false,"unique":false},"kind":{"type":"TEXT","notNull":false,"unique":false,"default":"lot"},"lot_number":{"type":"TEXT","notNull":true,"unique":false,"default":""},"search_key":{"type":"TEXT","notNull":false,"unique":false,"default":""},"identity_key":{"type":"TEXT","notNull":true,"unique":false,"default":""},"unique_number":{"type":"BOOLEAN","notNull":false,"unique":false,"default":false},"received_qty":{"type":"REAL","notNull":false,"unique":false},"unit":{"type":"TEXT","notNull":false,"unique":false,"default":""},"source_type":{"type":"TEXT","notNull":false,"unique":false,"default":""},"source_id":{"type":"TEXT","notNull":false,"unique":false,"default":""},"attributes":{"type":"TEXT","notNull":false,"unique":false},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"active"},"received_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"received_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"last_used_at":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"inventory_lots_identity_key_tenant_id_idx","columns":["identity_key","tenant_id"],"unique":true,"nullsNotDistinct":true},{"name":"inventory_lots_slug_context_idx","columns":["slug","context"]},{"name":"inventory_lots_search_key_idx","columns":["tenant_id","search_key"]},{"name":"inventory_lots_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"inventory_lots_received_by_profile_id_idx","columns":["received_by_profile_id"]}],"version":"eb959677"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:LotCorrection":{"name":"lotcorrection","className":"LotCorrection","qualifiedName":"@happyvertical/smrt-inventory:LotCorrection","collection":"lotcorrections","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/models/LotCorrection.ts","packageName":"@happyvertical/smrt-inventory","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}}},"lotId":{"type":"foreignKey","required":true,"related":"Lot","_meta":{"required":true,"constraint":{"engines":["postgres","sqlite"]},"onDelete":"RESTRICT","onUpdate":"RESTRICT"}},"kind":{"type":"text","required":true,"default":"attribute","description":"What was corrected.","_meta":{"required":true,"description":"What was corrected."},"enum":["lot_number","attribute","status","unlink","document_detach"]},"field":{"type":"text","required":false,"default":"","description":"Which attribute changed, if one did.","_meta":{"description":"Which attribute changed, if one did."}},"oldValue":{"type":"text","required":false,"description":"What it was.","_meta":{"description":"What it was."}},"newValue":{"type":"text","required":false,"description":"What it is now.","_meta":{"description":"What it is now."}},"reason":{"type":"text","required":true,"description":"Why it was corrected.","maxLength":1000,"_meta":{"required":true,"maxLength":1000,"description":"Why it was corrected."}},"usageId":{"type":"text","required":false,"_meta":{"nullable":true}},"documentId":{"type":"text","required":false,"_meta":{"nullable":true}},"actorProfileId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-profiles:Profile"},"correctedAt":{"type":"datetime","required":false,"description":"When it was corrected.","_meta":{"description":"When it was corrected."}}},"methods":{},"decoratorConfig":{"tableName":"inventory_lot_corrections","conflictColumns":["id"],"api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]},"indexes":[{"name":"inventory_lot_corrections_lot_idx","columns":["lotId"]}],"tenantScoped":{"mode":"optional"}},"extends":"SmrtObject","exportName":"LotCorrection","collectionExportName":"LotCorrectionCollection","validationRules":[{"field":"lotId","rule":"required","fieldType":"foreignKey"},{"field":"kind","rule":"required","fieldType":"text"},{"field":"reason","rule":"required","fieldType":"text"},{"field":"reason","rule":"maxLength","value":1000,"fieldType":"text"}],"schema":{"tableName":"inventory_lot_corrections","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_lot_corrections\\" (\\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 \\"lot_id\\" UUID NOT NULL,\\n \\"kind\\" TEXT NOT NULL DEFAULT 'attribute',\\n \\"field\\" TEXT DEFAULT '',\\n \\"old_value\\" TEXT,\\n \\"new_value\\" TEXT,\\n \\"reason\\" TEXT NOT NULL,\\n \\"usage_id\\" TEXT,\\n \\"document_id\\" TEXT,\\n \\"actor_profile_id\\" UUID,\\n \\"corrected_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},"lot_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"inventory_lots","column":"id","onDelete":"RESTRICT","onUpdate":"RESTRICT","engines":["postgres","sqlite"]}},"kind":{"type":"TEXT","notNull":true,"unique":false,"default":"attribute"},"field":{"type":"TEXT","notNull":false,"unique":false,"default":""},"old_value":{"type":"TEXT","notNull":false,"unique":false},"new_value":{"type":"TEXT","notNull":false,"unique":false},"reason":{"type":"TEXT","notNull":true,"unique":false},"usage_id":{"type":"TEXT","notNull":false,"unique":false},"document_id":{"type":"TEXT","notNull":false,"unique":false},"actor_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"corrected_at":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"inventory_lot_corrections_slug_context_idx","columns":["slug","context"]},{"name":"inventory_lot_corrections_lot_idx","columns":["lot_id"]},{"name":"inventory_lot_corrections_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"inventory_lot_corrections_actor_profile_id_idx","columns":["actor_profile_id"]}],"version":"d903252e"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:LotDocument":{"name":"lotdocument","className":"LotDocument","qualifiedName":"@happyvertical/smrt-inventory:LotDocument","collection":"lotdocuments","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/models/LotDocument.ts","packageName":"@happyvertical/smrt-inventory","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}}},"lotId":{"type":"foreignKey","required":true,"related":"Lot","_meta":{"required":true,"constraint":{"engines":["postgres","sqlite"]},"onDelete":"RESTRICT","onUpdate":"RESTRICT"}},"assetId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-assets:Asset"},"role":{"type":"text","required":false,"default":"certificate","description":"What the document is for, for example certificate.","_meta":{"description":"What the document is for, for example certificate."}},"attachedAt":{"type":"datetime","required":false,"description":"When it was attached.","_meta":{"description":"When it was attached."}},"attachedByProfileId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-profiles:Profile"},"detachedAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"detachedByProfileId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-profiles:Profile"}},"methods":{},"decoratorConfig":{"tableName":"inventory_lot_documents","api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]},"indexes":[{"name":"inventory_lot_documents_lot_idx","columns":["lotId"]}],"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"LotDocument","collectionExportName":"LotDocumentCollection","validationRules":[{"field":"lotId","rule":"required","fieldType":"foreignKey"}],"schema":{"tableName":"inventory_lot_documents","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_lot_documents\\" (\\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 \\"lot_id\\" UUID NOT NULL,\\n \\"asset_id\\" UUID,\\n \\"role\\" TEXT DEFAULT 'certificate',\\n \\"attached_at\\" TIMESTAMP,\\n \\"attached_by_profile_id\\" UUID,\\n \\"detached_at\\" TIMESTAMP,\\n \\"detached_by_profile_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},"lot_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"inventory_lots","column":"id","onDelete":"RESTRICT","onUpdate":"RESTRICT","engines":["postgres","sqlite"]}},"asset_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"role":{"type":"TEXT","notNull":false,"unique":false,"default":"certificate"},"attached_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"attached_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"detached_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"detached_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false}},"indexes":[{"name":"inventory_lot_documents_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"inventory_lot_documents_lot_idx","columns":["lot_id"]},{"name":"inventory_lot_documents_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"inventory_lot_documents_asset_id_idx","columns":["asset_id"]},{"name":"inventory_lot_documents_attached_by_profile_id_idx","columns":["attached_by_profile_id"]},{"name":"inventory_lot_documents_detached_by_profile_id_idx","columns":["detached_by_profile_id"]}],"version":"076940be"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:LotStockLevel":{"name":"lotstocklevel","className":"LotStockLevel","qualifiedName":"@happyvertical/smrt-inventory:LotStockLevel","collection":"lotstocklevels","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/models/LotStockLevel.ts","packageName":"@happyvertical/smrt-inventory","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}}},"lotId":{"type":"foreignKey","required":true,"related":"Lot","_meta":{"required":true,"constraint":{"engines":["postgres","sqlite"]},"onDelete":"RESTRICT","onUpdate":"RESTRICT"}},"skuId":{"type":"text","required":false,"default":"","description":"The item the lot is of.","_meta":{"description":"The item the lot is of."}},"locationId":{"type":"text","required":true,"default":"","description":"Where the lot is kept.","_meta":{"required":true,"description":"Where the lot is kept."}},"state":{"type":"text","required":true,"default":"available","description":"The state of this stock: available, allocated, wip, qc_hold or damaged.","_meta":{"required":true,"description":"The state of this stock: available, allocated, wip, qc_hold or damaged."},"enum":["available","allocated","wip","qc_hold","damaged"]},"qty":{"type":"decimal","required":false,"description":"How many are on hand.","_meta":{"description":"How many are on hand."}}},"methods":{},"decoratorConfig":{"tableName":"inventory_lot_stock_levels","conflictColumns":["lot_id","location_id","state","tenant_id"],"api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]},"tenantScoped":{"mode":"optional"}},"extends":"SmrtObject","exportName":"LotStockLevel","collectionExportName":"LotStockLevelCollection","validationRules":[{"field":"lotId","rule":"required","fieldType":"foreignKey"},{"field":"locationId","rule":"required","fieldType":"text"},{"field":"state","rule":"required","fieldType":"text"}],"schema":{"tableName":"inventory_lot_stock_levels","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_lot_stock_levels\\" (\\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 \\"lot_id\\" UUID NOT NULL,\\n \\"sku_id\\" TEXT DEFAULT '',\\n \\"location_id\\" TEXT NOT NULL DEFAULT '',\\n \\"state\\" TEXT NOT NULL DEFAULT 'available',\\n \\"qty\\" REAL\\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},"lot_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"inventory_lots","column":"id","onDelete":"RESTRICT","onUpdate":"RESTRICT","engines":["postgres","sqlite"]}},"sku_id":{"type":"TEXT","notNull":false,"unique":false,"default":""},"location_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"state":{"type":"TEXT","notNull":true,"unique":false,"default":"available"},"qty":{"type":"REAL","notNull":false,"unique":false}},"indexes":[{"name":"inventory_lot_stock_levels_lot_id_location_id_idx","columns":["lot_id","location_id","state","tenant_id"],"unique":true,"nullsNotDistinct":true},{"name":"inventory_lot_stock_levels_slug_context_idx","columns":["slug","context"]},{"name":"inventory_lot_stock_levels_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"6f6becfa"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:LotUsage":{"name":"lotusage","className":"LotUsage","qualifiedName":"@happyvertical/smrt-inventory:LotUsage","collection":"lotusages","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/models/LotUsage.ts","packageName":"@happyvertical/smrt-inventory","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}}},"lotId":{"type":"foreignKey","required":true,"related":"Lot","_meta":{"required":true,"constraint":{"engines":["postgres","sqlite"]},"onDelete":"RESTRICT","onUpdate":"RESTRICT"}},"consumerType":{"type":"text","required":true,"default":"","description":"What kind of record used the lot.","_meta":{"required":true,"description":"What kind of record used the lot."}},"consumerId":{"type":"text","required":true,"default":"","description":"The record that used the lot.","_meta":{"required":true,"description":"The record that used the lot."}},"qty":{"type":"decimal","required":false,"description":"How much was used.","_meta":{"nullable":true,"description":"How much was used."}},"unit":{"type":"text","required":false,"default":"","description":"The unit of measure.","_meta":{"description":"The unit of measure."}},"status":{"type":"text","required":false,"default":"active","description":"Whether the link still stands: active or unlinked.","_meta":{"description":"Whether the link still stands: active or unlinked."},"enum":["active","unlinked"]},"linkedAt":{"type":"datetime","required":false,"description":"When the lot was linked.","_meta":{"description":"When the lot was linked."}},"linkedByProfileId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-profiles:Profile"},"unlinkedAt":{"type":"datetime","required":false,"_meta":{"nullable":true}},"unlinkedByProfileId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-profiles:Profile"},"unlinkReason":{"type":"text","required":false,"description":"Why the link was removed.","_meta":{"description":"Why the link was removed."}},"movementId":{"type":"text","required":false,"description":"The stock movement for this use, if any.","_meta":{"nullable":true,"description":"The stock movement for this use, if any."}}},"methods":{},"decoratorConfig":{"tableName":"inventory_lot_usages","api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]},"indexes":[{"name":"inventory_lot_usages_consumer_idx","columns":["consumerType","consumerId"]},{"name":"inventory_lot_usages_lot_status_idx","columns":["lotId","status"]}],"tenantScoped":{"mode":"optional"},"conflictColumns":["tenant_id","slug","context"]},"extends":"SmrtObject","exportName":"LotUsage","collectionExportName":"LotUsageCollection","validationRules":[{"field":"lotId","rule":"required","fieldType":"foreignKey"},{"field":"consumerType","rule":"required","fieldType":"text"},{"field":"consumerId","rule":"required","fieldType":"text"}],"schema":{"tableName":"inventory_lot_usages","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_lot_usages\\" (\\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 \\"lot_id\\" UUID NOT NULL,\\n \\"consumer_type\\" TEXT NOT NULL DEFAULT '',\\n \\"consumer_id\\" TEXT NOT NULL DEFAULT '',\\n \\"qty\\" REAL,\\n \\"unit\\" TEXT DEFAULT '',\\n \\"status\\" TEXT DEFAULT 'active',\\n \\"linked_at\\" TIMESTAMP,\\n \\"linked_by_profile_id\\" UUID,\\n \\"unlinked_at\\" TIMESTAMP,\\n \\"unlinked_by_profile_id\\" UUID,\\n \\"unlink_reason\\" TEXT,\\n \\"movement_id\\" TEXT\\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},"lot_id":{"type":"UUID","referenceKind":"foreignKey","notNull":true,"unique":false,"foreignKey":{"table":"inventory_lots","column":"id","onDelete":"RESTRICT","onUpdate":"RESTRICT","engines":["postgres","sqlite"]}},"consumer_type":{"type":"TEXT","notNull":true,"unique":false,"default":""},"consumer_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"qty":{"type":"REAL","notNull":false,"unique":false},"unit":{"type":"TEXT","notNull":false,"unique":false,"default":""},"status":{"type":"TEXT","notNull":false,"unique":false,"default":"active"},"linked_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"linked_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"unlinked_at":{"type":"TIMESTAMP","notNull":false,"unique":false},"unlinked_by_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"unlink_reason":{"type":"TEXT","notNull":false,"unique":false},"movement_id":{"type":"TEXT","notNull":false,"unique":false}},"indexes":[{"name":"inventory_lot_usages_slug_context_idx","columns":["tenant_id","slug","context"],"unique":true,"nullsNotDistinct":true},{"name":"inventory_lot_usages_consumer_idx","columns":["consumer_type","consumer_id"]},{"name":"inventory_lot_usages_lot_status_idx","columns":["lot_id","status"]},{"name":"inventory_lot_usages_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"inventory_lot_usages_linked_by_profile_id_idx","columns":["linked_by_profile_id"]},{"name":"inventory_lot_usages_unlinked_by_profile_id_idx","columns":["unlinked_by_profile_id"]}],"version":"c106a419"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:StockLevel":{"name":"stocklevel","className":"StockLevel","qualifiedName":"@happyvertical/smrt-inventory:StockLevel","collection":"stocklevels","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/models/StockLevel.ts","packageName":"@happyvertical/smrt-inventory","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}}},"skuId":{"type":"text","required":true,"default":"","description":"The item being counted.","_meta":{"required":true,"description":"The item being counted."}},"locationId":{"type":"text","required":true,"default":"","description":"Where the stock is kept.","_meta":{"required":true,"description":"Where the stock is kept."}},"state":{"type":"text","required":true,"default":"available","description":"The state of this stock: available, allocated, wip, qc_hold or damaged.","_meta":{"required":true,"description":"The state of this stock: available, allocated, wip, qc_hold or damaged."},"enum":["available","allocated","wip","qc_hold","damaged"]},"qty":{"type":"decimal","required":false,"description":"How many are on hand.","_meta":{"description":"How many are on hand."}},"reorderPoint":{"type":"decimal","required":false,"_meta":{"nullable":true}},"reorderQuantity":{"type":"decimal","required":false,"_meta":{"nullable":true}}},"methods":{},"decoratorConfig":{"tableName":"inventory_stock_levels","conflictColumns":["sku_id","location_id","state","tenant_id"],"api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]},"tenantScoped":{"mode":"optional"}},"extends":"SmrtObject","exportName":"StockLevel","collectionExportName":"StockLevelCollection","validationRules":[{"field":"skuId","rule":"required","fieldType":"text"},{"field":"locationId","rule":"required","fieldType":"text"},{"field":"state","rule":"required","fieldType":"text"}],"schema":{"tableName":"inventory_stock_levels","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_stock_levels\\" (\\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 \\"sku_id\\" TEXT NOT NULL DEFAULT '',\\n \\"location_id\\" TEXT NOT NULL DEFAULT '',\\n \\"state\\" TEXT NOT NULL DEFAULT 'available',\\n \\"qty\\" REAL,\\n \\"reorder_point\\" REAL,\\n \\"reorder_quantity\\" REAL\\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},"sku_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"location_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"state":{"type":"TEXT","notNull":true,"unique":false,"default":"available"},"qty":{"type":"REAL","notNull":false,"unique":false},"reorder_point":{"type":"REAL","notNull":false,"unique":false},"reorder_quantity":{"type":"REAL","notNull":false,"unique":false}},"indexes":[{"name":"inventory_stock_levels_sku_id_location_id_idx","columns":["sku_id","location_id","state","tenant_id"],"unique":true,"nullsNotDistinct":true},{"name":"inventory_stock_levels_slug_context_idx","columns":["slug","context"]},{"name":"inventory_stock_levels_tenant_id_created_at_idx","columns":["tenant_id","created_at"]}],"version":"02387935"},"importPath":"@happyvertical/smrt-inventory"},"@happyvertical/smrt-inventory:StockMovement":{"name":"stockmovement","className":"StockMovement","qualifiedName":"@happyvertical/smrt-inventory:StockMovement","collection":"stockmovements","filePath":"/home/runner/work/smrt/smrt/packages/inventory/src/models/StockMovement.ts","packageName":"@happyvertical/smrt-inventory","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}}},"skuId":{"type":"text","required":true,"default":"","description":"The item that moved.","_meta":{"required":true,"description":"The item that moved."}},"locationId":{"type":"text","required":true,"default":"","description":"The place it moved at.","_meta":{"required":true,"description":"The place it moved at."}},"lotId":{"type":"text","required":false,"description":"The lot that moved, when tracked by lot.","_meta":{"nullable":true,"description":"The lot that moved, when tracked by lot."}},"fromState":{"type":"text","required":false,"description":"What it was before, for example available or damaged.","_meta":{"description":"What it was before, for example available or damaged."},"enum":["available","allocated","wip","qc_hold","damaged"]},"toState":{"type":"text","required":false,"description":"What it became.","_meta":{"description":"What it became."},"enum":["available","allocated","wip","qc_hold","damaged"]},"qty":{"type":"decimal","required":false,"description":"How many moved.","_meta":{"description":"How many moved."}},"reasonCode":{"type":"text","required":true,"default":"adjustment","description":"Why it moved, for example adjustment, sale or return.","_meta":{"required":true,"description":"Why it moved, for example adjustment, sale or return."}},"sourceType":{"type":"text","required":false,"default":""},"sourceId":{"type":"text","required":false,"default":""},"actorProfileId":{"type":"crossPackageRef","required":false,"related":"@happyvertical/smrt-profiles:Profile"},"note":{"type":"text","required":false,"default":"","description":"Anything worth remembering about this movement.","_meta":{"description":"Anything worth remembering about this movement."}},"occurredAt":{"type":"datetime","required":false,"description":"When it happened.","_meta":{"description":"When it happened."}}},"methods":{},"decoratorConfig":{"tableName":"inventory_stock_movements","conflictColumns":["id"],"api":{"include":["list","get"]},"mcp":{"include":["list","get"]},"cli":{"include":["list","get"]},"indexes":[{"name":"inventory_stock_movements_lot_idx","columns":["lotId"]}],"tenantScoped":{"mode":"optional"}},"extends":"SmrtObject","exportName":"StockMovement","collectionExportName":"StockMovementCollection","validationRules":[{"field":"skuId","rule":"required","fieldType":"text"},{"field":"locationId","rule":"required","fieldType":"text"},{"field":"reasonCode","rule":"required","fieldType":"text"}],"schema":{"tableName":"inventory_stock_movements","ddl":"CREATE TABLE IF NOT EXISTS \\"inventory_stock_movements\\" (\\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 \\"sku_id\\" TEXT NOT NULL DEFAULT '',\\n \\"location_id\\" TEXT NOT NULL DEFAULT '',\\n \\"lot_id\\" TEXT,\\n \\"from_state\\" TEXT,\\n \\"to_state\\" TEXT,\\n \\"qty\\" REAL,\\n \\"reason_code\\" TEXT NOT NULL DEFAULT 'adjustment',\\n \\"source_type\\" TEXT DEFAULT '',\\n \\"source_id\\" TEXT DEFAULT '',\\n \\"actor_profile_id\\" UUID,\\n \\"note\\" TEXT DEFAULT '',\\n \\"occurred_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},"sku_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"location_id":{"type":"TEXT","notNull":true,"unique":false,"default":""},"lot_id":{"type":"TEXT","notNull":false,"unique":false},"from_state":{"type":"TEXT","notNull":false,"unique":false},"to_state":{"type":"TEXT","notNull":false,"unique":false},"qty":{"type":"REAL","notNull":false,"unique":false},"reason_code":{"type":"TEXT","notNull":true,"unique":false,"default":"adjustment"},"source_type":{"type":"TEXT","notNull":false,"unique":false,"default":""},"source_id":{"type":"TEXT","notNull":false,"unique":false,"default":""},"actor_profile_id":{"type":"UUID","referenceKind":"crossPackageRef","notNull":false,"unique":false},"note":{"type":"TEXT","notNull":false,"unique":false,"default":""},"occurred_at":{"type":"TIMESTAMP","notNull":false,"unique":false}},"indexes":[{"name":"inventory_stock_movements_slug_context_idx","columns":["slug","context"]},{"name":"inventory_stock_movements_lot_idx","columns":["lot_id"]},{"name":"inventory_stock_movements_tenant_id_created_at_idx","columns":["tenant_id","created_at"]},{"name":"inventory_stock_movements_actor_profile_id_idx","columns":["actor_profile_id"]}],"version":"b426c2c5"},"importPath":"@happyvertical/smrt-inventory"}},"moduleType":"smrt","recipes":[{"id":"inventory.stock","className":"StockRecipe","label":"Inventory","summary":"Keep count of how many of each product you have.","synonyms":["stock","quantity","on hand"],"models":["@happyvertical/smrt-inventory:StockLevel","@happyvertical/smrt-inventory:InventoryLocation","@happyvertical/smrt-inventory:StockMovement"],"nav":[{"label":"Stock levels","model":"@happyvertical/smrt-inventory:StockLevel","icon":"archive","description":"How much of each item you have on hand right now.","noun":"stock entry"},{"label":"Locations","model":"@happyvertical/smrt-inventory:InventoryLocation","icon":"home","description":"The places you keep stock, like shelves, rooms or vans."},{"label":"Stock movements","model":"@happyvertical/smrt-inventory:StockMovement","icon":"truck","description":"Every time stock came in, went out or moved."}],"requires":[],"requiresAny":[["products.simple","products.clothing"]],"section":{"id":"catalog","label":"Catalog","icon":"package","description":"Products, ingredients and the stock you hold."},"options":{"@happyvertical/smrt-inventory:StockLevel":{"fields":{"reorderPoint":{"visibility":"hidden"},"reorderQuantity":{"visibility":"hidden"}}},"@happyvertical/smrt-inventory:InventoryLocation":{"fields":{"placeId":{"visibility":"hidden"},"active":{"visibility":"hidden"}}},"@happyvertical/smrt-inventory:StockMovement":{"fields":{"sourceType":{"visibility":"hidden"},"sourceId":{"visibility":"hidden"},"actorProfileId":{"visibility":"hidden"}}}},"help":{"markdown":"## Overview\\n\\nInventory keeps count of how many of each product you have. Each count is a\\nstock level: an item, the place it is kept and how many are on hand. Stock can\\nsit in more than one place, and every change is recorded as a stock movement so\\nthere is a trail.\\n\\n## Tasks\\n\\n### Count what you have\\n\\n1. Create a stock level for the item and the place it is kept.\\n2. Set **{field:qty}** to how many you have on hand.\\n3. Set **{field:state}** if the stock is not available to sell, for example\\n allocated or on quality hold.\\n\\n### Correct a count\\n\\n1. Find the stock level for the item.\\n2. Set **{field:qty}** to what you really have. Record why as a stock movement.\\n\\n### Keep stock in more than one place\\n\\n1. Add a location with a **{field:code}** and a **{field:name}**, and say what\\n sort it is in **{field:kind}**.\\n2. Create stock levels against that location.\\n","fieldRefs":["code","kind","name","qty","state"]},"demo":{"mode":"server","reasons":["Needs a server because its package depends on @happyvertical/smrt-tenancy, which do not build for a browser (#3624)."]}}],"smrtDependencies":["@happyvertical/smrt-core","@happyvertical/smrt-tenancy"],"browser":{"status":"server-only","issues":["#3624"],"via":["@happyvertical/smrt-tenancy"]},"stampsConstructors":true}`));
6
7
  //#endregion
7
8
  //#region src/models/InventoryLocation.ts
8
- var __defProp$2 = Object.defineProperty;
9
- var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
10
- var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, {
9
+ var __defProp$7 = Object.defineProperty;
10
+ var __getOwnPropDesc$7 = Object.getOwnPropertyDescriptor;
11
+ var __defNormalProp$7 = (obj, key, value) => key in obj ? __defProp$7(obj, key, {
11
12
  enumerable: true,
12
13
  configurable: true,
13
14
  writable: true,
14
15
  value
15
16
  }) : obj[key] = value;
16
- var __decorateClass$2 = (decorators, target, key, kind) => {
17
- var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
17
+ var __decorateClass$7 = (decorators, target, key, kind) => {
18
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$7(target, key) : target;
18
19
  for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
19
- if (kind && result) __defProp$2(target, key, result);
20
+ if (kind && result) __defProp$7(target, key, result);
20
21
  return result;
21
22
  };
22
- var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
23
+ var __publicField$7 = (obj, key, value) => __defNormalProp$7(obj, typeof key !== "symbol" ? key + "" : key, value);
23
24
  var InventoryLocation = class extends SmrtObject {
24
25
  tenantId = null;
25
26
  code = "";
@@ -44,15 +45,15 @@ var InventoryLocation = class extends SmrtObject {
44
45
  if (options.active !== void 0) this.active = options.active;
45
46
  }
46
47
  };
47
- __publicField$2(InventoryLocation, "__smrtPackage__", "@happyvertical/smrt-inventory");
48
- __decorateClass$2([tenantId({ nullable: true })], InventoryLocation.prototype, "tenantId", 2);
49
- __decorateClass$2([field({
48
+ __publicField$7(InventoryLocation, "__smrtPackage__", "@happyvertical/smrt-inventory");
49
+ __decorateClass$7([tenantId({ nullable: true })], InventoryLocation.prototype, "tenantId", 2);
50
+ __decorateClass$7([field({
50
51
  required: true,
51
52
  description: "A short code for the place stock is kept, for example MAIN."
52
53
  })], InventoryLocation.prototype, "code", 2);
53
- __decorateClass$2([field({ description: "What you call the place, for example Main warehouse." })], InventoryLocation.prototype, "name", 2);
54
- __decorateClass$2([field({ description: "What sort of place it is, for example a warehouse or a shop floor." })], InventoryLocation.prototype, "kind", 2);
55
- InventoryLocation = __decorateClass$2([TenantScoped({ mode: "optional" }), smrt({
54
+ __decorateClass$7([field({ description: "What you call the place, for example Main warehouse." })], InventoryLocation.prototype, "name", 2);
55
+ __decorateClass$7([field({ description: "What sort of place it is, for example a warehouse or a shop floor." })], InventoryLocation.prototype, "kind", 2);
56
+ InventoryLocation = __decorateClass$7([TenantScoped({ mode: "optional" }), smrt({
56
57
  tableName: "inventory_locations",
57
58
  conflictColumns: ["code", "tenant_id"],
58
59
  api: { include: [
@@ -111,6 +112,530 @@ var InventoryLocationCollection = class extends SmrtCollection {
111
112
  }
112
113
  };
113
114
  //#endregion
115
+ //#region src/models/Lot.ts
116
+ var __defProp$6 = Object.defineProperty;
117
+ var __getOwnPropDesc$6 = Object.getOwnPropertyDescriptor;
118
+ var __defNormalProp$6 = (obj, key, value) => key in obj ? __defProp$6(obj, key, {
119
+ enumerable: true,
120
+ configurable: true,
121
+ writable: true,
122
+ value
123
+ }) : obj[key] = value;
124
+ var __decorateClass$6 = (decorators, target, key, kind) => {
125
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$6(target, key) : target;
126
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
127
+ if (kind && result) __defProp$6(target, key, result);
128
+ return result;
129
+ };
130
+ var __publicField$6 = (obj, key, value) => __defNormalProp$6(obj, typeof key !== "symbol" ? key + "" : key, value);
131
+ var Lot = class extends SmrtObject {
132
+ tenantId = null;
133
+ skuId = null;
134
+ kind = "lot";
135
+ lotNumber = "";
136
+ searchKey = "";
137
+ identityKey = "";
138
+ uniqueNumber = false;
139
+ receivedQty = 0;
140
+ unit = "";
141
+ sourceType = "";
142
+ sourceId = "";
143
+ attributes = "{}";
144
+ status = "active";
145
+ receivedAt = /* @__PURE__ */ new Date();
146
+ receivedByProfileId = null;
147
+ lastUsedAt = null;
148
+ constructor(options = {}) {
149
+ super(options);
150
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
151
+ if (options.skuId !== void 0) this.skuId = options.skuId;
152
+ if (options.kind !== void 0) this.kind = options.kind;
153
+ if (options.lotNumber !== void 0) this.lotNumber = options.lotNumber;
154
+ if (options.searchKey !== void 0) this.searchKey = options.searchKey;
155
+ if (options.identityKey !== void 0) this.identityKey = options.identityKey;
156
+ if (options.uniqueNumber !== void 0) this.uniqueNumber = options.uniqueNumber;
157
+ if (options.receivedQty !== void 0) this.receivedQty = options.receivedQty;
158
+ if (options.unit !== void 0) this.unit = options.unit;
159
+ if (options.sourceType !== void 0) this.sourceType = options.sourceType;
160
+ if (options.sourceId !== void 0) this.sourceId = options.sourceId;
161
+ if (options.attributes !== void 0) this.setAttributes(options.attributes);
162
+ if (options.status !== void 0) this.status = options.status;
163
+ if (options.receivedAt !== void 0) this.receivedAt = options.receivedAt instanceof Date ? options.receivedAt : new Date(options.receivedAt);
164
+ if (options.receivedByProfileId !== void 0) this.receivedByProfileId = options.receivedByProfileId;
165
+ if (options.lastUsedAt !== void 0) this.lastUsedAt = options.lastUsedAt === null || options.lastUsedAt instanceof Date ? options.lastUsedAt : new Date(options.lastUsedAt);
166
+ }
167
+ /**
168
+ * Parse {@link attributes}. Returns an empty object for invalid JSON so
169
+ * callers need no try/catch.
170
+ */
171
+ getAttributes() {
172
+ if (!this.attributes) return {};
173
+ try {
174
+ const parsed = JSON.parse(this.attributes);
175
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
176
+ } catch {
177
+ return {};
178
+ }
179
+ }
180
+ /** Store attributes from an object or an already-serialized JSON string. */
181
+ setAttributes(value) {
182
+ this.attributes = typeof value === "string" ? value : JSON.stringify(value ?? {});
183
+ }
184
+ };
185
+ __publicField$6(Lot, "__smrtPackage__", "@happyvertical/smrt-inventory");
186
+ __decorateClass$6([tenantId({ nullable: true })], Lot.prototype, "tenantId", 2);
187
+ __decorateClass$6([field({
188
+ nullable: true,
189
+ description: "The item this lot is of, if catalogued."
190
+ })], Lot.prototype, "skuId", 2);
191
+ __decorateClass$6([field({ description: "Whether this is a batch (lot) or a single unit (serial)." })], Lot.prototype, "kind", 2);
192
+ __decorateClass$6([field({
193
+ required: true,
194
+ maxLength: 128,
195
+ description: "The batch code, supplier lot or serial number."
196
+ })], Lot.prototype, "lotNumber", 2);
197
+ __decorateClass$6([field({
198
+ maxLength: 160,
199
+ description: "The number in normal form, used for lookup."
200
+ })], Lot.prototype, "searchKey", 2);
201
+ __decorateClass$6([field({
202
+ required: true,
203
+ maxLength: 160,
204
+ description: "Internal identity that keeps lots from being created twice."
205
+ })], Lot.prototype, "identityKey", 2);
206
+ __decorateClass$6([field({ description: "Whether the number must be unique for this item." })], Lot.prototype, "uniqueNumber", 2);
207
+ __decorateClass$6([field({
208
+ type: "decimal",
209
+ description: "How many came in on the receipt."
210
+ })], Lot.prototype, "receivedQty", 2);
211
+ __decorateClass$6([field({ description: "The unit of measure." })], Lot.prototype, "unit", 2);
212
+ __decorateClass$6([field({ description: "What kind of record received this lot." })], Lot.prototype, "sourceType", 2);
213
+ __decorateClass$6([field({ description: "The record that received this lot." })], Lot.prototype, "sourceId", 2);
214
+ __decorateClass$6([field({
215
+ type: "text",
216
+ description: "Anything else worth knowing about this lot, stored as JSON."
217
+ })], Lot.prototype, "attributes", 2);
218
+ __decorateClass$6([field({ description: "Where the lot stands: active, quarantined, consumed or void." })], Lot.prototype, "status", 2);
219
+ __decorateClass$6([field({ description: "When the lot was received." })], Lot.prototype, "receivedAt", 2);
220
+ __decorateClass$6([crossPackageRef("@happyvertical/smrt-profiles:Profile")], Lot.prototype, "receivedByProfileId", 2);
221
+ __decorateClass$6([field({
222
+ type: "datetime",
223
+ nullable: true
224
+ })], Lot.prototype, "lastUsedAt", 2);
225
+ Lot = __decorateClass$6([TenantScoped({ mode: "optional" }), smrt({
226
+ tableName: "inventory_lots",
227
+ conflictColumns: ["identity_key", "tenant_id"],
228
+ api: { include: ["list", "get"] },
229
+ mcp: { include: ["list", "get"] },
230
+ cli: { include: ["list", "get"] },
231
+ indexes: [{
232
+ name: "inventory_lots_search_key_idx",
233
+ columns: ["tenantId", "searchKey"]
234
+ }]
235
+ })], Lot);
236
+ //#endregion
237
+ //#region src/collections/LotCollection.ts
238
+ function lotSearchKey(lotNumber) {
239
+ return lotNumber.trim().toUpperCase();
240
+ }
241
+ var LotCollection = class extends SmrtCollection {
242
+ static _itemClass = Lot;
243
+ /**
244
+ * Find lots by number, case-insensitively and ignoring surrounding
245
+ * whitespace. A number can match several lots (one supplier lot received
246
+ * twice); pass `skuId` to narrow. Oldest receipt first.
247
+ */
248
+ async findByLotNumber(lotNumber, options = {}) {
249
+ const where = { searchKey: lotSearchKey(lotNumber) };
250
+ if (options.skuId !== void 0) where.skuId = options.skuId;
251
+ return this.list({
252
+ where,
253
+ orderBy: "receivedAt ASC"
254
+ });
255
+ }
256
+ };
257
+ //#endregion
258
+ //#region src/models/LotCorrection.ts
259
+ var __defProp$5 = Object.defineProperty;
260
+ var __getOwnPropDesc$5 = Object.getOwnPropertyDescriptor;
261
+ var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, {
262
+ enumerable: true,
263
+ configurable: true,
264
+ writable: true,
265
+ value
266
+ }) : obj[key] = value;
267
+ var __decorateClass$5 = (decorators, target, key, kind) => {
268
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$5(target, key) : target;
269
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
270
+ if (kind && result) __defProp$5(target, key, result);
271
+ return result;
272
+ };
273
+ var __publicField$5 = (obj, key, value) => __defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
274
+ var LotCorrection = class extends SmrtObject {
275
+ tenantId = null;
276
+ lotId = "";
277
+ kind = "attribute";
278
+ field = "";
279
+ oldValue = "";
280
+ newValue = "";
281
+ reason = "";
282
+ usageId = null;
283
+ documentId = null;
284
+ actorProfileId = null;
285
+ correctedAt = /* @__PURE__ */ new Date();
286
+ constructor(options = {}) {
287
+ super(options);
288
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
289
+ if (options.lotId !== void 0) this.lotId = options.lotId;
290
+ if (options.kind !== void 0) this.kind = options.kind;
291
+ if (options.field !== void 0) this.field = options.field;
292
+ if (options.oldValue !== void 0) this.oldValue = options.oldValue;
293
+ if (options.newValue !== void 0) this.newValue = options.newValue;
294
+ if (options.reason !== void 0) this.reason = options.reason;
295
+ if (options.usageId !== void 0) this.usageId = options.usageId;
296
+ if (options.documentId !== void 0) this.documentId = options.documentId;
297
+ if (options.actorProfileId !== void 0) this.actorProfileId = options.actorProfileId;
298
+ if (options.correctedAt !== void 0) this.correctedAt = options.correctedAt instanceof Date ? options.correctedAt : new Date(options.correctedAt);
299
+ }
300
+ };
301
+ __publicField$5(LotCorrection, "__smrtPackage__", "@happyvertical/smrt-inventory");
302
+ __decorateClass$5([tenantId({ nullable: true })], LotCorrection.prototype, "tenantId", 2);
303
+ __decorateClass$5([foreignKey(Lot, {
304
+ required: true,
305
+ onDelete: "RESTRICT",
306
+ onUpdate: "RESTRICT",
307
+ constraint: { engines: ["postgres", "sqlite"] }
308
+ })], LotCorrection.prototype, "lotId", 2);
309
+ __decorateClass$5([field({
310
+ required: true,
311
+ description: "What was corrected."
312
+ })], LotCorrection.prototype, "kind", 2);
313
+ __decorateClass$5([field({ description: "Which attribute changed, if one did." })], LotCorrection.prototype, "field", 2);
314
+ __decorateClass$5([field({
315
+ type: "text",
316
+ description: "What it was."
317
+ })], LotCorrection.prototype, "oldValue", 2);
318
+ __decorateClass$5([field({
319
+ type: "text",
320
+ description: "What it is now."
321
+ })], LotCorrection.prototype, "newValue", 2);
322
+ __decorateClass$5([field({
323
+ type: "text",
324
+ required: true,
325
+ maxLength: 1e3,
326
+ description: "Why it was corrected."
327
+ })], LotCorrection.prototype, "reason", 2);
328
+ __decorateClass$5([field({ nullable: true })], LotCorrection.prototype, "usageId", 2);
329
+ __decorateClass$5([field({ nullable: true })], LotCorrection.prototype, "documentId", 2);
330
+ __decorateClass$5([crossPackageRef("@happyvertical/smrt-profiles:Profile")], LotCorrection.prototype, "actorProfileId", 2);
331
+ __decorateClass$5([field({ description: "When it was corrected." })], LotCorrection.prototype, "correctedAt", 2);
332
+ LotCorrection = __decorateClass$5([TenantScoped({ mode: "optional" }), smrt({
333
+ tableName: "inventory_lot_corrections",
334
+ conflictColumns: ["id"],
335
+ api: { include: ["list", "get"] },
336
+ mcp: { include: ["list", "get"] },
337
+ cli: { include: ["list", "get"] },
338
+ indexes: [{
339
+ name: "inventory_lot_corrections_lot_idx",
340
+ columns: ["lotId"]
341
+ }]
342
+ })], LotCorrection);
343
+ //#endregion
344
+ //#region src/collections/LotCorrectionCollection.ts
345
+ var LotCorrectionCollection = class extends SmrtCollection {
346
+ static _itemClass = LotCorrection;
347
+ };
348
+ //#endregion
349
+ //#region src/models/LotDocument.ts
350
+ var __defProp$4 = Object.defineProperty;
351
+ var __getOwnPropDesc$4 = Object.getOwnPropertyDescriptor;
352
+ var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, {
353
+ enumerable: true,
354
+ configurable: true,
355
+ writable: true,
356
+ value
357
+ }) : obj[key] = value;
358
+ var __decorateClass$4 = (decorators, target, key, kind) => {
359
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$4(target, key) : target;
360
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
361
+ if (kind && result) __defProp$4(target, key, result);
362
+ return result;
363
+ };
364
+ var __publicField$4 = (obj, key, value) => __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
365
+ var toDate$1 = (value) => value instanceof Date ? value : new Date(value);
366
+ var LotDocument = class extends SmrtObject {
367
+ tenantId = null;
368
+ lotId = "";
369
+ assetId = "";
370
+ role = "certificate";
371
+ attachedAt = /* @__PURE__ */ new Date();
372
+ attachedByProfileId = null;
373
+ detachedAt = null;
374
+ detachedByProfileId = null;
375
+ constructor(options = {}) {
376
+ super(options);
377
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
378
+ if (options.lotId !== void 0) this.lotId = options.lotId;
379
+ if (options.assetId !== void 0) this.assetId = options.assetId;
380
+ if (options.role !== void 0) this.role = options.role;
381
+ if (options.attachedAt !== void 0) this.attachedAt = toDate$1(options.attachedAt);
382
+ if (options.attachedByProfileId !== void 0) this.attachedByProfileId = options.attachedByProfileId;
383
+ if (options.detachedAt !== void 0) this.detachedAt = options.detachedAt === null ? null : toDate$1(options.detachedAt);
384
+ if (options.detachedByProfileId !== void 0) this.detachedByProfileId = options.detachedByProfileId;
385
+ }
386
+ };
387
+ __publicField$4(LotDocument, "__smrtPackage__", "@happyvertical/smrt-inventory");
388
+ __decorateClass$4([tenantId({ nullable: true })], LotDocument.prototype, "tenantId", 2);
389
+ __decorateClass$4([foreignKey(Lot, {
390
+ required: true,
391
+ onDelete: "RESTRICT",
392
+ onUpdate: "RESTRICT",
393
+ constraint: { engines: ["postgres", "sqlite"] }
394
+ })], LotDocument.prototype, "lotId", 2);
395
+ __decorateClass$4([crossPackageRef("@happyvertical/smrt-assets:Asset")], LotDocument.prototype, "assetId", 2);
396
+ __decorateClass$4([field({ description: "What the document is for, for example certificate." })], LotDocument.prototype, "role", 2);
397
+ __decorateClass$4([field({ description: "When it was attached." })], LotDocument.prototype, "attachedAt", 2);
398
+ __decorateClass$4([crossPackageRef("@happyvertical/smrt-profiles:Profile")], LotDocument.prototype, "attachedByProfileId", 2);
399
+ __decorateClass$4([field({
400
+ type: "datetime",
401
+ nullable: true
402
+ })], LotDocument.prototype, "detachedAt", 2);
403
+ __decorateClass$4([crossPackageRef("@happyvertical/smrt-profiles:Profile")], LotDocument.prototype, "detachedByProfileId", 2);
404
+ LotDocument = __decorateClass$4([TenantScoped({ mode: "optional" }), smrt({
405
+ tableName: "inventory_lot_documents",
406
+ api: { include: ["list", "get"] },
407
+ mcp: { include: ["list", "get"] },
408
+ cli: { include: ["list", "get"] },
409
+ indexes: [{
410
+ name: "inventory_lot_documents_lot_idx",
411
+ columns: ["lotId"]
412
+ }]
413
+ })], LotDocument);
414
+ //#endregion
415
+ //#region src/collections/LotDocumentCollection.ts
416
+ var LotDocumentCollection = class extends SmrtCollection {
417
+ static _itemClass = LotDocument;
418
+ };
419
+ //#endregion
420
+ //#region src/models/LotStockLevel.ts
421
+ var __defProp$3 = Object.defineProperty;
422
+ var __getOwnPropDesc$3 = Object.getOwnPropertyDescriptor;
423
+ var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, {
424
+ enumerable: true,
425
+ configurable: true,
426
+ writable: true,
427
+ value
428
+ }) : obj[key] = value;
429
+ var __decorateClass$3 = (decorators, target, key, kind) => {
430
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$3(target, key) : target;
431
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
432
+ if (kind && result) __defProp$3(target, key, result);
433
+ return result;
434
+ };
435
+ var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
436
+ var LotStockLevel = class extends SmrtObject {
437
+ tenantId = null;
438
+ lotId = "";
439
+ skuId = "";
440
+ locationId = "";
441
+ state = "available";
442
+ qty = 0;
443
+ constructor(options = {}) {
444
+ super(options);
445
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
446
+ if (options.lotId !== void 0) this.lotId = options.lotId;
447
+ if (options.skuId !== void 0) this.skuId = options.skuId;
448
+ if (options.locationId !== void 0) this.locationId = options.locationId;
449
+ if (options.state !== void 0) this.state = options.state;
450
+ if (options.qty !== void 0) this.qty = options.qty;
451
+ }
452
+ };
453
+ __publicField$3(LotStockLevel, "__smrtPackage__", "@happyvertical/smrt-inventory");
454
+ __decorateClass$3([tenantId({ nullable: true })], LotStockLevel.prototype, "tenantId", 2);
455
+ __decorateClass$3([foreignKey(Lot, {
456
+ required: true,
457
+ onDelete: "RESTRICT",
458
+ onUpdate: "RESTRICT",
459
+ constraint: { engines: ["postgres", "sqlite"] }
460
+ })], LotStockLevel.prototype, "lotId", 2);
461
+ __decorateClass$3([field({ description: "The item the lot is of." })], LotStockLevel.prototype, "skuId", 2);
462
+ __decorateClass$3([field({
463
+ required: true,
464
+ description: "Where the lot is kept."
465
+ })], LotStockLevel.prototype, "locationId", 2);
466
+ __decorateClass$3([field({
467
+ required: true,
468
+ description: "The state of this stock: available, allocated, wip, qc_hold or damaged."
469
+ })], LotStockLevel.prototype, "state", 2);
470
+ __decorateClass$3([field({
471
+ type: "decimal",
472
+ description: "How many are on hand."
473
+ })], LotStockLevel.prototype, "qty", 2);
474
+ LotStockLevel = __decorateClass$3([TenantScoped({ mode: "optional" }), smrt({
475
+ tableName: "inventory_lot_stock_levels",
476
+ conflictColumns: [
477
+ "lot_id",
478
+ "location_id",
479
+ "state",
480
+ "tenant_id"
481
+ ],
482
+ api: { include: ["list", "get"] },
483
+ mcp: { include: ["list", "get"] },
484
+ cli: { include: ["list", "get"] }
485
+ })], LotStockLevel);
486
+ //#endregion
487
+ //#region src/collections/LotStockLevelCollection.ts
488
+ var LotStockLevelCollection = class extends SmrtCollection {
489
+ static _itemClass = LotStockLevel;
490
+ /** The level row for a `(lotId, locationId, state)` tuple, or `null`. */
491
+ async getLevel(lotId, locationId, state = "available") {
492
+ return (await this.list({
493
+ where: {
494
+ lotId,
495
+ locationId,
496
+ state
497
+ },
498
+ limit: 1
499
+ }))[0] ?? null;
500
+ }
501
+ /**
502
+ * Sum of the lot's quantity across locations. Pass `state` to narrow
503
+ * to one state; omit it for the total across all states.
504
+ */
505
+ async onHand(lotId, state) {
506
+ const where = { lotId };
507
+ if (state) where.state = state;
508
+ return (await this.list({ where })).reduce((sum, row) => sum + Number(row.qty ?? 0), 0);
509
+ }
510
+ };
511
+ //#endregion
512
+ //#region src/models/LotUsage.ts
513
+ var __defProp$2 = Object.defineProperty;
514
+ var __getOwnPropDesc$2 = Object.getOwnPropertyDescriptor;
515
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, {
516
+ enumerable: true,
517
+ configurable: true,
518
+ writable: true,
519
+ value
520
+ }) : obj[key] = value;
521
+ var __decorateClass$2 = (decorators, target, key, kind) => {
522
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$2(target, key) : target;
523
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
524
+ if (kind && result) __defProp$2(target, key, result);
525
+ return result;
526
+ };
527
+ var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
528
+ var toDate = (value) => value instanceof Date ? value : new Date(value);
529
+ var LotUsage = class extends SmrtObject {
530
+ tenantId = null;
531
+ lotId = "";
532
+ consumerType = "";
533
+ consumerId = "";
534
+ qty = null;
535
+ unit = "";
536
+ status = "active";
537
+ linkedAt = /* @__PURE__ */ new Date();
538
+ linkedByProfileId = null;
539
+ unlinkedAt = null;
540
+ unlinkedByProfileId = null;
541
+ unlinkReason = "";
542
+ movementId = null;
543
+ constructor(options = {}) {
544
+ super(options);
545
+ if (options.tenantId !== void 0) this.tenantId = options.tenantId;
546
+ if (options.lotId !== void 0) this.lotId = options.lotId;
547
+ if (options.consumerType !== void 0) this.consumerType = options.consumerType;
548
+ if (options.consumerId !== void 0) this.consumerId = options.consumerId;
549
+ if (options.qty !== void 0) this.qty = options.qty;
550
+ if (options.unit !== void 0) this.unit = options.unit;
551
+ if (options.status !== void 0) this.status = options.status;
552
+ if (options.linkedAt !== void 0) this.linkedAt = toDate(options.linkedAt);
553
+ if (options.linkedByProfileId !== void 0) this.linkedByProfileId = options.linkedByProfileId;
554
+ if (options.unlinkedAt !== void 0) this.unlinkedAt = options.unlinkedAt === null ? null : toDate(options.unlinkedAt);
555
+ if (options.unlinkedByProfileId !== void 0) this.unlinkedByProfileId = options.unlinkedByProfileId;
556
+ if (options.unlinkReason !== void 0) this.unlinkReason = options.unlinkReason;
557
+ if (options.movementId !== void 0) this.movementId = options.movementId;
558
+ }
559
+ };
560
+ __publicField$2(LotUsage, "__smrtPackage__", "@happyvertical/smrt-inventory");
561
+ __decorateClass$2([tenantId({ nullable: true })], LotUsage.prototype, "tenantId", 2);
562
+ __decorateClass$2([foreignKey(Lot, {
563
+ required: true,
564
+ onDelete: "RESTRICT",
565
+ onUpdate: "RESTRICT",
566
+ constraint: { engines: ["postgres", "sqlite"] }
567
+ })], LotUsage.prototype, "lotId", 2);
568
+ __decorateClass$2([field({
569
+ required: true,
570
+ description: "What kind of record used the lot."
571
+ })], LotUsage.prototype, "consumerType", 2);
572
+ __decorateClass$2([field({
573
+ required: true,
574
+ description: "The record that used the lot."
575
+ })], LotUsage.prototype, "consumerId", 2);
576
+ __decorateClass$2([field({
577
+ type: "decimal",
578
+ nullable: true,
579
+ description: "How much was used."
580
+ })], LotUsage.prototype, "qty", 2);
581
+ __decorateClass$2([field({ description: "The unit of measure." })], LotUsage.prototype, "unit", 2);
582
+ __decorateClass$2([field({ description: "Whether the link still stands: active or unlinked." })], LotUsage.prototype, "status", 2);
583
+ __decorateClass$2([field({ description: "When the lot was linked." })], LotUsage.prototype, "linkedAt", 2);
584
+ __decorateClass$2([crossPackageRef("@happyvertical/smrt-profiles:Profile")], LotUsage.prototype, "linkedByProfileId", 2);
585
+ __decorateClass$2([field({
586
+ type: "datetime",
587
+ nullable: true
588
+ })], LotUsage.prototype, "unlinkedAt", 2);
589
+ __decorateClass$2([crossPackageRef("@happyvertical/smrt-profiles:Profile")], LotUsage.prototype, "unlinkedByProfileId", 2);
590
+ __decorateClass$2([field({
591
+ type: "text",
592
+ description: "Why the link was removed."
593
+ })], LotUsage.prototype, "unlinkReason", 2);
594
+ __decorateClass$2([field({
595
+ nullable: true,
596
+ description: "The stock movement for this use, if any."
597
+ })], LotUsage.prototype, "movementId", 2);
598
+ LotUsage = __decorateClass$2([TenantScoped({ mode: "optional" }), smrt({
599
+ tableName: "inventory_lot_usages",
600
+ api: { include: ["list", "get"] },
601
+ mcp: { include: ["list", "get"] },
602
+ cli: { include: ["list", "get"] },
603
+ indexes: [{
604
+ name: "inventory_lot_usages_consumer_idx",
605
+ columns: ["consumerType", "consumerId"]
606
+ }, {
607
+ name: "inventory_lot_usages_lot_status_idx",
608
+ columns: ["lotId", "status"]
609
+ }]
610
+ })], LotUsage);
611
+ //#endregion
612
+ //#region src/collections/LotUsageCollection.ts
613
+ var LotUsageCollection = class extends SmrtCollection {
614
+ static _itemClass = LotUsage;
615
+ /** Usages of the lot that have not been unlinked, oldest first. */
616
+ async findActiveForLot(lotId) {
617
+ return this.list({
618
+ where: {
619
+ lotId,
620
+ status: "active"
621
+ },
622
+ orderBy: "linkedAt ASC"
623
+ });
624
+ }
625
+ /** Every usage by one consumer, oldest first. Active only unless asked. */
626
+ async findForConsumer(consumerType, consumerId, options = {}) {
627
+ const where = {
628
+ consumerType,
629
+ consumerId
630
+ };
631
+ if (!options.includeUnlinked) where.status = "active";
632
+ return this.list({
633
+ where,
634
+ orderBy: "linkedAt ASC"
635
+ });
636
+ }
637
+ };
638
+ //#endregion
114
639
  //#region src/models/StockLevel.ts
115
640
  var __defProp$1 = Object.defineProperty;
116
641
  var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
@@ -268,6 +793,7 @@ var StockMovement = class extends SmrtObject {
268
793
  tenantId = null;
269
794
  skuId = "";
270
795
  locationId = "";
796
+ lotId = null;
271
797
  fromState = null;
272
798
  toState = null;
273
799
  qty = 0;
@@ -292,6 +818,7 @@ var StockMovement = class extends SmrtObject {
292
818
  if (options.tenantId !== void 0) this.tenantId = options.tenantId;
293
819
  if (options.skuId !== void 0) this.skuId = options.skuId;
294
820
  if (options.locationId !== void 0) this.locationId = options.locationId;
821
+ if (options.lotId !== void 0) this.lotId = options.lotId;
295
822
  if (options.fromState !== void 0) this.fromState = options.fromState;
296
823
  if (options.toState !== void 0) this.toState = options.toState;
297
824
  if (options.qty !== void 0) this.qty = options.qty;
@@ -313,6 +840,10 @@ __decorateClass([field({
313
840
  required: true,
314
841
  description: "The place it moved at."
315
842
  })], StockMovement.prototype, "locationId", 2);
843
+ __decorateClass([field({
844
+ nullable: true,
845
+ description: "The lot that moved, when tracked by lot."
846
+ })], StockMovement.prototype, "lotId", 2);
316
847
  __decorateClass([field({ description: "What it was before, for example available or damaged." })], StockMovement.prototype, "fromState", 2);
317
848
  __decorateClass([field({ description: "What it became." })], StockMovement.prototype, "toState", 2);
318
849
  __decorateClass([field({
@@ -331,7 +862,11 @@ StockMovement = __decorateClass([TenantScoped({ mode: "optional" }), smrt({
331
862
  conflictColumns: ["id"],
332
863
  api: { include: ["list", "get"] },
333
864
  mcp: { include: ["list", "get"] },
334
- cli: { include: ["list", "get"] }
865
+ cli: { include: ["list", "get"] },
866
+ indexes: [{
867
+ name: "inventory_stock_movements_lot_idx",
868
+ columns: ["lotId"]
869
+ }]
335
870
  })], StockMovement);
336
871
  //#endregion
337
872
  //#region src/collections/StockMovementCollection.ts
@@ -445,36 +980,89 @@ var StockRecipe = class extends SmrtRecipe {
445
980
  };
446
981
  };
447
982
  //#endregion
983
+ //#region src/services/lot-errors.ts
984
+ var LotNotFoundError = class extends Error {
985
+ constructor(lotId) {
986
+ super(`Lot not found: ${lotId}`);
987
+ this.lotId = lotId;
988
+ }
989
+ lotId;
990
+ name = "LotNotFoundError";
991
+ };
992
+ var LotMismatchError = class extends Error {
993
+ constructor(lotId, lotSkuId, skuId) {
994
+ super(`Lot ${lotId} is of sku ${lotSkuId}, not ${skuId}`);
995
+ this.lotId = lotId;
996
+ this.lotSkuId = lotSkuId;
997
+ this.skuId = skuId;
998
+ }
999
+ lotId;
1000
+ lotSkuId;
1001
+ skuId;
1002
+ name = "LotMismatchError";
1003
+ };
1004
+ var DuplicateLotError = class extends Error {
1005
+ constructor(lotNumber, existingLotId) {
1006
+ super(`A lot with number "${lotNumber}" already exists`);
1007
+ this.lotNumber = lotNumber;
1008
+ this.existingLotId = existingLotId;
1009
+ }
1010
+ lotNumber;
1011
+ existingLotId;
1012
+ name = "DuplicateLotError";
1013
+ };
1014
+ var LotInUseError = class extends Error {
1015
+ constructor(lotId, consumerType, consumerId) {
1016
+ super(`Lot ${lotId} is already in use by ${consumerType}:${consumerId}`);
1017
+ this.lotId = lotId;
1018
+ this.consumerType = consumerType;
1019
+ this.consumerId = consumerId;
1020
+ }
1021
+ lotId;
1022
+ consumerType;
1023
+ consumerId;
1024
+ name = "LotInUseError";
1025
+ };
1026
+ var LotCorrectionError = class extends Error {
1027
+ name = "LotCorrectionError";
1028
+ };
1029
+ //#endregion
448
1030
  //#region src/services/StockService.ts
449
1031
  var logger$1 = createLogger({ level: "info" });
450
1032
  var InsufficientStockError = class extends Error {
451
- constructor(skuId, locationId, state, requested, available) {
452
- super(`Insufficient stock: sku=${skuId} location=${locationId} state=${state} requested=${requested} available=${available}`);
1033
+ constructor(skuId, locationId, state, requested, available, lotId) {
1034
+ super(`Insufficient stock: sku=${skuId} location=${locationId} state=${state} ${lotId ? `lot=${lotId} ` : ""}requested=${requested} available=${available}`);
453
1035
  this.skuId = skuId;
454
1036
  this.locationId = locationId;
455
1037
  this.state = state;
456
1038
  this.requested = requested;
457
1039
  this.available = available;
1040
+ this.lotId = lotId;
458
1041
  }
459
1042
  skuId;
460
1043
  locationId;
461
1044
  state;
462
1045
  requested;
463
1046
  available;
1047
+ lotId;
464
1048
  name = "InsufficientStockError";
465
1049
  };
466
1050
  var StockService = class StockService {
467
- constructor(db, levels, movements, locations, inTransaction = false) {
1051
+ constructor(db, levels, movements, locations, lotLevels, lots, inTransaction = false) {
468
1052
  this.db = db;
469
1053
  this.levels = levels;
470
1054
  this.movements = movements;
471
1055
  this.locations = locations;
1056
+ this.lotLevels = lotLevels;
1057
+ this.lots = lots;
472
1058
  this.inTransaction = inTransaction;
473
1059
  }
474
1060
  db;
475
1061
  levels;
476
1062
  movements;
477
1063
  locations;
1064
+ lotLevels;
1065
+ lots;
478
1066
  inTransaction;
479
1067
  /** Internal factory — prefer {@link createStockService}. */
480
1068
  static async create(options) {
@@ -482,7 +1070,9 @@ var StockService = class StockService {
482
1070
  const levels = await StockLevelCollection.create({ db: resolved });
483
1071
  const movements = await StockMovementCollection.create({ db: resolved });
484
1072
  const locations = await InventoryLocationCollection.create({ db: resolved });
485
- return new StockService(resolved, levels, movements, locations);
1073
+ const lotLevels = await LotStockLevelCollection.create({ db: resolved });
1074
+ const lots = await LotCollection.create({ db: resolved });
1075
+ return new StockService(resolved, levels, movements, locations, lotLevels, lots);
486
1076
  }
487
1077
  /**
488
1078
  * Run `work` inside a single database transaction with a tx-bound
@@ -517,7 +1107,9 @@ var StockService = class StockService {
517
1107
  const levels = await StockLevelCollection.create({ db: txDb });
518
1108
  const movements = await StockMovementCollection.create({ db: txDb });
519
1109
  const locations = await InventoryLocationCollection.create({ db: txDb });
520
- return work(new StockService(txDb, levels, movements, locations, true));
1110
+ const lotLevels = await LotStockLevelCollection.create({ db: txDb });
1111
+ const lots = await LotCollection.create({ db: txDb });
1112
+ return work(new StockService(txDb, levels, movements, locations, lotLevels, lots, true));
521
1113
  });
522
1114
  }
523
1115
  /**
@@ -526,14 +1118,16 @@ var StockService = class StockService {
526
1118
  * callback), reuse it; otherwise open a fresh one.
527
1119
  */
528
1120
  async runAtomically(work) {
529
- if (this.inTransaction) return work({
1121
+ if (this.inTransaction) return work(this.txCollections());
1122
+ return this.withTransaction(async (tx) => work(tx.txCollections()));
1123
+ }
1124
+ txCollections() {
1125
+ return {
530
1126
  levels: this.levels,
531
- movements: this.movements
532
- });
533
- return this.withTransaction(async (tx) => work({
534
- levels: tx.levels,
535
- movements: tx.movements
536
- }));
1127
+ movements: this.movements,
1128
+ lotLevels: this.lotLevels,
1129
+ lots: this.lots
1130
+ };
537
1131
  }
538
1132
  /**
539
1133
  * Add `qty` to available stock at the given location. Used for
@@ -543,14 +1137,17 @@ var StockService = class StockService {
543
1137
  async receive(skuId, locationId, qty, options = {}) {
544
1138
  assertPositiveQty(qty, "receive");
545
1139
  await this.runAtomically(async (tx) => {
1140
+ const lotId = await requireLot(tx, skuId, options.lotId);
546
1141
  await adjustLevel(tx.levels, {
547
1142
  skuId,
548
1143
  locationId,
549
1144
  state: "available",
550
1145
  delta: qty
551
1146
  });
1147
+ await adjustLotLevel(tx, lotId, skuId, locationId, "available", qty);
552
1148
  await writeMovement(tx.movements, {
553
1149
  skuId,
1150
+ lotId,
554
1151
  locationId,
555
1152
  fromState: null,
556
1153
  toState: "available",
@@ -573,6 +1170,7 @@ var StockService = class StockService {
573
1170
  await this.runAtomically(async (tx) => {
574
1171
  await transitionState(tx, {
575
1172
  skuId,
1173
+ lotId: options.lotId,
576
1174
  locationId,
577
1175
  fromState: "available",
578
1176
  toState: "allocated",
@@ -595,6 +1193,7 @@ var StockService = class StockService {
595
1193
  await this.runAtomically(async (tx) => {
596
1194
  await transitionState(tx, {
597
1195
  skuId,
1196
+ lotId: options.lotId,
598
1197
  locationId,
599
1198
  fromState: "allocated",
600
1199
  toState: "available",
@@ -615,7 +1214,9 @@ var StockService = class StockService {
615
1214
  async fulfill(skuId, locationId, qty, options = {}) {
616
1215
  assertPositiveQty(qty, "fulfill");
617
1216
  await this.runAtomically(async (tx) => {
1217
+ const lotId = await requireLot(tx, skuId, options.lotId);
618
1218
  await assertAvailable(tx.levels, skuId, locationId, "allocated", qty);
1219
+ await assertLotAvailable(tx, lotId, skuId, locationId, "allocated", qty);
619
1220
  await adjustLevel(tx.levels, {
620
1221
  skuId,
621
1222
  locationId,
@@ -623,8 +1224,10 @@ var StockService = class StockService {
623
1224
  delta: -qty,
624
1225
  enforceNonNegative: false
625
1226
  });
1227
+ await adjustLotLevel(tx, lotId, skuId, locationId, "allocated", -qty);
626
1228
  await writeMovement(tx.movements, {
627
1229
  skuId,
1230
+ lotId,
628
1231
  locationId,
629
1232
  fromState: "allocated",
630
1233
  toState: null,
@@ -653,7 +1256,9 @@ var StockService = class StockService {
653
1256
  assertPositiveQty(qty, "transfer");
654
1257
  if (fromLocationId === toLocationId) throw new Error(`transfer: fromLocationId and toLocationId must differ (got ${fromLocationId})`);
655
1258
  await this.runAtomically(async (tx) => {
1259
+ const lotId = await requireLot(tx, skuId, options.lotId);
656
1260
  await assertAvailable(tx.levels, skuId, fromLocationId, "available", qty);
1261
+ await assertLotAvailable(tx, lotId, skuId, fromLocationId, "available", qty);
657
1262
  await adjustLevel(tx.levels, {
658
1263
  skuId,
659
1264
  locationId: fromLocationId,
@@ -661,8 +1266,10 @@ var StockService = class StockService {
661
1266
  delta: -qty,
662
1267
  enforceNonNegative: false
663
1268
  });
1269
+ await adjustLotLevel(tx, lotId, skuId, fromLocationId, "available", -qty);
664
1270
  await writeMovement(tx.movements, {
665
1271
  skuId,
1272
+ lotId,
666
1273
  locationId: fromLocationId,
667
1274
  fromState: "available",
668
1275
  toState: null,
@@ -679,8 +1286,10 @@ var StockService = class StockService {
679
1286
  state: "available",
680
1287
  delta: qty
681
1288
  });
1289
+ await adjustLotLevel(tx, lotId, skuId, toLocationId, "available", qty);
682
1290
  await writeMovement(tx.movements, {
683
1291
  skuId,
1292
+ lotId,
684
1293
  locationId: toLocationId,
685
1294
  fromState: null,
686
1295
  toState: "available",
@@ -734,7 +1343,11 @@ var StockService = class StockService {
734
1343
  if (!Number.isFinite(delta) || delta === 0) throw new Error(`adjust: delta must be a non-zero finite number (got ${delta})`);
735
1344
  const state = options.state ?? "available";
736
1345
  await this.runAtomically(async (tx) => {
737
- if (delta < 0) await assertAvailable(tx.levels, skuId, locationId, state, -delta);
1346
+ const lotId = await requireLot(tx, skuId, options.lotId);
1347
+ if (delta < 0) {
1348
+ await assertAvailable(tx.levels, skuId, locationId, state, -delta);
1349
+ await assertLotAvailable(tx, lotId, skuId, locationId, state, -delta);
1350
+ }
738
1351
  await adjustLevel(tx.levels, {
739
1352
  skuId,
740
1353
  locationId,
@@ -742,8 +1355,10 @@ var StockService = class StockService {
742
1355
  delta,
743
1356
  enforceNonNegative: false
744
1357
  });
1358
+ await adjustLotLevel(tx, lotId, skuId, locationId, state, delta);
745
1359
  await writeMovement(tx.movements, {
746
1360
  skuId,
1361
+ lotId,
747
1362
  locationId,
748
1363
  fromState: delta < 0 ? state : null,
749
1364
  toState: delta > 0 ? state : null,
@@ -762,8 +1377,43 @@ async function assertAvailable(levels, skuId, locationId, state, requested) {
762
1377
  const available = level ? Number(level.qty ?? 0) : 0;
763
1378
  if (available < requested) throw new InsufficientStockError(skuId, locationId, state, requested, available);
764
1379
  }
1380
+ async function requireLot(tx, skuId, lotId) {
1381
+ if (!lotId) return null;
1382
+ const lot = await tx.lots.get(lotId);
1383
+ if (!lot) throw new LotNotFoundError(lotId);
1384
+ if (lot.skuId && lot.skuId !== skuId) throw new LotMismatchError(lotId, lot.skuId, skuId);
1385
+ return lotId;
1386
+ }
1387
+ async function assertLotAvailable(tx, lotId, skuId, locationId, state, requested) {
1388
+ if (!lotId) return;
1389
+ const level = await tx.lotLevels.getLevel(lotId, locationId, state);
1390
+ const available = level ? Number(level.qty ?? 0) : 0;
1391
+ if (available < requested) throw new InsufficientStockError(skuId, locationId, state, requested, available, lotId);
1392
+ }
1393
+ async function adjustLotLevel(tx, lotId, skuId, locationId, state, delta) {
1394
+ if (!lotId) return;
1395
+ const existing = await tx.lotLevels.getLevel(lotId, locationId, state);
1396
+ const previous = existing ? Number(existing.qty ?? 0) : 0;
1397
+ const next = previous + delta;
1398
+ if (next < 0) throw new InsufficientStockError(skuId, locationId, state, Math.abs(delta), previous, lotId);
1399
+ if (existing) {
1400
+ existing.qty = next;
1401
+ await existing.save();
1402
+ return;
1403
+ }
1404
+ await tx.lotLevels.create({
1405
+ lotId,
1406
+ skuId,
1407
+ locationId,
1408
+ state,
1409
+ qty: next,
1410
+ _insertOnly: true
1411
+ });
1412
+ }
765
1413
  async function transitionState(tx, args) {
1414
+ const lotId = await requireLot(tx, args.skuId, args.lotId);
766
1415
  await assertAvailable(tx.levels, args.skuId, args.locationId, args.fromState, args.qty);
1416
+ await assertLotAvailable(tx, lotId, args.skuId, args.locationId, args.fromState, args.qty);
767
1417
  await adjustLevel(tx.levels, {
768
1418
  skuId: args.skuId,
769
1419
  locationId: args.locationId,
@@ -777,8 +1427,11 @@ async function transitionState(tx, args) {
777
1427
  state: args.toState,
778
1428
  delta: args.qty
779
1429
  });
1430
+ await adjustLotLevel(tx, lotId, args.skuId, args.locationId, args.fromState, -args.qty);
1431
+ await adjustLotLevel(tx, lotId, args.skuId, args.locationId, args.toState, args.qty);
780
1432
  await writeMovement(tx.movements, {
781
1433
  skuId: args.skuId,
1434
+ lotId,
782
1435
  locationId: args.locationId,
783
1436
  fromState: args.fromState,
784
1437
  toState: args.toState,
@@ -812,6 +1465,7 @@ async function adjustLevel(levels, options) {
812
1465
  async function writeMovement(movements, options) {
813
1466
  await movements.create({
814
1467
  skuId: options.skuId,
1468
+ lotId: options.lotId ?? null,
815
1469
  locationId: options.locationId,
816
1470
  fromState: options.fromState,
817
1471
  toState: options.toState,
@@ -938,6 +1592,489 @@ async function buildStockService(options) {
938
1592
  return createStockService({ db: options.db });
939
1593
  }
940
1594
  //#endregion
941
- export { InsufficientStockError, InventoryLocation, InventoryLocationCollection, StockLevel, StockLevelCollection, StockMovement, StockMovementCollection, StockRecipe, StockService, createStockService, installInventoryDispatchHandlers };
1595
+ //#region src/services/LotService.ts
1596
+ var MAX_LOT_NUMBER = 128;
1597
+ var MAX_IDENTITY_KEY = 160;
1598
+ var MAX_REASON = 1e3;
1599
+ var DEFAULT_TRACE_DEPTH = 10;
1600
+ var lastCorrectionMs = 0;
1601
+ function nextCorrectionTime() {
1602
+ lastCorrectionMs = Math.max(Date.now(), lastCorrectionMs + 1);
1603
+ return new Date(lastCorrectionMs);
1604
+ }
1605
+ function requireReason(reason) {
1606
+ const text = (reason ?? "").trim();
1607
+ if (!text) throw new LotCorrectionError("A reason is required");
1608
+ if (text.length > MAX_REASON) throw new LotCorrectionError(`Reason is over ${MAX_REASON} characters`);
1609
+ return text;
1610
+ }
1611
+ function normalizeLotNumber(lotNumber) {
1612
+ const text = (lotNumber ?? "").trim();
1613
+ if (!text) throw new Error("lotNumber is required");
1614
+ if (text.length > MAX_LOT_NUMBER) throw new Error(`lotNumber is over ${MAX_LOT_NUMBER} characters`);
1615
+ return text;
1616
+ }
1617
+ function numberIdentity(kind, skuId, searchKey) {
1618
+ return boundedIdentity(`u:${kind}:${skuId ?? ""}:${searchKey}`, "uh:");
1619
+ }
1620
+ async function boundedIdentity(key, hashPrefix) {
1621
+ if (key.length <= MAX_IDENTITY_KEY) return key;
1622
+ const digest = await globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(key));
1623
+ return `${hashPrefix}${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
1624
+ }
1625
+ function jsonOrEmpty(value) {
1626
+ return value === void 0 ? "" : JSON.stringify(value);
1627
+ }
1628
+ var LotService = class LotService {
1629
+ constructor(db, lots, usages, corrections, documents, movements, inTransaction = false) {
1630
+ this.db = db;
1631
+ this.lots = lots;
1632
+ this.usages = usages;
1633
+ this.corrections = corrections;
1634
+ this.documents = documents;
1635
+ this.movements = movements;
1636
+ this.inTransaction = inTransaction;
1637
+ }
1638
+ db;
1639
+ lots;
1640
+ usages;
1641
+ corrections;
1642
+ documents;
1643
+ movements;
1644
+ inTransaction;
1645
+ /** Internal factory — prefer {@link createLotService}. */
1646
+ static async create(options) {
1647
+ const resolved = await resolveDatabase(options.db);
1648
+ return new LotService(resolved, await LotCollection.create({ db: resolved }), await LotUsageCollection.create({ db: resolved }), await LotCorrectionCollection.create({ db: resolved }), await LotDocumentCollection.create({ db: resolved }), await StockMovementCollection.create({ db: resolved }));
1649
+ }
1650
+ /**
1651
+ * Run `work` in one database transaction with a tx-bound service. Nested
1652
+ * calls join the outer transaction. Same pattern as
1653
+ * {@link StockService.withTransaction}.
1654
+ *
1655
+ * A service built on a transaction handle (`createLotService({ db: tx.db })`)
1656
+ * opens a savepoint where the engine has them (PostgreSQL, SQLite). Where it
1657
+ * does not (DuckDB) the handle refuses with `NestedTransactionError` before
1658
+ * running anything, and `work` runs on the current handle instead, still
1659
+ * inside the caller's transaction.
1660
+ */
1661
+ async withTransaction(work) {
1662
+ if (this.inTransaction) return work(this);
1663
+ return this.scoped(work);
1664
+ }
1665
+ /**
1666
+ * Open a transaction (or savepoint) on this service's handle and run `work`
1667
+ * in it, even when already tx-bound. Falls back to the current handle when
1668
+ * the engine cannot nest.
1669
+ */
1670
+ async scoped(work) {
1671
+ const underlying = this.lots.db;
1672
+ if (typeof underlying?.transaction !== "function") return work(this);
1673
+ let entered = false;
1674
+ try {
1675
+ return await underlying.transaction(async (txDb) => {
1676
+ entered = true;
1677
+ const db = txDb;
1678
+ return work(new LotService(db, await LotCollection.create({ db }), await LotUsageCollection.create({ db }), await LotCorrectionCollection.create({ db }), await LotDocumentCollection.create({ db }), await StockMovementCollection.create({ db }), true));
1679
+ });
1680
+ } catch (error) {
1681
+ if (!entered && error instanceof NestedTransactionError) return work(this);
1682
+ throw error;
1683
+ }
1684
+ }
1685
+ /**
1686
+ * Record a lot or serial. Creates the identity only; it moves no stock
1687
+ * (see the module notes for composing with {@link StockService}).
1688
+ *
1689
+ * - Unique lots and all serials are keyed by SKU and number: receiving the
1690
+ * same number again throws {@link DuplicateLotError}.
1691
+ * - Otherwise a repeated `idempotencyKey` returns the lot already made.
1692
+ * - With neither, every call makes a new lot, even for a number already
1693
+ * seen: one supplier lot can arrive on two receipts.
1694
+ */
1695
+ async receiveLot(input) {
1696
+ const kind = input.kind ?? "lot";
1697
+ const lotNumber = normalizeLotNumber(input.lotNumber);
1698
+ const skuId = input.skuId ?? null;
1699
+ const unique = kind === "serial" ? true : input.unique ?? false;
1700
+ const searchKey = lotSearchKey(lotNumber);
1701
+ let identityKey;
1702
+ if (unique) identityKey = await numberIdentity(kind, skuId, searchKey);
1703
+ else if (input.idempotencyKey) identityKey = await boundedIdentity(`k:${input.idempotencyKey}`, "kh:");
1704
+ else identityKey = `r:${globalThis.crypto.randomUUID()}`;
1705
+ const existing = await this.findByIdentity(identityKey);
1706
+ if (existing) return this.resolveExisting(existing, unique, lotNumber);
1707
+ try {
1708
+ return await this.scoped((scope) => scope.lots.create({
1709
+ skuId,
1710
+ kind,
1711
+ lotNumber,
1712
+ searchKey,
1713
+ identityKey,
1714
+ uniqueNumber: unique,
1715
+ receivedQty: kind === "serial" ? 1 : input.receivedQty ?? 0,
1716
+ unit: input.unit ?? "",
1717
+ sourceType: input.sourceType ?? "",
1718
+ sourceId: input.sourceId ?? "",
1719
+ attributes: JSON.stringify(input.attributes ?? {}),
1720
+ status: input.status ?? "active",
1721
+ receivedAt: input.receivedAt ?? /* @__PURE__ */ new Date(),
1722
+ receivedByProfileId: input.actorProfileId ?? null,
1723
+ _insertOnly: true
1724
+ }));
1725
+ } catch (error) {
1726
+ if (!isUniqueViolationError(error)) throw error;
1727
+ const winner = await this.findByIdentity(identityKey);
1728
+ if (winner) return this.resolveExisting(winner, unique, lotNumber);
1729
+ throw error;
1730
+ }
1731
+ }
1732
+ resolveExisting(existing, unique, lotNumber) {
1733
+ if (unique) throw new DuplicateLotError(lotNumber, existing.id ?? null);
1734
+ return existing;
1735
+ }
1736
+ async findByIdentity(identityKey) {
1737
+ const tenantId = getCurrentTenant()?.tenantId ?? null;
1738
+ return (await this.lots.list({ where: { identityKey } })).find((row) => (row.tenantId ?? null) === tenantId) ?? null;
1739
+ }
1740
+ /**
1741
+ * Fix a mistyped lot number. Recomputes the lookup key and, for unique
1742
+ * lots and serials, the identity (a clash throws
1743
+ * {@link DuplicateLotError}). Refuses a change to the same number.
1744
+ */
1745
+ async correctLotNumber(lotId, lotNumber, options) {
1746
+ const reason = requireReason(options.reason);
1747
+ const next = normalizeLotNumber(lotNumber);
1748
+ return this.atomically(async (tx) => {
1749
+ const lot = await tx.requireLot(lotId);
1750
+ if (next === lot.lotNumber) throw new LotCorrectionError("The lot number is unchanged");
1751
+ const previous = lot.lotNumber;
1752
+ lot.lotNumber = next;
1753
+ lot.searchKey = lotSearchKey(next);
1754
+ if (lot.uniqueNumber) {
1755
+ lot.identityKey = await numberIdentity(lot.kind, lot.skuId, lot.searchKey);
1756
+ const clash = await tx.findByIdentity(lot.identityKey);
1757
+ if (clash && clash.id !== lot.id) throw new DuplicateLotError(next, clash.id ?? null);
1758
+ }
1759
+ try {
1760
+ await lot.save();
1761
+ } catch (error) {
1762
+ if (isUniqueViolationError(error)) throw new DuplicateLotError(next, null);
1763
+ throw error;
1764
+ }
1765
+ return {
1766
+ lot,
1767
+ correction: await tx.writeCorrection(lot, "lot_number", reason, {
1768
+ oldValue: previous,
1769
+ newValue: next,
1770
+ actorProfileId: options.actorProfileId
1771
+ })
1772
+ };
1773
+ });
1774
+ }
1775
+ /**
1776
+ * Change attributes. Each key in `patch` is set to its value, or removed
1777
+ * when `null`. One correction is written per key that actually changes;
1778
+ * refuses when none does. Values are stored as JSON.
1779
+ */
1780
+ async correctAttributes(lotId, patch, options) {
1781
+ const reason = requireReason(options.reason);
1782
+ return this.atomically(async (tx) => {
1783
+ const lot = await tx.requireLot(lotId);
1784
+ const attributes = lot.getAttributes();
1785
+ const changes = [];
1786
+ for (const [key, value] of Object.entries(patch)) {
1787
+ const before = jsonOrEmpty(attributes[key]);
1788
+ const after = value === null ? "" : jsonOrEmpty(value);
1789
+ if (before === after) continue;
1790
+ changes.push({
1791
+ key,
1792
+ before,
1793
+ after
1794
+ });
1795
+ if (value === null) delete attributes[key];
1796
+ else attributes[key] = value;
1797
+ }
1798
+ if (changes.length === 0) throw new LotCorrectionError("No attribute changes");
1799
+ lot.setAttributes(attributes);
1800
+ await lot.save();
1801
+ const corrections = [];
1802
+ for (const change of changes) corrections.push(await tx.writeCorrection(lot, "attribute", reason, {
1803
+ field: change.key,
1804
+ oldValue: change.before,
1805
+ newValue: change.after,
1806
+ actorProfileId: options.actorProfileId
1807
+ }));
1808
+ return {
1809
+ lot,
1810
+ corrections
1811
+ };
1812
+ });
1813
+ }
1814
+ /** Change status (for example to `quarantined`). Refuses an unchanged status. */
1815
+ async setStatus(lotId, status, options) {
1816
+ const reason = requireReason(options.reason);
1817
+ const next = status.trim();
1818
+ if (!next) throw new LotCorrectionError("A status is required");
1819
+ return this.atomically(async (tx) => {
1820
+ const lot = await tx.requireLot(lotId);
1821
+ if (lot.status === next) throw new LotCorrectionError("The status is unchanged");
1822
+ const previous = lot.status;
1823
+ lot.status = next;
1824
+ await lot.save();
1825
+ return {
1826
+ lot,
1827
+ correction: await tx.writeCorrection(lot, "status", reason, {
1828
+ oldValue: previous,
1829
+ newValue: next,
1830
+ actorProfileId: options.actorProfileId
1831
+ })
1832
+ };
1833
+ });
1834
+ }
1835
+ /**
1836
+ * Record that a consumer used the lot. Repeating the call for the same
1837
+ * consumer returns the active usage already on record, unchanged: a
1838
+ * different `qty` or `movementId` on the repeat is ignored. For an exclusive
1839
+ * lot (serials by default) a second consumer throws
1840
+ * {@link LotInUseError}. The lot row is saved so two concurrent usages
1841
+ * conflict on its revision instead of both landing.
1842
+ */
1843
+ async recordUsage(lotId, input) {
1844
+ const consumerType = (input.consumerType ?? "").trim();
1845
+ const consumerId = (input.consumerId ?? "").trim();
1846
+ if (!consumerType || !consumerId) throw new Error("recordUsage: consumerType and consumerId are required");
1847
+ if (consumerType === "Lot" && consumerId === lotId) throw new Error("recordUsage: a lot cannot be used by itself");
1848
+ return this.atomically(async (tx) => {
1849
+ const lot = await tx.requireLot(lotId);
1850
+ const active = await tx.usages.findActiveForLot(lotId);
1851
+ const same = active.find((usage2) => usage2.consumerType === consumerType && usage2.consumerId === consumerId);
1852
+ if (same) return same;
1853
+ const exclusive = input.exclusive ?? lot.kind === "serial";
1854
+ const other = active[0];
1855
+ if (exclusive && other) throw new LotInUseError(lotId, other.consumerType, other.consumerId);
1856
+ const usage = await tx.usages.create({
1857
+ lotId,
1858
+ consumerType,
1859
+ consumerId,
1860
+ qty: input.qty ?? null,
1861
+ unit: input.unit ?? "",
1862
+ status: "active",
1863
+ linkedAt: /* @__PURE__ */ new Date(),
1864
+ linkedByProfileId: input.actorProfileId ?? null,
1865
+ movementId: input.movementId ?? null
1866
+ });
1867
+ lot.lastUsedAt = /* @__PURE__ */ new Date();
1868
+ await lot.save();
1869
+ return usage;
1870
+ });
1871
+ }
1872
+ /**
1873
+ * Remove a mistaken usage. The row stays, marked `unlinked`, and a
1874
+ * correction records who removed it and why. Refuses a usage that is
1875
+ * already unlinked.
1876
+ */
1877
+ async unlinkUsage(usageId, options) {
1878
+ const reason = requireReason(options.reason);
1879
+ return this.atomically(async (tx) => {
1880
+ const usage = await tx.usages.get(usageId);
1881
+ if (!usage) throw new LotCorrectionError(`Usage not found: ${usageId}`);
1882
+ if (usage.status === "unlinked") throw new LotCorrectionError("The usage is already unlinked");
1883
+ const lot = await tx.requireLot(usage.lotId);
1884
+ usage.status = "unlinked";
1885
+ usage.unlinkedAt = /* @__PURE__ */ new Date();
1886
+ usage.unlinkedByProfileId = options.actorProfileId ?? null;
1887
+ usage.unlinkReason = reason;
1888
+ await usage.save();
1889
+ lot.lastUsedAt = /* @__PURE__ */ new Date();
1890
+ await lot.save();
1891
+ return {
1892
+ usage,
1893
+ correction: await tx.writeCorrection(lot, "unlink", reason, {
1894
+ oldValue: `${usage.consumerType}:${usage.consumerId}`,
1895
+ newValue: "",
1896
+ usageId: usage.id ?? void 0,
1897
+ actorProfileId: options.actorProfileId
1898
+ })
1899
+ };
1900
+ });
1901
+ }
1902
+ /** Attach a stored file to the lot. Repeating the same asset and role returns the active link. */
1903
+ async attachDocument(lotId, input) {
1904
+ const assetId = (input.assetId ?? "").trim();
1905
+ if (!assetId) throw new Error("attachDocument: assetId is required");
1906
+ const role = input.role ?? "certificate";
1907
+ return this.atomically(async (tx) => {
1908
+ const lot = await tx.requireLot(lotId);
1909
+ const active = (await tx.documents.list({ where: {
1910
+ lotId,
1911
+ assetId,
1912
+ role
1913
+ } })).find((doc) => !doc.detachedAt);
1914
+ if (active) return active;
1915
+ const document = await tx.documents.create({
1916
+ lotId,
1917
+ assetId,
1918
+ role,
1919
+ attachedAt: /* @__PURE__ */ new Date(),
1920
+ attachedByProfileId: input.actorProfileId ?? null
1921
+ });
1922
+ await lot.save();
1923
+ return document;
1924
+ });
1925
+ }
1926
+ /** Detach a document. The link stays on record with `detachedAt` set. */
1927
+ async detachDocument(documentId, options) {
1928
+ const reason = requireReason(options.reason);
1929
+ return this.atomically(async (tx) => {
1930
+ const document = await tx.documents.get(documentId);
1931
+ if (!document) throw new LotCorrectionError(`Document not found: ${documentId}`);
1932
+ if (document.detachedAt) throw new LotCorrectionError("The document is already detached");
1933
+ const lot = await tx.requireLot(document.lotId);
1934
+ document.detachedAt = /* @__PURE__ */ new Date();
1935
+ document.detachedByProfileId = options.actorProfileId ?? null;
1936
+ await document.save();
1937
+ return {
1938
+ document,
1939
+ correction: await tx.writeCorrection(lot, "document_detach", reason, {
1940
+ oldValue: document.assetId,
1941
+ newValue: "",
1942
+ documentId: document.id ?? void 0,
1943
+ actorProfileId: options.actorProfileId
1944
+ })
1945
+ };
1946
+ });
1947
+ }
1948
+ /** Every correction to the lot, oldest first. */
1949
+ async history(lotId) {
1950
+ return this.corrections.list({
1951
+ where: { lotId },
1952
+ orderBy: "correctedAt ASC"
1953
+ });
1954
+ }
1955
+ /**
1956
+ * Where a lot went: its usages, movements and documents, plus (as
1957
+ * `children`) the lots that consumed it, followed up to `depth` levels.
1958
+ *
1959
+ * Cost: each distinct lot in the trace takes four queries (usages,
1960
+ * movements, documents, and the lot itself). A lot reached by two paths
1961
+ * (a diamond) is expanded once and the same node object appears under both
1962
+ * parents. Bound large graphs with `depth`.
1963
+ */
1964
+ async traceForward(lotId, options = {}) {
1965
+ const depth = options.depth ?? DEFAULT_TRACE_DEPTH;
1966
+ const lot = await this.requireLot(lotId);
1967
+ return this.forwardNode(lot, options, depth, /* @__PURE__ */ new Set([lotId]), /* @__PURE__ */ new Map());
1968
+ }
1969
+ async forwardNode(lot, options, depth, path, memo) {
1970
+ const key = `${lot.id}:${depth}`;
1971
+ const known = memo.get(key);
1972
+ if (known) return known;
1973
+ const node = this.expandForward(lot, options, depth, path, memo);
1974
+ memo.set(key, node);
1975
+ return node;
1976
+ }
1977
+ async expandForward(lot, options, depth, path, memo) {
1978
+ const lotId = lot.id;
1979
+ const usages = await this.usages.list({
1980
+ where: options.includeUnlinked ? { lotId } : {
1981
+ lotId,
1982
+ status: "active"
1983
+ },
1984
+ orderBy: "linkedAt ASC"
1985
+ });
1986
+ const children = [];
1987
+ if (depth > 0) for (const usage of usages) {
1988
+ if (usage.consumerType !== "Lot" || path.has(usage.consumerId)) continue;
1989
+ const child = await this.lots.get(usage.consumerId);
1990
+ if (!child) continue;
1991
+ children.push(await this.forwardNode(child, options, depth - 1, /* @__PURE__ */ new Set([...path, usage.consumerId]), memo));
1992
+ }
1993
+ return {
1994
+ ...await this.nodeBase(lot, usages, options),
1995
+ children
1996
+ };
1997
+ }
1998
+ /**
1999
+ * Where a consumer's material came from: one node per lot it used, each
2000
+ * with (as `children`) the lots that went into that lot when it was
2001
+ * itself an assembly (consumer `('Lot', lotId)`), up to `depth` levels.
2002
+ *
2003
+ * Cost: one usage query per consumer visited plus, per lot, the lot,
2004
+ * movements and documents queries. A lot reached by two paths is expanded
2005
+ * once and shared. Bound large graphs with `depth`.
2006
+ */
2007
+ async traceBack(consumerType, consumerId, options = {}) {
2008
+ const depth = options.depth ?? DEFAULT_TRACE_DEPTH;
2009
+ return this.backNodes(consumerType, consumerId, options, depth, consumerType === "Lot" ? /* @__PURE__ */ new Set([consumerId]) : /* @__PURE__ */ new Set(), /* @__PURE__ */ new Map());
2010
+ }
2011
+ async backNodes(consumerType, consumerId, options, depth, path, memo) {
2012
+ const edges = await this.usages.findForConsumer(consumerType, consumerId, { includeUnlinked: options.includeUnlinked });
2013
+ const byLot = /* @__PURE__ */ new Map();
2014
+ for (const edge of edges) byLot.set(edge.lotId, [...byLot.get(edge.lotId) ?? [], edge]);
2015
+ const nodes = [];
2016
+ for (const [lotId, usages] of byLot) {
2017
+ if (path.has(lotId)) continue;
2018
+ const key = `${lotId}:${depth}:${usages.map((u) => u.id).join(",")}`;
2019
+ let node = memo.get(key);
2020
+ if (!node) {
2021
+ node = this.expandBack(lotId, usages, options, depth, path, memo);
2022
+ memo.set(key, node);
2023
+ }
2024
+ const resolved = await node;
2025
+ if (resolved) nodes.push(resolved);
2026
+ }
2027
+ return nodes;
2028
+ }
2029
+ async expandBack(lotId, usages, options, depth, path, memo) {
2030
+ const lot = await this.lots.get(lotId);
2031
+ if (!lot) return null;
2032
+ const children = depth > 0 ? await this.backNodes("Lot", lotId, options, depth - 1, /* @__PURE__ */ new Set([...path, lotId]), memo) : [];
2033
+ return {
2034
+ ...await this.nodeBase(lot, usages, options),
2035
+ children
2036
+ };
2037
+ }
2038
+ async nodeBase(lot, usages, options) {
2039
+ const lotId = lot.id;
2040
+ const [movements, documents] = await Promise.all([this.movements.list({
2041
+ where: { lotId },
2042
+ orderBy: "occurredAt ASC"
2043
+ }), this.documents.list({ where: { lotId } })]);
2044
+ return {
2045
+ lot,
2046
+ usages,
2047
+ movements,
2048
+ documents: options.includeUnlinked ? documents : documents.filter((doc) => !doc.detachedAt)
2049
+ };
2050
+ }
2051
+ atomically(work) {
2052
+ return this.withTransaction(work);
2053
+ }
2054
+ async requireLot(lotId) {
2055
+ const lot = await this.lots.get(lotId);
2056
+ if (!lot) throw new LotNotFoundError(lotId);
2057
+ return lot;
2058
+ }
2059
+ writeCorrection(lot, kind, reason, details) {
2060
+ return this.corrections.create({
2061
+ lotId: lot.id,
2062
+ kind,
2063
+ field: details.field ?? "",
2064
+ oldValue: details.oldValue,
2065
+ newValue: details.newValue,
2066
+ reason,
2067
+ usageId: details.usageId ?? null,
2068
+ documentId: details.documentId ?? null,
2069
+ actorProfileId: details.actorProfileId ?? null,
2070
+ correctedAt: nextCorrectionTime()
2071
+ });
2072
+ }
2073
+ };
2074
+ async function createLotService(options) {
2075
+ return LotService.create(options);
2076
+ }
2077
+ //#endregion
2078
+ export { DuplicateLotError, InsufficientStockError, InventoryLocation, InventoryLocationCollection, Lot, LotCollection, LotCorrection, LotCorrectionCollection, LotCorrectionError, LotDocument, LotDocumentCollection, LotInUseError, LotMismatchError, LotNotFoundError, LotService, LotStockLevel, LotStockLevelCollection, LotUsage, LotUsageCollection, StockLevel, StockLevelCollection, StockMovement, StockMovementCollection, StockRecipe, StockService, createLotService, createStockService, installInventoryDispatchHandlers, lotSearchKey };
942
2079
 
943
2080
  //# sourceMappingURL=index.js.map