@instockng/api-client 1.0.6 → 1.0.8

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 (178) hide show
  1. package/dist/apps/backend/src/generated/zod/index.d.ts +1114 -0
  2. package/dist/apps/backend/src/generated/zod/index.js +670 -0
  3. package/dist/apps/backend/src/http-app.d.ts +40 -0
  4. package/dist/apps/backend/src/http-app.js +134 -0
  5. package/dist/apps/backend/src/lib/brand-response.d.ts +14 -0
  6. package/dist/apps/backend/src/lib/brand-response.js +8 -0
  7. package/dist/apps/backend/src/lib/cart-helpers.d.ts +282 -0
  8. package/dist/apps/backend/src/lib/cart-helpers.js +121 -0
  9. package/dist/apps/backend/src/lib/cart-recovery.d.ts +30 -0
  10. package/dist/apps/backend/src/lib/cart-recovery.js +147 -0
  11. package/dist/apps/backend/src/lib/cart-response.d.ts +121 -0
  12. package/dist/apps/backend/src/lib/cart-response.js +150 -0
  13. package/dist/apps/backend/src/lib/clerk.d.ts +18 -0
  14. package/dist/apps/backend/src/lib/clerk.js +190 -0
  15. package/dist/apps/backend/src/lib/delivery-zone-response.d.ts +64 -0
  16. package/dist/apps/backend/src/lib/delivery-zone-response.js +24 -0
  17. package/dist/apps/backend/src/lib/discount-code-response.d.ts +42 -0
  18. package/dist/apps/backend/src/lib/discount-code-response.js +19 -0
  19. package/dist/apps/backend/src/lib/discount.d.ts +20 -0
  20. package/dist/apps/backend/src/lib/discount.js +35 -0
  21. package/dist/apps/backend/src/lib/inventory.d.ts +26 -0
  22. package/dist/apps/backend/src/lib/inventory.js +160 -0
  23. package/dist/apps/backend/src/lib/meta-capi.d.ts +53 -0
  24. package/dist/apps/backend/src/lib/meta-capi.js +151 -0
  25. package/dist/apps/backend/src/lib/openapi.d.ts +36 -0
  26. package/dist/apps/backend/src/lib/openapi.js +69 -0
  27. package/dist/apps/backend/src/lib/order-recovery.d.ts +459 -0
  28. package/dist/apps/backend/src/lib/order-recovery.js +378 -0
  29. package/dist/apps/backend/src/lib/order-response.d.ts +138 -0
  30. package/dist/apps/backend/src/lib/order-response.js +61 -0
  31. package/dist/apps/backend/src/lib/pricing.d.ts +39 -0
  32. package/dist/apps/backend/src/lib/pricing.js +62 -0
  33. package/dist/apps/backend/src/lib/prisma.d.ts +9 -0
  34. package/dist/apps/backend/src/lib/prisma.js +30 -0
  35. package/dist/apps/backend/src/lib/product-response.d.ts +82 -0
  36. package/dist/apps/backend/src/lib/product-response.js +29 -0
  37. package/dist/apps/backend/src/lib/sentry.d.ts +48 -0
  38. package/dist/apps/backend/src/lib/sentry.js +180 -0
  39. package/dist/apps/backend/src/lib/utils.d.ts +32 -0
  40. package/dist/apps/backend/src/lib/utils.js +63 -0
  41. package/dist/apps/backend/src/middleware/clerk-auth.d.ts +8 -0
  42. package/dist/apps/backend/src/middleware/clerk-auth.js +89 -0
  43. package/dist/apps/backend/src/middleware/cors.d.ts +8 -0
  44. package/dist/apps/backend/src/middleware/cors.js +11 -0
  45. package/dist/apps/backend/src/notifications/producers/meta-capi-producer.d.ts +62 -0
  46. package/dist/apps/backend/src/notifications/producers/meta-capi-producer.js +180 -0
  47. package/dist/apps/backend/src/notifications/producers/order-notification.d.ts +9 -0
  48. package/dist/apps/backend/src/notifications/producers/order-notification.js +18 -0
  49. package/dist/apps/backend/src/notifications/producers/prospect-recovery-notification.d.ts +10 -0
  50. package/dist/apps/backend/src/notifications/producers/prospect-recovery-notification.js +11 -0
  51. package/dist/apps/backend/src/routes/admin/abandoned-carts.d.ts +605 -0
  52. package/dist/apps/backend/src/routes/admin/abandoned-carts.js +194 -0
  53. package/dist/apps/backend/src/routes/admin/brands.d.ts +175 -0
  54. package/dist/apps/backend/src/routes/admin/brands.js +118 -0
  55. package/dist/apps/backend/src/routes/admin/customers.d.ts +308 -0
  56. package/dist/apps/backend/src/routes/admin/customers.js +39 -0
  57. package/dist/apps/backend/src/routes/admin/delivery-zones.d.ts +446 -0
  58. package/dist/apps/backend/src/routes/admin/delivery-zones.js +300 -0
  59. package/dist/apps/backend/src/routes/admin/discount-codes.d.ts +478 -0
  60. package/dist/apps/backend/src/routes/admin/discount-codes.js +418 -0
  61. package/dist/apps/backend/src/routes/admin/inventory.d.ts +273 -0
  62. package/dist/apps/backend/src/routes/admin/inventory.js +199 -0
  63. package/dist/apps/backend/src/routes/admin/orders.d.ts +1780 -0
  64. package/dist/apps/backend/src/routes/admin/orders.js +552 -0
  65. package/dist/apps/backend/src/routes/admin/products.d.ts +860 -0
  66. package/dist/apps/backend/src/routes/admin/products.js +126 -0
  67. package/dist/apps/backend/src/routes/admin/stats.d.ts +290 -0
  68. package/dist/apps/backend/src/routes/admin/stats.js +55 -0
  69. package/dist/apps/backend/src/routes/admin/variants.d.ts +239 -0
  70. package/dist/apps/backend/src/routes/admin/variants.js +197 -0
  71. package/dist/apps/backend/src/routes/admin/warehouses.d.ts +373 -0
  72. package/dist/apps/backend/src/routes/admin/warehouses.js +123 -0
  73. package/dist/apps/backend/src/routes/public/brands.d.ts +40 -0
  74. package/dist/apps/backend/src/routes/public/brands.js +38 -0
  75. package/dist/apps/backend/src/routes/public/carts.d.ts +2657 -0
  76. package/dist/apps/backend/src/routes/public/carts.js +778 -0
  77. package/dist/apps/backend/src/routes/public/delivery-zones.d.ts +37 -0
  78. package/dist/apps/backend/src/routes/public/delivery-zones.js +64 -0
  79. package/dist/apps/backend/src/routes/public/orders.d.ts +609 -0
  80. package/dist/apps/backend/src/routes/public/orders.js +184 -0
  81. package/dist/apps/backend/src/routes/public/products.d.ts +449 -0
  82. package/dist/apps/backend/src/routes/public/products.js +133 -0
  83. package/dist/apps/backend/src/types/index.d.ts +43 -0
  84. package/dist/apps/backend/src/types/index.js +2 -0
  85. package/dist/apps/backend/src/validators/brand.d.ts +17 -0
  86. package/dist/apps/backend/src/validators/brand.js +15 -0
  87. package/dist/apps/backend/src/validators/delivery-zone.d.ts +35 -0
  88. package/dist/apps/backend/src/validators/delivery-zone.js +55 -0
  89. package/dist/apps/backend/src/validators/discount-code.d.ts +74 -0
  90. package/dist/apps/backend/src/validators/discount-code.js +50 -0
  91. package/dist/apps/backend/src/validators/inventory.d.ts +20 -0
  92. package/dist/apps/backend/src/validators/inventory.js +15 -0
  93. package/dist/apps/backend/src/validators/order.d.ts +58 -0
  94. package/dist/apps/backend/src/validators/order.js +62 -0
  95. package/dist/apps/backend/src/validators/product.d.ts +18 -0
  96. package/dist/apps/backend/src/validators/product.js +19 -0
  97. package/dist/apps/backend/src/validators/variant.d.ts +19 -0
  98. package/dist/apps/backend/src/validators/variant.js +19 -0
  99. package/dist/apps/backend/src/validators/warehouse.d.ts +15 -0
  100. package/dist/apps/backend/src/validators/warehouse.js +15 -0
  101. package/dist/fetchers/orders.d.ts +258 -1
  102. package/dist/hooks/admin/orders.d.ts +285 -3
  103. package/dist/hooks/admin/orders.js +7 -4
  104. package/dist/hooks/public/orders.d.ts +258 -1
  105. package/dist/packages/api-client/src/backend-types.d.ts +10 -0
  106. package/dist/packages/api-client/src/backend-types.js +10 -0
  107. package/dist/packages/api-client/src/client.d.ts +20 -0
  108. package/dist/packages/api-client/src/client.js +40 -0
  109. package/dist/packages/api-client/src/enum-types.d.ts +8 -0
  110. package/dist/packages/api-client/src/enum-types.js +5 -0
  111. package/dist/packages/api-client/src/fetchers/brands.d.ts +25 -0
  112. package/dist/packages/api-client/src/fetchers/brands.js +26 -0
  113. package/dist/packages/api-client/src/fetchers/carts.d.ts +2337 -0
  114. package/dist/packages/api-client/src/fetchers/carts.js +174 -0
  115. package/dist/packages/api-client/src/fetchers/delivery-zones.d.ts +30 -0
  116. package/dist/packages/api-client/src/fetchers/delivery-zones.js +26 -0
  117. package/dist/packages/api-client/src/fetchers/index.d.ts +22 -0
  118. package/dist/packages/api-client/src/fetchers/index.js +22 -0
  119. package/dist/packages/api-client/src/fetchers/orders.d.ts +544 -0
  120. package/dist/packages/api-client/src/fetchers/orders.js +44 -0
  121. package/dist/packages/api-client/src/fetchers/products.d.ts +386 -0
  122. package/dist/packages/api-client/src/fetchers/products.js +42 -0
  123. package/dist/packages/api-client/src/hooks/admin/abandoned-carts.d.ts +535 -0
  124. package/dist/packages/api-client/src/hooks/admin/abandoned-carts.js +83 -0
  125. package/dist/packages/api-client/src/hooks/admin/brands.d.ts +79 -0
  126. package/dist/packages/api-client/src/hooks/admin/brands.js +108 -0
  127. package/dist/packages/api-client/src/hooks/admin/customers.d.ts +280 -0
  128. package/dist/packages/api-client/src/hooks/admin/customers.js +26 -0
  129. package/dist/packages/api-client/src/hooks/admin/delivery-zones.d.ts +278 -0
  130. package/dist/packages/api-client/src/hooks/admin/delivery-zones.js +176 -0
  131. package/dist/packages/api-client/src/hooks/admin/discount-codes.d.ts +299 -0
  132. package/dist/packages/api-client/src/hooks/admin/discount-codes.js +165 -0
  133. package/dist/packages/api-client/src/hooks/admin/index.d.ts +16 -0
  134. package/dist/packages/api-client/src/hooks/admin/index.js +16 -0
  135. package/dist/packages/api-client/src/hooks/admin/inventory.d.ts +224 -0
  136. package/dist/packages/api-client/src/hooks/admin/inventory.js +107 -0
  137. package/dist/packages/api-client/src/hooks/admin/orders.d.ts +1674 -0
  138. package/dist/packages/api-client/src/hooks/admin/orders.js +178 -0
  139. package/dist/packages/api-client/src/hooks/admin/products.d.ts +374 -0
  140. package/dist/packages/api-client/src/hooks/admin/products.js +89 -0
  141. package/dist/packages/api-client/src/hooks/admin/stats.d.ts +279 -0
  142. package/dist/packages/api-client/src/hooks/admin/stats.js +25 -0
  143. package/dist/packages/api-client/src/hooks/admin/variants.d.ts +115 -0
  144. package/dist/packages/api-client/src/hooks/admin/variants.js +127 -0
  145. package/dist/packages/api-client/src/hooks/admin/warehouses.d.ts +277 -0
  146. package/dist/packages/api-client/src/hooks/admin/warehouses.js +108 -0
  147. package/dist/packages/api-client/src/hooks/public/brands.d.ts +33 -0
  148. package/dist/packages/api-client/src/hooks/public/brands.js +30 -0
  149. package/dist/packages/api-client/src/hooks/public/carts.d.ts +2407 -0
  150. package/dist/packages/api-client/src/hooks/public/carts.js +213 -0
  151. package/dist/packages/api-client/src/hooks/public/delivery-zones.d.ts +36 -0
  152. package/dist/packages/api-client/src/hooks/public/delivery-zones.js +28 -0
  153. package/dist/packages/api-client/src/hooks/public/index.d.ts +10 -0
  154. package/dist/packages/api-client/src/hooks/public/index.js +10 -0
  155. package/dist/packages/api-client/src/hooks/public/orders.d.ts +563 -0
  156. package/dist/packages/api-client/src/hooks/public/orders.js +50 -0
  157. package/dist/packages/api-client/src/hooks/public/products.d.ts +398 -0
  158. package/dist/packages/api-client/src/hooks/public/products.js +47 -0
  159. package/dist/packages/api-client/src/hooks/use-query-unwrapped.d.ts +20 -0
  160. package/dist/packages/api-client/src/hooks/use-query-unwrapped.js +22 -0
  161. package/dist/packages/api-client/src/hooks/useApiConfig.d.ts +12 -0
  162. package/dist/packages/api-client/src/hooks/useApiConfig.js +14 -0
  163. package/dist/packages/api-client/src/index.d.ts +20 -0
  164. package/dist/packages/api-client/src/index.js +25 -0
  165. package/dist/packages/api-client/src/provider.d.ts +36 -0
  166. package/dist/packages/api-client/src/provider.js +54 -0
  167. package/dist/packages/api-client/src/rpc-client.d.ts +9639 -0
  168. package/dist/packages/api-client/src/rpc-client.js +78 -0
  169. package/dist/packages/api-client/src/rpc-types.d.ts +76 -0
  170. package/dist/packages/api-client/src/rpc-types.js +7 -0
  171. package/dist/packages/api-client/src/types.d.ts +34 -0
  172. package/dist/packages/api-client/src/types.js +16 -0
  173. package/dist/packages/api-client/src/utils/query-keys.d.ts +106 -0
  174. package/dist/packages/api-client/src/utils/query-keys.js +108 -0
  175. package/dist/rpc-client.d.ts +891 -319
  176. package/dist/utils/query-keys.d.ts +1 -1
  177. package/dist/utils/query-keys.js +1 -1
  178. package/package.json +1 -1
@@ -0,0 +1,418 @@
1
+ import { Hono } from 'hono';
2
+ import { zValidator } from '@hono/zod-validator';
3
+ import { getPrismaClient } from '../../lib/prisma';
4
+ import { createDiscountCodeSchema, updateDiscountCodeSchema, bulkGenerateCodesSchema, } from '../../validators/discount-code';
5
+ import { generateDiscountCode } from '../../lib/discount';
6
+ import { formatDiscountCodeResponse } from '../../lib/discount-code-response';
7
+ const app = new Hono()
8
+ // List discount codes
9
+ .get('/', async (c) => {
10
+ try {
11
+ const prisma = getPrismaClient(c.env.DATABASE_URL);
12
+ const page = parseInt(c.req.query('page') || '1');
13
+ const limit = parseInt(c.req.query('limit') || '20');
14
+ const category = c.req.query('category');
15
+ const brandId = c.req.query('brandId');
16
+ const isActive = c.req.query('isActive');
17
+ const search = c.req.query('search');
18
+ const where = {
19
+ deletedAt: null,
20
+ };
21
+ if (category && category !== 'all') {
22
+ where.category = category;
23
+ }
24
+ if (brandId) {
25
+ where.brandId = brandId;
26
+ }
27
+ if (isActive !== undefined) {
28
+ where.isActive = isActive === 'true';
29
+ }
30
+ if (search) {
31
+ where.OR = [
32
+ { code: { contains: search, mode: 'insensitive' } },
33
+ { description: { contains: search, mode: 'insensitive' } },
34
+ ];
35
+ }
36
+ const [codes, total] = await Promise.all([
37
+ prisma.discountCode.findMany({
38
+ where,
39
+ include: {
40
+ brand: true,
41
+ _count: {
42
+ select: {
43
+ usageLog: true,
44
+ },
45
+ },
46
+ },
47
+ orderBy: { createdAt: 'desc' },
48
+ skip: (page - 1) * limit,
49
+ take: limit,
50
+ }),
51
+ prisma.discountCode.count({ where }),
52
+ ]);
53
+ const enhancedCodes = codes.map(formatDiscountCodeResponse);
54
+ return c.json({
55
+ data: enhancedCodes,
56
+ pagination: {
57
+ page,
58
+ limit,
59
+ total,
60
+ totalPages: Math.ceil(total / limit),
61
+ },
62
+ });
63
+ }
64
+ catch (error) {
65
+ console.error('Error fetching discount codes:', error);
66
+ return c.json({ error: { code: 'INTERNAL_ERROR', message: error.message } }, 500);
67
+ }
68
+ })
69
+ // Get single discount code
70
+ .get('/:id', async (c) => {
71
+ try {
72
+ const id = c.req.param('id');
73
+ const prisma = getPrismaClient(c.env.DATABASE_URL);
74
+ const code = await prisma.discountCode.findFirst({
75
+ where: { id, deletedAt: null },
76
+ include: {
77
+ brand: true,
78
+ _count: {
79
+ select: {
80
+ usageLog: true,
81
+ orders: true,
82
+ },
83
+ },
84
+ },
85
+ });
86
+ if (!code) {
87
+ return c.json({ error: { code: 'NOT_FOUND', message: 'Discount code not found' } }, 404);
88
+ }
89
+ return c.json({
90
+ ...formatDiscountCodeResponse(code),
91
+ _count: code._count ? {
92
+ usageLog: code._count.usageLog,
93
+ orders: code._count.orders,
94
+ } : null,
95
+ });
96
+ }
97
+ catch (error) {
98
+ console.error('Error fetching discount code:', error);
99
+ return c.json({ error: { code: 'INTERNAL_ERROR', message: error.message } }, 500);
100
+ }
101
+ })
102
+ // Create discount code
103
+ .post('/', zValidator('json', createDiscountCodeSchema), async (c) => {
104
+ try {
105
+ const input = c.req.valid('json');
106
+ const prisma = getPrismaClient(c.env.DATABASE_URL);
107
+ const user = c.get('user');
108
+ // Check if code already exists
109
+ const existing = await prisma.discountCode.findUnique({
110
+ where: { code: input.code },
111
+ });
112
+ if (existing) {
113
+ return c.json({ error: { code: 'CODE_EXISTS', message: 'Discount code already exists' } }, 400);
114
+ }
115
+ // If brandId provided, verify it exists
116
+ if (input.brandId) {
117
+ const brand = await prisma.brand.findFirst({
118
+ where: { id: input.brandId, deletedAt: null },
119
+ });
120
+ if (!brand) {
121
+ return c.json({ error: { code: 'BRAND_NOT_FOUND', message: 'Brand not found' } }, 404);
122
+ }
123
+ }
124
+ // Create the discount code
125
+ const discountCode = await prisma.discountCode.create({
126
+ data: {
127
+ code: input.code,
128
+ brandId: input.brandId ?? null,
129
+ type: input.type,
130
+ value: input.value,
131
+ description: input.description,
132
+ category: input.category ?? 'manual',
133
+ minPurchase: input.minPurchase ?? null,
134
+ maxDiscount: input.maxDiscount ?? null,
135
+ usageLimit: input.usageLimit ?? null,
136
+ perCustomerLimit: input.perCustomerLimit ?? null,
137
+ validFrom: input.validFrom ? new Date(input.validFrom) : new Date(),
138
+ validUntil: input.validUntil ? new Date(input.validUntil) : null,
139
+ isActive: input.isActive ?? true,
140
+ isAutoApply: input.isAutoApply ?? false,
141
+ createdBy: user?.id,
142
+ },
143
+ include: {
144
+ brand: true,
145
+ },
146
+ });
147
+ return c.json(formatDiscountCodeResponse(discountCode), 201);
148
+ }
149
+ catch (error) {
150
+ console.error('Error creating discount code:', error);
151
+ return c.json({ error: { code: 'INTERNAL_ERROR', message: error.message } }, 500);
152
+ }
153
+ })
154
+ // Update discount code
155
+ .patch('/:id', zValidator('json', updateDiscountCodeSchema), async (c) => {
156
+ try {
157
+ const id = c.req.param('id');
158
+ const input = c.req.valid('json');
159
+ const prisma = getPrismaClient(c.env.DATABASE_URL);
160
+ const existing = await prisma.discountCode.findFirst({
161
+ where: { id, deletedAt: null },
162
+ });
163
+ if (!existing) {
164
+ return c.json({ error: { code: 'NOT_FOUND', message: 'Discount code not found' } }, 404);
165
+ }
166
+ // If updating code, check for duplicates
167
+ if (input.code && input.code !== existing.code) {
168
+ const duplicate = await prisma.discountCode.findUnique({
169
+ where: { code: input.code },
170
+ });
171
+ if (duplicate) {
172
+ return c.json({ error: { code: 'CODE_EXISTS', message: 'Discount code already exists' } }, 400);
173
+ }
174
+ }
175
+ const updateData = {};
176
+ if (input.code !== undefined)
177
+ updateData.code = input.code;
178
+ if (input.type !== undefined)
179
+ updateData.type = input.type;
180
+ if (input.value !== undefined)
181
+ updateData.value = input.value;
182
+ if (input.description !== undefined)
183
+ updateData.description = input.description;
184
+ if (input.category !== undefined)
185
+ updateData.category = input.category;
186
+ if (input.minPurchase !== undefined)
187
+ updateData.minPurchase = input.minPurchase;
188
+ if (input.maxDiscount !== undefined)
189
+ updateData.maxDiscount = input.maxDiscount;
190
+ if (input.usageLimit !== undefined)
191
+ updateData.usageLimit = input.usageLimit;
192
+ if (input.perCustomerLimit !== undefined)
193
+ updateData.perCustomerLimit = input.perCustomerLimit;
194
+ if (input.validFrom !== undefined)
195
+ updateData.validFrom = new Date(input.validFrom);
196
+ if (input.validUntil !== undefined)
197
+ updateData.validUntil = input.validUntil ? new Date(input.validUntil) : null;
198
+ if (input.isActive !== undefined)
199
+ updateData.isActive = input.isActive;
200
+ if (input.isAutoApply !== undefined)
201
+ updateData.isAutoApply = input.isAutoApply;
202
+ const discountCode = await prisma.discountCode.update({
203
+ where: { id },
204
+ data: updateData,
205
+ include: {
206
+ brand: true,
207
+ },
208
+ });
209
+ return c.json(formatDiscountCodeResponse(discountCode));
210
+ }
211
+ catch (error) {
212
+ console.error('Error updating discount code:', error);
213
+ return c.json({ error: { code: 'INTERNAL_ERROR', message: error.message } }, 500);
214
+ }
215
+ })
216
+ // Delete discount code (soft delete)
217
+ .delete('/:id', async (c) => {
218
+ try {
219
+ const id = c.req.param('id');
220
+ const prisma = getPrismaClient(c.env.DATABASE_URL);
221
+ const existing = await prisma.discountCode.findFirst({
222
+ where: { id, deletedAt: null },
223
+ });
224
+ if (!existing) {
225
+ return c.json({ error: { code: 'NOT_FOUND', message: 'Discount code not found' } }, 404);
226
+ }
227
+ await prisma.discountCode.update({
228
+ where: { id },
229
+ data: { deletedAt: new Date() },
230
+ });
231
+ return c.status(204);
232
+ }
233
+ catch (error) {
234
+ console.error('Error deleting discount code:', error);
235
+ return c.json({ error: { code: 'INTERNAL_ERROR', message: error.message } }, 500);
236
+ }
237
+ })
238
+ // Get discount code analytics
239
+ .get('/:id/analytics', async (c) => {
240
+ try {
241
+ const id = c.req.param('id');
242
+ const prisma = getPrismaClient(c.env.DATABASE_URL);
243
+ const code = await prisma.discountCode.findFirst({
244
+ where: { id, deletedAt: null },
245
+ include: {
246
+ brand: true,
247
+ usageLog: {
248
+ include: {
249
+ order: true,
250
+ },
251
+ orderBy: {
252
+ createdAt: 'desc',
253
+ },
254
+ },
255
+ },
256
+ });
257
+ if (!code) {
258
+ return c.json({ error: { code: 'NOT_FOUND', message: 'Discount code not found' } }, 404);
259
+ }
260
+ // Calculate analytics
261
+ const totalUses = code.usageLog.length;
262
+ const totalDiscountGiven = code.usageLog.reduce((sum, log) => sum + log.discountAmount.toNumber(), 0);
263
+ const totalRevenue = code.usageLog.reduce((sum, log) => sum + log.order.totalPrice.toNumber(), 0);
264
+ const averageOrderValue = totalUses > 0 ? totalRevenue / totalUses : 0;
265
+ const averageDiscount = totalUses > 0 ? totalDiscountGiven / totalUses : 0;
266
+ // Unique customers
267
+ const uniqueCustomers = new Set(code.usageLog.map((log) => log.customerPhone)).size;
268
+ // Usage by date (last 30 days)
269
+ const thirtyDaysAgo = new Date();
270
+ thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
271
+ const usageByDate = code.usageLog
272
+ .filter((log) => log.createdAt >= thirtyDaysAgo)
273
+ .reduce((acc, log) => {
274
+ const date = log.createdAt.toISOString().split('T')[0];
275
+ if (!acc[date]) {
276
+ acc[date] = { date, uses: 0, revenue: 0, discount: 0 };
277
+ }
278
+ acc[date].uses++;
279
+ acc[date].revenue += log.order.totalPrice;
280
+ acc[date].discount += log.discountAmount;
281
+ return acc;
282
+ }, {});
283
+ const usageByDateArray = Object.values(usageByDate).sort((a, b) => a.date.localeCompare(b.date));
284
+ // Top customers
285
+ const customerStats = code.usageLog.reduce((acc, log) => {
286
+ if (!acc[log.customerPhone]) {
287
+ acc[log.customerPhone] = { phone: log.customerPhone, uses: 0, revenue: 0, discount: 0 };
288
+ }
289
+ acc[log.customerPhone].uses++;
290
+ acc[log.customerPhone].revenue += log.order.totalPrice;
291
+ acc[log.customerPhone].discount += log.discountAmount;
292
+ return acc;
293
+ }, {});
294
+ const topCustomers = Object.values(customerStats)
295
+ .sort((a, b) => b.revenue - a.revenue)
296
+ .slice(0, 10);
297
+ // Recent usages with order details
298
+ const recentUsages = code.usageLog.slice(0, 20).map((log) => ({
299
+ id: log.id,
300
+ orderId: log.orderId,
301
+ customerPhone: log.customerPhone,
302
+ usedAt: log.createdAt,
303
+ discountAmount: log.discountAmount,
304
+ orderTotal: log.order.totalPrice,
305
+ order: {
306
+ id: log.order.id,
307
+ phone: log.order.phone,
308
+ },
309
+ }));
310
+ return c.json({
311
+ ...formatDiscountCodeResponse(code),
312
+ totalUses,
313
+ usagePercentage: code.usageLimit ? (totalUses / code.usageLimit) * 100 : null,
314
+ totalDiscountGiven,
315
+ totalRevenue,
316
+ averageOrderValue,
317
+ averageDiscount,
318
+ uniqueCustomers,
319
+ usageByDate: usageByDateArray,
320
+ topCustomers,
321
+ recentUsages,
322
+ });
323
+ }
324
+ catch (error) {
325
+ console.error('Error fetching discount code analytics:', error);
326
+ return c.json({ error: { code: 'INTERNAL_ERROR', message: error.message } }, 500);
327
+ }
328
+ })
329
+ // Bulk generate discount codes
330
+ .post('/bulk-generate', zValidator('json', bulkGenerateCodesSchema), async (c) => {
331
+ try {
332
+ const input = c.req.valid('json');
333
+ const prisma = getPrismaClient(c.env.DATABASE_URL);
334
+ const user = c.get('user');
335
+ const codes = [];
336
+ const codeRecords = [];
337
+ // Generate unique codes
338
+ for (let i = 0; i < input.count; i++) {
339
+ let code;
340
+ let attempts = 0;
341
+ const maxAttempts = 10;
342
+ do {
343
+ const randomPart = generateDiscountCode().replace('CART', '');
344
+ code = `${input.prefix}${randomPart}`;
345
+ attempts++;
346
+ if (attempts >= maxAttempts) {
347
+ return c.json({ error: { code: 'GENERATION_FAILED', message: 'Failed to generate unique codes' } }, 500);
348
+ }
349
+ } while (codes.includes(code) || await prisma.discountCode.findUnique({ where: { code } }));
350
+ codes.push(code);
351
+ codeRecords.push({
352
+ code,
353
+ brandId: input.brandId ?? null,
354
+ type: input.type,
355
+ value: input.value,
356
+ description: input.description ?? `Bulk generated - ${input.prefix}`,
357
+ category: input.category ?? 'manual',
358
+ minPurchase: input.minPurchase ?? null,
359
+ maxDiscount: input.maxDiscount ?? null,
360
+ usageLimit: 1, // Each bulk code can only be used once by default
361
+ perCustomerLimit: input.perCustomerLimit ?? 1,
362
+ validFrom: input.validFrom ? new Date(input.validFrom) : new Date(),
363
+ validUntil: input.validUntil ? new Date(input.validUntil) : null,
364
+ isActive: input.isActive ?? true,
365
+ isAutoApply: false,
366
+ createdBy: user?.id,
367
+ });
368
+ }
369
+ // Create all codes in a transaction
370
+ await prisma.discountCode.createMany({
371
+ data: codeRecords,
372
+ });
373
+ return c.json({
374
+ codes,
375
+ count: codes.length,
376
+ }, 201);
377
+ }
378
+ catch (error) {
379
+ console.error('Error bulk generating codes:', error);
380
+ return c.json({ error: { code: 'INTERNAL_ERROR', message: error.message } }, 500);
381
+ }
382
+ })
383
+ // Get overall discount code stats
384
+ .get('/stats/overview', async (c) => {
385
+ try {
386
+ const prisma = getPrismaClient(c.env.DATABASE_URL);
387
+ const [totalCodes, activeCodes, totalUsage, totalDiscountGiven, totalRevenue,] = await Promise.all([
388
+ prisma.discountCode.count({ where: { deletedAt: null } }),
389
+ prisma.discountCode.count({ where: { deletedAt: null, isActive: true } }),
390
+ prisma.discountCodeUsage.count(),
391
+ prisma.discountCodeUsage.aggregate({
392
+ _sum: { discountAmount: true },
393
+ }),
394
+ prisma.discountCodeUsage.aggregate({
395
+ _sum: { originalAmount: true },
396
+ }),
397
+ ]);
398
+ // Category breakdown
399
+ const codesByCategory = await prisma.discountCode.groupBy({
400
+ by: ['category'],
401
+ where: { deletedAt: null },
402
+ _count: true,
403
+ });
404
+ return c.json({
405
+ totalCodes,
406
+ activeCodes,
407
+ totalUsage,
408
+ totalDiscountGiven: totalDiscountGiven._sum.discountAmount || 0,
409
+ totalRevenue: totalRevenue._sum.originalAmount || 0,
410
+ codesByCategory,
411
+ });
412
+ }
413
+ catch (error) {
414
+ console.error('Error fetching discount code stats:', error);
415
+ return c.json({ error: { code: 'INTERNAL_ERROR', message: error.message } }, 500);
416
+ }
417
+ });
418
+ export default app;
@@ -0,0 +1,273 @@
1
+ import { AppContext } from '../../types';
2
+ declare const app: import("hono/hono-base").HonoBase<AppContext, {
3
+ "/": {
4
+ $get: {
5
+ input: {};
6
+ output: {
7
+ variantId: string;
8
+ warehouseId: string;
9
+ sku: string;
10
+ productName: string;
11
+ name: string;
12
+ brandName: string;
13
+ warehouseName: string;
14
+ inventoryCount: number;
15
+ lowStockThreshold: number;
16
+ isLowStock: boolean;
17
+ }[];
18
+ outputFormat: "json";
19
+ status: import("hono/utils/http-status").ContentfulStatusCode;
20
+ } | {
21
+ input: {};
22
+ output: {
23
+ error: {
24
+ code: string;
25
+ message: string;
26
+ };
27
+ };
28
+ outputFormat: "json";
29
+ status: 500;
30
+ };
31
+ };
32
+ } & {
33
+ "/adjust": {
34
+ $post: {
35
+ input: {};
36
+ output: {};
37
+ outputFormat: string;
38
+ status: import("hono/utils/http-status").StatusCode;
39
+ } | {
40
+ input: {};
41
+ output: {};
42
+ outputFormat: string;
43
+ status: import("hono/utils/http-status").StatusCode;
44
+ };
45
+ };
46
+ } & {
47
+ "/transfer": {
48
+ $post: {
49
+ input: {};
50
+ output: {};
51
+ outputFormat: string;
52
+ status: import("hono/utils/http-status").StatusCode;
53
+ } | {
54
+ input: {};
55
+ output: {};
56
+ outputFormat: string;
57
+ status: import("hono/utils/http-status").StatusCode;
58
+ };
59
+ };
60
+ } & {
61
+ "/transactions": {
62
+ $get: {
63
+ input: {};
64
+ output: {
65
+ data: {
66
+ id: string;
67
+ type: import("@prisma/client").$Enums.InventoryTransactionType;
68
+ quantity: number;
69
+ balanceAfter: number;
70
+ reason: string;
71
+ orderId: string;
72
+ orderNumber: number;
73
+ userId: string;
74
+ metadata: string | number | boolean | {
75
+ [x: string]: string | number | boolean | /*elided*/ any | {
76
+ [x: number]: string | number | boolean | /*elided*/ any | /*elided*/ any;
77
+ length: number;
78
+ toString: never;
79
+ toLocaleString: never;
80
+ pop: never;
81
+ push: never;
82
+ concat: never;
83
+ join: never;
84
+ reverse: never;
85
+ shift: never;
86
+ slice: never;
87
+ sort: never;
88
+ splice: never;
89
+ unshift: never;
90
+ indexOf: never;
91
+ lastIndexOf: never;
92
+ every: never;
93
+ some: never;
94
+ forEach: never;
95
+ map: never;
96
+ filter: never;
97
+ reduce: never;
98
+ reduceRight: never;
99
+ find: never;
100
+ findIndex: never;
101
+ fill: never;
102
+ copyWithin: never;
103
+ entries: never;
104
+ keys: never;
105
+ values: never;
106
+ includes: never;
107
+ flatMap: never;
108
+ flat: never;
109
+ [Symbol.iterator]: never;
110
+ readonly [Symbol.unscopables]: {
111
+ [x: number]: boolean;
112
+ length?: boolean;
113
+ toString?: boolean;
114
+ toLocaleString?: boolean;
115
+ pop?: boolean;
116
+ push?: boolean;
117
+ concat?: boolean;
118
+ join?: boolean;
119
+ reverse?: boolean;
120
+ shift?: boolean;
121
+ slice?: boolean;
122
+ sort?: boolean;
123
+ splice?: boolean;
124
+ unshift?: boolean;
125
+ indexOf?: boolean;
126
+ lastIndexOf?: boolean;
127
+ every?: boolean;
128
+ some?: boolean;
129
+ forEach?: boolean;
130
+ map?: boolean;
131
+ filter?: boolean;
132
+ reduce?: boolean;
133
+ reduceRight?: boolean;
134
+ find?: boolean;
135
+ findIndex?: boolean;
136
+ fill?: boolean;
137
+ copyWithin?: boolean;
138
+ entries?: boolean;
139
+ keys?: boolean;
140
+ values?: boolean;
141
+ includes?: boolean;
142
+ flatMap?: boolean;
143
+ flat?: boolean;
144
+ };
145
+ };
146
+ } | {
147
+ [x: number]: string | number | boolean | {
148
+ [x: string]: string | number | boolean | /*elided*/ any | /*elided*/ any;
149
+ } | /*elided*/ any;
150
+ length: number;
151
+ toString: never;
152
+ toLocaleString: never;
153
+ pop: never;
154
+ push: never;
155
+ concat: never;
156
+ join: never;
157
+ reverse: never;
158
+ shift: never;
159
+ slice: never;
160
+ sort: never;
161
+ splice: never;
162
+ unshift: never;
163
+ indexOf: never;
164
+ lastIndexOf: never;
165
+ every: never;
166
+ some: never;
167
+ forEach: never;
168
+ map: never;
169
+ filter: never;
170
+ reduce: never;
171
+ reduceRight: never;
172
+ find: never;
173
+ findIndex: never;
174
+ fill: never;
175
+ copyWithin: never;
176
+ entries: never;
177
+ keys: never;
178
+ values: never;
179
+ includes: never;
180
+ flatMap: never;
181
+ flat: never;
182
+ [Symbol.iterator]: never;
183
+ readonly [Symbol.unscopables]: {
184
+ [x: number]: boolean;
185
+ length?: boolean;
186
+ toString?: boolean;
187
+ toLocaleString?: boolean;
188
+ pop?: boolean;
189
+ push?: boolean;
190
+ concat?: boolean;
191
+ join?: boolean;
192
+ reverse?: boolean;
193
+ shift?: boolean;
194
+ slice?: boolean;
195
+ sort?: boolean;
196
+ splice?: boolean;
197
+ unshift?: boolean;
198
+ indexOf?: boolean;
199
+ lastIndexOf?: boolean;
200
+ every?: boolean;
201
+ some?: boolean;
202
+ forEach?: boolean;
203
+ map?: boolean;
204
+ filter?: boolean;
205
+ reduce?: boolean;
206
+ reduceRight?: boolean;
207
+ find?: boolean;
208
+ findIndex?: boolean;
209
+ fill?: boolean;
210
+ copyWithin?: boolean;
211
+ entries?: boolean;
212
+ keys?: boolean;
213
+ values?: boolean;
214
+ includes?: boolean;
215
+ flatMap?: boolean;
216
+ flat?: boolean;
217
+ };
218
+ };
219
+ createdAt: string;
220
+ variant: {
221
+ id: string;
222
+ sku: string;
223
+ name: string;
224
+ productName: string;
225
+ brandName: string;
226
+ };
227
+ warehouse: {
228
+ id: string;
229
+ name: string;
230
+ };
231
+ }[];
232
+ pagination: {
233
+ page: number;
234
+ limit: number;
235
+ total: number;
236
+ totalPages: number;
237
+ };
238
+ };
239
+ outputFormat: "json";
240
+ status: import("hono/utils/http-status").ContentfulStatusCode;
241
+ } | {
242
+ input: {};
243
+ output: {
244
+ error: {
245
+ code: string;
246
+ message: string;
247
+ };
248
+ };
249
+ outputFormat: "json";
250
+ status: 500;
251
+ };
252
+ };
253
+ } & {
254
+ "/low-stock": {
255
+ $get: {
256
+ input: {};
257
+ output: never;
258
+ outputFormat: "json";
259
+ status: import("hono/utils/http-status").ContentfulStatusCode;
260
+ } | {
261
+ input: {};
262
+ output: {
263
+ error: {
264
+ code: string;
265
+ message: string;
266
+ };
267
+ };
268
+ outputFormat: "json";
269
+ status: 500;
270
+ };
271
+ };
272
+ }, "/">;
273
+ export default app;