@instockng/api-client 1.0.8 → 1.0.10

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 (193) hide show
  1. package/dist/fetchers/brands.d.ts +1 -1
  2. package/dist/fetchers/carts.d.ts +85 -47
  3. package/dist/fetchers/delivery-zones.d.ts +2 -0
  4. package/dist/fetchers/orders.d.ts +26 -14
  5. package/dist/fetchers/products.d.ts +14 -6
  6. package/dist/hooks/admin/abandoned-carts.d.ts +18 -10
  7. package/dist/hooks/admin/brands.d.ts +4 -4
  8. package/dist/hooks/admin/customers.d.ts +13 -7
  9. package/dist/hooks/admin/delivery-zones.d.ts +23 -15
  10. package/dist/hooks/admin/discount-codes.d.ts +10 -10
  11. package/dist/hooks/admin/inventory.d.ts +4 -0
  12. package/dist/hooks/admin/orders.d.ts +78 -42
  13. package/dist/hooks/admin/products.d.ts +14 -6
  14. package/dist/hooks/admin/stats.d.ts +13 -7
  15. package/dist/hooks/admin/variants.d.ts +5 -5
  16. package/dist/hooks/admin/warehouses.d.ts +11 -7
  17. package/dist/hooks/public/brands.d.ts +1 -1
  18. package/dist/hooks/public/carts.d.ts +85 -47
  19. package/dist/hooks/public/delivery-zones.d.ts +2 -0
  20. package/dist/hooks/public/orders.d.ts +26 -14
  21. package/dist/hooks/public/products.d.ts +14 -6
  22. package/dist/rpc-client.d.ts +335 -187
  23. package/package.json +1 -1
  24. package/dist/apps/backend/src/generated/zod/index.d.ts +0 -1114
  25. package/dist/apps/backend/src/generated/zod/index.js +0 -670
  26. package/dist/apps/backend/src/http-app.d.ts +0 -40
  27. package/dist/apps/backend/src/http-app.js +0 -134
  28. package/dist/apps/backend/src/lib/brand-response.d.ts +0 -14
  29. package/dist/apps/backend/src/lib/brand-response.js +0 -8
  30. package/dist/apps/backend/src/lib/cart-helpers.d.ts +0 -282
  31. package/dist/apps/backend/src/lib/cart-helpers.js +0 -121
  32. package/dist/apps/backend/src/lib/cart-recovery.d.ts +0 -30
  33. package/dist/apps/backend/src/lib/cart-recovery.js +0 -147
  34. package/dist/apps/backend/src/lib/cart-response.d.ts +0 -121
  35. package/dist/apps/backend/src/lib/cart-response.js +0 -150
  36. package/dist/apps/backend/src/lib/clerk.d.ts +0 -18
  37. package/dist/apps/backend/src/lib/clerk.js +0 -190
  38. package/dist/apps/backend/src/lib/delivery-zone-response.d.ts +0 -64
  39. package/dist/apps/backend/src/lib/delivery-zone-response.js +0 -24
  40. package/dist/apps/backend/src/lib/discount-code-response.d.ts +0 -42
  41. package/dist/apps/backend/src/lib/discount-code-response.js +0 -19
  42. package/dist/apps/backend/src/lib/discount.d.ts +0 -20
  43. package/dist/apps/backend/src/lib/discount.js +0 -35
  44. package/dist/apps/backend/src/lib/inventory.d.ts +0 -26
  45. package/dist/apps/backend/src/lib/inventory.js +0 -160
  46. package/dist/apps/backend/src/lib/meta-capi.d.ts +0 -53
  47. package/dist/apps/backend/src/lib/meta-capi.js +0 -151
  48. package/dist/apps/backend/src/lib/openapi.d.ts +0 -36
  49. package/dist/apps/backend/src/lib/openapi.js +0 -69
  50. package/dist/apps/backend/src/lib/order-recovery.d.ts +0 -459
  51. package/dist/apps/backend/src/lib/order-recovery.js +0 -378
  52. package/dist/apps/backend/src/lib/order-response.d.ts +0 -138
  53. package/dist/apps/backend/src/lib/order-response.js +0 -61
  54. package/dist/apps/backend/src/lib/pricing.d.ts +0 -39
  55. package/dist/apps/backend/src/lib/pricing.js +0 -62
  56. package/dist/apps/backend/src/lib/prisma.d.ts +0 -9
  57. package/dist/apps/backend/src/lib/prisma.js +0 -30
  58. package/dist/apps/backend/src/lib/product-response.d.ts +0 -82
  59. package/dist/apps/backend/src/lib/product-response.js +0 -29
  60. package/dist/apps/backend/src/lib/sentry.d.ts +0 -48
  61. package/dist/apps/backend/src/lib/sentry.js +0 -180
  62. package/dist/apps/backend/src/lib/utils.d.ts +0 -32
  63. package/dist/apps/backend/src/lib/utils.js +0 -63
  64. package/dist/apps/backend/src/middleware/clerk-auth.d.ts +0 -8
  65. package/dist/apps/backend/src/middleware/clerk-auth.js +0 -89
  66. package/dist/apps/backend/src/middleware/cors.d.ts +0 -8
  67. package/dist/apps/backend/src/middleware/cors.js +0 -11
  68. package/dist/apps/backend/src/notifications/producers/meta-capi-producer.d.ts +0 -62
  69. package/dist/apps/backend/src/notifications/producers/meta-capi-producer.js +0 -180
  70. package/dist/apps/backend/src/notifications/producers/order-notification.d.ts +0 -9
  71. package/dist/apps/backend/src/notifications/producers/order-notification.js +0 -18
  72. package/dist/apps/backend/src/notifications/producers/prospect-recovery-notification.d.ts +0 -10
  73. package/dist/apps/backend/src/notifications/producers/prospect-recovery-notification.js +0 -11
  74. package/dist/apps/backend/src/routes/admin/abandoned-carts.d.ts +0 -605
  75. package/dist/apps/backend/src/routes/admin/abandoned-carts.js +0 -194
  76. package/dist/apps/backend/src/routes/admin/brands.d.ts +0 -175
  77. package/dist/apps/backend/src/routes/admin/brands.js +0 -118
  78. package/dist/apps/backend/src/routes/admin/customers.d.ts +0 -308
  79. package/dist/apps/backend/src/routes/admin/customers.js +0 -39
  80. package/dist/apps/backend/src/routes/admin/delivery-zones.d.ts +0 -446
  81. package/dist/apps/backend/src/routes/admin/delivery-zones.js +0 -300
  82. package/dist/apps/backend/src/routes/admin/discount-codes.d.ts +0 -478
  83. package/dist/apps/backend/src/routes/admin/discount-codes.js +0 -418
  84. package/dist/apps/backend/src/routes/admin/inventory.d.ts +0 -273
  85. package/dist/apps/backend/src/routes/admin/inventory.js +0 -199
  86. package/dist/apps/backend/src/routes/admin/orders.d.ts +0 -1780
  87. package/dist/apps/backend/src/routes/admin/orders.js +0 -552
  88. package/dist/apps/backend/src/routes/admin/products.d.ts +0 -860
  89. package/dist/apps/backend/src/routes/admin/products.js +0 -126
  90. package/dist/apps/backend/src/routes/admin/stats.d.ts +0 -290
  91. package/dist/apps/backend/src/routes/admin/stats.js +0 -55
  92. package/dist/apps/backend/src/routes/admin/variants.d.ts +0 -239
  93. package/dist/apps/backend/src/routes/admin/variants.js +0 -197
  94. package/dist/apps/backend/src/routes/admin/warehouses.d.ts +0 -373
  95. package/dist/apps/backend/src/routes/admin/warehouses.js +0 -123
  96. package/dist/apps/backend/src/routes/public/brands.d.ts +0 -40
  97. package/dist/apps/backend/src/routes/public/brands.js +0 -38
  98. package/dist/apps/backend/src/routes/public/carts.d.ts +0 -2657
  99. package/dist/apps/backend/src/routes/public/carts.js +0 -778
  100. package/dist/apps/backend/src/routes/public/delivery-zones.d.ts +0 -37
  101. package/dist/apps/backend/src/routes/public/delivery-zones.js +0 -64
  102. package/dist/apps/backend/src/routes/public/orders.d.ts +0 -609
  103. package/dist/apps/backend/src/routes/public/orders.js +0 -184
  104. package/dist/apps/backend/src/routes/public/products.d.ts +0 -449
  105. package/dist/apps/backend/src/routes/public/products.js +0 -133
  106. package/dist/apps/backend/src/types/index.d.ts +0 -43
  107. package/dist/apps/backend/src/types/index.js +0 -2
  108. package/dist/apps/backend/src/validators/brand.d.ts +0 -17
  109. package/dist/apps/backend/src/validators/brand.js +0 -15
  110. package/dist/apps/backend/src/validators/delivery-zone.d.ts +0 -35
  111. package/dist/apps/backend/src/validators/delivery-zone.js +0 -55
  112. package/dist/apps/backend/src/validators/discount-code.d.ts +0 -74
  113. package/dist/apps/backend/src/validators/discount-code.js +0 -50
  114. package/dist/apps/backend/src/validators/inventory.d.ts +0 -20
  115. package/dist/apps/backend/src/validators/inventory.js +0 -15
  116. package/dist/apps/backend/src/validators/order.d.ts +0 -58
  117. package/dist/apps/backend/src/validators/order.js +0 -62
  118. package/dist/apps/backend/src/validators/product.d.ts +0 -18
  119. package/dist/apps/backend/src/validators/product.js +0 -19
  120. package/dist/apps/backend/src/validators/variant.d.ts +0 -19
  121. package/dist/apps/backend/src/validators/variant.js +0 -19
  122. package/dist/apps/backend/src/validators/warehouse.d.ts +0 -15
  123. package/dist/apps/backend/src/validators/warehouse.js +0 -15
  124. package/dist/packages/api-client/src/backend-types.d.ts +0 -10
  125. package/dist/packages/api-client/src/backend-types.js +0 -10
  126. package/dist/packages/api-client/src/client.d.ts +0 -20
  127. package/dist/packages/api-client/src/client.js +0 -40
  128. package/dist/packages/api-client/src/enum-types.d.ts +0 -8
  129. package/dist/packages/api-client/src/enum-types.js +0 -5
  130. package/dist/packages/api-client/src/fetchers/brands.d.ts +0 -25
  131. package/dist/packages/api-client/src/fetchers/brands.js +0 -26
  132. package/dist/packages/api-client/src/fetchers/carts.d.ts +0 -2337
  133. package/dist/packages/api-client/src/fetchers/carts.js +0 -174
  134. package/dist/packages/api-client/src/fetchers/delivery-zones.d.ts +0 -30
  135. package/dist/packages/api-client/src/fetchers/delivery-zones.js +0 -26
  136. package/dist/packages/api-client/src/fetchers/index.d.ts +0 -22
  137. package/dist/packages/api-client/src/fetchers/index.js +0 -22
  138. package/dist/packages/api-client/src/fetchers/orders.d.ts +0 -544
  139. package/dist/packages/api-client/src/fetchers/orders.js +0 -44
  140. package/dist/packages/api-client/src/fetchers/products.d.ts +0 -386
  141. package/dist/packages/api-client/src/fetchers/products.js +0 -42
  142. package/dist/packages/api-client/src/hooks/admin/abandoned-carts.d.ts +0 -535
  143. package/dist/packages/api-client/src/hooks/admin/abandoned-carts.js +0 -83
  144. package/dist/packages/api-client/src/hooks/admin/brands.d.ts +0 -79
  145. package/dist/packages/api-client/src/hooks/admin/brands.js +0 -108
  146. package/dist/packages/api-client/src/hooks/admin/customers.d.ts +0 -280
  147. package/dist/packages/api-client/src/hooks/admin/customers.js +0 -26
  148. package/dist/packages/api-client/src/hooks/admin/delivery-zones.d.ts +0 -278
  149. package/dist/packages/api-client/src/hooks/admin/delivery-zones.js +0 -176
  150. package/dist/packages/api-client/src/hooks/admin/discount-codes.d.ts +0 -299
  151. package/dist/packages/api-client/src/hooks/admin/discount-codes.js +0 -165
  152. package/dist/packages/api-client/src/hooks/admin/index.d.ts +0 -16
  153. package/dist/packages/api-client/src/hooks/admin/index.js +0 -16
  154. package/dist/packages/api-client/src/hooks/admin/inventory.d.ts +0 -224
  155. package/dist/packages/api-client/src/hooks/admin/inventory.js +0 -107
  156. package/dist/packages/api-client/src/hooks/admin/orders.d.ts +0 -1674
  157. package/dist/packages/api-client/src/hooks/admin/orders.js +0 -178
  158. package/dist/packages/api-client/src/hooks/admin/products.d.ts +0 -374
  159. package/dist/packages/api-client/src/hooks/admin/products.js +0 -89
  160. package/dist/packages/api-client/src/hooks/admin/stats.d.ts +0 -279
  161. package/dist/packages/api-client/src/hooks/admin/stats.js +0 -25
  162. package/dist/packages/api-client/src/hooks/admin/variants.d.ts +0 -115
  163. package/dist/packages/api-client/src/hooks/admin/variants.js +0 -127
  164. package/dist/packages/api-client/src/hooks/admin/warehouses.d.ts +0 -277
  165. package/dist/packages/api-client/src/hooks/admin/warehouses.js +0 -108
  166. package/dist/packages/api-client/src/hooks/public/brands.d.ts +0 -33
  167. package/dist/packages/api-client/src/hooks/public/brands.js +0 -30
  168. package/dist/packages/api-client/src/hooks/public/carts.d.ts +0 -2407
  169. package/dist/packages/api-client/src/hooks/public/carts.js +0 -213
  170. package/dist/packages/api-client/src/hooks/public/delivery-zones.d.ts +0 -36
  171. package/dist/packages/api-client/src/hooks/public/delivery-zones.js +0 -28
  172. package/dist/packages/api-client/src/hooks/public/index.d.ts +0 -10
  173. package/dist/packages/api-client/src/hooks/public/index.js +0 -10
  174. package/dist/packages/api-client/src/hooks/public/orders.d.ts +0 -563
  175. package/dist/packages/api-client/src/hooks/public/orders.js +0 -50
  176. package/dist/packages/api-client/src/hooks/public/products.d.ts +0 -398
  177. package/dist/packages/api-client/src/hooks/public/products.js +0 -47
  178. package/dist/packages/api-client/src/hooks/use-query-unwrapped.d.ts +0 -20
  179. package/dist/packages/api-client/src/hooks/use-query-unwrapped.js +0 -22
  180. package/dist/packages/api-client/src/hooks/useApiConfig.d.ts +0 -12
  181. package/dist/packages/api-client/src/hooks/useApiConfig.js +0 -14
  182. package/dist/packages/api-client/src/index.d.ts +0 -20
  183. package/dist/packages/api-client/src/index.js +0 -25
  184. package/dist/packages/api-client/src/provider.d.ts +0 -36
  185. package/dist/packages/api-client/src/provider.js +0 -54
  186. package/dist/packages/api-client/src/rpc-client.d.ts +0 -9639
  187. package/dist/packages/api-client/src/rpc-client.js +0 -78
  188. package/dist/packages/api-client/src/rpc-types.d.ts +0 -76
  189. package/dist/packages/api-client/src/rpc-types.js +0 -7
  190. package/dist/packages/api-client/src/types.d.ts +0 -34
  191. package/dist/packages/api-client/src/types.js +0 -16
  192. package/dist/packages/api-client/src/utils/query-keys.d.ts +0 -106
  193. package/dist/packages/api-client/src/utils/query-keys.js +0 -108
@@ -1,378 +0,0 @@
1
- import { OrderStatus, ProspectReason, NoteType } from '@prisma/client';
2
- import { subHours, addHours } from 'date-fns';
3
- import { generateDiscountCode } from './discount';
4
- import { ORDER_INCLUDE_FULL } from './cart-helpers';
5
- // Recovery schedule configuration
6
- // Note: First email sent immediately after marking as prospect (since manual contact already attempted)
7
- export const PROSPECT_RECOVERY_SCHEDULE = [
8
- {
9
- attempt: 1,
10
- delayHours: 0, // Immediate - manual contact already attempted
11
- discountPercentage: 0,
12
- template: 'ProspectRecoveryReminder',
13
- subject: 'We tried reaching you about your order'
14
- },
15
- {
16
- attempt: 2,
17
- delayHours: 8,
18
- discountPercentage: 5,
19
- template: 'ProspectRecovery',
20
- subject: 'Your order is waiting - 5% discount inside'
21
- },
22
- {
23
- attempt: 3,
24
- delayHours: 24, // Next day
25
- discountPercentage: 10,
26
- template: 'ProspectRecovery',
27
- subject: 'Still interested? Save 10% today'
28
- },
29
- {
30
- attempt: 4,
31
- delayHours: 48, // Day 2
32
- discountPercentage: 15,
33
- template: 'ProspectRecoveryFinal',
34
- subject: 'Final chance - 15% off expires soon'
35
- }
36
- ];
37
- // Auto-cancel after 72 hours
38
- export const AUTO_CANCEL_HOURS = 72;
39
- /**
40
- * Find prospect orders ready for recovery attempt
41
- */
42
- export async function findProspectOrdersForRecovery(prisma, attemptNumber) {
43
- const schedule = PROSPECT_RECOVERY_SCHEDULE[attemptNumber - 1];
44
- if (!schedule) {
45
- throw new Error(`Invalid recovery attempt number: ${attemptNumber}`);
46
- }
47
- const windowStart = subHours(new Date(), schedule.delayHours);
48
- const windowEnd = subHours(windowStart, 1); // 1 hour window
49
- return prisma.order.findMany({
50
- where: {
51
- status: OrderStatus.prospect,
52
- recoveryAttempts: attemptNumber - 1,
53
- prospectSince: {
54
- gte: windowEnd,
55
- lt: windowStart
56
- },
57
- deletedAt: null,
58
- },
59
- include: {
60
- items: {
61
- include: {
62
- variant: {
63
- include: {
64
- product: true
65
- }
66
- }
67
- }
68
- },
69
- brand: true,
70
- recoveryDiscountCode: true,
71
- deliveryZone: true
72
- }
73
- });
74
- }
75
- /**
76
- * Prepare order for recovery attempt (atomic operation)
77
- */
78
- export async function prepareOrderForRecoveryAttempt(prisma, orderId, attemptNumber, brandId) {
79
- const schedule = PROSPECT_RECOVERY_SCHEDULE[attemptNumber - 1];
80
- if (!schedule) {
81
- throw new Error(`Invalid recovery attempt number: ${attemptNumber}`);
82
- }
83
- return prisma.$transaction(async (tx) => {
84
- // Update recovery attempts atomically
85
- const updateResult = await tx.order.updateMany({
86
- where: {
87
- id: orderId,
88
- status: OrderStatus.prospect,
89
- recoveryAttempts: attemptNumber - 1
90
- },
91
- data: {
92
- recoveryAttempts: attemptNumber,
93
- lastRecoveryAttemptAt: new Date()
94
- }
95
- });
96
- // If no rows updated, order was already claimed by another process
97
- if (updateResult.count === 0) {
98
- return null;
99
- }
100
- // Handle discount code creation/update
101
- if (schedule.discountPercentage > 0) {
102
- const order = await tx.order.findUnique({
103
- where: { id: orderId },
104
- include: { recoveryDiscountCode: true }
105
- });
106
- if (!order)
107
- return null;
108
- let discountCodeId = order.recoveryDiscountCodeId;
109
- if (!discountCodeId) {
110
- // Create new recovery discount code
111
- const code = generateDiscountCode();
112
- const discount = await tx.discountCode.create({
113
- data: {
114
- code,
115
- brandId,
116
- type: 'percentage',
117
- value: schedule.discountPercentage,
118
- usageLimit: 1,
119
- category: 'recovery',
120
- description: `Order recovery discount - ${schedule.discountPercentage}% off`,
121
- validFrom: new Date(),
122
- validUntil: addHours(new Date(), 72), // Valid for 72 hours
123
- isActive: true
124
- }
125
- });
126
- discountCodeId = discount.id;
127
- // Link discount to order
128
- await tx.order.update({
129
- where: { id: orderId },
130
- data: { recoveryDiscountCodeId: discountCodeId }
131
- });
132
- }
133
- else if (order.recoveryDiscountCode) {
134
- // Update existing discount if percentage increased
135
- const currentPercentage = order.recoveryDiscountCode.value.toNumber();
136
- if (schedule.discountPercentage > currentPercentage) {
137
- await tx.discountCode.update({
138
- where: { id: discountCodeId },
139
- data: {
140
- value: schedule.discountPercentage.toString(),
141
- description: `Order recovery discount - ${schedule.discountPercentage}% off`,
142
- validUntil: addHours(new Date(), 72) // Extend validity
143
- }
144
- });
145
- }
146
- }
147
- }
148
- // Return the prepared order
149
- return tx.order.findUnique({
150
- where: { id: orderId },
151
- include: {
152
- items: {
153
- include: {
154
- variant: {
155
- include: {
156
- product: true
157
- }
158
- }
159
- }
160
- },
161
- brand: true,
162
- recoveryDiscountCode: true,
163
- deliveryZone: true,
164
- notes: {
165
- orderBy: { createdAt: 'desc' },
166
- take: 5
167
- }
168
- }
169
- });
170
- });
171
- }
172
- /**
173
- * Convert prospect order back to pending when customer confirms
174
- */
175
- export async function confirmProspectOrder(prisma, orderId, userActionToken) {
176
- return prisma.$transaction(async (tx) => {
177
- // Validate order and token
178
- const order = await tx.order.findFirst({
179
- where: {
180
- id: orderId,
181
- userActionToken,
182
- status: OrderStatus.prospect
183
- },
184
- include: {
185
- recoveryDiscountCode: true
186
- }
187
- });
188
- if (!order) {
189
- throw new Error('Invalid order or token');
190
- }
191
- // Apply recovery discount if exists and not already applied
192
- let updateData = {
193
- status: OrderStatus.pending,
194
- wasRecovered: true
195
- };
196
- if (order.recoveryDiscountCodeId && !order.discountCodeId) {
197
- const discountAmount = order.totalPrice
198
- .mul(order.recoveryDiscountCode.value)
199
- .div(100);
200
- updateData.discountCodeId = order.recoveryDiscountCodeId;
201
- updateData.discountAmount = discountAmount;
202
- }
203
- // Update order status
204
- await tx.order.update({
205
- where: { id: orderId },
206
- data: updateData
207
- });
208
- // Add confirmation note
209
- await tx.orderNote.create({
210
- data: {
211
- orderId,
212
- type: NoteType.status_change,
213
- note: `Order recovered by customer via email link. Status changed from prospect to pending.`,
214
- metadata: {
215
- recoveryAttempt: order.recoveryAttempts,
216
- discountApplied: order.recoveryDiscountCode?.value.toNumber()
217
- }
218
- }
219
- });
220
- // Return the full order with all relations
221
- return tx.order.findUnique({
222
- where: { id: orderId },
223
- include: ORDER_INCLUDE_FULL
224
- });
225
- });
226
- }
227
- /**
228
- * Auto-cancel prospect orders that have exceeded the time limit
229
- */
230
- export async function autoCancelProspectOrders(prisma) {
231
- const cutoffTime = subHours(new Date(), AUTO_CANCEL_HOURS);
232
- const ordersToCancel = await prisma.order.findMany({
233
- where: {
234
- status: OrderStatus.prospect,
235
- prospectSince: {
236
- lte: cutoffTime
237
- }
238
- },
239
- select: {
240
- id: true,
241
- orderNumber: true
242
- }
243
- });
244
- const results = await Promise.all(ordersToCancel.map(async (order) => {
245
- try {
246
- await prisma.$transaction(async (tx) => {
247
- // Update order status
248
- await tx.order.update({
249
- where: { id: order.id },
250
- data: {
251
- status: OrderStatus.cancelled,
252
- cancellationReason: 'No response to confirmation attempts after 72 hours'
253
- }
254
- });
255
- // Add cancellation note
256
- await tx.orderNote.create({
257
- data: {
258
- orderId: order.id,
259
- type: NoteType.status_change,
260
- note: 'Order auto-cancelled after 72 hours with no customer response',
261
- metadata: {
262
- autoCancelled: true,
263
- prospectDuration: AUTO_CANCEL_HOURS
264
- }
265
- }
266
- });
267
- });
268
- return { orderId: order.id, success: true };
269
- }
270
- catch (error) {
271
- console.error(`Failed to auto-cancel order ${order.id}:`, error);
272
- return { orderId: order.id, success: false, error };
273
- }
274
- }));
275
- return {
276
- processed: results.length,
277
- succeeded: results.filter(r => r.success).length,
278
- failed: results.filter(r => !r.success).length
279
- };
280
- }
281
- /**
282
- * Get recovery statistics for dashboard
283
- */
284
- export async function getProspectRecoveryStats(prisma, brandId) {
285
- const whereClause = brandId ? { brandId } : {};
286
- const [totalProspects, recovered, cancelled, stillProspect] = await Promise.all([
287
- prisma.order.count({
288
- where: {
289
- ...whereClause,
290
- prospectSince: { not: null }
291
- }
292
- }),
293
- prisma.order.count({
294
- where: {
295
- ...whereClause,
296
- wasRecovered: true
297
- }
298
- }),
299
- prisma.order.count({
300
- where: {
301
- ...whereClause,
302
- prospectSince: { not: null },
303
- status: OrderStatus.cancelled
304
- }
305
- }),
306
- prisma.order.count({
307
- where: {
308
- ...whereClause,
309
- status: OrderStatus.prospect
310
- }
311
- })
312
- ]);
313
- const recoveryRate = totalProspects > 0
314
- ? ((recovered / totalProspects) * 100).toFixed(2)
315
- : '0.00';
316
- const revenueRecovered = await prisma.order.aggregate({
317
- where: {
318
- ...whereClause,
319
- wasRecovered: true,
320
- status: {
321
- in: [OrderStatus.shipped, OrderStatus.delivered]
322
- }
323
- },
324
- _sum: {
325
- totalPrice: true
326
- }
327
- });
328
- return {
329
- totalProspects,
330
- recovered,
331
- cancelled,
332
- stillProspect,
333
- recoveryRate: `${recoveryRate}%`,
334
- revenueRecovered: revenueRecovered._sum.totalPrice?.toNumber() || 0
335
- };
336
- }
337
- /**
338
- * Mark order as prospect with reason
339
- */
340
- export async function markOrderAsProspect(prisma, orderId, reason, notes, userId) {
341
- return prisma.$transaction(async (tx) => {
342
- // Update order status
343
- const order = await tx.order.update({
344
- where: { id: orderId },
345
- data: {
346
- status: OrderStatus.prospect,
347
- prospectSince: new Date(),
348
- prospectReason: reason,
349
- recoveryAttempts: 1, // First email sent immediately
350
- lastRecoveryAttemptAt: new Date()
351
- }
352
- });
353
- // Create prospect reason note
354
- const reasonLabels = {
355
- [ProspectReason.no_answer]: 'No answer',
356
- [ProspectReason.thinking]: 'Thinking about it',
357
- [ProspectReason.price_concern]: 'Concerned about price',
358
- [ProspectReason.will_call_back]: 'Will call back',
359
- [ProspectReason.unclear_commitment]: 'Unclear commitment',
360
- [ProspectReason.payment_issue]: 'Payment issue',
361
- [ProspectReason.other]: 'Other'
362
- };
363
- await tx.orderNote.create({
364
- data: {
365
- orderId,
366
- type: NoteType.prospect_reason,
367
- note: `Order set to prospect. Reason: ${reasonLabels[reason]}${notes ? ` - ${notes}` : ''}`,
368
- createdBy: userId,
369
- metadata: {
370
- previousStatus: OrderStatus.pending,
371
- prospectReason: reason,
372
- customNote: notes
373
- }
374
- }
375
- });
376
- return order;
377
- });
378
- }
@@ -1,138 +0,0 @@
1
- import { Prisma } from '@prisma/client';
2
- export type OrderDBResponse = Prisma.OrderGetPayload<{
3
- include: {
4
- brand: true;
5
- deliveryZone: {
6
- include: {
7
- state: true;
8
- };
9
- };
10
- items: {
11
- include: {
12
- variant: {
13
- include: {
14
- product: true;
15
- };
16
- };
17
- warehouse: true;
18
- };
19
- };
20
- };
21
- }>;
22
- export type OrderResponse = ReturnType<typeof formatOrderResponse>;
23
- export declare function formatOrderResponse(order: OrderDBResponse): {
24
- subtotal: number;
25
- deliveryCharge: number;
26
- totalPrice: number;
27
- discountAmount: number;
28
- createdAt: string;
29
- updatedAt: string;
30
- deletedAt: string;
31
- prospectSince: string;
32
- lastRecoveryAttemptAt: string;
33
- brand: {
34
- createdAt: string;
35
- updatedAt: string;
36
- deletedAt: string;
37
- id: string;
38
- name: string;
39
- slug: string;
40
- logoUrl: string | null;
41
- siteUrl: string;
42
- domain: string;
43
- metaPixelId: string | null;
44
- };
45
- deliveryZone: {
46
- deliveryCost: number;
47
- freeShippingThreshold: number;
48
- createdAt: string;
49
- updatedAt: string;
50
- deletedAt: string;
51
- state: {
52
- createdAt: string;
53
- updatedAt: string;
54
- deletedAt: string;
55
- id: string;
56
- name: string;
57
- isActive: boolean;
58
- };
59
- id: string;
60
- name: string;
61
- brandId: string | null;
62
- stateId: string;
63
- allowCOD: boolean;
64
- allowOnline: boolean;
65
- waybillOnly: boolean;
66
- estimatedDays: number | null;
67
- noteTitle: string | null;
68
- noteContent: string | null;
69
- isActive: boolean;
70
- };
71
- items: {
72
- priceAtPurchase: number;
73
- variant: {
74
- price: number;
75
- createdAt: string;
76
- updatedAt: string;
77
- deletedAt: string;
78
- product: {
79
- createdAt: string;
80
- updatedAt: string;
81
- deletedAt: string;
82
- id: string;
83
- name: string;
84
- slug: string;
85
- brandId: string;
86
- isActive: boolean;
87
- description: string | null;
88
- thumbnailUrl: string | null;
89
- quantityDiscounts: Prisma.JsonValue | null;
90
- };
91
- id: string;
92
- name: string | null;
93
- isActive: boolean;
94
- thumbnailUrl: string | null;
95
- productId: string;
96
- sku: string;
97
- trackInventory: boolean;
98
- lowStockThreshold: number | null;
99
- };
100
- warehouse: {
101
- createdAt: string;
102
- updatedAt: string;
103
- deletedAt: string;
104
- id: string;
105
- name: string;
106
- isActive: boolean;
107
- address: string | null;
108
- city: string | null;
109
- state: string | null;
110
- };
111
- id: string;
112
- orderId: string;
113
- variantId: string;
114
- warehouseId: string | null;
115
- quantity: number;
116
- }[];
117
- email: string | null;
118
- id: string;
119
- brandId: string;
120
- deliveryZoneId: string;
121
- recoveryAttempts: number;
122
- recoveryDiscountCodeId: string | null;
123
- wasRecovered: boolean;
124
- estimatedDays: number | null;
125
- orderNumber: number;
126
- firstName: string;
127
- lastName: string;
128
- phone: string;
129
- address: string;
130
- city: string;
131
- discountCodeId: string | null;
132
- paymentMethod: import("@prisma/client").$Enums.PaymentMethod;
133
- paystackReference: string | null;
134
- status: import("@prisma/client").$Enums.OrderStatus;
135
- cancellationReason: string | null;
136
- prospectReason: import("@prisma/client").$Enums.ProspectReason | null;
137
- userActionToken: string;
138
- };
@@ -1,61 +0,0 @@
1
- import { toNumber } from './utils';
2
- export function formatOrderResponse(order) {
3
- const subtotal = order.items.reduce((sum, item) => sum + (item.quantity * parseFloat(item.priceAtPurchase.toString())), 0);
4
- return {
5
- ...order,
6
- subtotal,
7
- deliveryCharge: toNumber(order.deliveryCharge),
8
- totalPrice: toNumber(order.totalPrice),
9
- discountAmount: order.discountAmount ? toNumber(order.discountAmount) : null,
10
- createdAt: order.createdAt.toISOString(),
11
- updatedAt: order.updatedAt.toISOString(),
12
- deletedAt: order.deletedAt ? order.deletedAt.toISOString() : null,
13
- prospectSince: order.prospectSince ? order.prospectSince.toISOString() : null,
14
- lastRecoveryAttemptAt: order.lastRecoveryAttemptAt ? order.lastRecoveryAttemptAt.toISOString() : null,
15
- brand: {
16
- ...order.brand,
17
- createdAt: order.brand.createdAt.toISOString(),
18
- updatedAt: order.brand.updatedAt.toISOString(),
19
- deletedAt: order.brand.deletedAt ? order.brand.deletedAt.toISOString() : null,
20
- },
21
- deliveryZone: {
22
- ...order.deliveryZone,
23
- deliveryCost: toNumber(order.deliveryZone.deliveryCost),
24
- freeShippingThreshold: order.deliveryZone.freeShippingThreshold
25
- ? toNumber(order.deliveryZone.freeShippingThreshold)
26
- : null,
27
- createdAt: order.deliveryZone.createdAt.toISOString(),
28
- updatedAt: order.deliveryZone.updatedAt.toISOString(),
29
- deletedAt: order.deliveryZone.deletedAt ? order.deliveryZone.deletedAt.toISOString() : null,
30
- state: {
31
- ...order.deliveryZone.state,
32
- createdAt: order.deliveryZone.state.createdAt.toISOString(),
33
- updatedAt: order.deliveryZone.state.updatedAt.toISOString(),
34
- deletedAt: order.deliveryZone.state.deletedAt ? order.deliveryZone.state.deletedAt.toISOString() : null,
35
- },
36
- },
37
- items: order.items.map((item) => ({
38
- ...item,
39
- priceAtPurchase: toNumber(item.priceAtPurchase),
40
- variant: {
41
- ...item.variant,
42
- price: toNumber(item.variant.price),
43
- createdAt: item.variant.createdAt.toISOString(),
44
- updatedAt: item.variant.updatedAt.toISOString(),
45
- deletedAt: item.variant.deletedAt ? item.variant.deletedAt.toISOString() : null,
46
- product: {
47
- ...item.variant.product,
48
- createdAt: item.variant.product.createdAt.toISOString(),
49
- updatedAt: item.variant.product.updatedAt.toISOString(),
50
- deletedAt: item.variant.product.deletedAt ? item.variant.product.deletedAt.toISOString() : null,
51
- },
52
- },
53
- warehouse: item.warehouse ? {
54
- ...item.warehouse,
55
- createdAt: item.warehouse.createdAt.toISOString(),
56
- updatedAt: item.warehouse.updatedAt.toISOString(),
57
- deletedAt: item.warehouse.deletedAt ? item.warehouse.deletedAt.toISOString() : null,
58
- } : null,
59
- })),
60
- };
61
- }
@@ -1,39 +0,0 @@
1
- import { Decimal } from '@prisma/client/runtime/library';
2
- export interface PriceCalculation {
3
- basePrice: number;
4
- discountPercent: number;
5
- finalPrice: number;
6
- subtotal: number;
7
- }
8
- /**
9
- * Calculate discounted price for a product based on total quantity across all variants
10
- */
11
- export declare function calculatePriceWithDiscount(basePrice: number | Decimal, quantity: number, totalProductQuantity: number, quantityDiscounts?: Record<string, number> | null): PriceCalculation;
12
- /**
13
- * Calculate total cost for a collection of cart items
14
- */
15
- export interface CartItemForCalculation {
16
- variantId: string;
17
- quantity: number;
18
- variant: {
19
- price: number | Decimal;
20
- product: {
21
- id: string;
22
- quantityDiscounts?: Record<string, number> | null;
23
- };
24
- };
25
- }
26
- export interface CartCostCalculation {
27
- items: Array<{
28
- variantId: string;
29
- quantity: number;
30
- basePrice: number;
31
- discountPercent: number;
32
- finalPrice: number;
33
- subtotal: number;
34
- }>;
35
- subtotal: number;
36
- deliveryCharge: number;
37
- total: number;
38
- }
39
- export declare function calculateCartCost(items: CartItemForCalculation[], deliveryCharge?: number | Decimal): CartCostCalculation;
@@ -1,62 +0,0 @@
1
- import { round, toNumber } from './utils';
2
- /**
3
- * Calculate discounted price for a product based on total quantity across all variants
4
- */
5
- export function calculatePriceWithDiscount(basePrice, quantity, totalProductQuantity, quantityDiscounts) {
6
- let discountPercent = 0;
7
- const basePriceNum = toNumber(basePrice);
8
- // Check if product has quantity discounts and apply based on total product quantity
9
- if (quantityDiscounts && typeof quantityDiscounts === 'object') {
10
- // Find the highest quantity tier that applies to total product quantity
11
- const applicableDiscounts = Object.entries(quantityDiscounts)
12
- .filter(([qty]) => totalProductQuantity >= parseInt(qty))
13
- .sort(([a], [b]) => parseInt(b) - parseInt(a)); // Sort descending
14
- if (applicableDiscounts.length > 0) {
15
- discountPercent = applicableDiscounts[0][1];
16
- }
17
- }
18
- // Apply discount to this variant's price
19
- const finalPrice = round(basePriceNum * (1 - discountPercent / 100));
20
- const subtotal = round(finalPrice * quantity);
21
- return {
22
- basePrice: basePriceNum,
23
- discountPercent,
24
- finalPrice,
25
- subtotal,
26
- };
27
- }
28
- export function calculateCartCost(items, deliveryCharge = 0) {
29
- // Group items by product to calculate quantity discounts at product level
30
- const itemsByProduct = new Map();
31
- for (const item of items) {
32
- const productId = item.variant.product.id;
33
- if (!itemsByProduct.has(productId)) {
34
- itemsByProduct.set(productId, []);
35
- }
36
- itemsByProduct.get(productId).push(item);
37
- }
38
- // Calculate prices for each item
39
- let subtotal = 0;
40
- const calculatedItems = items.map((item) => {
41
- const basePrice = item.variant.price;
42
- const productId = item.variant.product.id;
43
- // Calculate total quantity for this product across all variants
44
- const productItems = itemsByProduct.get(productId) || [];
45
- const totalProductQuantity = productItems.reduce((sum, i) => sum + i.quantity, 0);
46
- // Calculate price with discount
47
- const calculation = calculatePriceWithDiscount(basePrice, item.quantity, totalProductQuantity, item.variant.product.quantityDiscounts);
48
- subtotal += calculation.subtotal;
49
- return {
50
- variantId: item.variantId,
51
- quantity: item.quantity,
52
- ...calculation,
53
- };
54
- });
55
- const deliveryChargeNum = toNumber(deliveryCharge);
56
- return {
57
- items: calculatedItems,
58
- subtotal: round(subtotal),
59
- deliveryCharge: deliveryChargeNum,
60
- total: round(subtotal + deliveryChargeNum),
61
- };
62
- }
@@ -1,9 +0,0 @@
1
- import { PrismaClient } from '@prisma/client';
2
- /**
3
- * Creates a new Prisma client for each request.
4
- *
5
- * IMPORTANT: In Cloudflare Workers, we CANNOT use a singleton pattern because
6
- * I/O objects (like database connections) cannot be shared across different requests.
7
- * Each request must create its own Prisma instance.
8
- */
9
- export declare function getPrismaClient(databaseUrl: string): PrismaClient;