@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.
- package/CONFIGURATION_GUIDE.md +1 -0
- package/README.md +384 -0
- package/SCHEMA_ORGANIZATION.md +209 -0
- package/dist/configs/index.d.ts +85 -0
- package/dist/configs/index.js +555 -0
- package/dist/events/kafka.d.ts +40 -0
- package/dist/events/kafka.js +311 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +41 -0
- package/dist/interfaces/customer-events.d.ts +85 -0
- package/dist/interfaces/customer-events.js +2 -0
- package/dist/interfaces/inventory-events.d.ts +453 -0
- package/dist/interfaces/inventory-events.js +3 -0
- package/dist/interfaces/inventory-types.d.ts +894 -0
- package/dist/interfaces/inventory-types.js +3 -0
- package/dist/interfaces/order-events.d.ts +320 -0
- package/dist/interfaces/order-events.js +3 -0
- package/dist/lib/auditLogger.d.ts +162 -0
- package/dist/lib/auditLogger.js +626 -0
- package/dist/lib/authOrganization.d.ts +24 -0
- package/dist/lib/authOrganization.js +110 -0
- package/dist/lib/db.d.ts +6 -0
- package/dist/lib/db.js +88 -0
- package/dist/middleware/serviceAuth.d.ts +60 -0
- package/dist/middleware/serviceAuth.js +272 -0
- package/dist/middleware/storeOwnership.d.ts +15 -0
- package/dist/middleware/storeOwnership.js +156 -0
- package/dist/middleware/storeValidationMiddleware.d.ts +44 -0
- package/dist/middleware/storeValidationMiddleware.js +180 -0
- package/dist/middleware/userAuth.d.ts +27 -0
- package/dist/middleware/userAuth.js +218 -0
- package/dist/schemas/admin/admin-schema.d.ts +741 -0
- package/dist/schemas/admin/admin-schema.js +111 -0
- package/dist/schemas/ai-moderation/ai-moderation-schema.d.ts +648 -0
- package/dist/schemas/ai-moderation/ai-moderation-schema.js +88 -0
- package/dist/schemas/common/common-schemas.d.ts +436 -0
- package/dist/schemas/common/common-schemas.js +94 -0
- package/dist/schemas/compliance/compliance-schema.d.ts +3388 -0
- package/dist/schemas/compliance/compliance-schema.js +472 -0
- package/dist/schemas/compliance/kyc-schema.d.ts +2642 -0
- package/dist/schemas/compliance/kyc-schema.js +361 -0
- package/dist/schemas/customer/customer-schema.d.ts +2727 -0
- package/dist/schemas/customer/customer-schema.js +399 -0
- package/dist/schemas/index.d.ts +27 -0
- package/dist/schemas/index.js +138 -0
- package/dist/schemas/inventory/inventory-tables.d.ts +9476 -0
- package/dist/schemas/inventory/inventory-tables.js +1470 -0
- package/dist/schemas/inventory/lot-tables.d.ts +3281 -0
- package/dist/schemas/inventory/lot-tables.js +608 -0
- package/dist/schemas/order/order-schema.d.ts +5825 -0
- package/dist/schemas/order/order-schema.js +954 -0
- package/dist/schemas/product/discount-relations.d.ts +15 -0
- package/dist/schemas/product/discount-relations.js +34 -0
- package/dist/schemas/product/discount-schema.d.ts +1975 -0
- package/dist/schemas/product/discount-schema.js +297 -0
- package/dist/schemas/product/product-relations.d.ts +41 -0
- package/dist/schemas/product/product-relations.js +133 -0
- package/dist/schemas/product/product-schema.d.ts +4544 -0
- package/dist/schemas/product/product-schema.js +671 -0
- package/dist/schemas/store/store-audit-schema.d.ts +4135 -0
- package/dist/schemas/store/store-audit-schema.js +556 -0
- package/dist/schemas/store/store-schema.d.ts +3100 -0
- package/dist/schemas/store/store-schema.js +381 -0
- package/dist/schemas/store/store-settings-schema.d.ts +665 -0
- package/dist/schemas/store/store-settings-schema.js +141 -0
- package/dist/schemas/types.d.ts +50 -0
- package/dist/schemas/types.js +3 -0
- package/dist/types/events.d.ts +2396 -0
- package/dist/types/events.js +505 -0
- package/dist/utils/errorHandler.d.ts +12 -0
- package/dist/utils/errorHandler.js +36 -0
- package/dist/utils/subdomain.d.ts +6 -0
- package/dist/utils/subdomain.js +20 -0
- package/nul +8 -0
- package/package.json +43 -0
- package/src/configs/index.ts +654 -0
- package/src/events/kafka.ts +429 -0
- package/src/index.ts +26 -0
- package/src/interfaces/customer-events.ts +106 -0
- package/src/interfaces/inventory-events.ts +545 -0
- package/src/interfaces/inventory-types.ts +1004 -0
- package/src/interfaces/order-events.ts +381 -0
- package/src/lib/auditLogger.ts +1117 -0
- package/src/lib/authOrganization.ts +153 -0
- package/src/lib/db.ts +64 -0
- package/src/middleware/serviceAuth.ts +328 -0
- package/src/middleware/storeOwnership.ts +199 -0
- package/src/middleware/storeValidationMiddleware.ts +247 -0
- package/src/middleware/userAuth.ts +248 -0
- package/src/schemas/admin/admin-schema.ts +208 -0
- package/src/schemas/ai-moderation/ai-moderation-schema.ts +180 -0
- package/src/schemas/common/common-schemas.ts +108 -0
- package/src/schemas/compliance/compliance-schema.ts +927 -0
- package/src/schemas/compliance/kyc-schema.ts +649 -0
- package/src/schemas/customer/customer-schema.ts +576 -0
- package/src/schemas/index.ts +189 -0
- package/src/schemas/inventory/inventory-tables.ts +1927 -0
- package/src/schemas/inventory/lot-tables.ts +799 -0
- package/src/schemas/order/order-schema.ts +1400 -0
- package/src/schemas/product/discount-relations.ts +44 -0
- package/src/schemas/product/discount-schema.ts +464 -0
- package/src/schemas/product/product-relations.ts +187 -0
- package/src/schemas/product/product-schema.ts +955 -0
- package/src/schemas/store/ethiopian_business_api.md.resolved +212 -0
- package/src/schemas/store/store-audit-schema.ts +1257 -0
- package/src/schemas/store/store-schema.ts +661 -0
- package/src/schemas/store/store-settings-schema.ts +231 -0
- package/src/schemas/types.ts +67 -0
- package/src/types/events.ts +646 -0
- package/src/utils/errorHandler.ts +44 -0
- package/src/utils/subdomain.ts +19 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { relations } from "drizzle-orm";
|
|
2
|
+
import {
|
|
3
|
+
discounts,
|
|
4
|
+
discountUsages,
|
|
5
|
+
discountRules,
|
|
6
|
+
customerSegments,
|
|
7
|
+
discountAnalytics
|
|
8
|
+
} from "./discount-schema";
|
|
9
|
+
import { products, productVariants, collections } from "./product-schema";
|
|
10
|
+
|
|
11
|
+
// =====================================================
|
|
12
|
+
// DISCOUNT RELATIONS
|
|
13
|
+
// =====================================================
|
|
14
|
+
|
|
15
|
+
export const discountsRelations = relations(discounts, ({ many }) => ({
|
|
16
|
+
usages: many(discountUsages),
|
|
17
|
+
rules: many(discountRules),
|
|
18
|
+
analytics: many(discountAnalytics),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
export const discountUsagesRelations = relations(discountUsages, ({ one }) => ({
|
|
22
|
+
discount: one(discounts, {
|
|
23
|
+
fields: [discountUsages.discountId],
|
|
24
|
+
references: [discounts.id],
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
export const discountRulesRelations = relations(discountRules, ({ one }) => ({
|
|
29
|
+
discount: one(discounts, {
|
|
30
|
+
fields: [discountRules.discountId],
|
|
31
|
+
references: [discounts.id],
|
|
32
|
+
}),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
export const discountAnalyticsRelations = relations(discountAnalytics, ({ one }) => ({
|
|
36
|
+
discount: one(discounts, {
|
|
37
|
+
fields: [discountAnalytics.discountId],
|
|
38
|
+
references: [discounts.id],
|
|
39
|
+
}),
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
export const customerSegmentsRelations = relations(customerSegments, ({ many }) => ({
|
|
43
|
+
// Relations can be added here for customer segment memberships if needed
|
|
44
|
+
}));
|
|
@@ -0,0 +1,464 @@
|
|
|
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
|
+
// DISCOUNT ENUMS
|
|
18
|
+
// =====================================================
|
|
19
|
+
|
|
20
|
+
export const discountTypeEnum = pgEnum("discount_type", [
|
|
21
|
+
"PERCENTAGE",
|
|
22
|
+
"FIXED_AMOUNT",
|
|
23
|
+
"FREE_SHIPPING",
|
|
24
|
+
"BOGO", // Buy one get one
|
|
25
|
+
"TIERED", // Volume-based tiered discounts
|
|
26
|
+
"LOYALTY_POINTS", // Loyalty points redemption
|
|
27
|
+
"REFERRAL", // Referral rewards
|
|
28
|
+
"MEMBER_ONLY", // Membership-exclusive discounts
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
export const discountStatusEnum = pgEnum("discount_status", [
|
|
32
|
+
"DRAFT",
|
|
33
|
+
"ACTIVE",
|
|
34
|
+
"PAUSED",
|
|
35
|
+
"SCHEDULED",
|
|
36
|
+
"EXPIRED",
|
|
37
|
+
"DISABLED",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
export const discountTargetTypeEnum = pgEnum("discount_target_type", [
|
|
41
|
+
"STORE", // Store-wide discount
|
|
42
|
+
"COLLECTION", // Collection-specific discount
|
|
43
|
+
"PRODUCT", // Product-specific discount
|
|
44
|
+
"VARIANT", // Variant-specific discount
|
|
45
|
+
"CATEGORY", // Category-specific discount
|
|
46
|
+
"CUSTOMER_SEGMENT", // Customer segment-specific
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
export const discountApplicationTypeEnum = pgEnum("discount_application_type", [
|
|
50
|
+
"AUTOMATIC", // Automatically applied
|
|
51
|
+
"CODE", // Requires coupon code
|
|
52
|
+
"MANUAL", // Manually applied by staff
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
export const customerSegmentTypeEnum = pgEnum("customer_segment_type", [
|
|
56
|
+
"NEW_CUSTOMER",
|
|
57
|
+
"RETURNING_CUSTOMER",
|
|
58
|
+
"VIP",
|
|
59
|
+
"LOYALTY_TIER_1",
|
|
60
|
+
"LOYALTY_TIER_2",
|
|
61
|
+
"LOYALTY_TIER_3",
|
|
62
|
+
"GEOGRAPHIC",
|
|
63
|
+
"PURCHASE_HISTORY",
|
|
64
|
+
"CUSTOM",
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
export const discountStackingModeEnum = pgEnum("discount_stacking_mode", [
|
|
68
|
+
"NONE", // Cannot stack with other discounts
|
|
69
|
+
"ADDITIVE", // Add discount values together
|
|
70
|
+
"MULTIPLICATIVE", // Apply discounts multiplicatively
|
|
71
|
+
"BEST_ONLY", // Apply only the best discount
|
|
72
|
+
"CUSTOM", // Custom stacking logic
|
|
73
|
+
]);
|
|
74
|
+
|
|
75
|
+
// =====================================================
|
|
76
|
+
// MAIN DISCOUNTS TABLE
|
|
77
|
+
// =====================================================
|
|
78
|
+
|
|
79
|
+
export const discounts = pgTable(
|
|
80
|
+
"discounts",
|
|
81
|
+
{
|
|
82
|
+
id: text("id")
|
|
83
|
+
.primaryKey()
|
|
84
|
+
.$defaultFn(() => createId()),
|
|
85
|
+
storeId: text("store_id").notNull(),
|
|
86
|
+
|
|
87
|
+
// Basic Information
|
|
88
|
+
name: varchar("name", { length: 255 }).notNull(),
|
|
89
|
+
description: text("description"),
|
|
90
|
+
internalNotes: text("internal_notes"), // Staff notes
|
|
91
|
+
|
|
92
|
+
// Discount Configuration
|
|
93
|
+
type: discountTypeEnum("type").notNull(),
|
|
94
|
+
status: discountStatusEnum("status").notNull().default("DRAFT"),
|
|
95
|
+
applicationType: discountApplicationTypeEnum("application_type")
|
|
96
|
+
.notNull()
|
|
97
|
+
.default("AUTOMATIC"),
|
|
98
|
+
|
|
99
|
+
// Discount Values
|
|
100
|
+
value: decimal("value", { precision: 12, scale: 4 }).notNull(), // Main discount value
|
|
101
|
+
maxDiscountAmount: decimal("max_discount_amount", { precision: 12, scale: 2 }), // Cap for percentage discounts
|
|
102
|
+
|
|
103
|
+
// Coupon Code (for CODE application type)
|
|
104
|
+
couponCode: varchar("coupon_code", { length: 100 }),
|
|
105
|
+
codePrefix: varchar("code_prefix", { length: 20 }), // For auto-generated codes
|
|
106
|
+
|
|
107
|
+
// Targeting Configuration
|
|
108
|
+
targetType: discountTargetTypeEnum("target_type").notNull().default("STORE"),
|
|
109
|
+
targetIds: jsonb("target_ids").$type<string[]>().default([]), // IDs of targeted items
|
|
110
|
+
|
|
111
|
+
// Customer Eligibility
|
|
112
|
+
customerSegments: jsonb("customer_segments")
|
|
113
|
+
.$type<string[]>()
|
|
114
|
+
.default([]),
|
|
115
|
+
excludedCustomerIds: jsonb("excluded_customer_ids")
|
|
116
|
+
.$type<string[]>()
|
|
117
|
+
.default([]),
|
|
118
|
+
|
|
119
|
+
// Usage Limits
|
|
120
|
+
usageLimit: integer("usage_limit"), // Total usage limit
|
|
121
|
+
usageLimitPerCustomer: integer("usage_limit_per_customer"),
|
|
122
|
+
currentUsageCount: integer("current_usage_count").notNull().default(0),
|
|
123
|
+
|
|
124
|
+
// Minimum Requirements
|
|
125
|
+
minPurchaseAmount: decimal("min_purchase_amount", { precision: 12, scale: 2 }),
|
|
126
|
+
minQuantity: integer("min_quantity"),
|
|
127
|
+
minItems: integer("min_items"), // Minimum number of different items
|
|
128
|
+
|
|
129
|
+
// Time Constraints
|
|
130
|
+
startsAt: timestamp("starts_at", { withTimezone: true }),
|
|
131
|
+
endsAt: timestamp("ends_at", { withTimezone: true }),
|
|
132
|
+
timeZone: varchar("time_zone", { length: 50 }).default("UTC"),
|
|
133
|
+
|
|
134
|
+
// Schedule Configuration
|
|
135
|
+
scheduleConfig: jsonb("schedule_config").$type<{
|
|
136
|
+
recurringType?: "DAILY" | "WEEKLY" | "MONTHLY" | "YEARLY";
|
|
137
|
+
daysOfWeek?: number[]; // 0-6 (Sunday to Saturday)
|
|
138
|
+
hoursOfDay?: { start: number; end: number }; // 0-23
|
|
139
|
+
datesOfMonth?: number[]; // 1-31
|
|
140
|
+
monthsOfYear?: number[]; // 1-12
|
|
141
|
+
}>(),
|
|
142
|
+
|
|
143
|
+
// Geographic Restrictions
|
|
144
|
+
geoRestrictions: jsonb("geo_restrictions").$type<{
|
|
145
|
+
includedCountries?: string[];
|
|
146
|
+
excludedCountries?: string[];
|
|
147
|
+
includedStates?: string[];
|
|
148
|
+
excludedStates?: string[];
|
|
149
|
+
includedCities?: string[];
|
|
150
|
+
excludedCities?: string[];
|
|
151
|
+
includedZipCodes?: string[];
|
|
152
|
+
excludedZipCodes?: string[];
|
|
153
|
+
}>(),
|
|
154
|
+
|
|
155
|
+
// Sales Channel Configuration
|
|
156
|
+
salesChannels: jsonb("sales_channels")
|
|
157
|
+
.$type<("POS" | "MARKETPLACE" | "STOREFRONT")[]>()
|
|
158
|
+
.default(["POS", "MARKETPLACE", "STOREFRONT"]),
|
|
159
|
+
|
|
160
|
+
// Stacking Configuration
|
|
161
|
+
stackingMode: discountStackingModeEnum("stacking_mode")
|
|
162
|
+
.notNull()
|
|
163
|
+
.default("NONE"),
|
|
164
|
+
priority: integer("priority").notNull().default(0), // Higher number = higher priority
|
|
165
|
+
excludeOtherDiscounts: boolean("exclude_other_discounts")
|
|
166
|
+
.notNull()
|
|
167
|
+
.default(false),
|
|
168
|
+
|
|
169
|
+
// Advanced Configuration
|
|
170
|
+
customRules: jsonb("custom_rules").$type<{
|
|
171
|
+
conditions?: Array<{
|
|
172
|
+
field: string;
|
|
173
|
+
operator: "EQUALS" | "NOT_EQUALS" | "GREATER_THAN" | "LESS_THAN" | "CONTAINS" | "NOT_CONTAINS";
|
|
174
|
+
value: any;
|
|
175
|
+
logicalOperator?: "AND" | "OR";
|
|
176
|
+
}>;
|
|
177
|
+
actions?: Array<{
|
|
178
|
+
type: string;
|
|
179
|
+
parameters: Record<string, any>;
|
|
180
|
+
}>;
|
|
181
|
+
}>(),
|
|
182
|
+
|
|
183
|
+
// BOGO Configuration
|
|
184
|
+
bogoConfig: jsonb("bogo_config").$type<{
|
|
185
|
+
buyQuantity: number;
|
|
186
|
+
getQuantity: number;
|
|
187
|
+
getDiscountPercentage?: number; // 100 = free, 50 = 50% off
|
|
188
|
+
applicableToSameProduct?: boolean;
|
|
189
|
+
applicableToCollection?: string;
|
|
190
|
+
}>(),
|
|
191
|
+
|
|
192
|
+
// Tiered Discount Configuration
|
|
193
|
+
tieredConfig: jsonb("tiered_config").$type<Array<{
|
|
194
|
+
minQuantity: number;
|
|
195
|
+
discountPercentage?: number;
|
|
196
|
+
discountAmount?: number;
|
|
197
|
+
}>>(),
|
|
198
|
+
|
|
199
|
+
// Loyalty Points Configuration
|
|
200
|
+
loyaltyPointsConfig: jsonb("loyalty_points_config").$type<{
|
|
201
|
+
pointsRequired: number;
|
|
202
|
+
pointsValue: number; // Value of points in currency
|
|
203
|
+
allowPartialRedemption?: boolean;
|
|
204
|
+
}>(),
|
|
205
|
+
|
|
206
|
+
// Performance Tracking
|
|
207
|
+
totalSavings: decimal("total_savings", { precision: 12, scale: 2 })
|
|
208
|
+
.notNull()
|
|
209
|
+
.default("0"),
|
|
210
|
+
totalRevenue: decimal("total_revenue", { precision: 12, scale: 2 })
|
|
211
|
+
.notNull()
|
|
212
|
+
.default("0"),
|
|
213
|
+
conversionRate: decimal("conversion_rate", { precision: 5, scale: 4 }),
|
|
214
|
+
|
|
215
|
+
// Metadata
|
|
216
|
+
createdBy: text("created_by"), // User ID who created the discount
|
|
217
|
+
lastModifiedBy: text("last_modified_by"),
|
|
218
|
+
tags: jsonb("tags").$type<string[]>().default([]),
|
|
219
|
+
|
|
220
|
+
// Timestamps
|
|
221
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
222
|
+
.defaultNow()
|
|
223
|
+
.notNull(),
|
|
224
|
+
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
225
|
+
.defaultNow()
|
|
226
|
+
.notNull(),
|
|
227
|
+
deletedAt: timestamp("deleted_at", { withTimezone: true }),
|
|
228
|
+
},
|
|
229
|
+
(table) => ({
|
|
230
|
+
storeIdIndex: index("idx_discounts_store_id").on(table.storeId),
|
|
231
|
+
statusIndex: index("idx_discounts_status").on(table.status),
|
|
232
|
+
typeIndex: index("idx_discounts_type").on(table.type),
|
|
233
|
+
couponCodeIndex: index("idx_discounts_coupon_code").on(table.couponCode),
|
|
234
|
+
targetTypeIndex: index("idx_discounts_target_type").on(table.targetType),
|
|
235
|
+
priorityIndex: index("idx_discounts_priority").on(table.priority),
|
|
236
|
+
timeRangeIndex: index("idx_discounts_time_range").on(table.startsAt, table.endsAt),
|
|
237
|
+
usageIndex: index("idx_discounts_usage").on(table.currentUsageCount, table.usageLimit),
|
|
238
|
+
salesChannelsIndex: index("idx_discounts_sales_channels").on(table.salesChannels),
|
|
239
|
+
|
|
240
|
+
// Unique constraints
|
|
241
|
+
couponCodeUniquePerStore: unique("idx_discounts_coupon_store_unique").on(
|
|
242
|
+
table.storeId,
|
|
243
|
+
table.couponCode,
|
|
244
|
+
),
|
|
245
|
+
}),
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
// =====================================================
|
|
249
|
+
// DISCOUNT USAGE TRACKING
|
|
250
|
+
// =====================================================
|
|
251
|
+
|
|
252
|
+
export const discountUsages = pgTable(
|
|
253
|
+
"discount_usages",
|
|
254
|
+
{
|
|
255
|
+
id: text("id")
|
|
256
|
+
.primaryKey()
|
|
257
|
+
.$defaultFn(() => createId()),
|
|
258
|
+
discountId: text("discount_id").notNull(),
|
|
259
|
+
storeId: text("store_id").notNull(),
|
|
260
|
+
|
|
261
|
+
// Usage Context
|
|
262
|
+
orderId: text("order_id"), // Reference to order if applicable
|
|
263
|
+
customerId: text("customer_id"),
|
|
264
|
+
sessionId: text("session_id"), // For anonymous users
|
|
265
|
+
|
|
266
|
+
// Applied Discount Details
|
|
267
|
+
originalAmount: decimal("original_amount", { precision: 12, scale: 2 }).notNull(),
|
|
268
|
+
discountAmount: decimal("discount_amount", { precision: 12, scale: 2 }).notNull(),
|
|
269
|
+
finalAmount: decimal("final_amount", { precision: 12, scale: 2 }).notNull(),
|
|
270
|
+
|
|
271
|
+
// Context Information
|
|
272
|
+
appliedProducts: jsonb("applied_products").$type<Array<{
|
|
273
|
+
productId: string;
|
|
274
|
+
variantId?: string;
|
|
275
|
+
quantity: number;
|
|
276
|
+
originalPrice: number;
|
|
277
|
+
discountedPrice: number;
|
|
278
|
+
}>>(),
|
|
279
|
+
|
|
280
|
+
salesChannel: varchar("sales_channel", { length: 20 }), // POS, MARKETPLACE, STOREFRONT
|
|
281
|
+
locationId: text("location_id"), // Store location or warehouse
|
|
282
|
+
|
|
283
|
+
// Metadata
|
|
284
|
+
ipAddress: varchar("ip_address", { length: 45 }),
|
|
285
|
+
userAgent: text("user_agent"),
|
|
286
|
+
deviceType: varchar("device_type", { length: 20 }),
|
|
287
|
+
|
|
288
|
+
// Timestamps
|
|
289
|
+
appliedAt: timestamp("applied_at", { withTimezone: true })
|
|
290
|
+
.defaultNow()
|
|
291
|
+
.notNull(),
|
|
292
|
+
},
|
|
293
|
+
(table) => ({
|
|
294
|
+
discountIdIndex: index("idx_discount_usages_discount_id").on(table.discountId),
|
|
295
|
+
storeIdIndex: index("idx_discount_usages_store_id").on(table.storeId),
|
|
296
|
+
customerIdIndex: index("idx_discount_usages_customer_id").on(table.customerId),
|
|
297
|
+
orderIdIndex: index("idx_discount_usages_order_id").on(table.orderId),
|
|
298
|
+
appliedAtIndex: index("idx_discount_usages_applied_at").on(table.appliedAt),
|
|
299
|
+
salesChannelIndex: index("idx_discount_usages_sales_channel").on(table.salesChannel),
|
|
300
|
+
}),
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
// =====================================================
|
|
304
|
+
// DISCOUNT RULES ENGINE
|
|
305
|
+
// =====================================================
|
|
306
|
+
|
|
307
|
+
export const discountRules = pgTable(
|
|
308
|
+
"discount_rules",
|
|
309
|
+
{
|
|
310
|
+
id: text("id")
|
|
311
|
+
.primaryKey()
|
|
312
|
+
.$defaultFn(() => createId()),
|
|
313
|
+
discountId: text("discount_id").notNull(),
|
|
314
|
+
storeId: text("store_id").notNull(),
|
|
315
|
+
|
|
316
|
+
// Rule Configuration
|
|
317
|
+
name: varchar("name", { length: 255 }).notNull(),
|
|
318
|
+
description: text("description"),
|
|
319
|
+
ruleType: varchar("rule_type", { length: 50 }).notNull(), // CONDITION, ACTION, VALIDATION
|
|
320
|
+
|
|
321
|
+
// Rule Logic
|
|
322
|
+
conditions: jsonb("conditions").$type<Array<{
|
|
323
|
+
field: string; // e.g., "cart.total", "customer.orderCount", "product.category"
|
|
324
|
+
operator: "EQUALS" | "NOT_EQUALS" | "GREATER_THAN" | "LESS_THAN" | "IN" | "NOT_IN" | "CONTAINS" | "NOT_CONTAINS";
|
|
325
|
+
value: any;
|
|
326
|
+
logicalOperator?: "AND" | "OR";
|
|
327
|
+
}>>().notNull(),
|
|
328
|
+
|
|
329
|
+
actions: jsonb("actions").$type<Array<{
|
|
330
|
+
type: string; // e.g., "APPLY_DISCOUNT", "ADD_FREE_PRODUCT", "UPGRADE_SHIPPING"
|
|
331
|
+
parameters: Record<string, any>;
|
|
332
|
+
}>>().default([]),
|
|
333
|
+
|
|
334
|
+
// Execution Configuration
|
|
335
|
+
priority: integer("priority").notNull().default(0),
|
|
336
|
+
isActive: boolean("is_active").notNull().default(true),
|
|
337
|
+
|
|
338
|
+
// Timestamps
|
|
339
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
340
|
+
.defaultNow()
|
|
341
|
+
.notNull(),
|
|
342
|
+
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
343
|
+
.defaultNow()
|
|
344
|
+
.notNull(),
|
|
345
|
+
},
|
|
346
|
+
(table) => ({
|
|
347
|
+
discountIdIndex: index("idx_discount_rules_discount_id").on(table.discountId),
|
|
348
|
+
storeIdIndex: index("idx_discount_rules_store_id").on(table.storeId),
|
|
349
|
+
priorityIndex: index("idx_discount_rules_priority").on(table.priority),
|
|
350
|
+
activeIndex: index("idx_discount_rules_active").on(table.isActive),
|
|
351
|
+
}),
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
// =====================================================
|
|
355
|
+
// CUSTOMER SEGMENTS
|
|
356
|
+
// =====================================================
|
|
357
|
+
|
|
358
|
+
export const customerSegments = pgTable(
|
|
359
|
+
"customer_segments",
|
|
360
|
+
{
|
|
361
|
+
id: text("id")
|
|
362
|
+
.primaryKey()
|
|
363
|
+
.$defaultFn(() => createId()),
|
|
364
|
+
storeId: text("store_id").notNull(),
|
|
365
|
+
|
|
366
|
+
// Segment Information
|
|
367
|
+
name: varchar("name", { length: 255 }).notNull(),
|
|
368
|
+
description: text("description"),
|
|
369
|
+
segmentType: customerSegmentTypeEnum("segment_type").notNull(),
|
|
370
|
+
|
|
371
|
+
// Segment Criteria
|
|
372
|
+
criteria: jsonb("criteria").$type<{
|
|
373
|
+
orderCount?: { min?: number; max?: number };
|
|
374
|
+
totalSpent?: { min?: number; max?: number };
|
|
375
|
+
lastOrderDate?: { before?: string; after?: string };
|
|
376
|
+
avgOrderValue?: { min?: number; max?: number };
|
|
377
|
+
productCategories?: string[];
|
|
378
|
+
geoLocation?: {
|
|
379
|
+
countries?: string[];
|
|
380
|
+
states?: string[];
|
|
381
|
+
cities?: string[];
|
|
382
|
+
};
|
|
383
|
+
loyaltyTier?: string;
|
|
384
|
+
registrationDate?: { before?: string; after?: string };
|
|
385
|
+
customFields?: Record<string, any>;
|
|
386
|
+
}>(),
|
|
387
|
+
|
|
388
|
+
// Segment Statistics
|
|
389
|
+
memberCount: integer("member_count").notNull().default(0),
|
|
390
|
+
lastCalculatedAt: timestamp("last_calculated_at", { withTimezone: true }),
|
|
391
|
+
|
|
392
|
+
// Configuration
|
|
393
|
+
isActive: boolean("is_active").notNull().default(true),
|
|
394
|
+
autoUpdate: boolean("auto_update").notNull().default(true), // Auto-recalculate membership
|
|
395
|
+
|
|
396
|
+
// Timestamps
|
|
397
|
+
createdAt: timestamp("created_at", { withTimezone: true })
|
|
398
|
+
.defaultNow()
|
|
399
|
+
.notNull(),
|
|
400
|
+
updatedAt: timestamp("updated_at", { withTimezone: true })
|
|
401
|
+
.defaultNow()
|
|
402
|
+
.notNull(),
|
|
403
|
+
},
|
|
404
|
+
(table) => ({
|
|
405
|
+
storeIdIndex: index("idx_customer_segments_store_id").on(table.storeId),
|
|
406
|
+
segmentTypeIndex: index("idx_customer_segments_type").on(table.segmentType),
|
|
407
|
+
activeIndex: index("idx_customer_segments_active").on(table.isActive),
|
|
408
|
+
memberCountIndex: index("idx_customer_segments_member_count").on(table.memberCount),
|
|
409
|
+
}),
|
|
410
|
+
);
|
|
411
|
+
|
|
412
|
+
// =====================================================
|
|
413
|
+
// DISCOUNT ANALYTICS
|
|
414
|
+
// =====================================================
|
|
415
|
+
|
|
416
|
+
export const discountAnalytics = pgTable(
|
|
417
|
+
"discount_analytics",
|
|
418
|
+
{
|
|
419
|
+
id: text("id")
|
|
420
|
+
.primaryKey()
|
|
421
|
+
.$defaultFn(() => createId()),
|
|
422
|
+
discountId: text("discount_id").notNull(),
|
|
423
|
+
storeId: text("store_id").notNull(),
|
|
424
|
+
|
|
425
|
+
// Time Period
|
|
426
|
+
periodType: varchar("period_type", { length: 20 }).notNull(), // DAILY, WEEKLY, MONTHLY, YEARLY
|
|
427
|
+
periodStart: timestamp("period_start", { withTimezone: true }).notNull(),
|
|
428
|
+
periodEnd: timestamp("period_end", { withTimezone: true }).notNull(),
|
|
429
|
+
|
|
430
|
+
// Usage Metrics
|
|
431
|
+
totalUsages: integer("total_usages").notNull().default(0),
|
|
432
|
+
uniqueCustomers: integer("unique_customers").notNull().default(0),
|
|
433
|
+
totalSavings: decimal("total_savings", { precision: 12, scale: 2 }).notNull().default("0"),
|
|
434
|
+
totalRevenue: decimal("total_revenue", { precision: 12, scale: 2 }).notNull().default("0"),
|
|
435
|
+
avgOrderValue: decimal("avg_order_value", { precision: 12, scale: 2 }),
|
|
436
|
+
|
|
437
|
+
// Performance Metrics
|
|
438
|
+
conversionRate: decimal("conversion_rate", { precision: 5, scale: 4 }),
|
|
439
|
+
roi: decimal("roi", { precision: 8, scale: 4 }), // Return on Investment
|
|
440
|
+
customerAcquisitionCost: decimal("customer_acquisition_cost", { precision: 12, scale: 2 }),
|
|
441
|
+
|
|
442
|
+
// Channel Breakdown
|
|
443
|
+
channelBreakdown: jsonb("channel_breakdown").$type<Record<string, {
|
|
444
|
+
usages: number;
|
|
445
|
+
revenue: number;
|
|
446
|
+
savings: number;
|
|
447
|
+
}>>(),
|
|
448
|
+
|
|
449
|
+
// Timestamps
|
|
450
|
+
calculatedAt: timestamp("calculated_at", { withTimezone: true })
|
|
451
|
+
.defaultNow()
|
|
452
|
+
.notNull(),
|
|
453
|
+
},
|
|
454
|
+
(table) => ({
|
|
455
|
+
discountIdIndex: index("idx_discount_analytics_discount_id").on(table.discountId),
|
|
456
|
+
storeIdIndex: index("idx_discount_analytics_store_id").on(table.storeId),
|
|
457
|
+
periodIndex: index("idx_discount_analytics_period").on(
|
|
458
|
+
table.periodType,
|
|
459
|
+
table.periodStart,
|
|
460
|
+
table.periodEnd,
|
|
461
|
+
),
|
|
462
|
+
calculatedAtIndex: index("idx_discount_analytics_calculated_at").on(table.calculatedAt),
|
|
463
|
+
}),
|
|
464
|
+
);
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { relations } from "drizzle-orm";
|
|
2
|
+
import {
|
|
3
|
+
collections,
|
|
4
|
+
productAnalytics,
|
|
5
|
+
productCategories,
|
|
6
|
+
productCollections,
|
|
7
|
+
productImages,
|
|
8
|
+
productReviews,
|
|
9
|
+
products,
|
|
10
|
+
productVariants,
|
|
11
|
+
wholesalePricingTiers,
|
|
12
|
+
} from "./product-schema";
|
|
13
|
+
|
|
14
|
+
// =====================================================
|
|
15
|
+
// PRODUCT RELATIONS
|
|
16
|
+
// =====================================================
|
|
17
|
+
|
|
18
|
+
export const productsRelations = relations(products, ({ many, one }) => ({
|
|
19
|
+
// One product has many variants
|
|
20
|
+
variants: many(productVariants),
|
|
21
|
+
|
|
22
|
+
// One product has many images
|
|
23
|
+
images: many(productImages),
|
|
24
|
+
|
|
25
|
+
// One product has many reviews
|
|
26
|
+
reviews: many(productReviews),
|
|
27
|
+
|
|
28
|
+
// One product has many analytics records
|
|
29
|
+
analytics: many(productAnalytics),
|
|
30
|
+
|
|
31
|
+
// One product belongs to many collections (many-to-many)
|
|
32
|
+
productCollections: many(productCollections),
|
|
33
|
+
|
|
34
|
+
// One product has many wholesale pricing tiers
|
|
35
|
+
wholesalePricingTiers: many(wholesalePricingTiers),
|
|
36
|
+
|
|
37
|
+
// One product belongs to one category (optional)
|
|
38
|
+
category: one(productCategories, {
|
|
39
|
+
fields: [products.category],
|
|
40
|
+
references: [productCategories.handle],
|
|
41
|
+
}),
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
// =====================================================
|
|
45
|
+
// PRODUCT VARIANTS RELATIONS
|
|
46
|
+
// =====================================================
|
|
47
|
+
|
|
48
|
+
export const productVariantsRelations = relations(
|
|
49
|
+
productVariants,
|
|
50
|
+
({ one, many }) => ({
|
|
51
|
+
// One variant belongs to one product
|
|
52
|
+
product: one(products, {
|
|
53
|
+
fields: [productVariants.productId],
|
|
54
|
+
references: [products.id],
|
|
55
|
+
}),
|
|
56
|
+
|
|
57
|
+
// One variant can have many images
|
|
58
|
+
images: many(productImages),
|
|
59
|
+
|
|
60
|
+
// One variant can have many reviews
|
|
61
|
+
reviews: many(productReviews),
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// =====================================================
|
|
66
|
+
// COLLECTIONS RELATIONS
|
|
67
|
+
// =====================================================
|
|
68
|
+
|
|
69
|
+
export const collectionsRelations = relations(collections, ({ many }) => ({
|
|
70
|
+
// One collection has many products (many-to-many)
|
|
71
|
+
productCollections: many(productCollections),
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
// =====================================================
|
|
75
|
+
// PRODUCT COLLECTIONS JUNCTION RELATIONS
|
|
76
|
+
// =====================================================
|
|
77
|
+
|
|
78
|
+
export const productCollectionsRelations = relations(
|
|
79
|
+
productCollections,
|
|
80
|
+
({ one }) => ({
|
|
81
|
+
// Junction table relations
|
|
82
|
+
product: one(products, {
|
|
83
|
+
fields: [productCollections.productId],
|
|
84
|
+
references: [products.id],
|
|
85
|
+
}),
|
|
86
|
+
|
|
87
|
+
collection: one(collections, {
|
|
88
|
+
fields: [productCollections.collectionId],
|
|
89
|
+
references: [collections.id],
|
|
90
|
+
}),
|
|
91
|
+
}),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// =====================================================
|
|
95
|
+
// PRODUCT IMAGES RELATIONS
|
|
96
|
+
// =====================================================
|
|
97
|
+
|
|
98
|
+
export const productImagesRelations = relations(productImages, ({ one }) => ({
|
|
99
|
+
// One image belongs to one product
|
|
100
|
+
product: one(products, {
|
|
101
|
+
fields: [productImages.productId],
|
|
102
|
+
references: [products.id],
|
|
103
|
+
}),
|
|
104
|
+
|
|
105
|
+
// One image can belong to one variant (optional)
|
|
106
|
+
variant: one(productVariants, {
|
|
107
|
+
fields: [productImages.variantId],
|
|
108
|
+
references: [productVariants.id],
|
|
109
|
+
}),
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
// =====================================================
|
|
113
|
+
// PRODUCT CATEGORIES RELATIONS
|
|
114
|
+
// =====================================================
|
|
115
|
+
|
|
116
|
+
export const productCategoriesRelations = relations(
|
|
117
|
+
productCategories,
|
|
118
|
+
({ one, many }) => ({
|
|
119
|
+
// Self-referencing relationship for hierarchy
|
|
120
|
+
parent: one(productCategories, {
|
|
121
|
+
fields: [productCategories.parentId],
|
|
122
|
+
references: [productCategories.id],
|
|
123
|
+
relationName: "CategoryParent",
|
|
124
|
+
}),
|
|
125
|
+
|
|
126
|
+
children: many(productCategories, {
|
|
127
|
+
relationName: "CategoryParent",
|
|
128
|
+
}),
|
|
129
|
+
|
|
130
|
+
// One category can have many products
|
|
131
|
+
products: many(products),
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// =====================================================
|
|
136
|
+
// PRODUCT REVIEWS RELATIONS
|
|
137
|
+
// =====================================================
|
|
138
|
+
|
|
139
|
+
export const productReviewsRelations = relations(productReviews, ({ one }) => ({
|
|
140
|
+
// One review belongs to one product
|
|
141
|
+
product: one(products, {
|
|
142
|
+
fields: [productReviews.productId],
|
|
143
|
+
references: [products.id],
|
|
144
|
+
}),
|
|
145
|
+
|
|
146
|
+
// One review can belong to one variant (optional)
|
|
147
|
+
variant: one(productVariants, {
|
|
148
|
+
fields: [productReviews.variantId],
|
|
149
|
+
references: [productVariants.id],
|
|
150
|
+
}),
|
|
151
|
+
}));
|
|
152
|
+
|
|
153
|
+
// =====================================================
|
|
154
|
+
// PRODUCT ANALYTICS RELATIONS
|
|
155
|
+
// =====================================================
|
|
156
|
+
|
|
157
|
+
export const productAnalyticsRelations = relations(
|
|
158
|
+
productAnalytics,
|
|
159
|
+
({ one }) => ({
|
|
160
|
+
// One analytics record belongs to one product
|
|
161
|
+
product: one(products, {
|
|
162
|
+
fields: [productAnalytics.productId],
|
|
163
|
+
references: [products.id],
|
|
164
|
+
}),
|
|
165
|
+
}),
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// =====================================================
|
|
169
|
+
// WHOLESALE PRICING TIERS RELATIONS
|
|
170
|
+
// =====================================================
|
|
171
|
+
|
|
172
|
+
export const wholesalePricingTiersRelations = relations(
|
|
173
|
+
wholesalePricingTiers,
|
|
174
|
+
({ one }) => ({
|
|
175
|
+
// One tier belongs to one product
|
|
176
|
+
product: one(products, {
|
|
177
|
+
fields: [wholesalePricingTiers.productId],
|
|
178
|
+
references: [products.id],
|
|
179
|
+
}),
|
|
180
|
+
|
|
181
|
+
// One tier can belong to one variant (optional)
|
|
182
|
+
variant: one(productVariants, {
|
|
183
|
+
fields: [wholesalePricingTiers.variantId],
|
|
184
|
+
references: [productVariants.id],
|
|
185
|
+
}),
|
|
186
|
+
}),
|
|
187
|
+
);
|