@happyvertical/smrt-inventory 0.57.17 → 0.57.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +19 -1
- package/README.md +32 -2
- package/dist/collections/LotCollection.d.ts +22 -0
- package/dist/collections/LotCollection.d.ts.map +1 -0
- package/dist/collections/LotCorrectionCollection.d.ts +12 -0
- package/dist/collections/LotCorrectionCollection.d.ts.map +1 -0
- package/dist/collections/LotDocumentCollection.d.ts +12 -0
- package/dist/collections/LotDocumentCollection.d.ts.map +1 -0
- package/dist/collections/LotStockLevelCollection.d.ts +21 -0
- package/dist/collections/LotStockLevelCollection.d.ts.map +1 -0
- package/dist/collections/LotUsageCollection.d.ts +18 -0
- package/dist/collections/LotUsageCollection.d.ts.map +1 -0
- package/dist/collections/index.d.ts +5 -0
- package/dist/collections/index.d.ts.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1168 -31
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +2921 -288
- package/dist/models/Lot.d.ts +96 -0
- package/dist/models/Lot.d.ts.map +1 -0
- package/dist/models/LotCorrection.d.ts +55 -0
- package/dist/models/LotCorrection.d.ts.map +1 -0
- package/dist/models/LotDocument.d.ts +45 -0
- package/dist/models/LotDocument.d.ts.map +1 -0
- package/dist/models/LotStockLevel.d.ts +43 -0
- package/dist/models/LotStockLevel.d.ts.map +1 -0
- package/dist/models/LotUsage.d.ts +64 -0
- package/dist/models/LotUsage.d.ts.map +1 -0
- package/dist/models/StockMovement.d.ts +7 -0
- package/dist/models/StockMovement.d.ts.map +1 -1
- package/dist/models/index.d.ts +5 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/services/LotService.d.ts +258 -0
- package/dist/services/LotService.d.ts.map +1 -0
- package/dist/services/StockService.d.ts +25 -2
- package/dist/services/StockService.d.ts.map +1 -1
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/lot-errors.d.ts +44 -0
- package/dist/services/lot-errors.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +1333 -76
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -8
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.17","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.19","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$
|
|
9
|
-
var __getOwnPropDesc$
|
|
10
|
-
var __defNormalProp$
|
|
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$
|
|
17
|
-
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$
|
|
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$
|
|
20
|
+
if (kind && result) __defProp$7(target, key, result);
|
|
20
21
|
return result;
|
|
21
22
|
};
|
|
22
|
-
var __publicField$
|
|
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$
|
|
48
|
-
__decorateClass$
|
|
49
|
-
__decorateClass$
|
|
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$
|
|
54
|
-
__decorateClass$
|
|
55
|
-
InventoryLocation = __decorateClass$
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
534
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|