@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,1400 @@
1
+ import { createId } from "@paralleldrive/cuid2";
2
+ import { relations } from "drizzle-orm";
3
+ import {
4
+ boolean,
5
+ decimal,
6
+ index,
7
+ integer,
8
+ jsonb,
9
+ pgEnum,
10
+ pgTable,
11
+ text,
12
+ timestamp,
13
+ unique,
14
+ varchar,
15
+ } from "drizzle-orm/pg-core";
16
+
17
+ // =====================================================
18
+ // ENUMS
19
+ // =====================================================
20
+
21
+ export const orderTypeEnum = pgEnum("order_type", [
22
+ "ONLINE",
23
+ "POS",
24
+ "PICKUP",
25
+ "DELIVERY",
26
+ "MARKETPLACE",
27
+ "PHONE",
28
+ "WHOLESALE",
29
+ "SUBSCRIPTION",
30
+ "SUBSCRIPTION_RENEWAL",
31
+ ]);
32
+
33
+ export const orderStatusEnum = pgEnum("order_status", [
34
+ "DRAFT",
35
+ "PENDING",
36
+ "CONFIRMED",
37
+ "PROCESSING",
38
+ "PICKING",
39
+ "PACKED",
40
+ "SHIPPED",
41
+ "OUT_FOR_DELIVERY",
42
+ "DELIVERED",
43
+ "COMPLETED",
44
+ "CANCELLED",
45
+ "REFUNDED",
46
+ "PARTIALLY_REFUNDED",
47
+ "RETURNED",
48
+ "PARTIALLY_RETURNED",
49
+ "DISPUTED",
50
+ "ON_HOLD",
51
+ "FAILED",
52
+ ]);
53
+
54
+ export const paymentStatusEnum = pgEnum("payment_status", [
55
+ "PENDING",
56
+ "AUTHORIZED",
57
+ "PAID",
58
+ "PARTIALLY_PAID",
59
+ "FAILED",
60
+ "CANCELLED",
61
+ "REFUNDED",
62
+ "PARTIALLY_REFUNDED",
63
+ "VOIDED",
64
+ "CHARGEBACK",
65
+ "DISPUTED",
66
+ ]);
67
+
68
+ export const fulfillmentStatusEnum = pgEnum("fulfillment_status", [
69
+ "PENDING",
70
+ "ALLOCATED",
71
+ "PICKING",
72
+ "PICKED",
73
+ "PACKED",
74
+ "READY_FOR_PICKUP",
75
+ "SHIPPED",
76
+ "IN_TRANSIT",
77
+ "OUT_FOR_DELIVERY",
78
+ "DELIVERED",
79
+ "ATTEMPTED_DELIVERY",
80
+ "DELIVERY_FAILED",
81
+ "RETURNED_TO_SENDER",
82
+ "CANCELLED",
83
+ "LOST",
84
+ ]);
85
+
86
+ export const orderPriorityEnum = pgEnum("order_priority", [
87
+ "LOW",
88
+ "NORMAL",
89
+ "HIGH",
90
+ "URGENT",
91
+ "CRITICAL",
92
+ ]);
93
+
94
+ export const discountTypeEnum = pgEnum("discount_type", [
95
+ "PERCENTAGE",
96
+ "FIXED_AMOUNT",
97
+ "FREE_SHIPPING",
98
+ "BOGO",
99
+ "LOYALTY_POINTS",
100
+ "COUPON",
101
+ "PROMOTIONAL",
102
+ "BULK",
103
+ "EARLY_BIRD",
104
+ ]);
105
+
106
+ export const addressTypeEnum = pgEnum("address_type", [
107
+ "BILLING",
108
+ "SHIPPING",
109
+ "PICKUP",
110
+ "DELIVERY",
111
+ ]);
112
+
113
+ export const paymentMethodEnum = pgEnum("payment_method", [
114
+ "CREDIT_CARD",
115
+ "DEBIT_CARD",
116
+ "PAYPAL",
117
+ "APPLE_PAY",
118
+ "GOOGLE_PAY",
119
+ "BANK_TRANSFER",
120
+ "CASH",
121
+ "CHECK",
122
+ "STORE_CREDIT",
123
+ "GIFT_CARD",
124
+ "CRYPTOCURRENCY",
125
+ "BUY_NOW_PAY_LATER",
126
+ "INVOICE",
127
+ "COD", // Cash on Delivery
128
+ ]);
129
+
130
+ export const refundReasonEnum = pgEnum("refund_reason", [
131
+ "CUSTOMER_REQUEST",
132
+ "DEFECTIVE_PRODUCT",
133
+ "WRONG_ITEM",
134
+ "NOT_AS_DESCRIBED",
135
+ "DAMAGED_IN_SHIPPING",
136
+ "QUALITY_ISSUE",
137
+ "SIZE_ISSUE",
138
+ "CHANGE_OF_MIND",
139
+ "DUPLICATE_ORDER",
140
+ "FRAUD_PREVENTION",
141
+ "BUSINESS_POLICY",
142
+ "GOODWILL",
143
+ ]);
144
+
145
+ // =====================================================
146
+ // CORE ORDERS TABLE
147
+ // =====================================================
148
+
149
+ export const orders = pgTable(
150
+ "orders",
151
+ {
152
+ id: text("id")
153
+ .primaryKey()
154
+ .$defaultFn(() => createId()),
155
+
156
+ // External References
157
+ storeId: text("store_id").notNull(),
158
+ customerId: text("customer_id"), // Nullable for guest orders
159
+ userId: text("user_id"), // Account holder
160
+ sessionId: text("session_id"), // Browser/device session
161
+
162
+ // Order Identification
163
+ orderNumber: varchar("order_number", { length: 50 }).notNull(),
164
+ displayOrderNumber: varchar("display_order_number", { length: 50 }), // Customer-friendly number
165
+ externalOrderId: text("external_order_id"), // For marketplace orders
166
+ parentOrderId: text("parent_order_id"), // For split orders
167
+
168
+ // Order Classification
169
+ orderType: orderTypeEnum("order_type").notNull(),
170
+ orderSource: varchar("order_source", { length: 50 })
171
+ .notNull()
172
+ .$type<
173
+ | "WEB"
174
+ | "MOBILE_APP"
175
+ | "POS"
176
+ | "PHONE"
177
+ | "EMAIL"
178
+ | "SOCIAL_MEDIA"
179
+ | "MARKETPLACE"
180
+ | "API"
181
+ | "ADMIN"
182
+ | "IMPORT"
183
+ | "SUBSCRIPTION_RENEWAL"
184
+ >(),
185
+ marketplaceInfo: jsonb("marketplace_info").$type<{
186
+ platform?: string;
187
+ orderId?: string;
188
+ accountId?: string;
189
+ fees?: number;
190
+ commission?: number;
191
+ }>(),
192
+
193
+ // Status Management
194
+ status: orderStatusEnum("status").notNull().default("DRAFT"),
195
+ paymentStatus: paymentStatusEnum("payment_status")
196
+ .notNull()
197
+ .default("PENDING"),
198
+ fulfillmentStatus: fulfillmentStatusEnum("fulfillment_status")
199
+ .notNull()
200
+ .default("PENDING"),
201
+
202
+ // Priority and Flags
203
+ priority: orderPriorityEnum("priority").notNull().default("NORMAL"),
204
+ isGuestOrder: boolean("is_guest_order").notNull().default(false),
205
+ isTestOrder: boolean("is_test_order").notNull().default(false),
206
+ isGift: boolean("is_gift").notNull().default(false),
207
+ isPreorder: boolean("is_preorder").notNull().default(false),
208
+ isSubscription: boolean("is_subscription").notNull().default(false),
209
+ requiresShipping: boolean("requires_shipping").notNull().default(true),
210
+ requiresPickup: boolean("requires_pickup").notNull().default(false),
211
+
212
+ // Financial Information
213
+ currency: varchar("currency", { length: 3 }).notNull().default("USD"),
214
+ exchangeRate: decimal("exchange_rate", { precision: 10, scale: 6 }).default(
215
+ "1.000000",
216
+ ),
217
+
218
+ // Amounts (in minor currency units - cents)
219
+ subtotalAmount: decimal("subtotal_amount", { precision: 12, scale: 2 })
220
+ .notNull()
221
+ .default("0.00"),
222
+ taxAmount: decimal("tax_amount", { precision: 12, scale: 2 })
223
+ .notNull()
224
+ .default("0.00"),
225
+ shippingAmount: decimal("shipping_amount", { precision: 12, scale: 2 })
226
+ .notNull()
227
+ .default("0.00"),
228
+ discountAmount: decimal("discount_amount", { precision: 12, scale: 2 })
229
+ .notNull()
230
+ .default("0.00"),
231
+ tipAmount: decimal("tip_amount", { precision: 12, scale: 2 })
232
+ .notNull()
233
+ .default("0.00"),
234
+ feeAmount: decimal("fee_amount", { precision: 12, scale: 2 })
235
+ .notNull()
236
+ .default("0.00"),
237
+ totalAmount: decimal("total_amount", { precision: 12, scale: 2 }).notNull(),
238
+ paidAmount: decimal("paid_amount", { precision: 12, scale: 2 })
239
+ .notNull()
240
+ .default("0.00"),
241
+ refundedAmount: decimal("refunded_amount", { precision: 12, scale: 2 })
242
+ .notNull()
243
+ .default("0.00"),
244
+ outstandingAmount: decimal("outstanding_amount", {
245
+ precision: 12,
246
+ scale: 2,
247
+ })
248
+ .notNull()
249
+ .default("0.00"),
250
+
251
+ // Tax Information
252
+ taxRate: decimal("tax_rate", { precision: 5, scale: 4 }),
253
+ taxIncluded: boolean("tax_included").notNull().default(false),
254
+ taxExempt: boolean("tax_exempt").notNull().default(false),
255
+ taxExemptReason: text("tax_exempt_reason"),
256
+
257
+ // Customer Information (for guest orders)
258
+ guestEmail: varchar("guest_email", { length: 255 }),
259
+ guestPhone: varchar("guest_phone", { length: 20 }),
260
+ guestName: varchar("guest_name", { length: 100 }),
261
+
262
+ // Location Information
263
+ storeLocationId: text("store_location_id"), // Physical store/warehouse
264
+ posTerminalId: text("pos_terminal_id"), // POS terminal for in-store orders
265
+ salesAssociateId: text("sales_associate_id"), // Staff who processed the order
266
+
267
+ // Timing Information
268
+ requestedDeliveryDate: timestamp("requested_delivery_date", {
269
+ withTimezone: true,
270
+ }),
271
+ promisedDeliveryDate: timestamp("promised_delivery_date", {
272
+ withTimezone: true,
273
+ }),
274
+ actualDeliveryDate: timestamp("actual_delivery_date", {
275
+ withTimezone: true,
276
+ }),
277
+ orderPlacedAt: timestamp("order_placed_at", { withTimezone: true }),
278
+ orderConfirmedAt: timestamp("order_confirmed_at", { withTimezone: true }),
279
+ orderShippedAt: timestamp("order_shipped_at", { withTimezone: true }),
280
+ orderDeliveredAt: timestamp("order_delivered_at", { withTimezone: true }),
281
+ orderCompletedAt: timestamp("order_completed_at", { withTimezone: true }),
282
+
283
+ // Special Instructions and Notes
284
+ customerNotes: text("customer_notes"),
285
+ internalNotes: text("internal_notes"),
286
+ giftMessage: text("gift_message"),
287
+ specialInstructions: text("special_instructions"),
288
+ deliveryInstructions: text("delivery_instructions"),
289
+
290
+ // Marketing and Attribution
291
+ referrerUrl: text("referrer_url"),
292
+ utmSource: varchar("utm_source", { length: 100 }),
293
+ utmMedium: varchar("utm_medium", { length: 100 }),
294
+ utmCampaign: varchar("utm_campaign", { length: 100 }),
295
+ affiliateId: text("affiliate_id"),
296
+ promoCode: varchar("promo_code", { length: 50 }),
297
+
298
+ // Technical Information
299
+ userAgent: text("user_agent"),
300
+ ipAddress: varchar("ip_address", { length: 45 }),
301
+ deviceInfo: jsonb("device_info").$type<{
302
+ type?: string;
303
+ brand?: string;
304
+ model?: string;
305
+ os?: string;
306
+ browser?: string;
307
+ }>(),
308
+
309
+ // Risk and Fraud
310
+ riskScore: decimal("risk_score", { precision: 5, scale: 2 }),
311
+ fraudScore: decimal("fraud_score", { precision: 5, scale: 2 }),
312
+ isHighRisk: boolean("is_high_risk").notNull().default(false),
313
+ requiresVerification: boolean("requires_verification")
314
+ .notNull()
315
+ .default(false),
316
+ verifiedAt: timestamp("verified_at", { withTimezone: true }),
317
+
318
+ // Subscription Information
319
+ subscriptionId: text("subscription_id"),
320
+ subscriptionCycle: integer("subscription_cycle"),
321
+ isSubscriptionRenewal: boolean("is_subscription_renewal")
322
+ .notNull()
323
+ .default(false),
324
+
325
+ // Advanced Features
326
+ tags: jsonb("tags").$type<string[]>().default([]),
327
+ customFields: jsonb("custom_fields")
328
+ .$type<Record<string, unknown>>()
329
+ .default({}),
330
+ metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
331
+
332
+ // Analytics
333
+ conversionValue: decimal("conversion_value", { precision: 12, scale: 2 }),
334
+ customerLifetimeValue: decimal("customer_lifetime_value", {
335
+ precision: 12,
336
+ scale: 2,
337
+ }),
338
+ profitMargin: decimal("profit_margin", { precision: 5, scale: 2 }),
339
+
340
+ // Timestamps
341
+ createdAt: timestamp("created_at", { withTimezone: true })
342
+ .defaultNow()
343
+ .notNull(),
344
+ updatedAt: timestamp("updated_at", { withTimezone: true })
345
+ .defaultNow()
346
+ .notNull(),
347
+ deletedAt: timestamp("deleted_at", { withTimezone: true }),
348
+ },
349
+ (table) => ({
350
+ // Primary Indexes
351
+ orderNumberIndex: unique("idx_orders_order_number").on(
352
+ table.storeId,
353
+ table.orderNumber,
354
+ ),
355
+ externalOrderIndex: index("idx_orders_external_id").on(
356
+ table.externalOrderId,
357
+ ),
358
+
359
+ // Customer and User Indexes
360
+ customerIndex: index("idx_orders_customer").on(table.customerId),
361
+ userIndex: index("idx_orders_user").on(table.userId),
362
+ guestEmailIndex: index("idx_orders_guest_email").on(table.guestEmail),
363
+
364
+ // Status Indexes
365
+ statusIndex: index("idx_orders_status").on(table.status),
366
+ paymentStatusIndex: index("idx_orders_payment_status").on(
367
+ table.paymentStatus,
368
+ ),
369
+ fulfillmentStatusIndex: index("idx_orders_fulfillment_status").on(
370
+ table.fulfillmentStatus,
371
+ ),
372
+
373
+ // Type and Source Indexes
374
+ orderTypeIndex: index("idx_orders_type").on(table.orderType),
375
+ orderSourceIndex: index("idx_orders_source").on(table.orderSource),
376
+
377
+ // Location Indexes
378
+ storeLocationIndex: index("idx_orders_store_location").on(
379
+ table.storeLocationId,
380
+ ),
381
+ posTerminalIndex: index("idx_orders_pos_terminal").on(table.posTerminalId),
382
+
383
+ // Timing Indexes
384
+ placedAtIndex: index("idx_orders_placed_at").on(table.orderPlacedAt),
385
+ deliveryDateIndex: index("idx_orders_delivery_date").on(
386
+ table.promisedDeliveryDate,
387
+ ),
388
+ createdAtIndex: index("idx_orders_created_at").on(table.createdAt),
389
+
390
+ // Financial Indexes
391
+ totalAmountIndex: index("idx_orders_total_amount").on(table.totalAmount),
392
+ currencyIndex: index("idx_orders_currency").on(table.currency),
393
+
394
+ // Special Flags Indexes
395
+ priorityIndex: index("idx_orders_priority").on(table.priority),
396
+ testOrderIndex: index("idx_orders_test").on(table.isTestOrder),
397
+ guestOrderIndex: index("idx_orders_guest").on(table.isGuestOrder),
398
+
399
+ // Composite Indexes
400
+ storeStatusIndex: index("idx_orders_store_status").on(
401
+ table.storeId,
402
+ table.status,
403
+ ),
404
+ customerStatusIndex: index("idx_orders_customer_status").on(
405
+ table.customerId,
406
+ table.status,
407
+ ),
408
+ typeStatusIndex: index("idx_orders_type_status").on(
409
+ table.orderType,
410
+ table.status,
411
+ ),
412
+ dateRangeIndex: index("idx_orders_date_range").on(
413
+ table.storeId,
414
+ table.createdAt,
415
+ ),
416
+ }),
417
+ );
418
+
419
+ // =====================================================
420
+ // ORDER ITEMS TABLE
421
+ // =====================================================
422
+
423
+ export const orderItems = pgTable(
424
+ "order_items",
425
+ {
426
+ id: text("id")
427
+ .primaryKey()
428
+ .$defaultFn(() => createId()),
429
+
430
+ orderId: text("order_id")
431
+ .notNull()
432
+ .references(() => orders.id, { onDelete: "cascade" }),
433
+
434
+ // Product Information
435
+ productId: text("product_id").notNull(),
436
+ variantId: text("variant_id"),
437
+ sku: varchar("sku", { length: 100 }).notNull(),
438
+ productHandle: varchar("product_handle", { length: 255 }),
439
+
440
+ // Product Details (snapshot at time of order)
441
+ productTitle: varchar("product_title", { length: 255 }).notNull(),
442
+ variantTitle: varchar("variant_title", { length: 255 }),
443
+ productType: varchar("product_type", { length: 50 }),
444
+ vendor: varchar("vendor", { length: 100 }),
445
+
446
+ // Quantity and Pricing
447
+ quantity: integer("quantity").notNull(),
448
+ quantityShipped: integer("quantity_shipped").notNull().default(0),
449
+ quantityReturned: integer("quantity_returned").notNull().default(0),
450
+ quantityRefunded: integer("quantity_refunded").notNull().default(0),
451
+
452
+ unitPrice: decimal("unit_price", { precision: 12, scale: 2 }).notNull(),
453
+ compareAtPrice: decimal("compare_at_price", { precision: 12, scale: 2 }),
454
+ costPrice: decimal("cost_price", { precision: 12, scale: 2 }),
455
+ totalPrice: decimal("total_price", { precision: 12, scale: 2 }).notNull(),
456
+
457
+ // Discounts and Taxes
458
+ discountAmount: decimal("discount_amount", { precision: 12, scale: 2 })
459
+ .notNull()
460
+ .default("0.00"),
461
+ taxAmount: decimal("tax_amount", { precision: 12, scale: 2 })
462
+ .notNull()
463
+ .default("0.00"),
464
+
465
+ // Physical Properties
466
+ weight: decimal("weight", { precision: 8, scale: 3 }),
467
+ requiresShipping: boolean("requires_shipping").notNull().default(true),
468
+ isGiftWrap: boolean("is_gift_wrap").notNull().default(false),
469
+ giftWrapMessage: text("gift_wrap_message"),
470
+
471
+ // Customization
472
+ customization: jsonb("customization").$type<{
473
+ options?: Record<string, string>;
474
+ personalization?: string;
475
+ engraving?: string;
476
+ specialRequests?: string;
477
+ }>(),
478
+
479
+ // Inventory and Fulfillment
480
+ inventoryLocationId: text("inventory_location_id"),
481
+ fulfillmentService: varchar("fulfillment_service", { length: 50 }).default(
482
+ "MANUAL",
483
+ ),
484
+ trackingRequired: boolean("tracking_required").notNull().default(true),
485
+
486
+ // Subscription Information
487
+ isSubscriptionItem: boolean("is_subscription_item")
488
+ .notNull()
489
+ .default(false),
490
+ subscriptionFrequency: varchar("subscription_frequency", { length: 20 }),
491
+ subscriptionEndDate: timestamp("subscription_end_date", {
492
+ withTimezone: true,
493
+ }),
494
+
495
+ // Product Snapshot (preserve product data at time of order)
496
+ productSnapshot: jsonb("product_snapshot").$type<{
497
+ images?: string[];
498
+ description?: string;
499
+ attributes?: Record<string, unknown>;
500
+ categories?: string[];
501
+ }>(),
502
+
503
+ // Line Item Flags
504
+ isDigital: boolean("is_digital").notNull().default(false),
505
+ isBackorder: boolean("is_backorder").notNull().default(false),
506
+ isPreorder: boolean("is_preorder").notNull().default(false),
507
+
508
+ // Notes and Instructions
509
+ lineItemNotes: text("line_item_notes"),
510
+ internalNotes: text("internal_notes"),
511
+
512
+ // Analytics
513
+ profitMargin: decimal("profit_margin", { precision: 5, scale: 2 }),
514
+ marginAmount: decimal("margin_amount", { precision: 12, scale: 2 }),
515
+
516
+ // Timestamps
517
+ createdAt: timestamp("created_at", { withTimezone: true })
518
+ .defaultNow()
519
+ .notNull(),
520
+ updatedAt: timestamp("updated_at", { withTimezone: true })
521
+ .defaultNow()
522
+ .notNull(),
523
+ },
524
+ (table) => ({
525
+ // Foreign Key Indexes
526
+ orderIdIndex: index("idx_order_items_order_id").on(table.orderId),
527
+ productIdIndex: index("idx_order_items_product_id").on(table.productId),
528
+ variantIdIndex: index("idx_order_items_variant_id").on(table.variantId),
529
+ skuIndex: index("idx_order_items_sku").on(table.sku),
530
+
531
+ // Inventory and Fulfillment
532
+ inventoryLocationIndex: index("idx_order_items_inventory_location").on(
533
+ table.inventoryLocationId,
534
+ ),
535
+ fulfillmentServiceIndex: index("idx_order_items_fulfillment_service").on(
536
+ table.fulfillmentService,
537
+ ),
538
+
539
+ // Product Information
540
+ vendorIndex: index("idx_order_items_vendor").on(table.vendor),
541
+ productTypeIndex: index("idx_order_items_product_type").on(
542
+ table.productType,
543
+ ),
544
+
545
+ // Flags
546
+ digitalIndex: index("idx_order_items_digital").on(table.isDigital),
547
+ backorderIndex: index("idx_order_items_backorder").on(table.isBackorder),
548
+ subscriptionIndex: index("idx_order_items_subscription").on(
549
+ table.isSubscriptionItem,
550
+ ),
551
+
552
+ // Composite Indexes
553
+ orderProductIndex: index("idx_order_items_order_product").on(
554
+ table.orderId,
555
+ table.productId,
556
+ ),
557
+ orderSkuIndex: index("idx_order_items_order_sku").on(
558
+ table.orderId,
559
+ table.sku,
560
+ ),
561
+ }),
562
+ );
563
+
564
+ // =====================================================
565
+ // ORDER ADDRESSES TABLE
566
+ // =====================================================
567
+
568
+ export const orderAddresses = pgTable(
569
+ "order_addresses",
570
+ {
571
+ id: text("id")
572
+ .primaryKey()
573
+ .$defaultFn(() => createId()),
574
+
575
+ orderId: text("order_id")
576
+ .notNull()
577
+ .references(() => orders.id, { onDelete: "cascade" }),
578
+
579
+ addressType: addressTypeEnum("address_type").notNull(),
580
+
581
+ // Contact Information
582
+ firstName: varchar("first_name", { length: 100 }),
583
+ lastName: varchar("last_name", { length: 100 }),
584
+ company: varchar("company", { length: 100 }),
585
+ email: varchar("email", { length: 255 }),
586
+ phone: varchar("phone", { length: 20 }),
587
+
588
+ // Address Information
589
+ address1: varchar("address1", { length: 255 }).notNull(),
590
+ address2: varchar("address2", { length: 255 }),
591
+ city: varchar("city", { length: 100 }).notNull(),
592
+ province: varchar("province", { length: 100 }),
593
+ provinceCode: varchar("province_code", { length: 10 }),
594
+ country: varchar("country", { length: 100 }).notNull(),
595
+ countryCode: varchar("country_code", { length: 2 }).notNull(),
596
+ postalCode: varchar("postal_code", { length: 20 }),
597
+
598
+ // Geolocation
599
+ latitude: decimal("latitude", { precision: 10, scale: 8 }),
600
+ longitude: decimal("longitude", { precision: 11, scale: 8 }),
601
+ timezone: varchar("timezone", { length: 50 }),
602
+
603
+ // Delivery Instructions
604
+ deliveryInstructions: text("delivery_instructions"),
605
+ accessCode: varchar("access_code", { length: 20 }),
606
+ deliveryPreference: varchar("delivery_preference", { length: 50 }).$type<
607
+ | "FRONT_DOOR"
608
+ | "BACK_DOOR"
609
+ | "SIDE_DOOR"
610
+ | "MAILBOX"
611
+ | "SAFE_PLACE"
612
+ | "NEIGHBOR"
613
+ | "PICKUP_POINT"
614
+ >(),
615
+
616
+ // Validation Flags
617
+ isValidated: boolean("is_validated").notNull().default(false),
618
+ isResidential: boolean("is_residential").notNull().default(true),
619
+ isPOBox: boolean("is_po_box").notNull().default(false),
620
+ isMilitary: boolean("is_military").notNull().default(false),
621
+
622
+ // Timestamps
623
+ createdAt: timestamp("created_at", { withTimezone: true })
624
+ .defaultNow()
625
+ .notNull(),
626
+ updatedAt: timestamp("updated_at", { withTimezone: true })
627
+ .defaultNow()
628
+ .notNull(),
629
+ },
630
+ (table) => ({
631
+ orderIdIndex: index("idx_order_addresses_order_id").on(table.orderId),
632
+ addressTypeIndex: index("idx_order_addresses_type").on(table.addressType),
633
+ countryIndex: index("idx_order_addresses_country").on(table.countryCode),
634
+ postalCodeIndex: index("idx_order_addresses_postal_code").on(
635
+ table.postalCode,
636
+ ),
637
+ geoLocationIndex: index("idx_order_addresses_geo").on(
638
+ table.latitude,
639
+ table.longitude,
640
+ ),
641
+
642
+ // Ensure one address per type per order
643
+ uniqueOrderAddressType: unique("unq_order_address_type").on(
644
+ table.orderId,
645
+ table.addressType,
646
+ ),
647
+ }),
648
+ );
649
+
650
+ // =====================================================
651
+ // ORDER PAYMENTS TABLE
652
+ // =====================================================
653
+
654
+ export const orderPayments = pgTable(
655
+ "order_payments",
656
+ {
657
+ id: text("id")
658
+ .primaryKey()
659
+ .$defaultFn(() => createId()),
660
+
661
+ orderId: text("order_id")
662
+ .notNull()
663
+ .references(() => orders.id, { onDelete: "cascade" }),
664
+
665
+ // Payment Identification
666
+ paymentMethod: paymentMethodEnum("payment_method").notNull(),
667
+ paymentProvider: varchar("payment_provider", { length: 50 }),
668
+ transactionId: varchar("transaction_id", { length: 100 }),
669
+ externalTransactionId: varchar("external_transaction_id", { length: 100 }),
670
+ authorizationCode: varchar("authorization_code", { length: 50 }),
671
+
672
+ // Payment Status and Type
673
+ status: paymentStatusEnum("status").notNull().default("PENDING"),
674
+ paymentType: varchar("payment_type", { length: 20 })
675
+ .notNull()
676
+ .default("PAYMENT")
677
+ .$type<
678
+ "PAYMENT" | "REFUND" | "PARTIAL_REFUND" | "CHARGEBACK" | "ADJUSTMENT"
679
+ >(),
680
+
681
+ // Financial Information
682
+ currency: varchar("currency", { length: 3 }).notNull(),
683
+ amount: decimal("amount", { precision: 12, scale: 2 }).notNull(),
684
+ feeAmount: decimal("fee_amount", { precision: 12, scale: 2 })
685
+ .notNull()
686
+ .default("0.00"),
687
+ netAmount: decimal("net_amount", { precision: 12, scale: 2 }).notNull(),
688
+ exchangeRate: decimal("exchange_rate", { precision: 10, scale: 6 }).default(
689
+ "1.000000",
690
+ ),
691
+
692
+ // Card Information (for card payments)
693
+ cardLast4: varchar("card_last4", { length: 4 }),
694
+ cardBrand: varchar("card_brand", { length: 20 }),
695
+ cardType: varchar("card_type", { length: 20 }),
696
+ cardCountry: varchar("card_country", { length: 2 }),
697
+ cardFingerprint: varchar("card_fingerprint", { length: 100 }),
698
+
699
+ // Processing Information
700
+ processorResponse:
701
+ jsonb("processor_response").$type<Record<string, unknown>>(),
702
+ gatewayResponse: jsonb("gateway_response").$type<Record<string, unknown>>(),
703
+ riskAssessment: jsonb("risk_assessment").$type<{
704
+ score?: number;
705
+ level?: string;
706
+ factors?: string[];
707
+ recommendation?: string;
708
+ }>(),
709
+
710
+ // Timing Information
711
+ authorizedAt: timestamp("authorized_at", { withTimezone: true }),
712
+ capturedAt: timestamp("captured_at", { withTimezone: true }),
713
+ settledAt: timestamp("settled_at", { withTimezone: true }),
714
+ refundedAt: timestamp("refunded_at", { withTimezone: true }),
715
+ voidedAt: timestamp("voided_at", { withTimezone: true }),
716
+
717
+ // Customer Information
718
+ customerIp: varchar("customer_ip", { length: 45 }),
719
+ billingAddressId: text("billing_address_id"),
720
+
721
+ // Reference Information
722
+ parentPaymentId: text("parent_payment_id"), // For refunds
723
+ originalTransactionId: varchar("original_transaction_id", { length: 100 }), // For refunds/chargebacks
724
+
725
+ // Failure Information
726
+ failureCode: varchar("failure_code", { length: 50 }),
727
+ failureMessage: text("failure_message"),
728
+ declineCode: varchar("decline_code", { length: 50 }),
729
+
730
+ // Metadata
731
+ metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
732
+ processorData: jsonb("processor_data")
733
+ .$type<Record<string, unknown>>()
734
+ .default({}),
735
+
736
+ // Timestamps
737
+ createdAt: timestamp("created_at", { withTimezone: true })
738
+ .defaultNow()
739
+ .notNull(),
740
+ updatedAt: timestamp("updated_at", { withTimezone: true })
741
+ .defaultNow()
742
+ .notNull(),
743
+ },
744
+ (table) => ({
745
+ orderIdIndex: index("idx_order_payments_order_id").on(table.orderId),
746
+ statusIndex: index("idx_order_payments_status").on(table.status),
747
+ paymentMethodIndex: index("idx_order_payments_method").on(
748
+ table.paymentMethod,
749
+ ),
750
+ transactionIdIndex: index("idx_order_payments_transaction_id").on(
751
+ table.transactionId,
752
+ ),
753
+ externalTransactionIdIndex: index(
754
+ "idx_order_payments_external_transaction_id",
755
+ ).on(table.externalTransactionId),
756
+ paymentProviderIndex: index("idx_order_payments_provider").on(
757
+ table.paymentProvider,
758
+ ),
759
+ parentPaymentIndex: index("idx_order_payments_parent").on(
760
+ table.parentPaymentId,
761
+ ),
762
+ createdAtIndex: index("idx_order_payments_created_at").on(table.createdAt),
763
+
764
+ // Financial Indexes
765
+ amountIndex: index("idx_order_payments_amount").on(table.amount),
766
+ currencyIndex: index("idx_order_payments_currency").on(table.currency),
767
+
768
+ // Card Information Indexes
769
+ cardFingerprintIndex: index("idx_order_payments_card_fingerprint").on(
770
+ table.cardFingerprint,
771
+ ),
772
+ cardBrandIndex: index("idx_order_payments_card_brand").on(table.cardBrand),
773
+
774
+ // Composite Indexes
775
+ orderStatusIndex: index("idx_order_payments_order_status").on(
776
+ table.orderId,
777
+ table.status,
778
+ ),
779
+ methodStatusIndex: index("idx_order_payments_method_status").on(
780
+ table.paymentMethod,
781
+ table.status,
782
+ ),
783
+ }),
784
+ );
785
+
786
+ // =====================================================
787
+ // ORDER FULFILLMENTS TABLE
788
+ // =====================================================
789
+
790
+ export const orderFulfillments = pgTable(
791
+ "order_fulfillments",
792
+ {
793
+ id: text("id")
794
+ .primaryKey()
795
+ .$defaultFn(() => createId()),
796
+
797
+ orderId: text("order_id")
798
+ .notNull()
799
+ .references(() => orders.id, { onDelete: "cascade" }),
800
+
801
+ // Fulfillment Identification
802
+ fulfillmentNumber: varchar("fulfillment_number", { length: 50 }),
803
+ trackingNumber: varchar("tracking_number", { length: 100 }),
804
+ trackingUrl: text("tracking_url"),
805
+
806
+ // Status and Type
807
+ status: fulfillmentStatusEnum("status").notNull().default("PENDING"),
808
+ fulfillmentType: varchar("fulfillment_type", { length: 20 })
809
+ .notNull()
810
+ .$type<"SHIPPING" | "PICKUP" | "DELIVERY" | "DIGITAL" | "SERVICE">(),
811
+
812
+ // Carrier and Service Information
813
+ shippingCarrier: varchar("shipping_carrier", { length: 50 }),
814
+ shippingService: varchar("shipping_service", { length: 100 }),
815
+ shippingServiceCode: varchar("shipping_service_code", { length: 50 }),
816
+ estimatedDeliveryDate: timestamp("estimated_delivery_date", {
817
+ withTimezone: true,
818
+ }),
819
+ guaranteedDeliveryDate: timestamp("guaranteed_delivery_date", {
820
+ withTimezone: true,
821
+ }),
822
+
823
+ // Location Information
824
+ originLocationId: text("origin_location_id"),
825
+ destinationAddressId: text("destination_address_id"),
826
+
827
+ // Package Information
828
+ packageCount: integer("package_count").notNull().default(1),
829
+ totalWeight: decimal("total_weight", { precision: 8, scale: 3 }),
830
+ totalVolume: decimal("total_volume", { precision: 8, scale: 3 }),
831
+ packageDimensions: jsonb("package_dimensions").$type<{
832
+ length?: number;
833
+ width?: number;
834
+ height?: number;
835
+ unit?: "cm" | "in";
836
+ }>(),
837
+
838
+ // Shipping Costs
839
+ shippingCost: decimal("shipping_cost", { precision: 12, scale: 2 })
840
+ .notNull()
841
+ .default("0.00"),
842
+ insuranceCost: decimal("insurance_cost", { precision: 12, scale: 2 })
843
+ .notNull()
844
+ .default("0.00"),
845
+ handlingCost: decimal("handling_cost", { precision: 12, scale: 2 })
846
+ .notNull()
847
+ .default("0.00"),
848
+
849
+ // Special Services
850
+ signatureRequired: boolean("signature_required").notNull().default(false),
851
+ adultSignatureRequired: boolean("adult_signature_required")
852
+ .notNull()
853
+ .default(false),
854
+ insuranceRequired: boolean("insurance_required").notNull().default(false),
855
+ saturdayDelivery: boolean("saturday_delivery").notNull().default(false),
856
+
857
+ // Delivery Information
858
+ deliveryAttempts: integer("delivery_attempts").notNull().default(0),
859
+ lastDeliveryAttempt: timestamp("last_delivery_attempt", {
860
+ withTimezone: true,
861
+ }),
862
+ deliveryNotes: text("delivery_notes"),
863
+ proofOfDelivery: jsonb("proof_of_delivery").$type<{
864
+ signedBy?: string;
865
+ image?: string;
866
+ location?: { lat: number; lng: number };
867
+ timestamp?: string;
868
+ }>(),
869
+
870
+ // Timing Information
871
+ labelCreatedAt: timestamp("label_created_at", { withTimezone: true }),
872
+ pickedUpAt: timestamp("picked_up_at", { withTimezone: true }),
873
+ inTransitAt: timestamp("in_transit_at", { withTimezone: true }),
874
+ outForDeliveryAt: timestamp("out_for_delivery_at", { withTimezone: true }),
875
+ deliveredAt: timestamp("delivered_at", { withTimezone: true }),
876
+ returnedAt: timestamp("returned_at", { withTimezone: true }),
877
+
878
+ // Staff Information
879
+ packedBy: text("packed_by"),
880
+ shippedBy: text("shipped_by"),
881
+ deliveredBy: text("delivered_by"),
882
+
883
+ // Failure and Return Information
884
+ failureReason: text("failure_reason"),
885
+ returnReason: text("return_reason"),
886
+ returnTrackingNumber: varchar("return_tracking_number", { length: 100 }),
887
+
888
+ // Metadata
889
+ carrierData: jsonb("carrier_data")
890
+ .$type<Record<string, unknown>>()
891
+ .default({}),
892
+ trackingEvents: jsonb("tracking_events")
893
+ .$type<
894
+ Array<{
895
+ timestamp: string;
896
+ status: string;
897
+ location?: string;
898
+ description?: string;
899
+ }>
900
+ >()
901
+ .default([]),
902
+
903
+ // Timestamps
904
+ createdAt: timestamp("created_at", { withTimezone: true })
905
+ .defaultNow()
906
+ .notNull(),
907
+ updatedAt: timestamp("updated_at", { withTimezone: true })
908
+ .defaultNow()
909
+ .notNull(),
910
+ },
911
+ (table) => ({
912
+ orderIdIndex: index("idx_order_fulfillments_order_id").on(table.orderId),
913
+ statusIndex: index("idx_order_fulfillments_status").on(table.status),
914
+ trackingNumberIndex: index("idx_order_fulfillments_tracking_number").on(
915
+ table.trackingNumber,
916
+ ),
917
+ carrierIndex: index("idx_order_fulfillments_carrier").on(
918
+ table.shippingCarrier,
919
+ ),
920
+ fulfillmentTypeIndex: index("idx_order_fulfillments_type").on(
921
+ table.fulfillmentType,
922
+ ),
923
+ originLocationIndex: index("idx_order_fulfillments_origin").on(
924
+ table.originLocationId,
925
+ ),
926
+ destinationAddressIndex: index("idx_order_fulfillments_destination").on(
927
+ table.destinationAddressId,
928
+ ),
929
+ estimatedDeliveryIndex: index(
930
+ "idx_order_fulfillments_estimated_delivery",
931
+ ).on(table.estimatedDeliveryDate),
932
+ deliveredAtIndex: index("idx_order_fulfillments_delivered_at").on(
933
+ table.deliveredAt,
934
+ ),
935
+ createdAtIndex: index("idx_order_fulfillments_created_at").on(
936
+ table.createdAt,
937
+ ),
938
+
939
+ // Staff Indexes
940
+ packedByIndex: index("idx_order_fulfillments_packed_by").on(table.packedBy),
941
+ shippedByIndex: index("idx_order_fulfillments_shipped_by").on(
942
+ table.shippedBy,
943
+ ),
944
+
945
+ // Composite Indexes
946
+ orderStatusIndex: index("idx_order_fulfillments_order_status").on(
947
+ table.orderId,
948
+ table.status,
949
+ ),
950
+ carrierStatusIndex: index("idx_order_fulfillments_carrier_status").on(
951
+ table.shippingCarrier,
952
+ table.status,
953
+ ),
954
+ }),
955
+ );
956
+
957
+ // =====================================================
958
+ // ORDER DISCOUNTS TABLE
959
+ // =====================================================
960
+
961
+ export const orderDiscounts = pgTable(
962
+ "order_discounts",
963
+ {
964
+ id: text("id")
965
+ .primaryKey()
966
+ .$defaultFn(() => createId()),
967
+
968
+ orderId: text("order_id")
969
+ .notNull()
970
+ .references(() => orders.id, { onDelete: "cascade" }),
971
+
972
+ // Discount Identification
973
+ discountCode: varchar("discount_code", { length: 50 }),
974
+ discountType: discountTypeEnum("discount_type").notNull(),
975
+ discountSource: varchar("discount_source", { length: 50 })
976
+ .notNull()
977
+ .$type<
978
+ | "COUPON"
979
+ | "PROMOTION"
980
+ | "LOYALTY"
981
+ | "MANUAL"
982
+ | "AUTOMATIC"
983
+ | "BULK"
984
+ | "REFERRAL"
985
+ >(),
986
+
987
+ // Discount Details
988
+ title: varchar("title", { length: 255 }).notNull(),
989
+ description: text("description"),
990
+
991
+ // Discount Values
992
+ discountAmount: decimal("discount_amount", {
993
+ precision: 12,
994
+ scale: 2,
995
+ }).notNull(),
996
+ discountPercentage: decimal("discount_percentage", {
997
+ precision: 5,
998
+ scale: 2,
999
+ }),
1000
+ maximumDiscount: decimal("maximum_discount", { precision: 12, scale: 2 }),
1001
+
1002
+ // Application Rules
1003
+ appliedToOrderTotal: boolean("applied_to_order_total")
1004
+ .notNull()
1005
+ .default(true),
1006
+ appliedToShipping: boolean("applied_to_shipping").notNull().default(false),
1007
+ appliedToTax: boolean("applied_to_tax").notNull().default(false),
1008
+
1009
+ // Scope and Conditions
1010
+ minimumOrderAmount: decimal("minimum_order_amount", {
1011
+ precision: 12,
1012
+ scale: 2,
1013
+ }),
1014
+ maximumOrderAmount: decimal("maximum_order_amount", {
1015
+ precision: 12,
1016
+ scale: 2,
1017
+ }),
1018
+ applicableProductIds: jsonb("applicable_product_ids")
1019
+ .$type<string[]>()
1020
+ .default([]),
1021
+ applicableCategories: jsonb("applicable_categories")
1022
+ .$type<string[]>()
1023
+ .default([]),
1024
+ excludedProductIds: jsonb("excluded_product_ids")
1025
+ .$type<string[]>()
1026
+ .default([]),
1027
+
1028
+ // Customer Eligibility
1029
+ eligibleCustomerTypes: jsonb("eligible_customer_types")
1030
+ .$type<string[]>()
1031
+ .default([]),
1032
+ firstTimeCustomerOnly: boolean("first_time_customer_only")
1033
+ .notNull()
1034
+ .default(false),
1035
+ loyaltyPointsUsed: integer("loyalty_points_used").default(0),
1036
+
1037
+ // Validation and Usage
1038
+ isValid: boolean("is_valid").notNull().default(true),
1039
+ validationErrors: jsonb("validation_errors").$type<string[]>().default([]),
1040
+ usageCount: integer("usage_count").notNull().default(1),
1041
+
1042
+ // Campaign Information
1043
+ campaignId: text("campaign_id"),
1044
+ campaignName: varchar("campaign_name", { length: 100 }),
1045
+ affiliateId: text("affiliate_id"),
1046
+
1047
+ // Metadata
1048
+ metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
1049
+ promotionRules: jsonb("promotion_rules")
1050
+ .$type<Record<string, unknown>>()
1051
+ .default({}),
1052
+
1053
+ // Timestamps
1054
+ appliedAt: timestamp("applied_at", { withTimezone: true })
1055
+ .defaultNow()
1056
+ .notNull(),
1057
+ createdAt: timestamp("created_at", { withTimezone: true })
1058
+ .defaultNow()
1059
+ .notNull(),
1060
+ },
1061
+ (table) => ({
1062
+ orderIdIndex: index("idx_order_discounts_order_id").on(table.orderId),
1063
+ discountCodeIndex: index("idx_order_discounts_code").on(table.discountCode),
1064
+ discountTypeIndex: index("idx_order_discounts_type").on(table.discountType),
1065
+ discountSourceIndex: index("idx_order_discounts_source").on(
1066
+ table.discountSource,
1067
+ ),
1068
+ campaignIdIndex: index("idx_order_discounts_campaign").on(table.campaignId),
1069
+ affiliateIdIndex: index("idx_order_discounts_affiliate").on(
1070
+ table.affiliateId,
1071
+ ),
1072
+ appliedAtIndex: index("idx_order_discounts_applied_at").on(table.appliedAt),
1073
+
1074
+ // Composite Indexes
1075
+ orderDiscountTypeIndex: index("idx_order_discounts_order_type").on(
1076
+ table.orderId,
1077
+ table.discountType,
1078
+ ),
1079
+ codeValidIndex: index("idx_order_discounts_code_valid").on(
1080
+ table.discountCode,
1081
+ table.isValid,
1082
+ ),
1083
+ }),
1084
+ );
1085
+
1086
+ // =====================================================
1087
+ // ORDER HISTORY TABLE
1088
+ // =====================================================
1089
+
1090
+ export const orderHistory = pgTable(
1091
+ "order_history",
1092
+ {
1093
+ id: text("id")
1094
+ .primaryKey()
1095
+ .$defaultFn(() => createId()),
1096
+
1097
+ orderId: text("order_id")
1098
+ .notNull()
1099
+ .references(() => orders.id, { onDelete: "cascade" }),
1100
+
1101
+ // Status Change Information
1102
+ eventType: varchar("event_type", { length: 50 })
1103
+ .notNull()
1104
+ .$type<
1105
+ | "STATUS_CHANGE"
1106
+ | "PAYMENT_UPDATE"
1107
+ | "FULFILLMENT_UPDATE"
1108
+ | "ADDRESS_CHANGE"
1109
+ | "ITEM_ADDED"
1110
+ | "ITEM_REMOVED"
1111
+ | "ITEM_UPDATED"
1112
+ | "NOTE_ADDED"
1113
+ | "DISCOUNT_APPLIED"
1114
+ | "REFUND_PROCESSED"
1115
+ | "CANCELLATION"
1116
+ | "MODIFICATION"
1117
+ | "ESCALATION"
1118
+ | "RETURN_REQUEST"
1119
+ >(),
1120
+
1121
+ // Change Details
1122
+ fromValue: text("from_value"),
1123
+ toValue: text("to_value"),
1124
+ fieldChanged: varchar("field_changed", { length: 100 }),
1125
+ changeReason: text("change_reason"),
1126
+
1127
+ // Actor Information
1128
+ actorType: varchar("actor_type", { length: 20 })
1129
+ .notNull()
1130
+ .$type<"CUSTOMER" | "STAFF" | "SYSTEM" | "ADMIN" | "AUTOMATION">(),
1131
+ actorId: text("actor_id"),
1132
+ actorName: varchar("actor_name", { length: 100 }),
1133
+ actorRole: varchar("actor_role", { length: 50 }),
1134
+
1135
+ // Context Information
1136
+ source: varchar("source", { length: 50 })
1137
+ .notNull()
1138
+ .$type<
1139
+ | "WEB"
1140
+ | "MOBILE"
1141
+ | "POS"
1142
+ | "ADMIN"
1143
+ | "API"
1144
+ | "PHONE"
1145
+ | "EMAIL"
1146
+ | "CHAT"
1147
+ | "SYSTEM"
1148
+ >(),
1149
+ sessionId: text("session_id"),
1150
+ ipAddress: varchar("ip_address", { length: 45 }),
1151
+ userAgent: text("user_agent"),
1152
+
1153
+ // Detailed Information
1154
+ description: text("description"),
1155
+ internalNotes: text("internal_notes"),
1156
+ customerVisible: boolean("customer_visible").notNull().default(true),
1157
+
1158
+ // Related Data
1159
+ relatedEntityType: varchar("related_entity_type", { length: 50 }),
1160
+ relatedEntityId: text("related_entity_id"),
1161
+ changeData: jsonb("change_data")
1162
+ .$type<Record<string, unknown>>()
1163
+ .default({}),
1164
+
1165
+ // Impact Assessment
1166
+ businessImpact: varchar("business_impact", { length: 20 }).$type<
1167
+ "LOW" | "MEDIUM" | "HIGH" | "CRITICAL"
1168
+ >(),
1169
+ customerImpact: varchar("customer_impact", { length: 20 }).$type<
1170
+ "POSITIVE" | "NEUTRAL" | "NEGATIVE"
1171
+ >(),
1172
+
1173
+ // Metadata
1174
+ metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
1175
+
1176
+ // Timestamps
1177
+ occurredAt: timestamp("occurred_at", { withTimezone: true })
1178
+ .defaultNow()
1179
+ .notNull(),
1180
+ createdAt: timestamp("created_at", { withTimezone: true })
1181
+ .defaultNow()
1182
+ .notNull(),
1183
+ },
1184
+ (table) => ({
1185
+ orderIdIndex: index("idx_order_history_order_id").on(table.orderId),
1186
+ eventTypeIndex: index("idx_order_history_event_type").on(table.eventType),
1187
+ actorTypeIndex: index("idx_order_history_actor_type").on(table.actorType),
1188
+ actorIdIndex: index("idx_order_history_actor_id").on(table.actorId),
1189
+ sourceIndex: index("idx_order_history_source").on(table.source),
1190
+ occurredAtIndex: index("idx_order_history_occurred_at").on(
1191
+ table.occurredAt,
1192
+ ),
1193
+ customerVisibleIndex: index("idx_order_history_customer_visible").on(
1194
+ table.customerVisible,
1195
+ ),
1196
+
1197
+ // Composite Indexes
1198
+ orderEventIndex: index("idx_order_history_order_event").on(
1199
+ table.orderId,
1200
+ table.eventType,
1201
+ ),
1202
+ orderDateIndex: index("idx_order_history_order_date").on(
1203
+ table.orderId,
1204
+ table.occurredAt,
1205
+ ),
1206
+ actorEventIndex: index("idx_order_history_actor_event").on(
1207
+ table.actorId,
1208
+ table.eventType,
1209
+ ),
1210
+ }),
1211
+ );
1212
+
1213
+ // =====================================================
1214
+ // ORDER NOTES TABLE
1215
+ // =====================================================
1216
+
1217
+ export const orderNotes = pgTable(
1218
+ "order_notes",
1219
+ {
1220
+ id: text("id")
1221
+ .primaryKey()
1222
+ .$defaultFn(() => createId()),
1223
+
1224
+ orderId: text("order_id")
1225
+ .notNull()
1226
+ .references(() => orders.id, { onDelete: "cascade" }),
1227
+
1228
+ // Note Classification
1229
+ noteType: varchar("note_type", { length: 30 })
1230
+ .notNull()
1231
+ .$type<
1232
+ | "GENERAL"
1233
+ | "CUSTOMER_SERVICE"
1234
+ | "FULFILLMENT"
1235
+ | "PAYMENT"
1236
+ | "FRAUD"
1237
+ | "QUALITY"
1238
+ | "SHIPPING"
1239
+ | "RETURNS"
1240
+ | "ESCALATION"
1241
+ | "FOLLOW_UP"
1242
+ >(),
1243
+
1244
+ // Note Content
1245
+ title: varchar("title", { length: 255 }),
1246
+ content: text("content").notNull(),
1247
+
1248
+ // Visibility and Priority
1249
+ isCustomerVisible: boolean("is_customer_visible").notNull().default(false),
1250
+ isInternal: boolean("is_internal").notNull().default(true),
1251
+ priority: varchar("priority", { length: 20 })
1252
+ .notNull()
1253
+ .default("NORMAL")
1254
+ .$type<"LOW" | "NORMAL" | "HIGH" | "URGENT">(),
1255
+
1256
+ // Actor Information
1257
+ createdBy: text("created_by").notNull(),
1258
+ createdByName: varchar("created_by_name", { length: 100 }).notNull(),
1259
+ createdByRole: varchar("created_by_role", { length: 50 }),
1260
+ department: varchar("department", { length: 50 }),
1261
+
1262
+ // Follow-up and Actions
1263
+ requiresFollowUp: boolean("requires_follow_up").notNull().default(false),
1264
+ followUpDate: timestamp("follow_up_date", { withTimezone: true }),
1265
+ followUpAssignedTo: text("follow_up_assigned_to"),
1266
+ isFollowUpCompleted: boolean("is_follow_up_completed")
1267
+ .notNull()
1268
+ .default(false),
1269
+
1270
+ // Context and References
1271
+ relatedEntityType: varchar("related_entity_type", { length: 50 }),
1272
+ relatedEntityId: text("related_entity_id"),
1273
+ parentNoteId: text("parent_note_id"),
1274
+
1275
+ // Tagging and Categorization
1276
+ tags: jsonb("tags").$type<string[]>().default([]),
1277
+ category: varchar("category", { length: 50 }),
1278
+ sentiment: varchar("sentiment", { length: 20 }).$type<
1279
+ "POSITIVE" | "NEUTRAL" | "NEGATIVE"
1280
+ >(),
1281
+
1282
+ // Metadata
1283
+ metadata: jsonb("metadata").$type<Record<string, unknown>>().default({}),
1284
+
1285
+ // Timestamps
1286
+ createdAt: timestamp("created_at", { withTimezone: true })
1287
+ .defaultNow()
1288
+ .notNull(),
1289
+ updatedAt: timestamp("updated_at", { withTimezone: true })
1290
+ .defaultNow()
1291
+ .notNull(),
1292
+ },
1293
+ (table) => ({
1294
+ orderIdIndex: index("idx_order_notes_order_id").on(table.orderId),
1295
+ noteTypeIndex: index("idx_order_notes_type").on(table.noteType),
1296
+ createdByIndex: index("idx_order_notes_created_by").on(table.createdBy),
1297
+ priorityIndex: index("idx_order_notes_priority").on(table.priority),
1298
+ customerVisibleIndex: index("idx_order_notes_customer_visible").on(
1299
+ table.isCustomerVisible,
1300
+ ),
1301
+ followUpIndex: index("idx_order_notes_follow_up").on(
1302
+ table.requiresFollowUp,
1303
+ table.followUpDate,
1304
+ ),
1305
+ parentNoteIndex: index("idx_order_notes_parent").on(table.parentNoteId),
1306
+ createdAtIndex: index("idx_order_notes_created_at").on(table.createdAt),
1307
+
1308
+ // Composite Indexes
1309
+ orderNoteTypeIndex: index("idx_order_notes_order_type").on(
1310
+ table.orderId,
1311
+ table.noteType,
1312
+ ),
1313
+ orderCreatedIndex: index("idx_order_notes_order_created").on(
1314
+ table.orderId,
1315
+ table.createdAt,
1316
+ ),
1317
+ }),
1318
+ );
1319
+
1320
+ // =====================================================
1321
+ // RELATIONS
1322
+ // =====================================================
1323
+
1324
+ export const ordersRelations = relations(orders, ({ many, one }) => ({
1325
+ orderItems: many(orderItems),
1326
+ orderAddresses: many(orderAddresses),
1327
+ orderPayments: many(orderPayments),
1328
+ orderFulfillments: many(orderFulfillments),
1329
+ orderDiscounts: many(orderDiscounts),
1330
+ orderHistory: many(orderHistory),
1331
+ orderNotes: many(orderNotes),
1332
+
1333
+ // Parent order relation for split orders
1334
+ parentOrder: one(orders, {
1335
+ fields: [orders.parentOrderId],
1336
+ references: [orders.id],
1337
+ }),
1338
+ childOrders: many(orders),
1339
+ }));
1340
+
1341
+ export const orderItemsRelations = relations(orderItems, ({ one }) => ({
1342
+ order: one(orders, {
1343
+ fields: [orderItems.orderId],
1344
+ references: [orders.id],
1345
+ }),
1346
+ }));
1347
+
1348
+ export const orderAddressesRelations = relations(orderAddresses, ({ one }) => ({
1349
+ order: one(orders, {
1350
+ fields: [orderAddresses.orderId],
1351
+ references: [orders.id],
1352
+ }),
1353
+ }));
1354
+
1355
+ export const orderPaymentsRelations = relations(orderPayments, ({ one }) => ({
1356
+ order: one(orders, {
1357
+ fields: [orderPayments.orderId],
1358
+ references: [orders.id],
1359
+ }),
1360
+ parentPayment: one(orderPayments, {
1361
+ fields: [orderPayments.parentPaymentId],
1362
+ references: [orderPayments.id],
1363
+ }),
1364
+ }));
1365
+
1366
+ export const orderFulfillmentsRelations = relations(
1367
+ orderFulfillments,
1368
+ ({ one }) => ({
1369
+ order: one(orders, {
1370
+ fields: [orderFulfillments.orderId],
1371
+ references: [orders.id],
1372
+ }),
1373
+ }),
1374
+ );
1375
+
1376
+ export const orderDiscountsRelations = relations(orderDiscounts, ({ one }) => ({
1377
+ order: one(orders, {
1378
+ fields: [orderDiscounts.orderId],
1379
+ references: [orders.id],
1380
+ }),
1381
+ }));
1382
+
1383
+ export const orderHistoryRelations = relations(orderHistory, ({ one }) => ({
1384
+ order: one(orders, {
1385
+ fields: [orderHistory.orderId],
1386
+ references: [orders.id],
1387
+ }),
1388
+ }));
1389
+
1390
+ export const orderNotesRelations = relations(orderNotes, ({ one, many }) => ({
1391
+ order: one(orders, {
1392
+ fields: [orderNotes.orderId],
1393
+ references: [orders.id],
1394
+ }),
1395
+ parentNote: one(orderNotes, {
1396
+ fields: [orderNotes.parentNoteId],
1397
+ references: [orderNotes.id],
1398
+ }),
1399
+ childNotes: many(orderNotes),
1400
+ }));