@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,1927 @@
1
+ import { createId } from "@paralleldrive/cuid2";
2
+ import {
3
+ boolean,
4
+ decimal,
5
+ index,
6
+ integer,
7
+ jsonb,
8
+ pgEnum,
9
+ pgTable,
10
+ text,
11
+ timestamp,
12
+ unique,
13
+ varchar,
14
+ } from "drizzle-orm/pg-core";
15
+ import { z } from "zod";
16
+ import {
17
+ AuditTrailSchema,
18
+ BusinessHoursSchema,
19
+ ContactInfoSchema,
20
+ CuidSchema,
21
+ GeoLocationSchema,
22
+ } from "../common/common-schemas";
23
+
24
+ // =====================================================
25
+ // ENUMS
26
+ // =====================================================
27
+
28
+ export const inventoryLocationTypeEnum = pgEnum("inventory_location_type", [
29
+ "WAREHOUSE",
30
+ "POS",
31
+ ]);
32
+ export const inventoryStatusEnum = pgEnum("inventory_status", [
33
+ "IN_STOCK",
34
+ "OUT_OF_STOCK",
35
+ "RESERVED",
36
+ "DAMAGED",
37
+ "IN_TRANSIT",
38
+ "QUARANTINED",
39
+ "RETURNED",
40
+ "EXPIRED",
41
+ "LOST",
42
+ "ADJUSTED",
43
+ ]);
44
+
45
+ export const damageTypeEnum = pgEnum("damage_type", [
46
+ "PHYSICAL_DAMAGE",
47
+ "WATER_DAMAGE",
48
+ "FIRE_DAMAGE",
49
+ "MANUFACTURING_DEFECT",
50
+ "PACKAGING_DAMAGE",
51
+ "EXPIRED",
52
+ "CONTAMINATED",
53
+ "MISSING_PARTS",
54
+ "WRONG_ITEM",
55
+ "CUSTOMER_RETURN_DAMAGED",
56
+ "TRANSIT_DAMAGE",
57
+ "STORAGE_DAMAGE",
58
+ "THEFT_PARTIAL",
59
+ "OBSOLETE",
60
+ "OTHER",
61
+ ]);
62
+
63
+ export const damageSeverityEnum = pgEnum("damage_severity", [
64
+ "MINOR",
65
+ "MODERATE",
66
+ "SEVERE",
67
+ "COSMETIC",
68
+ ]);
69
+
70
+ export const movementTypeEnum = pgEnum("movement_type", [
71
+ "PURCHASE_RECEIPT",
72
+ "TRANSFER_IN",
73
+ "RETURN_FROM_CUSTOMER",
74
+ "PRODUCTION_COMPLETE",
75
+ "ADJUSTMENT_INCREASE",
76
+ "FOUND_INVENTORY",
77
+ "SALE",
78
+ "TRANSFER_OUT",
79
+ "RETURN_TO_SUPPLIER",
80
+ "DAMAGED_GOODS",
81
+ "EXPIRED_GOODS",
82
+ "THEFT_LOSS",
83
+ "ADJUSTMENT_DECREASE",
84
+ "WASTE",
85
+ "SAMPLE",
86
+ "PROMOTIONAL_USE",
87
+ "LOCATION_TRANSFER",
88
+ "ZONE_TRANSFER",
89
+ "BIN_TRANSFER",
90
+ "CYCLE_COUNT_ADJUSTMENT",
91
+ "PHYSICAL_COUNT_ADJUSTMENT",
92
+ "RESERVE",
93
+ "UNRESERVE",
94
+ "ALLOCATE",
95
+ "DEALLOCATE",
96
+ "MARK_DAMAGED",
97
+ "REPAIR_DAMAGED",
98
+ "DISPOSE_DAMAGED",
99
+ "QUARANTINE",
100
+ "RELEASE_QUARANTINE",
101
+ "STOCK_RECONCILIATION",
102
+ ]);
103
+
104
+ export const movementStatusEnum = pgEnum("movement_status", [
105
+ "DRAFT",
106
+ "PENDING",
107
+ "IN_PROGRESS",
108
+ "COMPLETED",
109
+ "CANCELLED",
110
+ "FAILED",
111
+ ]);
112
+
113
+ export const warehouseStatusEnum = pgEnum("warehouse_status", [
114
+ "ACTIVE",
115
+ "INACTIVE",
116
+ "CLOSED",
117
+ ]);
118
+ export const warehouseTypeEnum = pgEnum("warehouse_type", [
119
+ "DISTRIBUTION_CENTER",
120
+ "FULFILLMENT_CENTER",
121
+ "COLD_STORAGE",
122
+ "BULK_STORAGE",
123
+ "CROSS_DOCK",
124
+ "RETAIL_WAREHOUSE",
125
+ "MANUFACTURING_WAREHOUSE",
126
+ "3PL_WAREHOUSE",
127
+ ]);
128
+
129
+ // =====================================================
130
+ // Zod Enums and Schemas (co-located for single-source-of-truth)
131
+ // =====================================================
132
+
133
+ export const POSLocationTypeEnum = z.enum([
134
+ "RETAIL",
135
+ "KIOSK",
136
+ "POPUP",
137
+ "OTHER",
138
+ ]);
139
+ export const POSLocationStatusEnum = z.enum(["ACTIVE", "INACTIVE", "CLOSED"]);
140
+
141
+ export const WarehouseStatusEnum = z.enum(["ACTIVE", "INACTIVE", "CLOSED"]);
142
+ export const WarehouseTypeEnum = z.enum([
143
+ "DISTRIBUTION_CENTER",
144
+ "FULFILLMENT_CENTER",
145
+ "COLD_STORAGE",
146
+ "BULK_STORAGE",
147
+ "CROSS_DOCK",
148
+ "RETAIL_WAREHOUSE",
149
+ "MANUFACTURING_WAREHOUSE",
150
+ "3PL_WAREHOUSE",
151
+ ]);
152
+
153
+ export const InventoryStatusEnum = z.enum([
154
+ "IN_STOCK",
155
+ "OUT_OF_STOCK",
156
+ "RESERVED",
157
+ "DAMAGED",
158
+ "IN_TRANSIT",
159
+ "QUARANTINED",
160
+ "RETURNED",
161
+ "EXPIRED",
162
+ "LOST",
163
+ "ADJUSTED",
164
+ ]);
165
+ export const LocationTypeEnum = z.enum(["WAREHOUSE", "POS"]);
166
+
167
+ export const InventoryTransferStatusEnum = z.enum([
168
+ "PENDING",
169
+ "IN_TRANSIT",
170
+ "COMPLETED",
171
+ "CANCELLED",
172
+ "FAILED",
173
+ "PARTIAL",
174
+ "RETURNED",
175
+ ]);
176
+ export const InventoryTransferTypeEnum = z.enum([
177
+ "WAREHOUSE_TO_WAREHOUSE",
178
+ "WAREHOUSE_TO_POS",
179
+ "POS_TO_WAREHOUSE",
180
+ "POS_TO_POS",
181
+ "SUPPLIER_TO_WAREHOUSE",
182
+ "WAREHOUSE_TO_CUSTOMER",
183
+ "INTERNAL_TRANSFER",
184
+ ]);
185
+
186
+ export const PurchaseOrderStatusEnum = z.enum([
187
+ "DRAFT",
188
+ "SENT",
189
+ "ACKNOWLEDGED",
190
+ "APPROVED",
191
+ "PENDING_APPROVAL",
192
+ "PARTIALLY_RECEIVED",
193
+ "RECEIVED",
194
+ "COMPLETED",
195
+ "CANCELLED",
196
+ "DISPUTED",
197
+ ]);
198
+
199
+ export const SupplierStatusEnum = z.enum([
200
+ "ACTIVE",
201
+ "INACTIVE",
202
+ "SUSPENDED",
203
+ "UNDER_REVIEW",
204
+ "PENDING_APPROVAL",
205
+ "BLACKLISTED",
206
+ ]);
207
+ export const SupplierTypeEnum = z.enum([
208
+ "MANUFACTURER",
209
+ "DISTRIBUTOR",
210
+ "WHOLESALER",
211
+ "DROPSHIPPER",
212
+ "SERVICE_PROVIDER",
213
+ "LOCAL_VENDOR",
214
+ "INTERNATIONAL_VENDOR",
215
+ "BROKER",
216
+ "IMPORTER",
217
+ "EXPORTER",
218
+ "RETAILER",
219
+ "RESELLER",
220
+ "CONTRACTOR",
221
+ "CONSULTANT",
222
+ "PARTS_SUPPLIER",
223
+ "RAW_MATERIALS_SUPPLIER",
224
+ "PACKAGING_SUPPLIER",
225
+ "EQUIPMENT_SUPPLIER",
226
+ "MAINTENANCE_SUPPLIER",
227
+ "LOGISTICS_PROVIDER",
228
+ "FREIGHT_FORWARDER",
229
+ "CUSTOMS_BROKER",
230
+ "TECHNOLOGY_VENDOR",
231
+ "SOFTWARE_VENDOR",
232
+ "HARDWARE_VENDOR",
233
+ "OFFICE_SUPPLIES_VENDOR",
234
+ "UTILITIES_PROVIDER",
235
+ "CLEANING_SERVICES",
236
+ "SECURITY_SERVICES",
237
+ "MARKETING_SERVICES",
238
+ "PROFESSIONAL_SERVICES",
239
+ "TEMPORARY_STAFFING",
240
+ "SPECIALIZED_SUPPLIER",
241
+ "OTHER",
242
+ ]);
243
+ export const SupplierRatingEnum = z.enum([
244
+ "EXCELLENT",
245
+ "GOOD",
246
+ "AVERAGE",
247
+ "POOR",
248
+ "UNRATED",
249
+ ]);
250
+
251
+ export const QualityStatusEnum = z.enum([
252
+ "PENDING_INSPECTION",
253
+ "PASSED",
254
+ "FAILED",
255
+ "CONDITIONALLY_PASSED",
256
+ "QUARANTINED",
257
+ "REJECTED",
258
+ "REWORK_REQUIRED",
259
+ "RELEASED",
260
+ "HOLD",
261
+ ]);
262
+ export const InspectionTypeEnum = z.enum([
263
+ "INCOMING_INSPECTION",
264
+ "IN_PROCESS_INSPECTION",
265
+ "OUTGOING_INSPECTION",
266
+ "RANDOM_INSPECTION",
267
+ "CUSTOMER_COMPLAINT_INSPECTION",
268
+ "SUPPLIER_AUDIT",
269
+ "PERIODIC_REVIEW",
270
+ "EXPIRY_CHECK",
271
+ "DAMAGE_ASSESSMENT",
272
+ ]);
273
+ export const QualityGradeEnum = z.enum(["A", "B", "C", "D", "F"]);
274
+ export const DefectSeverityEnum = z.enum([
275
+ "CRITICAL",
276
+ "MAJOR",
277
+ "MINOR",
278
+ "COSMETIC",
279
+ ]);
280
+
281
+ // Zod schemas for key resources
282
+ export const InventorySchema = z.object({
283
+ id: CuidSchema,
284
+ storeId: z.string(),
285
+ productId: z.string(),
286
+ variantId: z.string().optional(),
287
+ sku: z.string(),
288
+ barcode: z.string(),
289
+ locationId: z.string(),
290
+ locationType: LocationTypeEnum,
291
+ quantity: z.number().int().nonnegative(),
292
+ reserved: z.number().int().nonnegative().optional(),
293
+ committed: z.number().int().nonnegative().optional(),
294
+ unitCost: z.number().nonnegative().optional(),
295
+ status: InventoryStatusEnum.default("IN_STOCK"),
296
+ batchNumber: z.string().optional(),
297
+ lotNumber: z.string().optional(),
298
+ expiryDate: z.coerce.date().optional(),
299
+ lowStockThreshold: z.number().int().nonnegative().optional(),
300
+ criticalStockThreshold: z.number().int().nonnegative().optional(),
301
+ available: z.number().int().nonnegative().default(0),
302
+ notes: z.string().optional(),
303
+ metadata: z.record(z.any()).optional(),
304
+ version: z.number().int().default(1),
305
+ isActive: z.boolean().default(true),
306
+ createdAt: z.date(),
307
+ updatedAt: z.date(),
308
+ createdBy: z.string().optional(),
309
+ updatedBy: z.string().optional(),
310
+ });
311
+
312
+ export const InventoryMovementSchema = z
313
+ .object({
314
+ id: CuidSchema,
315
+ storeId: z.string(),
316
+ movementNumber: z.string(),
317
+ movementType: z.enum([
318
+ "PURCHASE_RECEIPT",
319
+ "TRANSFER_IN",
320
+ "RETURN_FROM_CUSTOMER",
321
+ "PRODUCTION_COMPLETE",
322
+ "ADJUSTMENT_INCREASE",
323
+ "FOUND_INVENTORY",
324
+ "SALE",
325
+ "TRANSFER_OUT",
326
+ "RETURN_TO_SUPPLIER",
327
+ "DAMAGED_GOODS",
328
+ "EXPIRED_GOODS",
329
+ "THEFT_LOSS",
330
+ "ADJUSTMENT_DECREASE",
331
+ "WASTE",
332
+ "SAMPLE",
333
+ "PROMOTIONAL_USE",
334
+ "LOCATION_TRANSFER",
335
+ "ZONE_TRANSFER",
336
+ "BIN_TRANSFER",
337
+ "CYCLE_COUNT_ADJUSTMENT",
338
+ "PHYSICAL_COUNT_ADJUSTMENT",
339
+ "RESERVE",
340
+ "UNRESERVE",
341
+ "ALLOCATE",
342
+ "DEALLOCATE",
343
+ ]),
344
+ status: z.enum([
345
+ "DRAFT",
346
+ "PENDING",
347
+ "IN_PROGRESS",
348
+ "COMPLETED",
349
+ "CANCELLED",
350
+ "FAILED",
351
+ ]),
352
+ productId: z.string(),
353
+ variantId: z.string().optional(),
354
+ sku: z.string(),
355
+ quantityMoved: z.number(),
356
+ fromLocationId: z.string().optional(),
357
+ toLocationId: z.string().optional(),
358
+ unitCost: z.number().optional(),
359
+ reason: z.string().optional(),
360
+ movementDate: z.coerce.date(),
361
+ createdAt: z.date(),
362
+ updatedAt: z.date(),
363
+ })
364
+ .catchall(z.any());
365
+
366
+ export const InventoryTransferSchema = z
367
+ .object({ id: CuidSchema })
368
+ .catchall(z.any());
369
+ export const QualityControlSchema = z
370
+ .object({
371
+ id: CuidSchema,
372
+ qcNumber: z.string(),
373
+ inspectedBy: z.string(),
374
+ inspectionDate: z.date(),
375
+ createdAt: z.date(),
376
+ updatedAt: z.date(),
377
+ })
378
+ .catchall(z.any());
379
+ export const QualityTemplateSchema = z
380
+ .object({
381
+ id: CuidSchema,
382
+ createdAt: z.date(),
383
+ updatedAt: z.date(),
384
+ createdBy: z.string().optional(),
385
+ updatedBy: z.string().optional(),
386
+ })
387
+ .catchall(z.any());
388
+ export const SupplierQualityRatingSchema = z
389
+ .object({
390
+ id: CuidSchema,
391
+ reviewedBy: z.string().optional(),
392
+ reviewedAt: z.date().optional(),
393
+ createdAt: z.date(),
394
+ updatedAt: z.date(),
395
+ })
396
+ .catchall(z.any());
397
+
398
+ export const POSLocationSchema = z.object({
399
+ id: CuidSchema,
400
+ storeId: z.string(),
401
+ code: z.string(),
402
+ name: z.string(),
403
+ displayName: z.string().optional(),
404
+ type: POSLocationTypeEnum,
405
+ posType: POSLocationTypeEnum.optional(),
406
+ status: POSLocationStatusEnum,
407
+ geoLocation: GeoLocationSchema,
408
+ address: z
409
+ .object({
410
+ street: z.string().optional(),
411
+ line1: z.string().optional(),
412
+ line2: z.string().optional(),
413
+ city: z.string(),
414
+ state: z.string(),
415
+ postalCode: z.string(),
416
+ country: z.string(),
417
+ coordinates: z.object({ lat: z.number(), lng: z.number() }).optional(),
418
+ })
419
+ .optional(),
420
+ timezone: z.string().default("UTC"),
421
+ primaryContact: ContactInfoSchema.optional(),
422
+ managementContacts: z.array(ContactInfoSchema).optional(),
423
+ emergencyContact: ContactInfoSchema.optional(),
424
+ businessHours: BusinessHoursSchema.optional(),
425
+ posSystemInfo: z
426
+ .object({
427
+ systemName: z.string().optional(),
428
+ version: z.string().optional(),
429
+ lastSync: z.coerce.date().optional(),
430
+ supportedPaymentMethods: z.array(z.string()).optional(),
431
+ isActive: z.boolean().default(true),
432
+ })
433
+ .optional(),
434
+ allowBackorders: z.boolean().default(false).optional(),
435
+ autoFulfillment: z.boolean().default(true).optional(),
436
+ supportedFeatures: z
437
+ .array(
438
+ z.enum([
439
+ "CASH_PAYMENT",
440
+ "CARD_PAYMENT",
441
+ "MOBILE_PAYMENT",
442
+ "GIFT_CARDS",
443
+ "LAYAWAY",
444
+ "RETURNS_EXCHANGES",
445
+ "PRODUCT_LOOKUP",
446
+ "INVENTORY_CHECK",
447
+ "CUSTOMER_ORDERS",
448
+ "RESERVATIONS",
449
+ "LOYALTY_PROGRAM",
450
+ "PROMOTIONS",
451
+ "TAX_CALCULATION",
452
+ "RECEIPT_PRINTING",
453
+ "EMAIL_RECEIPTS",
454
+ "BARCODE_SCANNING",
455
+ "PRICE_CHECKING",
456
+ ]),
457
+ )
458
+ .optional(),
459
+ hardware: z
460
+ .array(
461
+ z.object({
462
+ id: z.string(),
463
+ type: z.enum([
464
+ "POS_TERMINAL",
465
+ "CASH_REGISTER",
466
+ "BARCODE_SCANNER",
467
+ "RECEIPT_PRINTER",
468
+ "CARD_READER",
469
+ "SCALE",
470
+ "SECURITY_CAMERA",
471
+ "CASH_DRAWER",
472
+ "DISPLAY_MONITOR",
473
+ "TABLET",
474
+ ]),
475
+ brand: z.string().optional(),
476
+ model: z.string().optional(),
477
+ serialNumber: z.string().optional(),
478
+ status: z.enum(["ACTIVE", "MAINTENANCE", "INACTIVE"]),
479
+ lastMaintenance: z.coerce.date().optional(),
480
+ }),
481
+ )
482
+ .optional(),
483
+ inventorySettings: z
484
+ .object({
485
+ allowNegativeInventory: z.boolean().default(false),
486
+ autoReorderEnabled: z.boolean().default(false),
487
+ lowStockWarningEnabled: z.boolean().default(true),
488
+ stockCountRequired: z.boolean().default(false),
489
+ barcodeRequired: z.boolean().default(false),
490
+ })
491
+ .optional(),
492
+ salesSettings: z
493
+ .object({
494
+ taxRate: z.number().min(0).max(100).optional(),
495
+ taxExemptAvailable: z.boolean().default(false),
496
+ discountingEnabled: z.boolean().default(true),
497
+ maxDiscountPercent: z.number().min(0).max(100).optional(),
498
+ returnsEnabled: z.boolean().default(true),
499
+ returnDayLimit: z.number().int().nonnegative().optional(),
500
+ receiptRequired: z.boolean().default(true),
501
+ })
502
+ .optional(),
503
+ customerServices: z
504
+ .array(
505
+ z.enum([
506
+ "PERSONAL_SHOPPING",
507
+ "ALTERATIONS",
508
+ "GIFT_WRAPPING",
509
+ "DELIVERY",
510
+ "INSTALLATION",
511
+ "REPAIR_SERVICE",
512
+ "CONSULTATION",
513
+ "TRAINING",
514
+ "RENTAL",
515
+ "CUSTOM_ORDERS",
516
+ ]),
517
+ )
518
+ .optional(),
519
+ operationalCosts: z
520
+ .object({
521
+ monthlyRent: z.number().nonnegative().optional(),
522
+ utilities: z.number().nonnegative().optional(),
523
+ labor: z.number().nonnegative().optional(),
524
+ maintenance: z.number().nonnegative().optional(),
525
+ insurance: z.number().nonnegative().optional(),
526
+ currency: z.string().length(3).default("USD"),
527
+ })
528
+ .optional(),
529
+ securityFeatures: z.array(z.string()).optional(),
530
+ complianceRequirements: z.array(z.string()).optional(),
531
+ lastSecurityAudit: z.coerce.date().optional(),
532
+ description: z.string().optional(),
533
+ notes: z.string().optional(),
534
+ internalNotes: z.string().optional(),
535
+ metadata: z.record(z.any()).optional(),
536
+ customAttributes: z.record(z.any()).optional(),
537
+ version: z.number().int().default(1),
538
+ isActive: z.boolean().default(true),
539
+ createdAt: z.date(),
540
+ updatedAt: z.date(),
541
+ createdBy: z.string().optional(),
542
+ updatedBy: z.string().optional(),
543
+ auditTrail: z.array(AuditTrailSchema).optional(),
544
+ });
545
+
546
+ export const WarehouseSchema = z.object({
547
+ id: CuidSchema,
548
+ storeId: z.string(),
549
+ code: z.string(),
550
+ name: z.string(),
551
+ displayName: z.string().optional(),
552
+ type: WarehouseTypeEnum,
553
+ status: WarehouseStatusEnum,
554
+ address: z
555
+ .object({
556
+ street: z.string(),
557
+ city: z.string(),
558
+ state: z.string(),
559
+ postalCode: z.string(),
560
+ country: z.string(),
561
+ coordinates: z.object({ lat: z.number(), lng: z.number() }).optional(),
562
+ })
563
+ .optional(),
564
+ timezone: z.string().default("UTC"),
565
+ primaryContact: z
566
+ .object({ name: z.string(), email: z.string(), phone: z.string() })
567
+ .optional(),
568
+ totalArea: z.number().nonnegative().optional(),
569
+ storageArea: z.number().nonnegative().optional(),
570
+ ceilingHeight: z.number().nonnegative().optional(),
571
+ capacity: z
572
+ .object({
573
+ volumetric: z.number().nonnegative().optional(),
574
+ weight: z.number().nonnegative().optional(),
575
+ palletPositions: z.number().int().nonnegative().optional(),
576
+ pickingLocations: z.number().int().nonnegative().optional(),
577
+ unit: z.string().optional(),
578
+ })
579
+ .optional(),
580
+ zones: z
581
+ .array(
582
+ z.object({
583
+ id: z.string(),
584
+ code: z.string(),
585
+ name: z.string(),
586
+ type: z.string(),
587
+ description: z.string().optional(),
588
+ capacity: z.number().nonnegative().optional(),
589
+ temperature: z
590
+ .object({ min: z.number(), max: z.number(), unit: z.string() })
591
+ .optional(),
592
+ humidity: z
593
+ .object({
594
+ min: z.number().min(0).max(100),
595
+ max: z.number().min(0).max(100),
596
+ })
597
+ .optional(),
598
+ binLocations: z
599
+ .array(
600
+ z.object({
601
+ id: z.string(),
602
+ code: z.string(),
603
+ row: z.string().optional(),
604
+ bay: z.string().optional(),
605
+ level: z.string().optional(),
606
+ capacity: z.number().nonnegative().optional(),
607
+ isActive: z.boolean().default(true),
608
+ }),
609
+ )
610
+ .optional(),
611
+ isActive: z.boolean().default(true),
612
+ }),
613
+ )
614
+ .optional(),
615
+ description: z.string().optional(),
616
+ notes: z.string().optional(),
617
+ internalNotes: z.string().optional(),
618
+ metadata: z.record(z.any()).optional(),
619
+ version: z.number().int().default(1),
620
+ isActive: z.boolean().default(true),
621
+ createdAt: z.date(),
622
+ updatedAt: z.date(),
623
+ createdBy: z.string().optional(),
624
+ updatedBy: z.string().optional(),
625
+ auditTrail: z.array(AuditTrailSchema).optional(),
626
+ });
627
+
628
+ // =====================================================
629
+ // INVENTORY TABLE
630
+ // =====================================================
631
+
632
+ export const inventory = pgTable(
633
+ "inventory",
634
+ {
635
+ id: text("id")
636
+ .primaryKey()
637
+ .$defaultFn(() => createId()),
638
+ storeId: text("store_id").notNull(),
639
+ productId: text("product_id").notNull(),
640
+ variantId: text("variant_id"),
641
+ sku: varchar("sku", { length: 255 }).notNull(),
642
+ barcode: varchar("barcode", { length: 255 }).notNull(),
643
+ locationId: text("location_id").notNull(),
644
+ locationType: inventoryLocationTypeEnum("location_type").notNull(),
645
+
646
+ // Zone and Bin Information
647
+ zoneId: text("zone_id"),
648
+ binLocation: varchar("bin_location", { length: 100 }),
649
+
650
+ // Batch and Serial Tracking
651
+ batchNumber: varchar("batch_number", { length: 255 }),
652
+ lotNumber: varchar("lot_number", { length: 255 }),
653
+ serialNumbers: jsonb("serial_numbers").$type<string[]>(),
654
+
655
+ // Quantity Management
656
+ quantity: integer("quantity").notNull().default(0),
657
+ reserved: integer("reserved").notNull().default(0),
658
+ available: integer("available").notNull().default(0),
659
+ committed: integer("committed").notNull().default(0),
660
+ inTransit: integer("in_transit").notNull().default(0),
661
+ onOrder: integer("on_order").notNull().default(0),
662
+
663
+ // Stock Level Management
664
+ reorderPoint: integer("reorder_point"),
665
+ maxStockLevel: integer("max_stock_level"),
666
+ safetyStock: integer("safety_stock").notNull().default(0),
667
+
668
+ // Unit of Measure
669
+ baseUnitId: text("base_unit_id").notNull(),
670
+ unitOfMeasure: varchar("unit_of_measure", { length: 50 })
671
+ .notNull()
672
+ .default("EACH"),
673
+
674
+ // Cost Information
675
+ unitCost: decimal("unit_cost", { precision: 12, scale: 4 }),
676
+ averageCost: decimal("average_cost", { precision: 12, scale: 4 }),
677
+ lastCost: decimal("last_cost", { precision: 12, scale: 4 }),
678
+ standardCost: decimal("standard_cost", { precision: 12, scale: 4 }),
679
+ costingMethod: varchar("costing_method", { length: 20 }),
680
+
681
+ // Status and Condition
682
+ status: inventoryStatusEnum("status").notNull().default("IN_STOCK"),
683
+ condition: varchar("condition", { length: 100 }),
684
+ qualityGrade: varchar("quality_grade", { length: 50 }),
685
+
686
+ // Date Information
687
+ expiryDate: timestamp("expiry_date", { withTimezone: true }),
688
+ manufactureDate: timestamp("manufacture_date", { withTimezone: true }),
689
+ receivedDate: timestamp("received_date", { withTimezone: true }),
690
+ lastCountedAt: timestamp("last_counted_at", { withTimezone: true }),
691
+ lastMovementDate: timestamp("last_movement_date", { withTimezone: true }),
692
+
693
+ // Supplier Information
694
+ supplierId: text("supplier_id"),
695
+ supplierSku: varchar("supplier_sku", { length: 255 }),
696
+
697
+ // Alert Thresholds
698
+ lowStockThreshold: integer("low_stock_threshold"),
699
+ criticalStockThreshold: integer("critical_stock_threshold"),
700
+ expiryWarningDays: integer("expiry_warning_days").notNull().default(30),
701
+
702
+ // Tracking Configuration
703
+ trackSerial: boolean("track_serial").notNull().default(false),
704
+ trackBatch: boolean("track_batch").notNull().default(false),
705
+ trackExpiry: boolean("track_expiry").notNull().default(false),
706
+
707
+ // Damaged Stock Tracking
708
+ damagedQuantity: integer("damaged_quantity").notNull().default(0),
709
+ damageType: damageTypeEnum("damage_type"),
710
+ damageSeverity: damageSeverityEnum("damage_severity"),
711
+ damageReason: text("damage_reason"),
712
+ damageValue: decimal("damage_value", { precision: 12, scale: 4 }),
713
+ damageDate: timestamp("damage_date", { withTimezone: true }),
714
+ damageReportedBy: text("damage_reported_by"),
715
+ damageNotes: text("damage_notes"),
716
+ isRepairable: boolean("is_repairable").notNull().default(false),
717
+ repairCost: decimal("repair_cost", { precision: 12, scale: 4 }),
718
+ disposalDate: timestamp("disposal_date", { withTimezone: true }),
719
+ disposalMethod: varchar("disposal_method", { length: 100 }),
720
+ insuranceClaim: jsonb("insurance_claim").$type<{
721
+ claimNumber?: string;
722
+ amount?: number;
723
+ status?: string;
724
+ filedDate?: string;
725
+ resolvedDate?: string;
726
+ notes?: string;
727
+ }>(),
728
+
729
+ // Quality Tracking
730
+ qualityScore: integer("quality_score"),
731
+ lastQualityCheck: timestamp("last_quality_check", { withTimezone: true }),
732
+
733
+ // Quarantine Tracking
734
+ quarantinedQuantity: integer("quarantined_quantity").notNull().default(0),
735
+ quarantineReason: text("quarantine_reason"),
736
+ quarantineDate: timestamp("quarantine_date", { withTimezone: true }),
737
+ quarantineReleaseDate: timestamp("quarantine_release_date", { withTimezone: true }),
738
+
739
+ // Multi-location Tracking
740
+ isVirtualLocation: boolean("is_virtual_location").notNull().default(false),
741
+ parentInventoryId: text("parent_inventory_id"),
742
+
743
+ // ABC Classification
744
+ abcClassification: varchar("abc_classification", { length: 1 }),
745
+
746
+ // Notes and Metadata
747
+ notes: text("notes"),
748
+ internalNotes: text("internal_notes"),
749
+ metadata: jsonb("metadata").$type<Record<string, any>>().default({}),
750
+ customAttributes: jsonb("custom_attributes")
751
+ .$type<Record<string, any>>()
752
+ .default({}),
753
+
754
+ // Version Control
755
+ version: integer("version").notNull().default(1),
756
+ isActive: boolean("is_active").notNull().default(true),
757
+
758
+ // Timestamps
759
+ createdAt: timestamp("created_at", { withTimezone: true })
760
+ .defaultNow()
761
+ .notNull(),
762
+ updatedAt: timestamp("updated_at", { withTimezone: true })
763
+ .defaultNow()
764
+ .notNull(),
765
+ createdBy: text("created_by"),
766
+ updatedBy: text("updated_by"),
767
+ },
768
+ (table) => ({
769
+ // Performance indexes
770
+ storeIdIndex: index("idx_inventory_store_id").on(table.storeId),
771
+ productIdIndex: index("idx_inventory_product_id").on(table.productId),
772
+ locationIdIndex: index("idx_inventory_location_id").on(table.locationId),
773
+ skuIndex: index("idx_inventory_sku").on(table.sku),
774
+ barcodeIndex: index("idx_inventory_barcode").on(table.barcode),
775
+ statusIndex: index("idx_inventory_status").on(table.status),
776
+
777
+ // Unique constraints
778
+ uniqueProductLocation: unique("idx_inventory_product_location_unique").on(
779
+ table.storeId,
780
+ table.productId,
781
+ table.locationId,
782
+ table.variantId,
783
+ ),
784
+ uniqueSkuBarcode: unique("idx_inventory_sku_barcode_unique").on(
785
+ table.storeId,
786
+ table.locationId,
787
+ table.sku,
788
+ table.barcode,
789
+ ),
790
+
791
+ // Quantity and alerts indexes
792
+ quantityIndex: index("idx_inventory_quantity").on(table.quantity),
793
+ lowStockIndex: index("idx_inventory_low_stock").on(table.lowStockThreshold),
794
+ expiryIndex: index("idx_inventory_expiry").on(table.expiryDate),
795
+ }),
796
+ );
797
+
798
+ // =====================================================
799
+ // INVENTORY MOVEMENTS TABLE
800
+ // =====================================================
801
+
802
+ export const inventoryMovements = pgTable(
803
+ "inventory_movements",
804
+ {
805
+ id: text("id")
806
+ .primaryKey()
807
+ .$defaultFn(() => createId()),
808
+ storeId: text("store_id").notNull(),
809
+ movementNumber: varchar("movement_number", { length: 100 }).notNull(),
810
+
811
+ // Movement Classification
812
+ movementType: movementTypeEnum("movement_type").notNull(),
813
+ reason: varchar("reason", { length: 255 }),
814
+ status: movementStatusEnum("status").notNull().default("DRAFT"),
815
+
816
+ // Product Information
817
+ productId: text("product_id").notNull(),
818
+ variantId: text("variant_id"),
819
+ sku: varchar("sku", { length: 255 }).notNull(),
820
+ productName: varchar("product_name", { length: 255 }),
821
+
822
+ // Location Information
823
+ fromLocationId: text("from_location_id"),
824
+ fromLocationType: varchar("from_location_type", { length: 50 }),
825
+ fromZoneId: text("from_zone_id"),
826
+ fromBinLocation: varchar("from_bin_location", { length: 100 }),
827
+
828
+ toLocationId: text("to_location_id"),
829
+ toLocationType: varchar("to_location_type", { length: 50 }),
830
+ toZoneId: text("to_zone_id"),
831
+ toBinLocation: varchar("to_bin_location", { length: 100 }),
832
+
833
+ // Quantity Information
834
+ quantityMoved: integer("quantity_moved").notNull(),
835
+ unitOfMeasure: varchar("unit_of_measure", { length: 50 })
836
+ .notNull()
837
+ .default("EACH"),
838
+
839
+ // Batch and Serial Tracking
840
+ batchNumber: varchar("batch_number", { length: 255 }),
841
+ lotNumber: varchar("lot_number", { length: 255 }),
842
+ serialNumbers: jsonb("serial_numbers").$type<string[]>(),
843
+ expiryDate: timestamp("expiry_date", { withTimezone: true }),
844
+ manufacturingDate: timestamp("manufacturing_date", { withTimezone: true }),
845
+
846
+ // Cost Information
847
+ unitCost: decimal("unit_cost", { precision: 12, scale: 4 }),
848
+ totalCost: decimal("total_cost", { precision: 12, scale: 4 }),
849
+ costingMethod: varchar("costing_method", { length: 20 }),
850
+
851
+ // Reference Information
852
+ referenceType: varchar("reference_type", { length: 50 }),
853
+ referenceId: text("reference_id"),
854
+ referenceNumber: varchar("reference_number", { length: 100 }),
855
+
856
+ // Quality and Condition
857
+ conditionBefore: varchar("condition_before", { length: 100 }),
858
+ conditionAfter: varchar("condition_after", { length: 100 }),
859
+ qualityGrade: varchar("quality_grade", { length: 50 }),
860
+
861
+ // Date Information
862
+ movementDate: timestamp("movement_date", { withTimezone: true }).notNull(),
863
+ scheduledDate: timestamp("scheduled_date", { withTimezone: true }),
864
+ completedDate: timestamp("completed_date", { withTimezone: true }),
865
+
866
+ // Processing Information
867
+ processedBy: text("processed_by"),
868
+ approvedBy: text("approved_by"),
869
+ approvedAt: timestamp("approved_at", { withTimezone: true }),
870
+
871
+ // Notes
872
+ notes: text("notes"),
873
+ internalNotes: text("internal_notes"),
874
+
875
+ // Timestamps
876
+ createdAt: timestamp("created_at", { withTimezone: true })
877
+ .defaultNow()
878
+ .notNull(),
879
+ updatedAt: timestamp("updated_at", { withTimezone: true })
880
+ .defaultNow()
881
+ .notNull(),
882
+ createdBy: text("created_by"),
883
+ updatedBy: text("updated_by"),
884
+ },
885
+ (table) => ({
886
+ // Performance indexes
887
+ storeIdIndex: index("idx_movements_store_id").on(table.storeId),
888
+ productIdIndex: index("idx_movements_product_id").on(table.productId),
889
+ movementTypeIndex: index("idx_movements_type").on(table.movementType),
890
+ statusIndex: index("idx_movements_status").on(table.status),
891
+ movementDateIndex: index("idx_movements_date").on(table.movementDate),
892
+
893
+ // Unique movement number per store
894
+ uniqueMovementNumber: unique("idx_movements_number_unique").on(
895
+ table.storeId,
896
+ table.movementNumber,
897
+ ),
898
+ }),
899
+ );
900
+
901
+ // =====================================================
902
+ // WAREHOUSES TABLE
903
+ // =====================================================
904
+
905
+ export const warehouses = pgTable(
906
+ "warehouses",
907
+ {
908
+ id: text("id")
909
+ .primaryKey()
910
+ .$defaultFn(() => createId()),
911
+ storeId: text("store_id").notNull(),
912
+ code: varchar("code", { length: 100 }).notNull(),
913
+ name: varchar("name", { length: 255 }).notNull(),
914
+ displayName: varchar("display_name", { length: 255 }),
915
+ type: warehouseTypeEnum("type").notNull(),
916
+ status: warehouseStatusEnum("status").notNull().default("ACTIVE"),
917
+
918
+ // Location Information
919
+ address: jsonb("address").$type<{
920
+ street: string;
921
+ city: string;
922
+ state: string;
923
+ postalCode: string;
924
+ country: string;
925
+ coordinates?: { lat: number; lng: number };
926
+ }>(),
927
+ timezone: varchar("timezone", { length: 50 }).notNull().default("UTC"),
928
+
929
+ // Contact Information
930
+ primaryContact: jsonb("primary_contact").$type<{
931
+ name: string;
932
+ email: string;
933
+ phone: string;
934
+ }>(),
935
+
936
+ // Physical Characteristics
937
+ totalArea: decimal("total_area", { precision: 10, scale: 2 }),
938
+ storageArea: decimal("storage_area", { precision: 10, scale: 2 }),
939
+ ceilingHeight: decimal("ceiling_height", { precision: 8, scale: 2 }),
940
+ numberOfDocks: integer("number_of_docks"),
941
+
942
+ // Capacity Management
943
+ capacity: jsonb("capacity").$type<{
944
+ volumetric?: number;
945
+ weight?: number;
946
+ palletPositions?: number;
947
+ pickingLocations?: number;
948
+ unit?: string;
949
+ }>(),
950
+
951
+ // Zone Configuration
952
+ zones: jsonb("zones")
953
+ .$type<
954
+ Array<{
955
+ id: string;
956
+ code: string;
957
+ name: string;
958
+ type: string;
959
+ description?: string;
960
+ capacity?: number;
961
+ temperature?: { min: number; max: number; unit: string };
962
+ humidity?: { min: number; max: number };
963
+ isActive: boolean;
964
+ }>
965
+ >()
966
+ .default([]),
967
+
968
+ // Notes and Metadata
969
+ description: text("description"),
970
+ notes: text("notes"),
971
+ internalNotes: text("internal_notes"),
972
+ metadata: jsonb("metadata").$type<Record<string, any>>().default({}),
973
+
974
+ // Version Control
975
+ version: integer("version").notNull().default(1),
976
+ isActive: boolean("is_active").notNull().default(true),
977
+
978
+ // Timestamps
979
+ createdAt: timestamp("created_at", { withTimezone: true })
980
+ .defaultNow()
981
+ .notNull(),
982
+ updatedAt: timestamp("updated_at", { withTimezone: true })
983
+ .defaultNow()
984
+ .notNull(),
985
+ createdBy: text("created_by"),
986
+ updatedBy: text("updated_by"),
987
+ },
988
+ (table) => ({
989
+ // Performance indexes
990
+ storeIdIndex: index("idx_warehouses_store_id").on(table.storeId),
991
+ statusIndex: index("idx_warehouses_status").on(table.status),
992
+ typeIndex: index("idx_warehouses_type").on(table.type),
993
+
994
+ // Unique constraints
995
+ uniqueCodePerStore: unique("idx_warehouses_code_unique").on(
996
+ table.storeId,
997
+ table.code,
998
+ ),
999
+ }),
1000
+ );
1001
+
1002
+ // =====================================================
1003
+ // POS LOCATIONS TABLE
1004
+ // =====================================================
1005
+
1006
+ export const posLocations = pgTable(
1007
+ "pos_locations",
1008
+ {
1009
+ id: text("id")
1010
+ .primaryKey()
1011
+ .$defaultFn(() => createId()),
1012
+ storeId: text("store_id").notNull(),
1013
+ code: varchar("code", { length: 100 }).notNull(),
1014
+ name: varchar("name", { length: 255 }).notNull(),
1015
+ displayName: varchar("display_name", { length: 255 }),
1016
+ status: varchar("status", { length: 20 }).notNull().default("ACTIVE"),
1017
+
1018
+ // Location Information
1019
+ // Keep simple address for backward compatibility
1020
+ address: jsonb("address").$type<{
1021
+ street: string;
1022
+ city: string;
1023
+ state: string;
1024
+ postalCode: string;
1025
+ country: string;
1026
+ coordinates?: { lat: number; lng: number };
1027
+ }>(),
1028
+ // Advanced geo location object
1029
+ geoLocation: jsonb("geo_location").$type<{
1030
+ lat: number;
1031
+ lng: number;
1032
+ address?: {
1033
+ line1: string;
1034
+ line2?: string;
1035
+ city: string;
1036
+ state: string;
1037
+ postalCode: string;
1038
+ country: string;
1039
+ };
1040
+ }>(),
1041
+ timezone: varchar("timezone", { length: 50 }).notNull().default("UTC"),
1042
+
1043
+ // Contact Information
1044
+ primaryContact: jsonb("primary_contact").$type<{
1045
+ name?: string;
1046
+ email?: string;
1047
+ phone?: string;
1048
+ alternatePhone?: string;
1049
+ }>(),
1050
+ managementContacts: jsonb("management_contacts").$type<
1051
+ Array<{
1052
+ name?: string;
1053
+ email?: string;
1054
+ phone?: string;
1055
+ alternatePhone?: string;
1056
+ }>
1057
+ >(),
1058
+ emergencyContact: jsonb("emergency_contact").$type<{
1059
+ name?: string;
1060
+ email?: string;
1061
+ phone?: string;
1062
+ }>(),
1063
+
1064
+ // Business Hours
1065
+ businessHours: jsonb("business_hours").$type<{
1066
+ timezone?: string;
1067
+ monday?: { open: string; close: string; closed?: boolean };
1068
+ tuesday?: { open: string; close: string; closed?: boolean };
1069
+ wednesday?: { open: string; close: string; closed?: boolean };
1070
+ thursday?: { open: string; close: string; closed?: boolean };
1071
+ friday?: { open: string; close: string; closed?: boolean };
1072
+ saturday?: { open: string; close: string; closed?: boolean };
1073
+ sunday?: { open: string; close: string; closed?: boolean };
1074
+ holidays?: Array<{ date: string; name: string; closed?: boolean }>;
1075
+ }>(),
1076
+
1077
+ // Physical Characteristics
1078
+ floorArea: decimal("floor_area", { precision: 10, scale: 2 }),
1079
+ storageArea: decimal("storage_area", { precision: 10, scale: 2 }),
1080
+ customerArea: decimal("customer_area", { precision: 10, scale: 2 }),
1081
+ numberOfRegister: integer("number_of_register"),
1082
+ parkingSpaces: integer("parking_spaces"),
1083
+
1084
+ // POS Configuration
1085
+ posType: varchar("pos_type", { length: 50 }).notNull().default("RETAIL"),
1086
+ allowBackorders: boolean("allow_backorders").notNull().default(false),
1087
+ autoFulfillment: boolean("auto_fulfillment").notNull().default(true),
1088
+
1089
+ // Equipment and Technology
1090
+ posSystemInfo: jsonb("pos_system_info").$type<{
1091
+ systemName?: string;
1092
+ version?: string;
1093
+ lastSync?: Date;
1094
+ supportedPaymentMethods?: string[];
1095
+ isActive?: boolean;
1096
+ }>(),
1097
+ supportedFeatures: jsonb("supported_features").$type<string[]>(),
1098
+ hardware:
1099
+ jsonb("hardware").$type<
1100
+ Array<{
1101
+ id: string;
1102
+ type:
1103
+ | "POS_TERMINAL"
1104
+ | "CASH_REGISTER"
1105
+ | "BARCODE_SCANNER"
1106
+ | "RECEIPT_PRINTER"
1107
+ | "CARD_READER"
1108
+ | "SCALE"
1109
+ | "SECURITY_CAMERA"
1110
+ | "CASH_DRAWER"
1111
+ | "DISPLAY_MONITOR"
1112
+ | "TABLET";
1113
+ brand?: string;
1114
+ model?: string;
1115
+ serialNumber?: string;
1116
+ status: "ACTIVE" | "MAINTENANCE" | "INACTIVE";
1117
+ lastMaintenance?: Date;
1118
+ }>
1119
+ >(),
1120
+
1121
+ // Inventory & Sales Configuration
1122
+ inventorySettings: jsonb("inventory_settings").$type<{
1123
+ allowNegativeInventory?: boolean;
1124
+ autoReorderEnabled?: boolean;
1125
+ lowStockWarningEnabled?: boolean;
1126
+ stockCountRequired?: boolean;
1127
+ barcodeRequired?: boolean;
1128
+ }>(),
1129
+ salesSettings: jsonb("sales_settings").$type<{
1130
+ taxRate?: number;
1131
+ taxExemptAvailable?: boolean;
1132
+ discountingEnabled?: boolean;
1133
+ maxDiscountPercent?: number;
1134
+ returnsEnabled?: boolean;
1135
+ returnDayLimit?: number;
1136
+ receiptRequired?: boolean;
1137
+ }>(),
1138
+
1139
+ // Customer Services
1140
+ customerServices: jsonb("customer_services").$type<string[]>(),
1141
+
1142
+ // Financial Information
1143
+ operationalCosts: jsonb("operational_costs").$type<{
1144
+ monthlyRent?: number;
1145
+ utilities?: number;
1146
+ labor?: number;
1147
+ maintenance?: number;
1148
+ insurance?: number;
1149
+ currency?: string;
1150
+ }>(),
1151
+
1152
+ // Security and Compliance
1153
+ securityFeatures: jsonb("security_features").$type<string[]>(),
1154
+ complianceRequirements: jsonb("compliance_requirements").$type<string[]>(),
1155
+ lastSecurityAudit: timestamp("last_security_audit", { withTimezone: true }),
1156
+
1157
+ // Notes and Metadata
1158
+ description: text("description"),
1159
+ notes: text("notes"),
1160
+ internalNotes: text("internal_notes"),
1161
+ metadata: jsonb("metadata").$type<Record<string, any>>().default({}),
1162
+ customAttributes: jsonb("custom_attributes")
1163
+ .$type<Record<string, any>>()
1164
+ .default({}),
1165
+
1166
+ // Version Control
1167
+ version: integer("version").notNull().default(1),
1168
+ isActive: boolean("is_active").notNull().default(true),
1169
+
1170
+ // Timestamps & Audit
1171
+ createdAt: timestamp("created_at", { withTimezone: true })
1172
+ .defaultNow()
1173
+ .notNull(),
1174
+ updatedAt: timestamp("updated_at", { withTimezone: true })
1175
+ .defaultNow()
1176
+ .notNull(),
1177
+ createdBy: text("created_by"),
1178
+ updatedBy: text("updated_by"),
1179
+ auditTrail: jsonb("audit_trail").$type<Array<Record<string, any>>>(),
1180
+ },
1181
+ (table) => ({
1182
+ // Performance indexes
1183
+ storeIdIndex: index("idx_pos_locations_store_id").on(table.storeId),
1184
+ statusIndex: index("idx_pos_locations_status").on(table.status),
1185
+ posTypeIndex: index("idx_pos_locations_pos_type").on(table.posType),
1186
+
1187
+ // Unique constraints
1188
+ uniqueCodePerStore: unique("idx_pos_locations_code_unique").on(
1189
+ table.storeId,
1190
+ table.code,
1191
+ ),
1192
+ }),
1193
+ );
1194
+
1195
+ // =====================================================
1196
+ // INVENTORY TRANSFERS TABLE
1197
+ // =====================================================
1198
+
1199
+ export const inventoryTransferStatusEnum = pgEnum("inventory_transfer_status", [
1200
+ "PENDING",
1201
+ "IN_TRANSIT",
1202
+ "COMPLETED",
1203
+ "CANCELLED",
1204
+ "FAILED",
1205
+ "PARTIAL",
1206
+ "RETURNED",
1207
+ ]);
1208
+
1209
+ export const inventoryTransferTypeEnum = pgEnum("inventory_transfer_type", [
1210
+ "WAREHOUSE_TO_WAREHOUSE",
1211
+ "WAREHOUSE_TO_POS",
1212
+ "POS_TO_WAREHOUSE",
1213
+ "POS_TO_POS",
1214
+ "SUPPLIER_TO_WAREHOUSE",
1215
+ "WAREHOUSE_TO_CUSTOMER",
1216
+ "INTERNAL_TRANSFER",
1217
+ ]);
1218
+
1219
+ export const inventoryTransfers = pgTable(
1220
+ "inventory_transfers",
1221
+ {
1222
+ id: text("id")
1223
+ .primaryKey()
1224
+ .$defaultFn(() => createId()),
1225
+ storeId: text("store_id").notNull(),
1226
+ transferNumber: varchar("transfer_number", { length: 100 }).notNull(),
1227
+
1228
+ // Location Information
1229
+ fromLocationId: text("from_location_id").notNull(),
1230
+ fromLocationType: varchar("from_location_type", { length: 50 }).notNull(),
1231
+ toLocationId: text("to_location_id").notNull(),
1232
+ toLocationType: varchar("to_location_type", { length: 50 }).notNull(),
1233
+
1234
+ // Transfer Details
1235
+ status: inventoryTransferStatusEnum("status").notNull().default("PENDING"),
1236
+ transferType: inventoryTransferTypeEnum("transfer_type").notNull(),
1237
+ priority: varchar("priority", { length: 20 }).notNull().default("NORMAL"),
1238
+
1239
+ // Shipping and Tracking
1240
+ shippingMethod: varchar("shipping_method", { length: 100 }),
1241
+ trackingNumber: varchar("tracking_number", { length: 100 }),
1242
+ carrierName: varchar("carrier_name", { length: 100 }),
1243
+
1244
+ // Cost Information
1245
+ shippingCost: decimal("shipping_cost", { precision: 12, scale: 4 }).default(
1246
+ "0",
1247
+ ),
1248
+ totalCost: decimal("total_cost", { precision: 12, scale: 4 }),
1249
+ currency: varchar("currency", { length: 3 }).notNull().default("USD"),
1250
+
1251
+ // Date Management
1252
+ requestedDate: timestamp("requested_date", { withTimezone: true }),
1253
+ scheduledDate: timestamp("scheduled_date", { withTimezone: true }),
1254
+ shippedDate: timestamp("shipped_date", { withTimezone: true }),
1255
+ expectedDeliveryDate: timestamp("expected_delivery_date", {
1256
+ withTimezone: true,
1257
+ }),
1258
+ actualDeliveryDate: timestamp("actual_delivery_date", {
1259
+ withTimezone: true,
1260
+ }),
1261
+ initiatedAt: timestamp("initiated_at", { withTimezone: true }).notNull(),
1262
+ completedAt: timestamp("completed_at", { withTimezone: true }),
1263
+ cancelledAt: timestamp("cancelled_at", { withTimezone: true }),
1264
+
1265
+ // Authorization and Approval
1266
+ requiresApproval: boolean("requires_approval").notNull().default(false),
1267
+ approvedBy: text("approved_by"),
1268
+ approvedAt: timestamp("approved_at", { withTimezone: true }),
1269
+ rejectedBy: text("rejected_by"),
1270
+ rejectedAt: timestamp("rejected_at", { withTimezone: true }),
1271
+ rejectionReason: text("rejection_reason"),
1272
+
1273
+ // Document References
1274
+ referenceOrderNumber: varchar("reference_order_number", { length: 100 }),
1275
+ internalReference: varchar("internal_reference", { length: 100 }),
1276
+ externalReference: varchar("external_reference", { length: 100 }),
1277
+
1278
+ // Quality Control
1279
+ requiresInspection: boolean("requires_inspection").notNull().default(false),
1280
+ inspectedBy: text("inspected_by"),
1281
+ inspectedAt: timestamp("inspected_at", { withTimezone: true }),
1282
+ qualityNotes: text("quality_notes"),
1283
+
1284
+ // Discrepancy Tracking
1285
+ hasDiscrepancies: boolean("has_discrepancies").notNull().default(false),
1286
+ discrepancyNotes: text("discrepancy_notes"),
1287
+
1288
+ // Notes
1289
+ notes: text("notes"),
1290
+ internalNotes: text("internal_notes"),
1291
+ shippingInstructions: text("shipping_instructions"),
1292
+
1293
+ // Timestamps
1294
+ createdAt: timestamp("created_at", { withTimezone: true })
1295
+ .defaultNow()
1296
+ .notNull(),
1297
+ updatedAt: timestamp("updated_at", { withTimezone: true })
1298
+ .defaultNow()
1299
+ .notNull(),
1300
+ createdBy: text("created_by"),
1301
+ updatedBy: text("updated_by"),
1302
+ },
1303
+ (table) => ({
1304
+ storeIdIndex: index("idx_transfers_store_id").on(table.storeId),
1305
+ statusIndex: index("idx_transfers_status").on(table.status),
1306
+ transferNumberIndex: index("idx_transfers_number").on(table.transferNumber),
1307
+ uniqueTransferNumber: unique("idx_transfers_number_unique").on(
1308
+ table.storeId,
1309
+ table.transferNumber,
1310
+ ),
1311
+ }),
1312
+ );
1313
+
1314
+ export const inventoryTransferItems = pgTable(
1315
+ "inventory_transfer_items",
1316
+ {
1317
+ id: text("id")
1318
+ .primaryKey()
1319
+ .$defaultFn(() => createId()),
1320
+ transferId: text("transfer_id").notNull(),
1321
+ productId: text("product_id").notNull(),
1322
+ variantId: text("variant_id"),
1323
+ sku: varchar("sku", { length: 255 }).notNull(),
1324
+ quantity: integer("quantity").notNull(),
1325
+ receivedQuantity: integer("received_quantity").default(0),
1326
+ batchNumber: varchar("batch_number", { length: 255 }),
1327
+ lotNumber: varchar("lot_number", { length: 255 }),
1328
+ serialNumbers: jsonb("serial_numbers").$type<string[]>(),
1329
+ expiryDate: timestamp("expiry_date", { withTimezone: true }),
1330
+ metadata: jsonb("metadata").$type<Record<string, any>>().default({}),
1331
+
1332
+ createdAt: timestamp("created_at", { withTimezone: true })
1333
+ .defaultNow()
1334
+ .notNull(),
1335
+ updatedAt: timestamp("updated_at", { withTimezone: true })
1336
+ .defaultNow()
1337
+ .notNull(),
1338
+ },
1339
+ (table) => ({
1340
+ transferIdIndex: index("idx_transfer_items_transfer_id").on(
1341
+ table.transferId,
1342
+ ),
1343
+ productIdIndex: index("idx_transfer_items_product_id").on(table.productId),
1344
+ }),
1345
+ );
1346
+
1347
+ // =====================================================
1348
+ // QUALITY CONTROLS TABLE
1349
+ // =====================================================
1350
+
1351
+ export const qualityStatusEnum = pgEnum("quality_status", [
1352
+ "PENDING_INSPECTION",
1353
+ "PASSED",
1354
+ "FAILED",
1355
+ "CONDITIONALLY_PASSED",
1356
+ "QUARANTINED",
1357
+ "REJECTED",
1358
+ "REWORK_REQUIRED",
1359
+ "RELEASED",
1360
+ "HOLD",
1361
+ ]);
1362
+
1363
+ export const inspectionTypeEnum = pgEnum("inspection_type", [
1364
+ "INCOMING_INSPECTION",
1365
+ "IN_PROCESS_INSPECTION",
1366
+ "OUTGOING_INSPECTION",
1367
+ "RANDOM_INSPECTION",
1368
+ "CUSTOMER_COMPLAINT_INSPECTION",
1369
+ "SUPPLIER_AUDIT",
1370
+ "PERIODIC_REVIEW",
1371
+ "EXPIRY_CHECK",
1372
+ "DAMAGE_ASSESSMENT",
1373
+ ]);
1374
+
1375
+ export const qualityControls = pgTable(
1376
+ "quality_controls",
1377
+ {
1378
+ id: text("id")
1379
+ .primaryKey()
1380
+ .$defaultFn(() => createId()),
1381
+ storeId: text("store_id").notNull(),
1382
+ qcNumber: varchar("qc_number", { length: 100 }).notNull(),
1383
+
1384
+ // Product Information
1385
+ productId: text("product_id").notNull(),
1386
+ variantId: text("variant_id"),
1387
+ sku: varchar("sku", { length: 255 }).notNull(),
1388
+ productName: varchar("product_name", { length: 255 }),
1389
+
1390
+ // Batch/Lot Information
1391
+ lotId: text("lot_id"), // FK to inventory_lots
1392
+ batchNumber: varchar("batch_number", { length: 100 }),
1393
+ lotNumber: varchar("lot_number", { length: 100 }),
1394
+ serialNumbers: jsonb("serial_numbers"),
1395
+ autoQuarantined: boolean("auto_quarantined").notNull().default(false),
1396
+
1397
+ // Location and Quantity
1398
+ locationId: text("location_id").notNull(),
1399
+ quantity: integer("quantity").notNull(),
1400
+ sampleSize: integer("sample_size"),
1401
+
1402
+ // Inspection Details
1403
+ inspectionType: inspectionTypeEnum("inspection_type").notNull(),
1404
+ inspectionDate: timestamp("inspection_date", {
1405
+ withTimezone: true,
1406
+ }).notNull(),
1407
+ inspectedBy: text("inspected_by").notNull(),
1408
+ inspectionDuration: integer("inspection_duration"), // minutes
1409
+
1410
+ // Quality Assessment
1411
+ overallStatus: qualityStatusEnum("overall_status").notNull(),
1412
+ qualityGrade: varchar("quality_grade", { length: 20 }),
1413
+ qualityScore: decimal("quality_score", { precision: 5, scale: 2 }),
1414
+
1415
+ // Inspection Criteria and Results
1416
+ criteria: jsonb("criteria").notNull(),
1417
+ defects: jsonb("defects"),
1418
+ measurements: jsonb("measurements"),
1419
+ certificates: jsonb("certificates"),
1420
+
1421
+ // Documentation
1422
+ imageUrls: jsonb("image_urls"),
1423
+ documentUrls: jsonb("document_urls"),
1424
+
1425
+ // Supplier Information (for incoming inspections)
1426
+ supplierId: text("supplier_id"),
1427
+ supplierName: varchar("supplier_name", { length: 255 }),
1428
+ purchaseOrderNumber: varchar("purchase_order_number", { length: 100 }),
1429
+
1430
+ // Action Required
1431
+ actionRequired: boolean("action_required").notNull().default(false),
1432
+ correctionActions: jsonb("correction_actions"),
1433
+
1434
+ // Follow-up Requirements
1435
+ requiresReInspection: boolean("requires_re_inspection")
1436
+ .notNull()
1437
+ .default(false),
1438
+ reInspectionDate: timestamp("re_inspection_date", { withTimezone: true }),
1439
+ reInspectionNotes: text("re_inspection_notes"),
1440
+
1441
+ // Disposition
1442
+ dispositionDate: timestamp("disposition_date", { withTimezone: true }),
1443
+ dispositionAction: varchar("disposition_action", { length: 50 }),
1444
+ dispositionNotes: text("disposition_notes"),
1445
+ dispositionBy: text("disposition_by"),
1446
+
1447
+ // Status Updates
1448
+ completedAt: timestamp("completed_at", { withTimezone: true }),
1449
+ approvedBy: text("approved_by"),
1450
+ approvedAt: timestamp("approved_at", { withTimezone: true }),
1451
+
1452
+ // Customer Notification
1453
+ customerNotified: boolean("customer_notified").notNull().default(false),
1454
+ customerNotificationDate: timestamp("customer_notification_date", {
1455
+ withTimezone: true,
1456
+ }),
1457
+ customerResponse: text("customer_response"),
1458
+
1459
+ // Notes
1460
+ notes: text("notes"),
1461
+
1462
+ // Timestamps
1463
+ createdAt: timestamp("created_at", { withTimezone: true })
1464
+ .defaultNow()
1465
+ .notNull(),
1466
+ updatedAt: timestamp("updated_at", { withTimezone: true })
1467
+ .defaultNow()
1468
+ .notNull(),
1469
+ createdBy: text("created_by"),
1470
+ updatedBy: text("updated_by"),
1471
+ },
1472
+ (table) => ({
1473
+ storeIdIndex: index("idx_quality_controls_store_id").on(table.storeId),
1474
+ qcNumberIndex: index("idx_quality_controls_qc_number").on(table.qcNumber),
1475
+ statusIndex: index("idx_quality_controls_status").on(table.overallStatus),
1476
+ inspectionDateIndex: index("idx_quality_controls_inspection_date").on(
1477
+ table.inspectionDate,
1478
+ ),
1479
+ uniqueQcNumber: unique("idx_quality_controls_number_unique").on(
1480
+ table.storeId,
1481
+ table.qcNumber,
1482
+ ),
1483
+ }),
1484
+ );
1485
+
1486
+ export const qualityTemplates = pgTable(
1487
+ "quality_templates",
1488
+ {
1489
+ id: text("id")
1490
+ .primaryKey()
1491
+ .$defaultFn(() => createId()),
1492
+ storeId: text("store_id").notNull(),
1493
+ name: varchar("name", { length: 255 }).notNull(),
1494
+ version: varchar("version", { length: 50 }).notNull(),
1495
+ isActive: boolean("is_active").notNull().default(true),
1496
+
1497
+ // Template Configuration
1498
+ criteria: jsonb("criteria").$type<any[]>().default([]),
1499
+ inspectionTypes: jsonb("inspection_types").$type<string[]>().default([]),
1500
+
1501
+ // Notes
1502
+ description: text("description"),
1503
+ notes: text("notes"),
1504
+
1505
+ // Timestamps
1506
+ createdAt: timestamp("created_at", { withTimezone: true })
1507
+ .defaultNow()
1508
+ .notNull(),
1509
+ updatedAt: timestamp("updated_at", { withTimezone: true })
1510
+ .defaultNow()
1511
+ .notNull(),
1512
+ createdBy: text("created_by"),
1513
+ updatedBy: text("updated_by"),
1514
+ },
1515
+ (table) => ({
1516
+ storeIdIndex: index("idx_quality_templates_store_id").on(table.storeId),
1517
+ nameIndex: index("idx_quality_templates_name").on(table.name),
1518
+ isActiveIndex: index("idx_quality_templates_active").on(table.isActive),
1519
+ }),
1520
+ );
1521
+
1522
+ // =====================================================
1523
+ // SUPPLIER QUALITY RATINGS TABLE
1524
+ // =====================================================
1525
+
1526
+ export const supplierQualityRatings = pgTable(
1527
+ "supplier_quality_ratings",
1528
+ {
1529
+ id: text("id")
1530
+ .primaryKey()
1531
+ .$defaultFn(() => createId()),
1532
+ storeId: text("store_id").notNull(),
1533
+ supplierId: text("supplier_id").notNull(),
1534
+
1535
+ // Rating Period
1536
+ ratingPeriod: jsonb("rating_period")
1537
+ .$type<{
1538
+ startDate: Date;
1539
+ endDate: Date;
1540
+ }>()
1541
+ .notNull(),
1542
+
1543
+ // Inspection Metrics
1544
+ totalInspections: integer("total_inspections").notNull(),
1545
+ passedInspections: integer("passed_inspections").notNull(),
1546
+ failedInspections: integer("failed_inspections").notNull(),
1547
+
1548
+ // Performance Metrics
1549
+ qualityScore: decimal("quality_score", { precision: 5, scale: 2 }),
1550
+ onTimeDeliveryRate: decimal("on_time_delivery_rate", {
1551
+ precision: 5,
1552
+ scale: 2,
1553
+ }),
1554
+ defectRate: decimal("defect_rate", { precision: 5, scale: 2 }),
1555
+
1556
+ // Overall Rating
1557
+ overallRating: varchar("overall_rating", { length: 20 }),
1558
+
1559
+ // Notes
1560
+ notes: text("notes"),
1561
+
1562
+ // Timestamps
1563
+ createdAt: timestamp("created_at", { withTimezone: true })
1564
+ .defaultNow()
1565
+ .notNull(),
1566
+ updatedAt: timestamp("updated_at", { withTimezone: true })
1567
+ .defaultNow()
1568
+ .notNull(),
1569
+ createdBy: text("created_by"),
1570
+ updatedBy: text("updated_by"),
1571
+ },
1572
+ (table) => ({
1573
+ storeIdIndex: index("idx_supplier_quality_ratings_store_id").on(
1574
+ table.storeId,
1575
+ ),
1576
+ supplierIdIndex: index("idx_supplier_quality_ratings_supplier_id").on(
1577
+ table.supplierId,
1578
+ ),
1579
+ }),
1580
+ );
1581
+
1582
+ // =====================================================
1583
+ // SUPPLIERS TABLE
1584
+ // =====================================================
1585
+
1586
+ export const supplierStatusEnum = pgEnum("supplier_status", [
1587
+ "ACTIVE",
1588
+ "INACTIVE",
1589
+ "SUSPENDED",
1590
+ "UNDER_REVIEW",
1591
+ "PENDING_APPROVAL",
1592
+ "BLACKLISTED",
1593
+ ]);
1594
+
1595
+ export const supplierTypeEnum = pgEnum("supplier_type", [
1596
+ "MANUFACTURER",
1597
+ "DISTRIBUTOR",
1598
+ "WHOLESALER",
1599
+ "DROPSHIPPER",
1600
+ "SERVICE_PROVIDER",
1601
+ "LOCAL_VENDOR",
1602
+ "INTERNATIONAL_VENDOR",
1603
+ "BROKER",
1604
+ "IMPORTER",
1605
+ "EXPORTER",
1606
+ "RETAILER",
1607
+ "RESELLER",
1608
+ "CONTRACTOR",
1609
+ "CONSULTANT",
1610
+ "PARTS_SUPPLIER",
1611
+ "RAW_MATERIALS_SUPPLIER",
1612
+ "PACKAGING_SUPPLIER",
1613
+ "EQUIPMENT_SUPPLIER",
1614
+ "MAINTENANCE_SUPPLIER",
1615
+ "LOGISTICS_PROVIDER",
1616
+ "FREIGHT_FORWARDER",
1617
+ "CUSTOMS_BROKER",
1618
+ "TECHNOLOGY_VENDOR",
1619
+ "SOFTWARE_VENDOR",
1620
+ "HARDWARE_VENDOR",
1621
+ "OFFICE_SUPPLIES_VENDOR",
1622
+ "UTILITIES_PROVIDER",
1623
+ "CLEANING_SERVICES",
1624
+ "SECURITY_SERVICES",
1625
+ "MARKETING_SERVICES",
1626
+ "PROFESSIONAL_SERVICES",
1627
+ "TEMPORARY_STAFFING",
1628
+ "SPECIALIZED_SUPPLIER",
1629
+ "OTHER",
1630
+ ]);
1631
+
1632
+ export const supplierRatingEnum = pgEnum("supplier_rating", [
1633
+ "EXCELLENT",
1634
+ "GOOD",
1635
+ "AVERAGE",
1636
+ "POOR",
1637
+ "UNRATED",
1638
+ ]);
1639
+
1640
+ export const suppliers = pgTable(
1641
+ "suppliers",
1642
+ {
1643
+ id: text("id")
1644
+ .primaryKey()
1645
+ .$defaultFn(() => createId()),
1646
+ storeId: text("store_id").notNull(),
1647
+ name: varchar("name", { length: 255 }).notNull(),
1648
+ code: varchar("code", { length: 100 }).notNull(),
1649
+ status: supplierStatusEnum("status").notNull().default("ACTIVE"),
1650
+ type: supplierTypeEnum("type").notNull().default("MANUFACTURER"),
1651
+
1652
+ // Contact Information
1653
+ primaryContact: jsonb("primary_contact").$type<{
1654
+ name?: string;
1655
+ email?: string;
1656
+ phone?: string;
1657
+ alternatePhone?: string;
1658
+ }>(),
1659
+
1660
+ // Address Information
1661
+ address: jsonb("address").$type<{
1662
+ street: string;
1663
+ city: string;
1664
+ state: string;
1665
+ postalCode: string;
1666
+ country: string;
1667
+ }>(),
1668
+ billingAddress: jsonb("billing_address").$type<{
1669
+ street: string;
1670
+ city: string;
1671
+ state: string;
1672
+ postalCode: string;
1673
+ country: string;
1674
+ }>(),
1675
+
1676
+ // Payment Terms
1677
+ paymentTerms: jsonb("payment_terms").$type<{
1678
+ termsDays: number;
1679
+ discountDays?: number;
1680
+ discountPercentage?: number;
1681
+ penaltyRate?: number;
1682
+ }>(),
1683
+
1684
+ // Business Information
1685
+ taxId: varchar("tax_id", { length: 100 }),
1686
+ businessLicense: varchar("business_license", { length: 100 }),
1687
+ certifications: jsonb("certifications").$type<string[]>().default([]),
1688
+
1689
+ // Performance Metrics
1690
+ rating: supplierRatingEnum("rating"),
1691
+ onTimeDeliveryRate: decimal("on_time_delivery_rate", {
1692
+ precision: 5,
1693
+ scale: 2,
1694
+ }),
1695
+ qualityRating: decimal("quality_rating", { precision: 5, scale: 2 }),
1696
+
1697
+ // Business Terms
1698
+ minimumOrderAmount: decimal("minimum_order_amount", {
1699
+ precision: 12,
1700
+ scale: 4,
1701
+ }),
1702
+ leadTimeDays: integer("lead_time_days"),
1703
+ deliveryMethod: varchar("delivery_method", { length: 100 }),
1704
+ currency: varchar("currency", { length: 3 }).notNull().default("USD"),
1705
+
1706
+ // Metadata
1707
+ tags: jsonb("tags").$type<string[]>().default([]),
1708
+ notes: text("notes"),
1709
+ metadata: jsonb("metadata").$type<Record<string, any>>().default({}),
1710
+
1711
+ // Status
1712
+ isActive: boolean("is_active").notNull().default(true),
1713
+
1714
+ // Timestamps
1715
+ createdAt: timestamp("created_at", { withTimezone: true })
1716
+ .defaultNow()
1717
+ .notNull(),
1718
+ updatedAt: timestamp("updated_at", { withTimezone: true })
1719
+ .defaultNow()
1720
+ .notNull(),
1721
+ createdBy: text("created_by"),
1722
+ updatedBy: text("updated_by"),
1723
+ },
1724
+ (table) => ({
1725
+ storeIdIndex: index("idx_suppliers_store_id").on(table.storeId),
1726
+ statusIndex: index("idx_suppliers_status").on(table.status),
1727
+ typeIndex: index("idx_suppliers_type").on(table.type),
1728
+ ratingIndex: index("idx_suppliers_rating").on(table.rating),
1729
+ uniqueCodePerStore: unique("idx_suppliers_code_unique").on(
1730
+ table.storeId,
1731
+ table.code,
1732
+ ),
1733
+ }),
1734
+ );
1735
+
1736
+ // =====================================================
1737
+ // PURCHASE ORDERS TABLE
1738
+ // =====================================================
1739
+
1740
+ export const purchaseOrderStatusEnum = pgEnum("purchase_order_status", [
1741
+ "DRAFT",
1742
+ "SENT",
1743
+ "ACKNOWLEDGED",
1744
+ "APPROVED",
1745
+ "PENDING_APPROVAL",
1746
+ "PARTIALLY_RECEIVED",
1747
+ "RECEIVED",
1748
+ "COMPLETED",
1749
+ "CANCELLED",
1750
+ "DISPUTED",
1751
+ ]);
1752
+
1753
+ export const purchaseOrders = pgTable(
1754
+ "purchase_orders",
1755
+ {
1756
+ id: text("id")
1757
+ .primaryKey()
1758
+ .$defaultFn(() => createId()),
1759
+ storeId: text("store_id").notNull(),
1760
+ supplierId: text("supplier_id").notNull(),
1761
+ orderNumber: varchar("order_number", { length: 100 }).notNull(),
1762
+ status: purchaseOrderStatusEnum("status").notNull().default("DRAFT"),
1763
+
1764
+ // Order Details
1765
+ orderDate: timestamp("order_date", { withTimezone: true }).notNull(),
1766
+ requestedDeliveryDate: timestamp("requested_delivery_date", {
1767
+ withTimezone: true,
1768
+ }),
1769
+ expectedDeliveryDate: timestamp("expected_delivery_date", {
1770
+ withTimezone: true,
1771
+ }),
1772
+ actualDeliveryDate: timestamp("actual_delivery_date", {
1773
+ withTimezone: true,
1774
+ }),
1775
+
1776
+ // Financial Information
1777
+ subtotal: decimal("subtotal", { precision: 12, scale: 4 }).notNull(),
1778
+ taxAmount: decimal("tax_amount", { precision: 12, scale: 4 }).default("0"),
1779
+ discountAmount: decimal("discount_amount", {
1780
+ precision: 12,
1781
+ scale: 4,
1782
+ }).default("0"),
1783
+ shippingAmount: decimal("shipping_amount", {
1784
+ precision: 12,
1785
+ scale: 4,
1786
+ }).default("0"),
1787
+ totalAmount: decimal("total_amount", { precision: 12, scale: 4 }).notNull(),
1788
+ currency: varchar("currency", { length: 3 }).notNull().default("USD"),
1789
+
1790
+ // Shipping Information
1791
+ shippingAddress: jsonb("shipping_address").$type<{
1792
+ street: string;
1793
+ city: string;
1794
+ state: string;
1795
+ postalCode: string;
1796
+ country: string;
1797
+ }>(),
1798
+
1799
+ // Payment Terms
1800
+ paymentTerms: varchar("payment_terms", { length: 50 }).notNull(),
1801
+
1802
+ // Status Tracking
1803
+ approvedBy: text("approved_by"),
1804
+ approvedAt: timestamp("approved_at", { withTimezone: true }),
1805
+ sentDate: timestamp("sent_date", { withTimezone: true }),
1806
+ acknowledgedDate: timestamp("acknowledged_date", { withTimezone: true }),
1807
+
1808
+ // Notes
1809
+ notes: text("notes"),
1810
+ internalNotes: text("internal_notes"),
1811
+
1812
+ // Timestamps
1813
+ createdAt: timestamp("created_at", { withTimezone: true })
1814
+ .defaultNow()
1815
+ .notNull(),
1816
+ updatedAt: timestamp("updated_at", { withTimezone: true })
1817
+ .defaultNow()
1818
+ .notNull(),
1819
+ createdBy: text("created_by"),
1820
+ updatedBy: text("updated_by"),
1821
+ },
1822
+ (table) => ({
1823
+ storeIdIndex: index("idx_purchase_orders_store_id").on(table.storeId),
1824
+ supplierIdIndex: index("idx_purchase_orders_supplier_id").on(
1825
+ table.supplierId,
1826
+ ),
1827
+ statusIndex: index("idx_purchase_orders_status").on(table.status),
1828
+ orderDateIndex: index("idx_purchase_orders_order_date").on(table.orderDate),
1829
+ uniqueOrderNumber: unique("idx_purchase_orders_number_unique").on(
1830
+ table.storeId,
1831
+ table.orderNumber,
1832
+ ),
1833
+ }),
1834
+ );
1835
+
1836
+ export const purchaseOrderItems = pgTable(
1837
+ "purchase_order_items",
1838
+ {
1839
+ id: text("id")
1840
+ .primaryKey()
1841
+ .$defaultFn(() => createId()),
1842
+ orderId: text("order_id").notNull(),
1843
+ productId: text("product_id").notNull(),
1844
+ variantId: text("variant_id"),
1845
+ sku: varchar("sku", { length: 255 }).notNull(),
1846
+
1847
+ // Quantity and Pricing
1848
+ quantity: integer("quantity").notNull(),
1849
+ receivedQuantity: integer("received_quantity").default(0),
1850
+ unitPrice: decimal("unit_price", { precision: 12, scale: 4 }).notNull(),
1851
+ totalPrice: decimal("total_price", { precision: 12, scale: 4 }).notNull(),
1852
+ taxRate: decimal("tax_rate", { precision: 5, scale: 2 }).default("0"),
1853
+ discountRate: decimal("discount_rate", { precision: 5, scale: 2 }).default(
1854
+ "0",
1855
+ ),
1856
+
1857
+ // Notes
1858
+ notes: text("notes"),
1859
+
1860
+ // Timestamps
1861
+ createdAt: timestamp("created_at", { withTimezone: true })
1862
+ .defaultNow()
1863
+ .notNull(),
1864
+ updatedAt: timestamp("updated_at", { withTimezone: true })
1865
+ .defaultNow()
1866
+ .notNull(),
1867
+ },
1868
+ (table) => ({
1869
+ orderIdIndex: index("idx_purchase_order_items_order_id").on(table.orderId),
1870
+ productIdIndex: index("idx_purchase_order_items_product_id").on(
1871
+ table.productId,
1872
+ ),
1873
+ }),
1874
+ );
1875
+
1876
+ export const productSuppliers = pgTable(
1877
+ "product_suppliers",
1878
+ {
1879
+ id: text("id")
1880
+ .primaryKey()
1881
+ .$defaultFn(() => createId()),
1882
+ storeId: text("store_id").notNull(),
1883
+ productId: text("product_id").notNull(),
1884
+ supplierId: text("supplier_id").notNull(),
1885
+
1886
+ // Supplier Product Information
1887
+ supplierSku: varchar("supplier_sku", { length: 255 }),
1888
+ supplierProductName: varchar("supplier_product_name", { length: 255 }),
1889
+
1890
+ // Pricing and Terms
1891
+ unitCost: decimal("unit_cost", { precision: 12, scale: 4 }),
1892
+ cost: decimal("cost", { precision: 12, scale: 4 }),
1893
+ currency: varchar("currency", { length: 3 }).notNull().default("USD"),
1894
+ minimumOrderQuantity: integer("minimum_order_quantity"),
1895
+ leadTime: integer("lead_time"),
1896
+ packSize: integer("pack_size").default(1),
1897
+ availability: varchar("availability", { length: 20 }).default("IN_STOCK"),
1898
+
1899
+ // Status
1900
+ isActive: boolean("is_active").notNull().default(true),
1901
+ isPreferred: boolean("is_preferred").notNull().default(false),
1902
+
1903
+ // Timestamps
1904
+ createdAt: timestamp("created_at", { withTimezone: true })
1905
+ .defaultNow()
1906
+ .notNull(),
1907
+ updatedAt: timestamp("updated_at", { withTimezone: true })
1908
+ .defaultNow()
1909
+ .notNull(),
1910
+ lastOrderDate: timestamp("last_order_date", { withTimezone: true }),
1911
+ lastCostUpdate: timestamp("last_cost_update", { withTimezone: true }),
1912
+ },
1913
+ (table) => ({
1914
+ storeIdIndex: index("idx_product_suppliers_store_id").on(table.storeId),
1915
+ productIdIndex: index("idx_product_suppliers_product_id").on(
1916
+ table.productId,
1917
+ ),
1918
+ supplierIdIndex: index("idx_product_suppliers_supplier_id").on(
1919
+ table.supplierId,
1920
+ ),
1921
+ uniqueProductSupplier: unique("idx_product_suppliers_unique").on(
1922
+ table.storeId,
1923
+ table.productId,
1924
+ table.supplierId,
1925
+ ),
1926
+ }),
1927
+ );