@axova/shared 1.0.0 → 1.0.2

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 (33) hide show
  1. package/dist/index.d.ts +0 -1
  2. package/dist/index.js +0 -2
  3. package/dist/middleware/storeOwnership.js +3 -22
  4. package/dist/middleware/storeValidationMiddleware.js +39 -16
  5. package/dist/schemas/admin/admin-schema.d.ts +2 -2
  6. package/dist/schemas/ai-moderation/ai-moderation-schema.d.ts +2 -2
  7. package/dist/schemas/index.d.ts +0 -26
  8. package/dist/schemas/index.js +3 -121
  9. package/dist/schemas/store/storefront-config-schema.d.ts +1320 -0
  10. package/dist/schemas/store/storefront-config-schema.js +109 -0
  11. package/dist/utils/subdomain.d.ts +1 -1
  12. package/dist/utils/subdomain.js +15 -10
  13. package/package.json +1 -1
  14. package/src/index.ts +0 -3
  15. package/src/middleware/storeOwnership.ts +3 -21
  16. package/src/middleware/storeValidationMiddleware.ts +50 -17
  17. package/src/schemas/index.ts +5 -189
  18. package/src/utils/subdomain.ts +15 -11
  19. package/nul +0 -8
  20. package/src/schemas/compliance/compliance-schema.ts +0 -927
  21. package/src/schemas/compliance/kyc-schema.ts +0 -649
  22. package/src/schemas/customer/customer-schema.ts +0 -576
  23. package/src/schemas/inventory/inventory-tables.ts +0 -1927
  24. package/src/schemas/inventory/lot-tables.ts +0 -799
  25. package/src/schemas/order/order-schema.ts +0 -1400
  26. package/src/schemas/product/discount-relations.ts +0 -44
  27. package/src/schemas/product/discount-schema.ts +0 -464
  28. package/src/schemas/product/product-relations.ts +0 -187
  29. package/src/schemas/product/product-schema.ts +0 -955
  30. package/src/schemas/store/ethiopian_business_api.md.resolved +0 -212
  31. package/src/schemas/store/store-audit-schema.ts +0 -1257
  32. package/src/schemas/store/store-schema.ts +0 -661
  33. package/src/schemas/store/store-settings-schema.ts +0 -231
@@ -1,955 +0,0 @@
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
-
16
- // =====================================================
17
- // ENUMS
18
- // =====================================================
19
-
20
- export const productStatusEnum = pgEnum("product_status", [
21
- "DRAFT",
22
- "ACTIVE",
23
- "INACTIVE",
24
- "ARCHIVED",
25
- "OUT_OF_STOCK",
26
- ]);
27
-
28
- export const productTypeEnum = pgEnum("product_type", [
29
- "PHYSICAL",
30
- "DIGITAL",
31
- "SERVICE",
32
- "SUBSCRIPTION",
33
- "BUNDLE",
34
- "WHOLESALE",
35
- ]);
36
-
37
- export const inventoryPolicyEnum = pgEnum("inventory_policy", [
38
- "TRACK", // Track inventory
39
- "CONTINUE", // Continue selling when out of stock
40
- "DENY", // Stop selling when out of stock
41
- ]);
42
-
43
- export const fulfillmentServiceEnum = pgEnum("fulfillment_service", [
44
- "MANUAL",
45
- "AUTOMATED",
46
- "THIRD_PARTY",
47
- "DROPSHIPPING",
48
- ]);
49
-
50
- export const collectionTypeEnum = pgEnum("collection_type", [
51
- "MANUAL", // Manually curated
52
- "AUTOMATIC", // Rule-based automatic
53
- "SMART", // AI-powered smart collections
54
- ]);
55
-
56
- export const discountTypeEnum = pgEnum("discount_type", [
57
- "PERCENTAGE",
58
- "FIXED_AMOUNT",
59
- "FREE_SHIPPING",
60
- "BOGO", // Buy one get one
61
- ]);
62
-
63
- export const variantOptionTypeEnum = pgEnum("variant_option_type", [
64
- "COLOR",
65
- "SIZE",
66
- "MATERIAL",
67
- "STYLE",
68
- "WEIGHT",
69
- "VOLUME",
70
- "CUSTOM",
71
- ]);
72
-
73
- export const salesChannelEnum = pgEnum("sales_channel", [
74
- "POS",
75
- "MARKETPLACE",
76
- "STOREFRONT",
77
- ]);
78
-
79
- // =====================================================
80
- // CORE PRODUCT TABLE
81
- // =====================================================
82
-
83
- export const products = pgTable(
84
- "products",
85
- {
86
- id: text("id")
87
- .primaryKey()
88
- .$defaultFn(() => createId()),
89
- storeId: text("store_id").notNull(),
90
-
91
- // Basic Information
92
- title: varchar("title", { length: 255 }).notNull(),
93
- description: text("description"),
94
- shortDescription: varchar("short_description", { length: 500 }),
95
- handle: varchar("handle", { length: 255 }).notNull(), // URL-friendly slug
96
-
97
- // Product Classification
98
- productType: productTypeEnum("product_type").notNull().default("PHYSICAL"),
99
- vendor: varchar("vendor", { length: 255 }),
100
- supplierId: text("supplier_id"),
101
- category: varchar("category", { length: 255 }),
102
- subcategory: varchar("subcategory", { length: 255 }),
103
- tags: jsonb("tags").$type<string[]>().default([]),
104
-
105
- // Product Specifics
106
- features: jsonb("features").$type<string[]>().default([]),
107
- specifications: jsonb("specifications")
108
- .$type<Array<{ name: string; value: string }>>()
109
- .default([]),
110
-
111
- // Additional Product Fields
112
- unitOfMeasure: varchar("unit_of_measure", { length: 50 }), // e.g., "piece", "kg", "liter", "box"
113
- countryOfOrigin: varchar("country_of_origin", { length: 100 }), // Country where product is manufactured
114
- hsCode: varchar("hs_code", { length: 20 }), // Harmonized System code for customs
115
- returnable: boolean("returnable").notNull().default(true), // Whether product can be returned
116
- weightUnit: varchar("weight_unit", { length: 10 }).default("kg"), // kg, lb, oz, g
117
-
118
- // Status and Visibility
119
- status: productStatusEnum("status").notNull().default("DRAFT"),
120
- isPublished: boolean("is_published").notNull().default(false),
121
- publishedAt: timestamp("published_at", { withTimezone: true }),
122
- isFeatured: boolean("is_featured").notNull().default(false),
123
-
124
- // Sales Channels - Control where product is available
125
- salesChannels: jsonb("sales_channels")
126
- .$type<("POS" | "MARKETPLACE" | "STOREFRONT")[]>()
127
- .notNull()
128
- .default(["POS", "MARKETPLACE", "STOREFRONT"]),
129
-
130
- // Advanced Sales Channel Configuration
131
- salesChannelConfig: jsonb("sales_channel_config")
132
- .$type<{
133
- POS?: {
134
- enabled: boolean;
135
- pricing?: { type: "default" | "custom"; customPrice?: number };
136
- availability?: { allLocations: boolean; specificLocations?: string[] };
137
- };
138
- MARKETPLACE?: {
139
- enabled: boolean;
140
- platforms?: string[]; // e.g., ["amazon", "ebay", "etsy"]
141
- pricing?: { type: "default" | "custom"; customPrice?: number };
142
- syncInventory?: boolean;
143
- };
144
- STOREFRONT?: {
145
- enabled: boolean;
146
- featured?: boolean;
147
- pricing?: { type: "default" | "custom"; customPrice?: number };
148
- visibility?: "public" | "private" | "members-only";
149
- };
150
- }>()
151
- .default({}),
152
-
153
- // Pricing (in cents for precision)
154
- basePrice: decimal("base_price", { precision: 12, scale: 2 }).notNull(),
155
- compareAtPrice: decimal("compare_at_price", { precision: 12, scale: 2 }),
156
- costPerItem: decimal("cost_per_item", { precision: 12, scale: 2 }),
157
-
158
- // Wholesale Pricing
159
- wholesalePrice: decimal("wholesale_price", { precision: 12, scale: 2 }),
160
- minWholesaleQuantity: integer("min_wholesale_quantity").default(1),
161
- maxWholesaleQuantity: integer("max_wholesale_quantity"),
162
- wholesaleEnabled: boolean("wholesale_enabled").notNull().default(false),
163
-
164
- // Inventory Management
165
- inventoryPolicy: inventoryPolicyEnum("inventory_policy")
166
- .notNull()
167
- .default("TRACK"),
168
- inventoryQuantity: integer("inventory_quantity").notNull().default(0),
169
- inventoryTrackingEnabled: boolean("inventory_tracking_enabled")
170
- .notNull()
171
- .default(true),
172
- trackStock: boolean("track_stock").notNull().default(false),
173
- stockLocationId: text("stock_location_id"), // POS location or warehouse ID
174
- stockLocationType: varchar("stock_location_type", { length: 20 }), // "POS" or "WAREHOUSE"
175
- lowStockThreshold: integer("low_stock_threshold").default(10),
176
- allowBackorders: boolean("allow_backorders").notNull().default(false),
177
-
178
- // Advanced Inventory Tracking Features
179
- autoReorder: boolean("auto_reorder").notNull().default(false), // Automatic reorder when stock is low
180
- batchTracking: boolean("batch_tracking").notNull().default(false), // Track products by batch/lot number
181
- serialTracking: boolean("serial_tracking").notNull().default(false), // Track individual serial numbers
182
- expiryTracking: boolean("expiry_tracking").notNull().default(false), // Track expiry dates
183
-
184
- // Physical Properties
185
- weight: decimal("weight", { precision: 8, scale: 3 }), // in kg
186
- dimensions: jsonb("dimensions").$type<{
187
- length?: number;
188
- width?: number;
189
- height?: number;
190
- unit?: "cm" | "in";
191
- }>(),
192
-
193
- // Shipping and Fulfillment
194
- requiresShipping: boolean("requires_shipping").notNull().default(true),
195
- fulfillmentService: fulfillmentServiceEnum("fulfillment_service")
196
- .notNull()
197
- .default("MANUAL"),
198
- shippingClass: varchar("shipping_class", { length: 100 }),
199
-
200
- // SEO and Marketing
201
- seoTitle: varchar("seo_title", { length: 255 }),
202
- seoDescription: varchar("seo_description", { length: 500 }),
203
- seoKeywords: jsonb("seo_keywords").$type<string[]>().default([]),
204
- metaFields: jsonb("meta_fields")
205
- .$type<Record<string, unknown>>()
206
- .default({}),
207
-
208
- // Advanced Features
209
- customFields: jsonb("custom_fields")
210
- .$type<Record<string, unknown>>()
211
- .default({}),
212
- variantOptions: jsonb("variant_options")
213
- .$type<
214
- Array<{
215
- name: string;
216
- type: string;
217
- values: string[];
218
- displayType: "dropdown" | "color" | "button" | "swatch";
219
- }>
220
- >()
221
- .default([]),
222
-
223
- // Analytics and Performance
224
- totalViews: integer("total_views").notNull().default(0),
225
- totalSales: integer("total_sales").notNull().default(0),
226
- totalRevenue: decimal("total_revenue", { precision: 12, scale: 2 })
227
- .notNull()
228
- .default("0"),
229
- conversionRate: decimal("conversion_rate", { precision: 5, scale: 4 }),
230
- averageRating: decimal("average_rating", { precision: 3, scale: 2 }),
231
- reviewCount: integer("review_count").notNull().default(0),
232
-
233
- // Timestamps
234
- createdAt: timestamp("created_at", { withTimezone: true })
235
- .defaultNow()
236
- .notNull(),
237
- updatedAt: timestamp("updated_at", { withTimezone: true })
238
- .defaultNow()
239
- .notNull(),
240
- deletedAt: timestamp("deleted_at", { withTimezone: true }),
241
- },
242
- (table) => ({
243
- // Performance indexes
244
- storeIdIndex: index("idx_products_store_id").on(table.storeId),
245
- handleUniquePerStore: unique("idx_products_handle_store_unique").on(
246
- table.storeId,
247
- table.handle,
248
- ),
249
- statusIndex: index("idx_products_status").on(table.status),
250
- publishedIndex: index("idx_products_published").on(
251
- table.isPublished,
252
- table.publishedAt,
253
- ),
254
- featuredIndex: index("idx_products_featured").on(table.isFeatured),
255
- categoryIndex: index("idx_products_category").on(table.category),
256
- vendorIndex: index("idx_products_vendor").on(table.vendor),
257
-
258
- // Analytics indexes
259
- salesIndex: index("idx_products_sales").on(table.totalSales),
260
- revenueIndex: index("idx_products_revenue").on(table.totalRevenue),
261
- ratingIndex: index("idx_products_rating").on(table.averageRating),
262
-
263
- // Search indexes
264
- titleIndex: index("idx_products_title").on(table.title),
265
- fullTextSearch: index("idx_products_search").on(
266
- table.title,
267
- table.description,
268
- table.tags,
269
- ),
270
-
271
- // Inventory indexes
272
- inventoryIndex: index("idx_products_inventory").on(table.inventoryQuantity),
273
- lowStockIndex: index("idx_products_low_stock").on(
274
- table.inventoryQuantity,
275
- table.lowStockThreshold,
276
- ),
277
- trackStockIndex: index("idx_products_track_stock").on(table.trackStock),
278
- stockLocationIndex: index("idx_products_stock_location").on(
279
- table.stockLocationId,
280
- table.stockLocationType,
281
- ),
282
-
283
- // Sales channel index
284
- salesChannelsIndex: index("idx_products_sales_channels").on(
285
- table.salesChannels,
286
- ),
287
- }),
288
- );
289
-
290
- // =====================================================
291
- // PRODUCT VARIANTS TABLE
292
- // =====================================================
293
-
294
- export const productVariants = pgTable(
295
- "product_variants",
296
- {
297
- id: text("id")
298
- .primaryKey()
299
- .$defaultFn(() => createId()),
300
- productId: text("product_id")
301
- .notNull()
302
- .references(() => products.id, { onDelete: "cascade" }),
303
- storeId: text("store_id").notNull(),
304
-
305
- // Variant Identification
306
- title: varchar("title", { length: 255 }).notNull(),
307
- compareAtPrice: decimal("compare_at_price", { precision: 12, scale: 2 }),
308
-
309
- // Pricing (in cents)
310
- price: decimal("price", { precision: 12, scale: 2 }).notNull(),
311
- costPerItem: decimal("cost_per_item", { precision: 12, scale: 2 }),
312
-
313
- // Inventory
314
- inventoryQuantity: integer("inventory_quantity").notNull().default(0),
315
- inventoryItemId: text("inventory_item_id"), // Reference to inventory system
316
- fulfillmentService: fulfillmentServiceEnum("fulfillment_service")
317
- .notNull()
318
- .default("MANUAL"),
319
-
320
- // Physical Properties
321
- weight: decimal("weight", { precision: 8, scale: 3 }),
322
- dimensions: jsonb("dimensions").$type<{
323
- length?: number;
324
- width?: number;
325
- height?: number;
326
- unit?: "cm" | "in";
327
- }>(),
328
-
329
- // Variant Options (e.g., color: red, size: large)
330
- option1: varchar("option1", { length: 255 }),
331
- option2: varchar("option2", { length: 255 }),
332
- option3: varchar("option3", { length: 255 }),
333
- optionValues: jsonb("option_values")
334
- .$type<Record<string, string>>()
335
- .default({}),
336
-
337
- // Status and Availability
338
- availableForSale: boolean("available_for_sale").notNull().default(true),
339
- requiresShipping: boolean("requires_shipping").notNull().default(true),
340
-
341
- // Sales Channels - Control where variant is available (inherits from product if not set)
342
- salesChannels: jsonb("sales_channels")
343
- .$type<("POS" | "MARKETPLACE" | "STOREFRONT")[]>()
344
- .default(["POS", "MARKETPLACE", "STOREFRONT"]),
345
-
346
-
347
- // Media
348
- imageId: text("image_id"), // Primary image for this variant
349
- imageUrl: varchar("image_url", { length: 500 }),
350
- imageAltText: varchar("image_alt_text", { length: 255 }),
351
-
352
- // Advanced Features
353
- customFields: jsonb("custom_fields")
354
- .$type<Record<string, unknown>>()
355
- .default({}),
356
- taxCode: varchar("tax_code", { length: 50 }),
357
-
358
- // Analytics
359
- totalSales: integer("total_sales").notNull().default(0),
360
- totalRevenue: decimal("total_revenue", { precision: 12, scale: 2 })
361
- .notNull()
362
- .default("0"),
363
-
364
- // Position for ordering
365
- position: integer("position").notNull().default(0),
366
-
367
- // Timestamps
368
- createdAt: timestamp("created_at", { withTimezone: true })
369
- .defaultNow()
370
- .notNull(),
371
- updatedAt: timestamp("updated_at", { withTimezone: true })
372
- .defaultNow()
373
- .notNull(),
374
- },
375
- (table) => ({
376
- // Performance indexes
377
- productIdIndex: index("idx_variants_product_id").on(table.productId),
378
- storeIdIndex: index("idx_variants_store_id").on(table.storeId),
379
- priceIndex: index("idx_variants_price").on(table.price),
380
- inventoryIndex: index("idx_variants_inventory").on(table.inventoryQuantity),
381
- availableIndex: index("idx_variants_available").on(table.availableForSale),
382
- positionIndex: index("idx_variants_position").on(
383
- table.productId,
384
- table.position,
385
- ),
386
-
387
- // Option indexes for filtering
388
- option1Index: index("idx_variants_option1").on(table.option1),
389
- option2Index: index("idx_variants_option2").on(table.option2),
390
- option3Index: index("idx_variants_option3").on(table.option3),
391
-
392
- // Sales channel index
393
- salesChannelsIndex: index("idx_variants_sales_channels").on(
394
- table.salesChannels,
395
- ),
396
- }),
397
- );
398
-
399
- // =====================================================
400
- // COLLECTIONS TABLE
401
- // =====================================================
402
-
403
- export const collections = pgTable(
404
- "collections",
405
- {
406
- id: text("id")
407
- .primaryKey()
408
- .$defaultFn(() => createId()),
409
- storeId: text("store_id").notNull(),
410
-
411
- // Basic Information
412
- title: varchar("title", { length: 255 }).notNull(),
413
- description: text("description"),
414
- handle: varchar("handle", { length: 255 }).notNull(),
415
-
416
- // Collection Type and Rules
417
- collectionType: collectionTypeEnum("collection_type")
418
- .notNull()
419
- .default("MANUAL"),
420
- rules: jsonb("rules")
421
- .$type<
422
- Array<{
423
- field: string;
424
- relation:
425
- | "equals"
426
- | "not_equals"
427
- | "contains"
428
- | "starts_with"
429
- | "ends_with"
430
- | "greater_than"
431
- | "less_than";
432
- condition: string;
433
- }>
434
- >()
435
- .default([]),
436
-
437
-
438
- // SEO and Marketing
439
- seoTitle: varchar("seo_title", { length: 255 }),
440
- seoDescription: varchar("seo_description", { length: 500 }),
441
- seoKeywords: jsonb("seo_keywords").$type<string[]>().default([]),
442
-
443
- // Media
444
- imageUrl: varchar("image_url", { length: 500 }),
445
- imageAltText: varchar("image_alt_text", { length: 255 }),
446
-
447
- // Display and Sorting
448
- sortOrder: varchar("sort_order", { length: 50 }).default("manual"), // manual, best-selling, created, price-asc, price-desc
449
- isPublished: boolean("is_published").notNull().default(false),
450
- publishedAt: timestamp("published_at", { withTimezone: true }),
451
-
452
- // Advanced Features
453
- customFields: jsonb("custom_fields")
454
- .$type<Record<string, unknown>>()
455
- .default({}),
456
- templateSuffix: varchar("template_suffix", { length: 100 }),
457
-
458
- // Analytics
459
- totalProducts: integer("total_products").notNull().default(0),
460
- totalViews: integer("total_views").notNull().default(0),
461
-
462
- // Timestamps
463
- createdAt: timestamp("created_at", { withTimezone: true })
464
- .defaultNow()
465
- .notNull(),
466
- updatedAt: timestamp("updated_at", { withTimezone: true })
467
- .defaultNow()
468
- .notNull(),
469
- },
470
- (table) => ({
471
- // Performance indexes
472
- storeIdIndex: index("idx_collections_store_id").on(table.storeId),
473
- handleUniquePerStore: unique("idx_collections_handle_store_unique").on(
474
- table.storeId,
475
- table.handle,
476
- ),
477
- typeIndex: index("idx_collections_type").on(table.collectionType),
478
- publishedIndex: index("idx_collections_published").on(
479
- table.isPublished,
480
- table.publishedAt,
481
- ),
482
- titleIndex: index("idx_collections_title").on(table.title),
483
- }),
484
- );
485
-
486
- // =====================================================
487
- // PRODUCT COLLECTIONS JUNCTION TABLE
488
- // =====================================================
489
-
490
- export const productCollections = pgTable(
491
- "product_collections",
492
- {
493
- id: text("id")
494
- .primaryKey()
495
- .$defaultFn(() => createId()),
496
- productId: text("product_id")
497
- .notNull()
498
- .references(() => products.id, { onDelete: "cascade" }),
499
- collectionId: text("collection_id")
500
- .notNull()
501
- .references(() => collections.id, { onDelete: "cascade" }),
502
- position: integer("position").notNull().default(0),
503
- isManuallyAdded: boolean("is_manually_added").notNull().default(false),
504
-
505
- // Timestamps
506
- createdAt: timestamp("created_at", { withTimezone: true })
507
- .defaultNow()
508
- .notNull(),
509
- },
510
- (table) => ({
511
- // Unique constraint to prevent duplicates
512
- productCollectionUnique: unique("idx_product_collection_unique").on(
513
- table.productId,
514
- table.collectionId,
515
- ),
516
- productIdIndex: index("idx_product_collections_product").on(
517
- table.productId,
518
- ),
519
- collectionIdIndex: index("idx_product_collections_collection").on(
520
- table.collectionId,
521
- ),
522
- positionIndex: index("idx_product_collections_position").on(
523
- table.collectionId,
524
- table.position,
525
- ),
526
- }),
527
- );
528
-
529
- // =====================================================
530
- // PRODUCT IMAGES TABLE
531
- // =====================================================
532
-
533
- export const productImages = pgTable(
534
- "product_images",
535
- {
536
- id: text("id")
537
- .primaryKey()
538
- .$defaultFn(() => createId()),
539
- productId: text("product_id")
540
- .notNull()
541
- .references(() => products.id, { onDelete: "cascade" }),
542
- variantId: text("variant_id").references(() => productVariants.id, {
543
- onDelete: "set null",
544
- }),
545
-
546
- // Image Information
547
- url: varchar("url", { length: 500 }).notNull(),
548
- altText: varchar("alt_text", { length: 255 }),
549
- width: integer("width"),
550
- height: integer("height"),
551
- size: integer("size"), // File size in bytes
552
-
553
- // Image Variants (different sizes)
554
- thumbnailUrl: varchar("thumbnail_url", { length: 500 }),
555
- mediumUrl: varchar("medium_url", { length: 500 }),
556
- largeUrl: varchar("large_url", { length: 500 }),
557
-
558
- // Metadata
559
- filename: varchar("filename", { length: 255 }),
560
- contentType: varchar("content_type", { length: 100 }),
561
- position: integer("position").notNull().default(0),
562
-
563
- // Advanced Features
564
- isMain: boolean("is_main").notNull().default(false),
565
- customFields: jsonb("custom_fields")
566
- .$type<Record<string, unknown>>()
567
- .default({}),
568
-
569
- // Timestamps
570
- createdAt: timestamp("created_at", { withTimezone: true })
571
- .defaultNow()
572
- .notNull(),
573
- updatedAt: timestamp("updated_at", { withTimezone: true })
574
- .defaultNow()
575
- .notNull(),
576
- },
577
- (table) => ({
578
- // Performance indexes
579
- productIdIndex: index("idx_product_images_product").on(table.productId),
580
- variantIdIndex: index("idx_product_images_variant").on(table.variantId),
581
- positionIndex: index("idx_product_images_position").on(
582
- table.productId,
583
- table.position,
584
- ),
585
- mainImageIndex: index("idx_product_images_main").on(
586
- table.productId,
587
- table.isMain,
588
- ),
589
- }),
590
- );
591
-
592
- // =====================================================
593
- // PRODUCT CATEGORIES TABLE
594
- // =====================================================
595
-
596
- export const productCategories = pgTable(
597
- "product_categories",
598
- {
599
- id: text("id")
600
- .primaryKey()
601
- .$defaultFn(() => createId()),
602
- storeId: text("store_id").notNull(),
603
-
604
- // Category Information
605
- name: varchar("name", { length: 255 }).notNull(),
606
- description: text("description"),
607
- handle: varchar("handle", { length: 255 }).notNull(),
608
-
609
- // Hierarchy
610
- parentId: text("parent_id"),
611
- level: integer("level").notNull().default(0),
612
- path: varchar("path", { length: 1000 }), // e.g., /electronics/phones/smartphones
613
-
614
- // Display
615
- imageUrl: varchar("image_url", { length: 500 }),
616
- iconUrl: varchar("icon_url", { length: 500 }),
617
- position: integer("position").notNull().default(0),
618
- isVisible: boolean("is_visible").notNull().default(true),
619
-
620
- // SEO
621
- seoTitle: varchar("seo_title", { length: 255 }),
622
- seoDescription: varchar("seo_description", { length: 500 }),
623
-
624
- // Analytics
625
- productCount: integer("product_count").notNull().default(0),
626
-
627
- // Timestamps
628
- createdAt: timestamp("created_at", { withTimezone: true })
629
- .defaultNow()
630
- .notNull(),
631
- updatedAt: timestamp("updated_at", { withTimezone: true })
632
- .defaultNow()
633
- .notNull(),
634
- },
635
- (table) => ({
636
- // Performance indexes
637
- storeIdIndex: index("idx_categories_store_id").on(table.storeId),
638
- handleUniquePerStore: unique("idx_categories_handle_store_unique").on(
639
- table.storeId,
640
- table.handle,
641
- ),
642
- parentIdIndex: index("idx_categories_parent").on(table.parentId),
643
- levelIndex: index("idx_categories_level").on(table.level),
644
- visibleIndex: index("idx_categories_visible").on(table.isVisible),
645
- positionIndex: index("idx_categories_position").on(
646
- table.parentId,
647
- table.position,
648
- ),
649
- }),
650
- );
651
-
652
- // =====================================================
653
- // PRODUCT REVIEWS TABLE
654
- // =====================================================
655
-
656
- export const productReviews = pgTable(
657
- "product_reviews",
658
- {
659
- id: text("id")
660
- .primaryKey()
661
- .$defaultFn(() => createId()),
662
- productId: text("product_id")
663
- .notNull()
664
- .references(() => products.id, { onDelete: "cascade" }),
665
- variantId: text("variant_id").references(() => productVariants.id, {
666
- onDelete: "set null",
667
- }),
668
- storeId: text("store_id").notNull(),
669
-
670
- // Review Information
671
- customerId: text("customer_id"), // Customer who wrote the review
672
- customerName: varchar("customer_name", { length: 255 }),
673
- customerEmail: varchar("customer_email", { length: 255 }),
674
-
675
- // Review Content
676
- title: varchar("title", { length: 255 }),
677
- content: text("content").notNull(),
678
- rating: integer("rating").notNull(), // 1-5 scale
679
-
680
- // Status and Moderation
681
- status: varchar("status", { length: 50 }).notNull().default("pending"), // pending, approved, rejected
682
- isVerifiedPurchase: boolean("is_verified_purchase")
683
- .notNull()
684
- .default(false),
685
- isRecommended: boolean("is_recommended"),
686
-
687
- // Helpfulness
688
- helpfulCount: integer("helpful_count").notNull().default(0),
689
- unhelpfulCount: integer("unhelpful_count").notNull().default(0),
690
-
691
- // Metadata
692
- reviewSource: varchar("review_source", { length: 100 }), // web, mobile, import
693
- ipAddress: varchar("ip_address", { length: 45 }),
694
- userAgent: varchar("user_agent", { length: 500 }),
695
-
696
- // Timestamps
697
- createdAt: timestamp("created_at", { withTimezone: true })
698
- .defaultNow()
699
- .notNull(),
700
- updatedAt: timestamp("updated_at", { withTimezone: true })
701
- .defaultNow()
702
- .notNull(),
703
- moderatedAt: timestamp("moderated_at", { withTimezone: true }),
704
- },
705
- (table) => ({
706
- // Performance indexes
707
- productIdIndex: index("idx_reviews_product").on(table.productId),
708
- storeIdIndex: index("idx_reviews_store").on(table.storeId),
709
- customerIdIndex: index("idx_reviews_customer").on(table.customerId),
710
- statusIndex: index("idx_reviews_status").on(table.status),
711
- ratingIndex: index("idx_reviews_rating").on(table.rating),
712
- verifiedIndex: index("idx_reviews_verified").on(table.isVerifiedPurchase),
713
- createdAtIndex: index("idx_reviews_created").on(table.createdAt),
714
- }),
715
- );
716
-
717
- // =====================================================
718
- // PRODUCT ANALYTICS TABLE
719
- // =====================================================
720
-
721
- export const productAnalytics = pgTable(
722
- "product_analytics",
723
- {
724
- id: text("id")
725
- .primaryKey()
726
- .$defaultFn(() => createId()),
727
- productId: text("product_id")
728
- .notNull()
729
- .references(() => products.id, { onDelete: "cascade" }),
730
- storeId: text("store_id").notNull(),
731
-
732
- // Time-based metrics
733
- date: timestamp("date", { withTimezone: true }).notNull(),
734
- hour: integer("hour"), // 0-23 for hourly analytics
735
-
736
- // Traffic Metrics
737
- views: integer("views").notNull().default(0),
738
- uniqueViews: integer("unique_views").notNull().default(0),
739
- sessions: integer("sessions").notNull().default(0),
740
- bounceRate: decimal("bounce_rate", { precision: 5, scale: 4 }),
741
-
742
- // Conversion Metrics
743
- addToCarts: integer("add_to_carts").notNull().default(0),
744
- purchases: integer("purchases").notNull().default(0),
745
- revenue: decimal("revenue", { precision: 12, scale: 2 })
746
- .notNull()
747
- .default("0"),
748
- conversionRate: decimal("conversion_rate", { precision: 5, scale: 4 }),
749
-
750
- // Engagement Metrics
751
- averageTimeOnPage: integer("average_time_on_page"), // in seconds
752
- clickThroughRate: decimal("click_through_rate", { precision: 5, scale: 4 }),
753
- shareCount: integer("share_count").notNull().default(0),
754
- wishlistAdds: integer("wishlist_adds").notNull().default(0),
755
-
756
- // Search and Discovery
757
- searchImpressions: integer("search_impressions").notNull().default(0),
758
- searchClicks: integer("search_clicks").notNull().default(0),
759
- searchRanking: decimal("search_ranking", { precision: 8, scale: 4 }),
760
-
761
- // Geographic and Device Data
762
- topCountries: jsonb("top_countries")
763
- .$type<Array<{ country: string; views: number }>>()
764
- .default([]),
765
- topDevices: jsonb("top_devices")
766
- .$type<Array<{ device: string; views: number }>>()
767
- .default([]),
768
-
769
- // Timestamps
770
- createdAt: timestamp("created_at", { withTimezone: true })
771
- .defaultNow()
772
- .notNull(),
773
- updatedAt: timestamp("updated_at", { withTimezone: true })
774
- .defaultNow()
775
- .notNull(),
776
- },
777
- (table) => ({
778
- // Performance indexes for analytics queries
779
- productDateIndex: index("idx_analytics_product_date").on(
780
- table.productId,
781
- table.date,
782
- ),
783
- storeIdIndex: index("idx_analytics_store").on(table.storeId),
784
- dateIndex: index("idx_analytics_date").on(table.date),
785
- hourlyIndex: index("idx_analytics_hourly").on(
786
- table.productId,
787
- table.date,
788
- table.hour,
789
- ),
790
- viewsIndex: index("idx_analytics_views").on(table.views),
791
- revenueIndex: index("idx_analytics_revenue").on(table.revenue),
792
- conversionIndex: index("idx_analytics_conversion").on(table.conversionRate),
793
- }),
794
- );
795
-
796
- // =====================================================
797
- // WHOLESALE PRICING TIERS TABLE
798
- // =====================================================
799
-
800
- export const wholesalePricingTiers = pgTable(
801
- "wholesale_pricing_tiers",
802
- {
803
- id: text("id")
804
- .primaryKey()
805
- .$defaultFn(() => createId()),
806
- productId: text("product_id")
807
- .notNull()
808
- .references(() => products.id, { onDelete: "cascade" }),
809
- variantId: text("variant_id").references(() => productVariants.id, {
810
- onDelete: "cascade",
811
- }),
812
- storeId: text("store_id").notNull(),
813
-
814
- // Tier Information
815
- tierName: varchar("tier_name", { length: 100 }).notNull(),
816
- tierDescription: text("tier_description"),
817
- tierPriority: integer("tier_priority").notNull().default(0), // Higher priority = applied first
818
- minQuantity: integer("min_quantity").notNull(),
819
- maxQuantity: integer("max_quantity"),
820
- price: decimal("price", { precision: 12, scale: 2 }).notNull(),
821
- discountPercentage: decimal("discount_percentage", {
822
- precision: 5,
823
- scale: 2,
824
- }),
825
-
826
- // Enhanced Tier Conditions
827
- customerType: varchar("customer_type", { length: 50 }), // "RETAILER", "DISTRIBUTOR", "RESELLER", "VIP"
828
- customerTags: jsonb("customer_tags").$type<string[]>().default([]), // Additional customer segmentation
829
- region: varchar("region", { length: 100 }),
830
- country: varchar("country", { length: 50 }),
831
- currency: varchar("currency", { length: 3 }).default("USD"),
832
- minimumOrderValue: decimal("minimum_order_value", {
833
- precision: 12,
834
- scale: 2,
835
- }),
836
-
837
- // Advanced Pricing Features
838
- bulkDiscountType: varchar("bulk_discount_type", { length: 20 }).default(
839
- "PERCENTAGE",
840
- ), // PERCENTAGE, FIXED_AMOUNT, TIERED
841
- compoundDiscount: boolean("compound_discount").notNull().default(false), // Can be combined with other discounts
842
- exclusiveDiscount: boolean("exclusive_discount").notNull().default(false), // Cannot be combined
843
- maxDiscountAmount: decimal("max_discount_amount", {
844
- precision: 12,
845
- scale: 2,
846
- }),
847
-
848
- // Validity and Scheduling
849
- validFrom: timestamp("valid_from", { withTimezone: true }),
850
- validTo: timestamp("valid_to", { withTimezone: true }),
851
- isActive: boolean("is_active").notNull().default(true),
852
- isScheduled: boolean("is_scheduled").notNull().default(false),
853
- scheduleStart: timestamp("schedule_start", { withTimezone: true }),
854
- scheduleEnd: timestamp("schedule_end", { withTimezone: true }),
855
-
856
- // Additional Features
857
- freeShipping: boolean("free_shipping").notNull().default(false),
858
- freeShippingThreshold: decimal("free_shipping_threshold", {
859
- precision: 12,
860
- scale: 2,
861
- }),
862
- expeditedShipping: boolean("expedited_shipping").notNull().default(false),
863
- dropShippingEnabled: boolean("drop_shipping_enabled")
864
- .notNull()
865
- .default(false),
866
-
867
- // Payment Terms
868
- paymentTerms: varchar("payment_terms", { length: 50 }), // "NET_30", "NET_60", "COD", "PREPAID"
869
- earlyPaymentDiscount: decimal("early_payment_discount", {
870
- precision: 5,
871
- scale: 2,
872
- }),
873
- latePaymentPenalty: decimal("late_payment_penalty", {
874
- precision: 5,
875
- scale: 2,
876
- }),
877
-
878
- // Inventory and Fulfillment
879
- reservedInventory: integer("reserved_inventory").default(0),
880
- autoReplenishment: boolean("auto_replenishment").notNull().default(false),
881
- replenishmentThreshold: integer("replenishment_threshold"),
882
- leadTimedays: integer("lead_time_days").default(0),
883
-
884
- // Analytics and Tracking
885
- totalOrdersCount: integer("total_orders_count").notNull().default(0),
886
- totalRevenue: decimal("total_revenue", { precision: 12, scale: 2 })
887
- .notNull()
888
- .default("0"),
889
- averageOrderValue: decimal("average_order_value", {
890
- precision: 12,
891
- scale: 2,
892
- }),
893
- lastUsedAt: timestamp("last_used_at", { withTimezone: true }),
894
-
895
- // Custom Fields and Metadata
896
- customFields: jsonb("custom_fields")
897
- .$type<Record<string, unknown>>()
898
- .default({}),
899
- internalNotes: text("internal_notes"),
900
- customerVisibleNotes: text("customer_visible_notes"),
901
-
902
- // Timestamps
903
- createdAt: timestamp("created_at", { withTimezone: true })
904
- .defaultNow()
905
- .notNull(),
906
- updatedAt: timestamp("updated_at", { withTimezone: true })
907
- .defaultNow()
908
- .notNull(),
909
- },
910
- (table) => ({
911
- // Performance indexes
912
- productIdIndex: index("idx_wholesale_tiers_product").on(table.productId),
913
- variantIdIndex: index("idx_wholesale_tiers_variant").on(table.variantId),
914
- storeIdIndex: index("idx_wholesale_tiers_store").on(table.storeId),
915
- quantityIndex: index("idx_wholesale_tiers_quantity").on(
916
- table.minQuantity,
917
- table.maxQuantity,
918
- ),
919
- customerTypeIndex: index("idx_wholesale_tiers_customer_type").on(
920
- table.customerType,
921
- ),
922
- activeIndex: index("idx_wholesale_tiers_active").on(table.isActive),
923
- validityIndex: index("idx_wholesale_tiers_validity").on(
924
- table.validFrom,
925
- table.validTo,
926
- ),
927
-
928
- // Enhanced indexes for new features
929
- priorityIndex: index("idx_wholesale_tiers_priority").on(table.tierPriority),
930
- scheduledIndex: index("idx_wholesale_tiers_scheduled").on(
931
- table.isScheduled,
932
- table.scheduleStart,
933
- table.scheduleEnd,
934
- ),
935
- regionIndex: index("idx_wholesale_tiers_region").on(table.region),
936
- countryIndex: index("idx_wholesale_tiers_country").on(table.country),
937
- paymentTermsIndex: index("idx_wholesale_tiers_payment_terms").on(
938
- table.paymentTerms,
939
- ),
940
- revenueIndex: index("idx_wholesale_tiers_revenue").on(table.totalRevenue),
941
- lastUsedIndex: index("idx_wholesale_tiers_last_used").on(table.lastUsedAt),
942
-
943
- // Composite indexes for complex queries
944
- activeScheduleIndex: index("idx_wholesale_tiers_active_schedule").on(
945
- table.isActive,
946
- table.isScheduled,
947
- table.scheduleStart,
948
- ),
949
- customerLocationIndex: index("idx_wholesale_tiers_customer_location").on(
950
- table.customerType,
951
- table.region,
952
- table.country,
953
- ),
954
- }),
955
- );