@axova/shared 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/CONFIGURATION_GUIDE.md +1 -0
  2. package/README.md +384 -0
  3. package/SCHEMA_ORGANIZATION.md +209 -0
  4. package/dist/configs/index.d.ts +85 -0
  5. package/dist/configs/index.js +555 -0
  6. package/dist/events/kafka.d.ts +40 -0
  7. package/dist/events/kafka.js +311 -0
  8. package/dist/index.d.ts +13 -0
  9. package/dist/index.js +41 -0
  10. package/dist/interfaces/customer-events.d.ts +85 -0
  11. package/dist/interfaces/customer-events.js +2 -0
  12. package/dist/interfaces/inventory-events.d.ts +453 -0
  13. package/dist/interfaces/inventory-events.js +3 -0
  14. package/dist/interfaces/inventory-types.d.ts +894 -0
  15. package/dist/interfaces/inventory-types.js +3 -0
  16. package/dist/interfaces/order-events.d.ts +320 -0
  17. package/dist/interfaces/order-events.js +3 -0
  18. package/dist/lib/auditLogger.d.ts +162 -0
  19. package/dist/lib/auditLogger.js +626 -0
  20. package/dist/lib/authOrganization.d.ts +24 -0
  21. package/dist/lib/authOrganization.js +110 -0
  22. package/dist/lib/db.d.ts +6 -0
  23. package/dist/lib/db.js +88 -0
  24. package/dist/middleware/serviceAuth.d.ts +60 -0
  25. package/dist/middleware/serviceAuth.js +272 -0
  26. package/dist/middleware/storeOwnership.d.ts +15 -0
  27. package/dist/middleware/storeOwnership.js +156 -0
  28. package/dist/middleware/storeValidationMiddleware.d.ts +44 -0
  29. package/dist/middleware/storeValidationMiddleware.js +180 -0
  30. package/dist/middleware/userAuth.d.ts +27 -0
  31. package/dist/middleware/userAuth.js +218 -0
  32. package/dist/schemas/admin/admin-schema.d.ts +741 -0
  33. package/dist/schemas/admin/admin-schema.js +111 -0
  34. package/dist/schemas/ai-moderation/ai-moderation-schema.d.ts +648 -0
  35. package/dist/schemas/ai-moderation/ai-moderation-schema.js +88 -0
  36. package/dist/schemas/common/common-schemas.d.ts +436 -0
  37. package/dist/schemas/common/common-schemas.js +94 -0
  38. package/dist/schemas/compliance/compliance-schema.d.ts +3388 -0
  39. package/dist/schemas/compliance/compliance-schema.js +472 -0
  40. package/dist/schemas/compliance/kyc-schema.d.ts +2642 -0
  41. package/dist/schemas/compliance/kyc-schema.js +361 -0
  42. package/dist/schemas/customer/customer-schema.d.ts +2727 -0
  43. package/dist/schemas/customer/customer-schema.js +399 -0
  44. package/dist/schemas/index.d.ts +27 -0
  45. package/dist/schemas/index.js +138 -0
  46. package/dist/schemas/inventory/inventory-tables.d.ts +9476 -0
  47. package/dist/schemas/inventory/inventory-tables.js +1470 -0
  48. package/dist/schemas/inventory/lot-tables.d.ts +3281 -0
  49. package/dist/schemas/inventory/lot-tables.js +608 -0
  50. package/dist/schemas/order/order-schema.d.ts +5825 -0
  51. package/dist/schemas/order/order-schema.js +954 -0
  52. package/dist/schemas/product/discount-relations.d.ts +15 -0
  53. package/dist/schemas/product/discount-relations.js +34 -0
  54. package/dist/schemas/product/discount-schema.d.ts +1975 -0
  55. package/dist/schemas/product/discount-schema.js +297 -0
  56. package/dist/schemas/product/product-relations.d.ts +41 -0
  57. package/dist/schemas/product/product-relations.js +133 -0
  58. package/dist/schemas/product/product-schema.d.ts +4544 -0
  59. package/dist/schemas/product/product-schema.js +671 -0
  60. package/dist/schemas/store/store-audit-schema.d.ts +4135 -0
  61. package/dist/schemas/store/store-audit-schema.js +556 -0
  62. package/dist/schemas/store/store-schema.d.ts +3100 -0
  63. package/dist/schemas/store/store-schema.js +381 -0
  64. package/dist/schemas/store/store-settings-schema.d.ts +665 -0
  65. package/dist/schemas/store/store-settings-schema.js +141 -0
  66. package/dist/schemas/types.d.ts +50 -0
  67. package/dist/schemas/types.js +3 -0
  68. package/dist/types/events.d.ts +2396 -0
  69. package/dist/types/events.js +505 -0
  70. package/dist/utils/errorHandler.d.ts +12 -0
  71. package/dist/utils/errorHandler.js +36 -0
  72. package/dist/utils/subdomain.d.ts +6 -0
  73. package/dist/utils/subdomain.js +20 -0
  74. package/nul +8 -0
  75. package/package.json +43 -0
  76. package/src/configs/index.ts +654 -0
  77. package/src/events/kafka.ts +429 -0
  78. package/src/index.ts +26 -0
  79. package/src/interfaces/customer-events.ts +106 -0
  80. package/src/interfaces/inventory-events.ts +545 -0
  81. package/src/interfaces/inventory-types.ts +1004 -0
  82. package/src/interfaces/order-events.ts +381 -0
  83. package/src/lib/auditLogger.ts +1117 -0
  84. package/src/lib/authOrganization.ts +153 -0
  85. package/src/lib/db.ts +64 -0
  86. package/src/middleware/serviceAuth.ts +328 -0
  87. package/src/middleware/storeOwnership.ts +199 -0
  88. package/src/middleware/storeValidationMiddleware.ts +247 -0
  89. package/src/middleware/userAuth.ts +248 -0
  90. package/src/schemas/admin/admin-schema.ts +208 -0
  91. package/src/schemas/ai-moderation/ai-moderation-schema.ts +180 -0
  92. package/src/schemas/common/common-schemas.ts +108 -0
  93. package/src/schemas/compliance/compliance-schema.ts +927 -0
  94. package/src/schemas/compliance/kyc-schema.ts +649 -0
  95. package/src/schemas/customer/customer-schema.ts +576 -0
  96. package/src/schemas/index.ts +189 -0
  97. package/src/schemas/inventory/inventory-tables.ts +1927 -0
  98. package/src/schemas/inventory/lot-tables.ts +799 -0
  99. package/src/schemas/order/order-schema.ts +1400 -0
  100. package/src/schemas/product/discount-relations.ts +44 -0
  101. package/src/schemas/product/discount-schema.ts +464 -0
  102. package/src/schemas/product/product-relations.ts +187 -0
  103. package/src/schemas/product/product-schema.ts +955 -0
  104. package/src/schemas/store/ethiopian_business_api.md.resolved +212 -0
  105. package/src/schemas/store/store-audit-schema.ts +1257 -0
  106. package/src/schemas/store/store-schema.ts +661 -0
  107. package/src/schemas/store/store-settings-schema.ts +231 -0
  108. package/src/schemas/types.ts +67 -0
  109. package/src/types/events.ts +646 -0
  110. package/src/utils/errorHandler.ts +44 -0
  111. package/src/utils/subdomain.ts +19 -0
  112. package/tsconfig.json +21 -0
@@ -0,0 +1,608 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LotAnalyticsSchema = exports.GetAvailableLotsSchema = exports.GetLotsQuerySchema = exports.SplitLotSchema = exports.MergeLotsSchema = exports.TransferLotSchema = exports.AdjustLotQuantitySchema = exports.ReleaseLotReservationSchema = exports.ReserveLotQuantitySchema = exports.CreateLotSchema = exports.InitializeProductInventorySchema = exports.LotSchema = exports.inventoryLotAlerts = exports.inventoryLotMerges = exports.inventoryLotSplits = exports.inventoryLotMovements = exports.inventoryLots = exports.alertStatusEnum = exports.alertSeverityEnum = exports.alertTypeEnum = exports.lotMovementTypeEnum = exports.qcStatusEnum = exports.lotStatusEnum = void 0;
4
+ const cuid2_1 = require("@paralleldrive/cuid2");
5
+ const pg_core_1 = require("drizzle-orm/pg-core");
6
+ // =====================================================
7
+ // LOT & BATCH MANAGEMENT ENUMS
8
+ // =====================================================
9
+ exports.lotStatusEnum = (0, pg_core_1.pgEnum)("lot_status", [
10
+ "PO_INCOMING", // Incoming from purchase order, not yet received
11
+ "PENDING", // Just received, not yet QC'd
12
+ "ACTIVE", // Available for use
13
+ "QUARANTINED", // Held for investigation
14
+ "ALLOCATED", // Reserved for orders
15
+ "EXPIRED", // Past expiry date
16
+ "RECALLED", // Subject to recall
17
+ "DEPLETED", // Fully consumed
18
+ "RETURNED", // Returned to supplier
19
+ "DISPOSED", // Disposed/destroyed
20
+ ]);
21
+ exports.qcStatusEnum = (0, pg_core_1.pgEnum)("qc_status", [
22
+ "NOT_REQUIRED",
23
+ "PENDING",
24
+ "PASSED",
25
+ "FAILED",
26
+ ]);
27
+ // Quality status is handled via relation to quality_controls table
28
+ // We don't need a separate enum for lots
29
+ exports.lotMovementTypeEnum = (0, pg_core_1.pgEnum)("lot_movement_type", [
30
+ "RECEIPT",
31
+ "SALE",
32
+ "TRANSFER",
33
+ "ADJUSTMENT",
34
+ "ADJUSTMENT_INCREASE",
35
+ "ADJUSTMENT_DECREASE",
36
+ "RETURN_FROM_CUSTOMER",
37
+ "RETURN_TO_SUPPLIER",
38
+ "QUARANTINE",
39
+ "RELEASE",
40
+ "SPLIT",
41
+ "MERGE",
42
+ "DISPOSAL",
43
+ "RECALL",
44
+ "EXPIRY",
45
+ "PRODUCTION_USE",
46
+ "SAMPLE",
47
+ "DAMAGED",
48
+ "RESERVE",
49
+ "UNRESERVE",
50
+ "CYCLE_COUNT_ADJUSTMENT",
51
+ "PO_INCOMING",
52
+ ]);
53
+ exports.alertTypeEnum = (0, pg_core_1.pgEnum)("alert_type", [
54
+ "EXPIRY_WARNING",
55
+ "EXPIRING_SOON",
56
+ "EXPIRED",
57
+ "LOW_QUANTITY",
58
+ "QUALITY_ISSUE",
59
+ "RECALL_REQUIRED",
60
+ "TEMPERATURE_BREACH",
61
+ "STORAGE_VIOLATION",
62
+ "COMPLIANCE_DUE",
63
+ ]);
64
+ exports.alertSeverityEnum = (0, pg_core_1.pgEnum)("alert_severity", [
65
+ "INFO",
66
+ "WARNING",
67
+ "CRITICAL",
68
+ ]);
69
+ exports.alertStatusEnum = (0, pg_core_1.pgEnum)("alert_status", [
70
+ "PENDING",
71
+ "ACKNOWLEDGED",
72
+ "RESOLVED",
73
+ "DISMISSED",
74
+ ]);
75
+ // =====================================================
76
+ // INVENTORY LOTS TABLE
77
+ // =====================================================
78
+ exports.inventoryLots = (0, pg_core_1.pgTable)("inventory_lots", {
79
+ // Identification
80
+ id: (0, pg_core_1.text)("id")
81
+ .primaryKey()
82
+ .$defaultFn(() => (0, cuid2_1.createId)()),
83
+ storeId: (0, pg_core_1.text)("store_id").notNull(),
84
+ lotNumber: (0, pg_core_1.varchar)("lot_number", { length: 255 }).notNull(),
85
+ batchNumber: (0, pg_core_1.varchar)("batch_number", { length: 255 }),
86
+ internalCode: (0, pg_core_1.varchar)("internal_code", { length: 255 }),
87
+ // Product Association
88
+ productId: (0, pg_core_1.text)("product_id").notNull(),
89
+ variantId: (0, pg_core_1.text)("variant_id"),
90
+ sku: (0, pg_core_1.varchar)("sku", { length: 255 }).notNull(),
91
+ // Barcode (unique identifier for scanning and tracking)
92
+ barcode: (0, pg_core_1.varchar)("barcode", { length: 255 }),
93
+ // Location Information (REQUIRED - Lots are location-based)
94
+ locationId: (0, pg_core_1.text)("location_id").notNull(), // Warehouse or POS location ID
95
+ locationType: (0, pg_core_1.varchar)("location_type", { length: 20 }).notNull(), // WAREHOUSE or POS
96
+ // Supplier Information
97
+ supplierId: (0, pg_core_1.text)("supplier_id"),
98
+ supplierLotNumber: (0, pg_core_1.varchar)("supplier_lot_number", { length: 255 }),
99
+ supplierBatchNumber: (0, pg_core_1.varchar)("supplier_batch_number", { length: 255 }),
100
+ supplierInvoiceNumber: (0, pg_core_1.varchar)("supplier_invoice_number", { length: 255 }),
101
+ // Quantities
102
+ originalQuantity: (0, pg_core_1.integer)("original_quantity").notNull(),
103
+ currentQuantity: (0, pg_core_1.integer)("current_quantity").notNull(),
104
+ allocatedQuantity: (0, pg_core_1.integer)("allocated_quantity").notNull().default(0),
105
+ quarantinedQuantity: (0, pg_core_1.integer)("quarantined_quantity").notNull().default(0),
106
+ damagedQuantity: (0, pg_core_1.integer)("damaged_quantity").notNull().default(0),
107
+ soldQuantity: (0, pg_core_1.integer)("sold_quantity").notNull().default(0),
108
+ returnedQuantity: (0, pg_core_1.integer)("returned_quantity").notNull().default(0),
109
+ // Enhanced Inventory Management Fields
110
+ availableQuantity: (0, pg_core_1.integer)("available_quantity").notNull().default(0), // currentQuantity - allocatedQuantity - quarantinedQuantity - damagedQuantity
111
+ reservedQuantity: (0, pg_core_1.integer)("reserved_quantity").notNull().default(0), // Reserved for specific orders/customers
112
+ // Stock Level Management
113
+ reorderPoint: (0, pg_core_1.integer)("reorder_point"), // Minimum quantity before reorder alert
114
+ maxStockLevel: (0, pg_core_1.integer)("max_stock_level"), // Maximum allowed quantity
115
+ safetyStock: (0, pg_core_1.integer)("safety_stock").notNull().default(0), // Buffer quantity
116
+ // Unit of Measure
117
+ unitOfMeasure: (0, pg_core_1.varchar)("unit_of_measure", { length: 50 }).notNull().default("EACH"), // EACH, CASE, PALLET, etc.
118
+ // Serial Number Tracking
119
+ trackSerial: (0, pg_core_1.boolean)("track_serial").notNull().default(false), // Enable serial number tracking
120
+ serialNumbers: (0, pg_core_1.jsonb)("serial_numbers").$type(), // Individual serial numbers when trackSerial=true
121
+ // Initial Inventory Flag
122
+ isInitialInventory: (0, pg_core_1.boolean)("is_initial_inventory").notNull().default(false), // Marks lots created during product setup
123
+ // Dates
124
+ manufactureDate: (0, pg_core_1.date)("manufacture_date"),
125
+ expiryDate: (0, pg_core_1.date)("expiry_date"),
126
+ bestBeforeDate: (0, pg_core_1.date)("best_before_date"),
127
+ receivedDate: (0, pg_core_1.timestamp)("received_date", { withTimezone: true }).notNull(),
128
+ firstUseDate: (0, pg_core_1.timestamp)("first_use_date", { withTimezone: true }),
129
+ lastMovementDate: (0, pg_core_1.timestamp)("last_movement_date", { withTimezone: true }),
130
+ // Status
131
+ status: (0, exports.lotStatusEnum)("status").notNull().default("PENDING"),
132
+ qcStatus: (0, exports.qcStatusEnum)("qc_status").notNull().default("NOT_REQUIRED"),
133
+ // Quality Control (via relation to quality_controls table)
134
+ qualityControlId: (0, pg_core_1.text)("quality_control_id"), // FK to quality_controls
135
+ lastQcDate: (0, pg_core_1.timestamp)("last_qc_date", { withTimezone: true }),
136
+ // Traceability
137
+ parentLotId: (0, pg_core_1.text)("parent_lot_id"),
138
+ genealogy: (0, pg_core_1.jsonb)("genealogy").$type(),
139
+ splitCount: (0, pg_core_1.integer)("split_count").notNull().default(0),
140
+ // Quality Control metadata (detailed QC in quality_controls table)
141
+ qcCertificateNumber: (0, pg_core_1.varchar)("qc_certificate_number", { length: 255 }),
142
+ qcNotes: (0, pg_core_1.text)("qc_notes"),
143
+ // Compliance
144
+ regulatoryCompliance: (0, pg_core_1.jsonb)("regulatory_compliance").$type(),
145
+ certificates: (0, pg_core_1.jsonb)("certificates").$type(),
146
+ requiresRecall: (0, pg_core_1.boolean)("requires_recall").notNull().default(false),
147
+ recallDate: (0, pg_core_1.timestamp)("recall_date", { withTimezone: true }),
148
+ recallReason: (0, pg_core_1.text)("recall_reason"),
149
+ // Storage Conditions
150
+ storageLocationId: (0, pg_core_1.text)("storage_location_id"),
151
+ storageZone: (0, pg_core_1.varchar)("storage_zone", { length: 255 }),
152
+ storageTempMin: (0, pg_core_1.decimal)("storage_temp_min", { precision: 5, scale: 2 }),
153
+ storageTempMax: (0, pg_core_1.decimal)("storage_temp_max", { precision: 5, scale: 2 }),
154
+ storageHumidityMin: (0, pg_core_1.decimal)("storage_humidity_min", { precision: 5, scale: 2 }),
155
+ storageHumidityMax: (0, pg_core_1.decimal)("storage_humidity_max", { precision: 5, scale: 2 }),
156
+ specialHandling: (0, pg_core_1.jsonb)("special_handling").$type(),
157
+ // Cost & Value
158
+ unitCost: (0, pg_core_1.decimal)("unit_cost", { precision: 15, scale: 4 }),
159
+ landedCost: (0, pg_core_1.decimal)("landed_cost", { precision: 15, scale: 4 }),
160
+ totalCost: (0, pg_core_1.decimal)("total_cost", { precision: 15, scale: 4 }),
161
+ currency: (0, pg_core_1.varchar)("currency", { length: 3 }).notNull().default("USD"),
162
+ // Alerts & Notifications
163
+ expiryAlertDays: (0, pg_core_1.integer)("expiry_alert_days").notNull().default(30),
164
+ lowQuantityAlert: (0, pg_core_1.integer)("low_quantity_alert"),
165
+ alertRecipients: (0, pg_core_1.jsonb)("alert_recipients").$type(),
166
+ // Metadata
167
+ customAttributes: (0, pg_core_1.jsonb)("custom_attributes").$type().default({}),
168
+ tags: (0, pg_core_1.text)("tags").array(),
169
+ notes: (0, pg_core_1.text)("notes"),
170
+ // Timestamps
171
+ createdAt: (0, pg_core_1.timestamp)("created_at", { withTimezone: true }).defaultNow().notNull(),
172
+ updatedAt: (0, pg_core_1.timestamp)("updated_at", { withTimezone: true }).defaultNow().notNull(),
173
+ createdBy: (0, pg_core_1.text)("created_by"),
174
+ updatedBy: (0, pg_core_1.text)("updated_by"),
175
+ // Soft Delete
176
+ isActive: (0, pg_core_1.boolean)("is_active").notNull().default(true),
177
+ deletedAt: (0, pg_core_1.timestamp)("deleted_at", { withTimezone: true }),
178
+ deletedBy: (0, pg_core_1.text)("deleted_by"),
179
+ }, (table) => ({
180
+ storeIdIndex: (0, pg_core_1.index)("idx_lots_store_id").on(table.storeId),
181
+ lotNumberIndex: (0, pg_core_1.index)("idx_lots_lot_number").on(table.lotNumber),
182
+ productIdIndex: (0, pg_core_1.index)("idx_lots_product_id").on(table.productId),
183
+ skuIndex: (0, pg_core_1.index)("idx_lots_sku").on(table.sku),
184
+ barcodeIndex: (0, pg_core_1.index)("idx_lots_barcode").on(table.barcode),
185
+ statusIndex: (0, pg_core_1.index)("idx_lots_status").on(table.status),
186
+ expiryDateIndex: (0, pg_core_1.index)("idx_lots_expiry_date").on(table.expiryDate),
187
+ qualityControlIdIndex: (0, pg_core_1.index)("idx_lots_quality_control_id").on(table.qualityControlId),
188
+ locationIndex: (0, pg_core_1.index)("idx_lots_location").on(table.locationId, table.locationType),
189
+ // New Inventory Management Indexes
190
+ availableQuantityIndex: (0, pg_core_1.index)("idx_lots_available_quantity").on(table.availableQuantity),
191
+ reorderPointIndex: (0, pg_core_1.index)("idx_lots_reorder_point").on(table.reorderPoint),
192
+ isInitialInventoryIndex: (0, pg_core_1.index)("idx_lots_is_initial_inventory").on(table.isInitialInventory),
193
+ storeProductLocationIndex: (0, pg_core_1.index)("idx_lots_store_product_location").on(table.storeId, table.productId, table.locationId),
194
+ // Unique Constraints
195
+ uniqueLotNumber: (0, pg_core_1.unique)("idx_lots_lot_number_unique").on(table.storeId, table.lotNumber),
196
+ uniqueBarcode: (0, pg_core_1.unique)("idx_lots_barcode_unique").on(table.storeId, table.barcode),
197
+ }));
198
+ // =====================================================
199
+ // INVENTORY LOT MOVEMENTS TABLE
200
+ // =====================================================
201
+ exports.inventoryLotMovements = (0, pg_core_1.pgTable)("inventory_lot_movements", {
202
+ id: (0, pg_core_1.text)("id")
203
+ .primaryKey()
204
+ .$defaultFn(() => (0, cuid2_1.createId)()),
205
+ lotId: (0, pg_core_1.text)("lot_id").notNull(),
206
+ storeId: (0, pg_core_1.text)("store_id").notNull(),
207
+ // Movement Details
208
+ movementType: (0, exports.lotMovementTypeEnum)("movement_type").notNull(),
209
+ quantityMoved: (0, pg_core_1.integer)("quantity_moved").notNull(),
210
+ quantityBefore: (0, pg_core_1.integer)("quantity_before").notNull(),
211
+ quantityAfter: (0, pg_core_1.integer)("quantity_after").notNull(),
212
+ // Source & Destination
213
+ fromLocationId: (0, pg_core_1.text)("from_location_id"),
214
+ toLocationId: (0, pg_core_1.text)("to_location_id"),
215
+ fromStatus: (0, exports.lotStatusEnum)("from_status"),
216
+ toStatus: (0, exports.lotStatusEnum)("to_status"),
217
+ // Related Transactions
218
+ orderId: (0, pg_core_1.text)("order_id"),
219
+ transferId: (0, pg_core_1.text)("transfer_id"),
220
+ adjustmentId: (0, pg_core_1.text)("adjustment_id"),
221
+ returnId: (0, pg_core_1.text)("return_id"),
222
+ // User & Context
223
+ performedBy: (0, pg_core_1.text)("performed_by").notNull(),
224
+ reason: (0, pg_core_1.text)("reason"),
225
+ notes: (0, pg_core_1.text)("notes"),
226
+ // Timestamps
227
+ movementDate: (0, pg_core_1.timestamp)("movement_date", { withTimezone: true }).defaultNow().notNull(),
228
+ createdAt: (0, pg_core_1.timestamp)("created_at", { withTimezone: true }).defaultNow().notNull(),
229
+ }, (table) => ({
230
+ lotIdIndex: (0, pg_core_1.index)("idx_lot_movements_lot_id").on(table.lotId),
231
+ storeIdIndex: (0, pg_core_1.index)("idx_lot_movements_store_id").on(table.storeId),
232
+ movementDateIndex: (0, pg_core_1.index)("idx_lot_movements_date").on(table.movementDate),
233
+ movementTypeIndex: (0, pg_core_1.index)("idx_lot_movements_type").on(table.movementType),
234
+ }));
235
+ // =====================================================
236
+ // INVENTORY LOT SPLITS TABLE
237
+ // =====================================================
238
+ exports.inventoryLotSplits = (0, pg_core_1.pgTable)("inventory_lot_splits", {
239
+ id: (0, pg_core_1.text)("id")
240
+ .primaryKey()
241
+ .$defaultFn(() => (0, cuid2_1.createId)()),
242
+ parentLotId: (0, pg_core_1.text)("parent_lot_id").notNull(),
243
+ // Split Details
244
+ splitDate: (0, pg_core_1.timestamp)("split_date", { withTimezone: true }).notNull(),
245
+ splitReason: (0, pg_core_1.text)("split_reason"),
246
+ originalQuantity: (0, pg_core_1.integer)("original_quantity").notNull(),
247
+ // Child Lots
248
+ childLots: (0, pg_core_1.jsonb)("child_lots").$type().notNull(),
249
+ // User
250
+ performedBy: (0, pg_core_1.text)("performed_by").notNull(),
251
+ notes: (0, pg_core_1.text)("notes"),
252
+ createdAt: (0, pg_core_1.timestamp)("created_at", { withTimezone: true }).defaultNow().notNull(),
253
+ }, (table) => ({
254
+ parentLotIdIndex: (0, pg_core_1.index)("idx_lot_splits_parent_lot_id").on(table.parentLotId),
255
+ splitDateIndex: (0, pg_core_1.index)("idx_lot_splits_date").on(table.splitDate),
256
+ }));
257
+ // =====================================================
258
+ // INVENTORY LOT MERGES TABLE
259
+ // =====================================================
260
+ exports.inventoryLotMerges = (0, pg_core_1.pgTable)("inventory_lot_merges", {
261
+ id: (0, pg_core_1.text)("id")
262
+ .primaryKey()
263
+ .$defaultFn(() => (0, cuid2_1.createId)()),
264
+ targetLotId: (0, pg_core_1.text)("target_lot_id").notNull(),
265
+ // Merge Details
266
+ mergeDate: (0, pg_core_1.timestamp)("merge_date", { withTimezone: true }).notNull(),
267
+ mergeReason: (0, pg_core_1.text)("merge_reason"),
268
+ // Source Lots
269
+ sourceLots: (0, pg_core_1.jsonb)("source_lots").$type().notNull(),
270
+ totalQuantity: (0, pg_core_1.integer)("total_quantity").notNull(),
271
+ // Validation
272
+ compatibilityCheck: (0, pg_core_1.jsonb)("compatibility_check").$type(),
273
+ approvedBy: (0, pg_core_1.text)("approved_by"),
274
+ // User
275
+ performedBy: (0, pg_core_1.text)("performed_by").notNull(),
276
+ notes: (0, pg_core_1.text)("notes"),
277
+ createdAt: (0, pg_core_1.timestamp)("created_at", { withTimezone: true }).defaultNow().notNull(),
278
+ }, (table) => ({
279
+ targetLotIdIndex: (0, pg_core_1.index)("idx_lot_merges_target_lot_id").on(table.targetLotId),
280
+ mergeDateIndex: (0, pg_core_1.index)("idx_lot_merges_date").on(table.mergeDate),
281
+ }));
282
+ // =====================================================
283
+ // INVENTORY LOT ALERTS TABLE
284
+ // =====================================================
285
+ exports.inventoryLotAlerts = (0, pg_core_1.pgTable)("inventory_lot_alerts", {
286
+ id: (0, pg_core_1.text)("id")
287
+ .primaryKey()
288
+ .$defaultFn(() => (0, cuid2_1.createId)()),
289
+ lotId: (0, pg_core_1.text)("lot_id").notNull(),
290
+ storeId: (0, pg_core_1.text)("store_id").notNull(),
291
+ // Alert Details
292
+ alertType: (0, exports.alertTypeEnum)("alert_type").notNull(),
293
+ severity: (0, exports.alertSeverityEnum)("severity").notNull(),
294
+ message: (0, pg_core_1.text)("message").notNull(),
295
+ // Status
296
+ status: (0, exports.alertStatusEnum)("status").notNull().default("PENDING"),
297
+ acknowledged: (0, pg_core_1.boolean)("acknowledged").notNull().default(false),
298
+ acknowledgedBy: (0, pg_core_1.text)("acknowledged_by"),
299
+ acknowledgedAt: (0, pg_core_1.timestamp)("acknowledged_at", { withTimezone: true }),
300
+ // Resolution
301
+ resolved: (0, pg_core_1.boolean)("resolved").notNull().default(false),
302
+ resolvedBy: (0, pg_core_1.text)("resolved_by"),
303
+ resolvedAt: (0, pg_core_1.timestamp)("resolved_at", { withTimezone: true }),
304
+ resolutionNotes: (0, pg_core_1.text)("resolution_notes"),
305
+ // Notification
306
+ notificationSent: (0, pg_core_1.boolean)("notification_sent").notNull().default(false),
307
+ notificationChannels: (0, pg_core_1.jsonb)("notification_channels").$type(),
308
+ recipients: (0, pg_core_1.jsonb)("recipients").$type(),
309
+ createdAt: (0, pg_core_1.timestamp)("created_at", { withTimezone: true }).defaultNow().notNull(),
310
+ }, (table) => ({
311
+ lotIdIndex: (0, pg_core_1.index)("idx_lot_alerts_lot_id").on(table.lotId),
312
+ storeIdIndex: (0, pg_core_1.index)("idx_lot_alerts_store_id").on(table.storeId),
313
+ statusIndex: (0, pg_core_1.index)("idx_lot_alerts_status").on(table.status),
314
+ alertTypeIndex: (0, pg_core_1.index)("idx_lot_alerts_type").on(table.alertType),
315
+ severityIndex: (0, pg_core_1.index)("idx_lot_alerts_severity").on(table.severity),
316
+ }));
317
+ // =====================================================
318
+ // ZOD VALIDATION SCHEMAS
319
+ // =====================================================
320
+ const zod_1 = require("zod");
321
+ // Base Lot Schema
322
+ exports.LotSchema = zod_1.z.object({
323
+ id: zod_1.z.string().cuid2(),
324
+ storeId: zod_1.z.string(),
325
+ lotNumber: zod_1.z.string(),
326
+ batchNumber: zod_1.z.string().optional(),
327
+ internalCode: zod_1.z.string().optional(),
328
+ // Product Association
329
+ productId: zod_1.z.string(),
330
+ variantId: zod_1.z.string().optional(),
331
+ sku: zod_1.z.string(),
332
+ // Location
333
+ locationId: zod_1.z.string(),
334
+ locationType: zod_1.z.enum(["WAREHOUSE", "POS"]),
335
+ // Supplier
336
+ supplierId: zod_1.z.string().optional(),
337
+ supplierLotNumber: zod_1.z.string().optional(),
338
+ supplierBatchNumber: zod_1.z.string().optional(),
339
+ supplierInvoiceNumber: zod_1.z.string().optional(),
340
+ // Quantities
341
+ originalQuantity: zod_1.z.number().int().nonnegative(),
342
+ currentQuantity: zod_1.z.number().int().nonnegative(),
343
+ availableQuantity: zod_1.z.number().int().nonnegative(),
344
+ allocatedQuantity: zod_1.z.number().int().nonnegative().default(0),
345
+ reservedQuantity: zod_1.z.number().int().nonnegative().default(0),
346
+ quarantinedQuantity: zod_1.z.number().int().nonnegative().default(0),
347
+ damagedQuantity: zod_1.z.number().int().nonnegative().default(0),
348
+ soldQuantity: zod_1.z.number().int().nonnegative().default(0),
349
+ returnedQuantity: zod_1.z.number().int().nonnegative().default(0),
350
+ // Stock Management
351
+ reorderPoint: zod_1.z.number().int().nonnegative().optional(),
352
+ maxStockLevel: zod_1.z.number().int().nonnegative().optional(),
353
+ safetyStock: zod_1.z.number().int().nonnegative().default(0),
354
+ unitOfMeasure: zod_1.z.string().default("EACH"),
355
+ // Serial Tracking
356
+ trackSerial: zod_1.z.boolean().default(false),
357
+ serialNumbers: zod_1.z.array(zod_1.z.object({
358
+ serialNumber: zod_1.z.string(),
359
+ status: zod_1.z.enum(["AVAILABLE", "SOLD", "RESERVED", "DAMAGED", "RETURNED"]),
360
+ soldTo: zod_1.z.string().optional(),
361
+ soldDate: zod_1.z.string().optional(),
362
+ reservedFor: zod_1.z.string().optional(),
363
+ reservedDate: zod_1.z.string().optional(),
364
+ })).optional(),
365
+ // Flags
366
+ isInitialInventory: zod_1.z.boolean().default(false),
367
+ // Dates
368
+ manufactureDate: zod_1.z.coerce.date().optional(),
369
+ expiryDate: zod_1.z.coerce.date().optional(),
370
+ bestBeforeDate: zod_1.z.coerce.date().optional(),
371
+ receivedDate: zod_1.z.coerce.date(),
372
+ firstUseDate: zod_1.z.coerce.date().optional(),
373
+ lastMovementDate: zod_1.z.coerce.date().optional(),
374
+ // Status
375
+ status: zod_1.z.enum(["PENDING", "ACTIVE", "QUARANTINED", "ALLOCATED", "EXPIRED", "RECALLED", "DEPLETED", "RETURNED", "DISPOSED"]),
376
+ // Quality Control
377
+ qualityControlId: zod_1.z.string().optional(),
378
+ qcCertificateNumber: zod_1.z.string().optional(),
379
+ qcNotes: zod_1.z.string().optional(),
380
+ // Cost
381
+ unitCost: zod_1.z.number().nonnegative().optional(),
382
+ landedCost: zod_1.z.number().nonnegative().optional(),
383
+ totalCost: zod_1.z.number().nonnegative().optional(),
384
+ currency: zod_1.z.string().length(3).default("USD"),
385
+ // Metadata
386
+ notes: zod_1.z.string().optional(),
387
+ tags: zod_1.z.array(zod_1.z.string()).optional(),
388
+ customAttributes: zod_1.z.record(zod_1.z.any()).optional(),
389
+ // Timestamps
390
+ createdAt: zod_1.z.coerce.date(),
391
+ updatedAt: zod_1.z.coerce.date(),
392
+ createdBy: zod_1.z.string().optional(),
393
+ updatedBy: zod_1.z.string().optional(),
394
+ isActive: zod_1.z.boolean().default(true),
395
+ });
396
+ // Initialize Product Inventory Schema (for product_svc integration)
397
+ exports.InitializeProductInventorySchema = zod_1.z.object({
398
+ productId: zod_1.z.string().cuid2(),
399
+ variantId: zod_1.z.string().cuid2().optional(),
400
+ sku: zod_1.z.string().optional(), // Auto-generated if not provided
401
+ locations: zod_1.z.array(zod_1.z.object({
402
+ locationId: zod_1.z.string(),
403
+ locationType: zod_1.z.enum(["WAREHOUSE", "POS"]),
404
+ initialQuantity: zod_1.z.number().int().nonnegative(),
405
+ // Barcode for lot tracking
406
+ barcode: zod_1.z.string().optional(),
407
+ // Stock Management
408
+ reorderPoint: zod_1.z.number().int().nonnegative().optional(),
409
+ maxStockLevel: zod_1.z.number().int().nonnegative().optional(),
410
+ safetyStock: zod_1.z.number().int().nonnegative().default(0),
411
+ unitOfMeasure: zod_1.z.string().default("EACH"),
412
+ // Cost
413
+ unitCost: zod_1.z.number().nonnegative().optional(),
414
+ landedCost: zod_1.z.number().nonnegative().optional(),
415
+ // Dates
416
+ expiryDate: zod_1.z.coerce.date().optional(),
417
+ manufactureDate: zod_1.z.coerce.date().optional(),
418
+ // Supplier
419
+ supplierId: zod_1.z.string().optional(),
420
+ supplierLotNumber: zod_1.z.string().optional(),
421
+ // Serial Tracking
422
+ trackSerial: zod_1.z.boolean().default(false),
423
+ serialNumbers: zod_1.z.array(zod_1.z.string()).optional(), // List of serial numbers to assign
424
+ // Metadata
425
+ notes: zod_1.z.string().optional(),
426
+ tags: zod_1.z.array(zod_1.z.string()).optional(),
427
+ })).min(1),
428
+ });
429
+ // Create Lot Schema
430
+ exports.CreateLotSchema = zod_1.z.object({
431
+ storeId: zod_1.z.string(),
432
+ lotNumber: zod_1.z.string().optional(), // Auto-generated if not provided
433
+ batchNumber: zod_1.z.string().optional(),
434
+ // Product
435
+ productId: zod_1.z.string(),
436
+ variantId: zod_1.z.string().optional(),
437
+ sku: zod_1.z.string(),
438
+ barcode: zod_1.z.string().optional(),
439
+ // Location
440
+ locationId: zod_1.z.string(),
441
+ locationType: zod_1.z.enum(["WAREHOUSE", "POS"]),
442
+ // Supplier
443
+ supplierId: zod_1.z.string().optional(),
444
+ supplierLotNumber: zod_1.z.string().optional(),
445
+ supplierBatchNumber: zod_1.z.string().optional(),
446
+ supplierInvoiceNumber: zod_1.z.string().optional(),
447
+ // Quantities
448
+ originalQuantity: zod_1.z.number().int().positive(),
449
+ reorderPoint: zod_1.z.number().int().nonnegative().optional(),
450
+ maxStockLevel: zod_1.z.number().int().nonnegative().optional(),
451
+ safetyStock: zod_1.z.number().int().nonnegative().default(0),
452
+ unitOfMeasure: zod_1.z.string().default("EACH"),
453
+ // Dates
454
+ expiryDate: zod_1.z.coerce.date().optional(),
455
+ manufactureDate: zod_1.z.coerce.date().optional(),
456
+ receivedDate: zod_1.z.coerce.date().default(() => new Date()),
457
+ // Cost
458
+ unitCost: zod_1.z.number().nonnegative().optional(),
459
+ landedCost: zod_1.z.number().nonnegative().optional(),
460
+ // Storage
461
+ storageLocationId: zod_1.z.string().optional(),
462
+ storageZone: zod_1.z.string().optional(),
463
+ storageTempMin: zod_1.z.number().optional(),
464
+ storageTempMax: zod_1.z.number().optional(),
465
+ // Serial Tracking
466
+ trackSerial: zod_1.z.boolean().default(false),
467
+ serialNumbers: zod_1.z.array(zod_1.z.string()).optional(),
468
+ // Flags
469
+ isInitialInventory: zod_1.z.boolean().default(false),
470
+ // Metadata
471
+ notes: zod_1.z.string().optional(),
472
+ tags: zod_1.z.array(zod_1.z.string()).optional(),
473
+ customAttributes: zod_1.z.record(zod_1.z.any()).optional(),
474
+ // User
475
+ createdBy: zod_1.z.string(),
476
+ });
477
+ // Reserve Lot Quantity Schema
478
+ exports.ReserveLotQuantitySchema = zod_1.z.object({
479
+ lotId: zod_1.z.string().cuid2(),
480
+ quantity: zod_1.z.number().int().positive(),
481
+ orderId: zod_1.z.string().optional(),
482
+ customerId: zod_1.z.string().optional(),
483
+ reason: zod_1.z.string(),
484
+ expiresAt: zod_1.z.coerce.date().optional(), // Reservation expiry
485
+ notes: zod_1.z.string().optional(),
486
+ performedBy: zod_1.z.string(),
487
+ });
488
+ // Release Lot Reservation Schema
489
+ exports.ReleaseLotReservationSchema = zod_1.z.object({
490
+ lotId: zod_1.z.string().cuid2(),
491
+ quantity: zod_1.z.number().int().positive(),
492
+ reservationId: zod_1.z.string().optional(),
493
+ reason: zod_1.z.string(),
494
+ performedBy: zod_1.z.string(),
495
+ });
496
+ // Adjust Lot Quantity Schema
497
+ exports.AdjustLotQuantitySchema = zod_1.z.object({
498
+ lotId: zod_1.z.string().cuid2(),
499
+ quantityChange: zod_1.z.number().int(), // Can be positive or negative
500
+ adjustmentType: zod_1.z.enum([
501
+ "CYCLE_COUNT",
502
+ "DAMAGE",
503
+ "FOUND",
504
+ "LOST",
505
+ "CORRECTION",
506
+ "THEFT",
507
+ "WASTE",
508
+ "SAMPLE",
509
+ "EXPIRED",
510
+ "RETURNED",
511
+ ]),
512
+ reason: zod_1.z.string().min(1),
513
+ notes: zod_1.z.string().optional(),
514
+ performedBy: zod_1.z.string(),
515
+ });
516
+ // Transfer Lot Schema
517
+ exports.TransferLotSchema = zod_1.z.object({
518
+ lotId: zod_1.z.string().cuid2(),
519
+ toLocationId: zod_1.z.string(),
520
+ toLocationType: zod_1.z.enum(["WAREHOUSE", "POS"]),
521
+ quantity: zod_1.z.number().int().positive().optional(), // If not provided, transfer entire lot
522
+ splitIfPartial: zod_1.z.boolean().default(true), // Create new lot for partial transfer
523
+ reason: zod_1.z.string().min(1),
524
+ notes: zod_1.z.string().optional(),
525
+ expectedArrivalDate: zod_1.z.coerce.date().optional(),
526
+ performedBy: zod_1.z.string(),
527
+ });
528
+ // Merge Lots Schema
529
+ exports.MergeLotsSchema = zod_1.z.object({
530
+ targetLotId: zod_1.z.string().cuid2(),
531
+ sourceLotIds: zod_1.z.array(zod_1.z.string().cuid2()).min(1),
532
+ reason: zod_1.z.string().min(1),
533
+ performedBy: zod_1.z.string(),
534
+ approvedBy: zod_1.z.string().optional(),
535
+ notes: zod_1.z.string().optional(),
536
+ });
537
+ // Split Lot Schema
538
+ exports.SplitLotSchema = zod_1.z.object({
539
+ parentLotId: zod_1.z.string().cuid2(),
540
+ splits: zod_1.z.array(zod_1.z.object({
541
+ quantity: zod_1.z.number().int().positive(),
542
+ locationId: zod_1.z.string().optional(), // If different from parent
543
+ locationType: zod_1.z.enum(["WAREHOUSE", "POS"]).optional(),
544
+ notes: zod_1.z.string().optional(),
545
+ })).min(1),
546
+ reason: zod_1.z.string().min(1),
547
+ performedBy: zod_1.z.string(),
548
+ });
549
+ // Get Lots Query Schema
550
+ exports.GetLotsQuerySchema = zod_1.z.object({
551
+ storeId: zod_1.z.string(),
552
+ productId: zod_1.z.string().optional(),
553
+ variantId: zod_1.z.string().optional(),
554
+ sku: zod_1.z.string().optional(),
555
+ status: zod_1.z.enum(["PENDING", "ACTIVE", "QUARANTINED", "ALLOCATED", "EXPIRED", "RECALLED", "DEPLETED", "RETURNED", "DISPOSED"]).optional(),
556
+ locationId: zod_1.z.string().optional(),
557
+ locationType: zod_1.z.enum(["WAREHOUSE", "POS"]).optional(),
558
+ qualityControlId: zod_1.z.string().optional(),
559
+ expiringBefore: zod_1.z.coerce.date().optional(),
560
+ isInitialInventory: zod_1.z.boolean().optional(),
561
+ lowStock: zod_1.z.boolean().optional(), // currentQuantity < reorderPoint
562
+ page: zod_1.z.coerce.number().int().positive().default(1),
563
+ limit: zod_1.z.coerce.number().int().positive().max(100).default(20),
564
+ sortBy: zod_1.z.enum(["lotNumber", "receivedDate", "expiryDate", "currentQuantity", "availableQuantity", "status"]).default("receivedDate"),
565
+ sortOrder: zod_1.z.enum(["asc", "desc"]).default("desc"),
566
+ });
567
+ // Get Available Lots for Order (FIFO) Schema
568
+ exports.GetAvailableLotsSchema = zod_1.z.object({
569
+ storeId: zod_1.z.string(),
570
+ productId: zod_1.z.string(),
571
+ variantId: zod_1.z.string().optional(),
572
+ quantity: zod_1.z.number().int().positive(),
573
+ locationId: zod_1.z.string().optional(),
574
+ selectionMethod: zod_1.z.enum(["FIFO", "FEFO", "LIFO"]).default("FIFO"),
575
+ });
576
+ // Lot Analytics Response Schema
577
+ exports.LotAnalyticsSchema = zod_1.z.object({
578
+ productId: zod_1.z.string(),
579
+ totalLots: zod_1.z.number().int(),
580
+ totalQuantity: zod_1.z.number().int(),
581
+ availableQuantity: zod_1.z.number().int(),
582
+ allocatedQuantity: zod_1.z.number().int(),
583
+ reservedQuantity: zod_1.z.number().int(),
584
+ quarantinedQuantity: zod_1.z.number().int(),
585
+ damagedQuantity: zod_1.z.number().int(),
586
+ byLocation: zod_1.z.array(zod_1.z.object({
587
+ locationId: zod_1.z.string(),
588
+ locationType: zod_1.z.enum(["WAREHOUSE", "POS"]),
589
+ lotCount: zod_1.z.number().int(),
590
+ totalQuantity: zod_1.z.number().int(),
591
+ availableQuantity: zod_1.z.number().int(),
592
+ })),
593
+ byStatus: zod_1.z.record(zod_1.z.string(), zod_1.z.number().int()),
594
+ expiringLots: zod_1.z.array(zod_1.z.object({
595
+ lotId: zod_1.z.string(),
596
+ lotNumber: zod_1.z.string(),
597
+ expiryDate: zod_1.z.coerce.date(),
598
+ daysUntilExpiry: zod_1.z.number().int(),
599
+ currentQuantity: zod_1.z.number().int(),
600
+ })),
601
+ lowStockLots: zod_1.z.array(zod_1.z.object({
602
+ lotId: zod_1.z.string(),
603
+ lotNumber: zod_1.z.string(),
604
+ currentQuantity: zod_1.z.number().int(),
605
+ reorderPoint: zod_1.z.number().int(),
606
+ shortfall: zod_1.z.number().int(),
607
+ })),
608
+ });