@stacksjs/commerce 0.70.53 → 0.70.55

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 (165) hide show
  1. package/package.json +3 -2
  2. package/src/carts/destroy.ts +54 -0
  3. package/src/carts/fetch.ts +20 -0
  4. package/src/carts/index.ts +13 -0
  5. package/src/carts/store.ts +75 -0
  6. package/src/carts/update.ts +78 -0
  7. package/src/coupons/destroy.ts +49 -0
  8. package/src/coupons/fetch.ts +510 -0
  9. package/src/coupons/index.ts +30 -0
  10. package/src/coupons/store.ts +46 -0
  11. package/src/coupons/update.ts +147 -0
  12. package/src/customers/destroy.ts +62 -0
  13. package/src/customers/fetch.ts +20 -0
  14. package/src/customers/index.ts +17 -0
  15. package/src/customers/store.ts +45 -0
  16. package/src/customers/update.ts +44 -0
  17. package/src/devices/destroy.ts +54 -0
  18. package/src/devices/export.ts +98 -0
  19. package/src/devices/fetch.ts +125 -0
  20. package/src/devices/index.ts +31 -0
  21. package/src/devices/store.ts +82 -0
  22. package/src/devices/update.ts +125 -0
  23. package/src/errors/destroy.ts +54 -0
  24. package/src/errors/fetch.ts +197 -0
  25. package/src/errors/index.ts +28 -0
  26. package/src/errors/update.ts +116 -0
  27. package/src/gift-cards/destroy.ts +87 -0
  28. package/src/gift-cards/fetch.ts +431 -0
  29. package/src/gift-cards/index.ts +26 -0
  30. package/src/gift-cards/store.ts +56 -0
  31. package/src/gift-cards/update.ts +102 -0
  32. package/src/index.ts +78 -0
  33. package/src/orders/destroy.ts +111 -0
  34. package/src/orders/events.ts +121 -0
  35. package/src/orders/export.ts +158 -0
  36. package/src/orders/fetch.ts +379 -0
  37. package/src/orders/guards.ts +261 -0
  38. package/src/orders/index.ts +91 -0
  39. package/src/orders/place-order.ts +274 -0
  40. package/src/orders/store.ts +51 -0
  41. package/src/orders/totals.ts +190 -0
  42. package/src/orders/update.ts +164 -0
  43. package/src/orders/webhook.ts +312 -0
  44. package/src/payments/destroy.ts +56 -0
  45. package/src/payments/fetch.ts +288 -0
  46. package/src/payments/index.ts +16 -0
  47. package/src/payments/store.ts +63 -0
  48. package/src/payments/update.ts +52 -0
  49. package/src/product.ts +0 -0
  50. package/src/products/categories/destroy.ts +103 -0
  51. package/src/products/categories/fetch.ts +285 -0
  52. package/src/products/categories/index.ts +36 -0
  53. package/src/products/categories/store.ts +87 -0
  54. package/src/products/categories/update.ts +251 -0
  55. package/src/products/index.ts +35 -0
  56. package/src/products/items/destroy.ts +54 -0
  57. package/src/products/items/fetch.ts +282 -0
  58. package/src/products/items/index.ts +26 -0
  59. package/src/products/items/store.ts +84 -0
  60. package/src/products/items/update.ts +324 -0
  61. package/src/products/manufacturers/destroy.ts +56 -0
  62. package/src/products/manufacturers/fetch.ts +117 -0
  63. package/src/products/manufacturers/index.ts +24 -0
  64. package/src/products/manufacturers/store.ts +82 -0
  65. package/src/products/manufacturers/update.ts +113 -0
  66. package/src/products/reviews/destroy.ts +56 -0
  67. package/src/products/reviews/fetch.ts +63 -0
  68. package/src/products/reviews/index.ts +25 -0
  69. package/src/products/reviews/store.ts +46 -0
  70. package/src/products/reviews/update.ts +82 -0
  71. package/src/products/units/destroy.ts +56 -0
  72. package/src/products/units/fetch.ts +20 -0
  73. package/src/products/units/index.ts +13 -0
  74. package/src/products/units/store.ts +162 -0
  75. package/src/products/units/update.ts +159 -0
  76. package/src/products/variants/destroy.ts +56 -0
  77. package/src/products/variants/fetch.ts +20 -0
  78. package/src/products/variants/index.ts +21 -0
  79. package/src/products/variants/store.ts +136 -0
  80. package/src/products/variants/update.ts +104 -0
  81. package/src/receipts/destroy.ts +54 -0
  82. package/src/receipts/export.ts +0 -0
  83. package/src/receipts/fetch.ts +222 -0
  84. package/src/receipts/index.ts +24 -0
  85. package/src/receipts/printer.ts +69 -0
  86. package/src/receipts/store.ts +69 -0
  87. package/src/receipts/update.ts +124 -0
  88. package/src/shippings/delivery-routes/destroy.ts +54 -0
  89. package/src/shippings/delivery-routes/fetch.ts +42 -0
  90. package/src/shippings/delivery-routes/index.ts +26 -0
  91. package/src/shippings/delivery-routes/store.ts +69 -0
  92. package/src/shippings/delivery-routes/update.ts +120 -0
  93. package/src/shippings/digital-deliveries/destroy.ts +111 -0
  94. package/src/shippings/digital-deliveries/fetch.ts +20 -0
  95. package/src/shippings/digital-deliveries/index.ts +26 -0
  96. package/src/shippings/digital-deliveries/store.ts +76 -0
  97. package/src/shippings/digital-deliveries/update.ts +129 -0
  98. package/src/shippings/drivers/destroy.ts +78 -0
  99. package/src/shippings/drivers/fetch.ts +20 -0
  100. package/src/shippings/drivers/index.ts +24 -0
  101. package/src/shippings/drivers/store.ts +69 -0
  102. package/src/shippings/drivers/update.ts +117 -0
  103. package/src/shippings/index.ts +39 -0
  104. package/src/shippings/license-keys/destroy.ts +111 -0
  105. package/src/shippings/license-keys/fetch.ts +20 -0
  106. package/src/shippings/license-keys/index.ts +26 -0
  107. package/src/shippings/license-keys/store.ts +77 -0
  108. package/src/shippings/license-keys/update.ts +117 -0
  109. package/src/shippings/shipping-methods/destroy.ts +111 -0
  110. package/src/shippings/shipping-methods/fetch.ts +62 -0
  111. package/src/shippings/shipping-methods/index.ts +28 -0
  112. package/src/shippings/shipping-methods/store.ts +123 -0
  113. package/src/shippings/shipping-methods/update.ts +120 -0
  114. package/src/shippings/shipping-rates/destroy.ts +102 -0
  115. package/src/shippings/shipping-rates/fetch.ts +320 -0
  116. package/src/shippings/shipping-rates/index.ts +36 -0
  117. package/src/shippings/shipping-rates/store.ts +102 -0
  118. package/src/shippings/shipping-rates/update.ts +139 -0
  119. package/src/shippings/shipping-zones/destroy.ts +111 -0
  120. package/src/shippings/shipping-zones/fetch.ts +96 -0
  121. package/src/shippings/shipping-zones/index.ts +26 -0
  122. package/src/shippings/shipping-zones/store.ts +74 -0
  123. package/src/shippings/shipping-zones/update.ts +156 -0
  124. package/src/tax/destroy.ts +54 -0
  125. package/src/tax/fetch.ts +20 -0
  126. package/src/tax/index.ts +24 -0
  127. package/src/tax/store.ts +69 -0
  128. package/src/tax/update.ts +119 -0
  129. package/src/tests/coupons.test.ts +90 -0
  130. package/src/tests/customers.test.ts +39 -0
  131. package/src/tests/device.test.ts +111 -0
  132. package/src/tests/digitals.test.ts +31 -0
  133. package/src/tests/drivers.test.ts +25 -0
  134. package/src/tests/gift-cards.test.ts +36 -0
  135. package/src/tests/license.test.ts +31 -0
  136. package/src/tests/manufacturers.test.ts +80 -0
  137. package/src/tests/orders.test.ts +25 -0
  138. package/src/tests/payments.test.ts +75 -0
  139. package/src/tests/rates.test.ts +367 -0
  140. package/src/tests/receipts.test.ts +287 -0
  141. package/src/tests/restaurant.test.ts +522 -0
  142. package/src/tests/reviews.test.ts +165 -0
  143. package/src/tests/routes.test.ts +51 -0
  144. package/src/tests/setup.ts +149 -0
  145. package/src/tests/shipping.test.ts +88 -0
  146. package/src/tests/tax.test.ts +25 -0
  147. package/src/tests/units.test.ts +110 -0
  148. package/src/tests/variants.test.ts +133 -0
  149. package/src/tests/waitlist.test.ts +303 -0
  150. package/src/tests/zones.test.ts +86 -0
  151. package/src/types.ts +312 -0
  152. package/src/utils/typed-stats.ts +67 -0
  153. package/src/waitlists/index.ts +19 -0
  154. package/src/waitlists/products/destroy.ts +65 -0
  155. package/src/waitlists/products/export.ts +113 -0
  156. package/src/waitlists/products/fetch.ts +362 -0
  157. package/src/waitlists/products/index.ts +33 -0
  158. package/src/waitlists/products/store.ts +112 -0
  159. package/src/waitlists/products/update.ts +125 -0
  160. package/src/waitlists/restaurant/destroy.ts +65 -0
  161. package/src/waitlists/restaurant/export.ts +107 -0
  162. package/src/waitlists/restaurant/fetch.ts +538 -0
  163. package/src/waitlists/restaurant/index.ts +43 -0
  164. package/src/waitlists/restaurant/store.ts +109 -0
  165. package/src/waitlists/restaurant/update.ts +198 -0
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Typed helpers for the aggregate-stats queries the commerce package uses
3
+ * heavily. The bun-query-builder `eb` expression-builder callback is
4
+ * effectively `any` from the consumer side (its return type is the
5
+ * builder's internal `SelectExpressionList`, not exported), so without a
6
+ * thin wrapper every call site has to `as any` both the callback param
7
+ * and the awaited result. That dropped *all* type safety for column
8
+ * names, bindings, and result shapes — fields like `averageSize` could
9
+ * be renamed in one place and silently break another.
10
+ *
11
+ * These helpers give us a typed result shape (caller declares the keys)
12
+ * and a single localized `as any` at the bun-query-builder boundary.
13
+ */
14
+
15
+ import type { StacksExpressionBuilder } from '@stacksjs/database'
16
+ import { db } from '@stacksjs/database'
17
+
18
+ /**
19
+ * Stats descriptors map: each key is the result-row alias; each value is
20
+ * a `{ kind, column, where? }` triple describing the aggregate to emit.
21
+ */
22
+ export type StatsDescriptor =
23
+ | { kind: 'count', column?: string, filter?: { column: string, op: string, value: unknown } }
24
+ | { kind: 'sum', column: string, filter?: { column: string, op: string, value: unknown } }
25
+ | { kind: 'avg', column: string, filter?: { column: string, op: string, value: unknown } }
26
+ | { kind: 'min', column: string, filter?: { column: string, op: string, value: unknown } }
27
+ | { kind: 'max', column: string, filter?: { column: string, op: string, value: unknown } }
28
+
29
+ /**
30
+ * Run a typed aggregate query against a table.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * const stats = await aggregateStats('receipts', {
35
+ * total: { kind: 'count' },
36
+ * success: { kind: 'count', filter: { column: 'status', op: '=', value: 'success' } },
37
+ * averageSize: { kind: 'avg', column: 'size' },
38
+ * }, qb => qb.where('timestamp', '>=', start).where('timestamp', '<=', end))
39
+ * // typeof stats: { total: number; success: number; averageSize: number }
40
+ * ```
41
+ */
42
+ export async function aggregateStats<TKeys extends string>(
43
+ table: string,
44
+ descriptors: Record<TKeys, StatsDescriptor>,
45
+ applyWhere?: (qb: any) => any,
46
+ ): Promise<Record<TKeys, number>> {
47
+ let query: any = (db as any).selectFrom(table)
48
+ if (applyWhere) query = applyWhere(query)
49
+
50
+ query = query.select((eb: StacksExpressionBuilder) => Object.entries(descriptors).map(([alias, d]) => {
51
+ const col = (d as { column?: string }).column ?? 'id'
52
+ let expr = eb.fn[(d as StatsDescriptor).kind](col)
53
+ if ((d as { filter?: unknown }).filter) {
54
+ const f = (d as StatsDescriptor & { filter: NonNullable<StatsDescriptor['filter']> }).filter
55
+ expr = expr.filterWhere(f.column, f.op, f.value)
56
+ }
57
+ return expr.as(alias)
58
+ }))
59
+
60
+ const row = await query.executeTakeFirst()
61
+ const out = {} as Record<TKeys, number>
62
+ for (const alias of Object.keys(descriptors) as TKeys[]) {
63
+ const v = row?.[alias]
64
+ out[alias] = typeof v === 'number' ? v : Number(v ?? 0) || 0
65
+ }
66
+ return out
67
+ }
@@ -0,0 +1,19 @@
1
+ import * as products from './products'
2
+ import * as restaurant from './restaurant'
3
+
4
+ export {
5
+ products,
6
+ restaurant,
7
+ }
8
+
9
+ interface WaitlistsNamespace {
10
+ products: typeof products
11
+ restaurant: typeof restaurant
12
+ }
13
+
14
+ const waitlists: WaitlistsNamespace = {
15
+ products,
16
+ restaurant,
17
+ }
18
+
19
+ export default waitlists
@@ -0,0 +1,65 @@
1
+ type WaitlistProductJsonResponse = ModelRow<typeof WaitlistProduct>
2
+ import { db } from '@stacksjs/database'
3
+
4
+ /**
5
+ * Delete a waitlist product entry by ID
6
+ *
7
+ * @param id The ID of the waitlist product to delete
8
+ * @returns The deleted waitlist product record
9
+ */
10
+ export async function destroy(id: number): Promise<WaitlistProductJsonResponse | undefined> {
11
+ try {
12
+ // First get the waitlist product to return it after deletion
13
+ const waitlistProduct = await db
14
+ .selectFrom('waitlist_products')
15
+ .where('id', '=', id)
16
+ .selectAll()
17
+ .executeTakeFirst()
18
+
19
+ if (!waitlistProduct)
20
+ return undefined
21
+
22
+ // Delete the waitlist product
23
+ await db
24
+ .deleteFrom('waitlist_products')
25
+ .where('id', '=', id)
26
+ .execute()
27
+
28
+ return waitlistProduct as WaitlistProductJsonResponse
29
+ }
30
+ catch (error) {
31
+ if (error instanceof Error) {
32
+ throw new TypeError(`Failed to delete waitlist product: ${error.message}`)
33
+ }
34
+
35
+ throw error
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Delete multiple waitlist product entries by their IDs
41
+ *
42
+ * @param ids Array of waitlist product IDs to delete
43
+ * @returns Number of waitlist products deleted
44
+ */
45
+ export async function bulkDestroy(ids: number[]): Promise<number> {
46
+ if (!ids.length)
47
+ return 0
48
+
49
+ try {
50
+ // Delete all waitlist products with the given IDs
51
+ const result = await db
52
+ .deleteFrom('waitlist_products')
53
+ .where('id', 'in', ids)
54
+ .execute()
55
+
56
+ return (result as any).length || 0
57
+ }
58
+ catch (error) {
59
+ if (error instanceof Error) {
60
+ throw new TypeError(`Failed to delete waitlist products in bulk: ${error.message}`)
61
+ }
62
+
63
+ throw error
64
+ }
65
+ }
@@ -0,0 +1,113 @@
1
+ type WaitlistProductJsonResponse = ModelRow<typeof WaitlistProduct>
2
+ import type { SpreadsheetWrapper } from 'ts-spreadsheets'
3
+ import { createSpreadsheet } from 'ts-spreadsheets'
4
+ import { fetchAll } from './fetch'
5
+
6
+ /**
7
+ * Represents the structure of an exported waitlist product
8
+ */
9
+ export interface ExportedWaitlistProduct {
10
+ 'ID': number
11
+ 'Name': string
12
+ 'Email': string
13
+ 'Phone': string
14
+ 'Party Size': number
15
+ 'Notification Preference': string
16
+ 'Source': string
17
+ 'Status': string
18
+ 'Created At': string
19
+ 'Notified At': string
20
+ 'Purchased At': string
21
+ 'Cancelled At': string
22
+ 'Notes': string
23
+ }
24
+
25
+ /**
26
+ * Export waitlist products to a spreadsheet
27
+ * @param format The format of the spreadsheet (default is CSV)
28
+ * @returns Spreadsheet object ready for download or storage
29
+ */
30
+ export async function exportWaitlist(format: 'csv' | 'excel' = 'csv'): Promise<SpreadsheetWrapper> {
31
+ // Fetch all waitlist products
32
+ const waitlistProducts = await fetchAll()
33
+
34
+ // Prepare data for spreadsheet
35
+ const spreadsheetData = prepareWaitlistForExport(waitlistProducts)
36
+
37
+ // Create and return spreadsheet
38
+ return createSpreadsheet(spreadsheetData, { type: format })
39
+ }
40
+
41
+ /**
42
+ * Prepare waitlist data for spreadsheet export
43
+ * @param waitlistProducts Array of waitlist product objects
44
+ * @returns Spreadsheet data structure
45
+ */
46
+ function prepareWaitlistForExport(waitlistProducts: WaitlistProductJsonResponse[]) {
47
+ // Define headings
48
+ const headings: (keyof ExportedWaitlistProduct)[] = [
49
+ 'ID',
50
+ 'Name',
51
+ 'Email',
52
+ 'Phone',
53
+ 'Party Size',
54
+ 'Notification Preference',
55
+ 'Source',
56
+ 'Status',
57
+ 'Created At',
58
+ 'Notified At',
59
+ 'Purchased At',
60
+ 'Cancelled At',
61
+ 'Notes',
62
+ ]
63
+
64
+ // Transform waitlist products into export format
65
+ const data = waitlistProducts.map((product) => {
66
+ return [
67
+ product.id,
68
+ product.name,
69
+ product.email,
70
+ product.phone || 'N/A',
71
+ (product as any).party_size,
72
+ product.notificationPreference,
73
+ product.source,
74
+ product.status,
75
+ product.created_at,
76
+ product.notifiedAt || 'N/A',
77
+ product.purchasedAt || 'N/A',
78
+ product.cancelledAt || 'N/A',
79
+ product.notes || 'N/A',
80
+ ]
81
+ })
82
+
83
+ return { headings, data }
84
+ }
85
+
86
+ /**
87
+ * Export waitlist products and automatically download
88
+ * @param format The format of the spreadsheet (default is CSV)
89
+ * @param filename Optional filename for the download
90
+ * @returns Download response
91
+ */
92
+ export async function downloadWaitlist(format: 'csv' | 'excel' = 'csv', filename?: string): Promise<Response> {
93
+ const spreadsheet = await exportWaitlist(format)
94
+
95
+ // Use default filename if not provided
96
+ const defaultFilename = `waitlist_export_${new Date().toISOString().split('T')[0]}.${format}`
97
+ return spreadsheet.download(filename || defaultFilename)
98
+ }
99
+
100
+ /**
101
+ * Store waitlist export to disk
102
+ * @param format The format of the spreadsheet (default is CSV)
103
+ * @param path Optional path to store the file
104
+ * @returns Path where the file is stored
105
+ */
106
+ export async function storeWaitlistExport(format: 'csv' | 'excel' = 'csv', path?: string): Promise<void> {
107
+ const spreadsheet = await exportWaitlist(format)
108
+
109
+ // Use default path and filename if not provided
110
+ const defaultPath = `waitlist_export_${new Date().toISOString().split('T')[0]}.${format}`
111
+
112
+ spreadsheet.store(path || defaultPath)
113
+ }
@@ -0,0 +1,362 @@
1
+ import type { StacksExpressionBuilder } from '@stacksjs/database'
2
+ import { db } from '@stacksjs/database'
3
+ import { formatDate } from '@stacksjs/orm'
4
+ type WaitlistProductJsonResponse = ModelRow<typeof WaitlistProduct>
5
+
6
+ /**
7
+ * Fetch a waitlist product by ID
8
+ */
9
+ export async function fetchById(id: number): Promise<WaitlistProductJsonResponse | undefined> {
10
+ return await db
11
+ .selectFrom('waitlist_products')
12
+ .where('id', '=', id)
13
+ .selectAll()
14
+ .executeTakeFirst() as WaitlistProductJsonResponse | undefined
15
+ }
16
+
17
+ /**
18
+ * Fetch all waitlist products
19
+ */
20
+ export async function fetchAll(): Promise<WaitlistProductJsonResponse[]> {
21
+ return await db.selectFrom('waitlist_products').selectAll().execute() as WaitlistProductJsonResponse[]
22
+ }
23
+
24
+ /**
25
+ * Fetch the count of waitlist products grouped by source
26
+ * @param startDate Optional start date to filter by
27
+ * @param endDate Optional end date to filter by
28
+ * @returns An object containing the count for each source
29
+ */
30
+ export async function fetchCountBySource(
31
+ startDate?: Date,
32
+ _endDate?: Date,
33
+ ): Promise<Record<string, number>> {
34
+ let query = db
35
+ .selectFrom('waitlist_products')
36
+ .select(['source', (eb: StacksExpressionBuilder) => eb.fn.count('id').as('count')] as any)
37
+ .groupBy('source') as any
38
+
39
+ if (startDate && _endDate) {
40
+ const startDateStr = formatDate(startDate)
41
+ const endDateStr = formatDate(_endDate)
42
+ query = query
43
+ .where('created_at', '>=', startDateStr)
44
+ .where('created_at', '<=', endDateStr)
45
+ }
46
+
47
+ const results = await query.execute()
48
+
49
+ return results.reduce((acc: any, { source, count }: any) => {
50
+ acc[source] = count
51
+ return acc
52
+ }, {} as Record<string, number>)
53
+ }
54
+
55
+ /**
56
+ * Fetch the count of waitlist products for a specific date
57
+ * @param date The date to count entries for (defaults to today)
58
+ * @returns The count of entries for the specified date
59
+ */
60
+ export async function fetchCountByDate(date: Date = new Date()): Promise<number> {
61
+ const startOfDay = new Date(date)
62
+ startOfDay.setHours(0, 0, 0, 0)
63
+
64
+ const endOfDay = new Date(date)
65
+ endOfDay.setHours(23, 59, 59, 999)
66
+
67
+ const startDateStr = formatDate(startOfDay)
68
+ const endDateStr = formatDate(endOfDay)
69
+
70
+ const result = await db
71
+ .selectFrom('waitlist_products')
72
+ .select(((eb: StacksExpressionBuilder) => eb.fn.count('id').as('count')) as any)
73
+ .where('created_at', '>=', startDateStr)
74
+ .where('created_at', '<=', endDateStr)
75
+ .executeTakeFirst() as { count: number } | undefined
76
+
77
+ return result?.count ?? 0
78
+ }
79
+
80
+ /**
81
+ * Fetch the count of waitlist products with a specific quantity
82
+ * @param quantity The quantity to count orders for
83
+ * @returns The count of orders with the specified quantity
84
+ */
85
+ export async function fetchCountByQuantity(quantity: number): Promise<number> {
86
+ const result = await db
87
+ .selectFrom('waitlist_products')
88
+ .select(((eb: StacksExpressionBuilder) => eb.fn.count('id').as('count')) as any)
89
+ .where('quantity', '=', quantity)
90
+ .executeTakeFirst() as { count: number } | undefined
91
+
92
+ return result?.count ?? 0
93
+ }
94
+
95
+ /**
96
+ * Fetch the count of waitlist products grouped by quantity
97
+ * @param startDate Optional start date to filter by
98
+ * @param endDate Optional end date to filter by
99
+ * @returns An object containing the count for each quantity
100
+ */
101
+ export async function fetchCountByAllQuantities(
102
+ startDate?: Date,
103
+ _endDate?: Date,
104
+ ): Promise<Record<number, number>> {
105
+ let query = db
106
+ .selectFrom('waitlist_products')
107
+ .select(['quantity', (eb: StacksExpressionBuilder) => eb.fn.count('id').as('count')] as any)
108
+ .groupBy('quantity') as any
109
+
110
+ if (startDate && _endDate) {
111
+ const startDateStr = formatDate(startDate)
112
+ const endDateStr = formatDate(_endDate)
113
+ query = query
114
+ .where('created_at', '>=', startDateStr)
115
+ .where('created_at', '<=', endDateStr)
116
+ }
117
+
118
+ const results = await query.execute()
119
+
120
+ return results.reduce((acc: any, { quantity, count }: any) => {
121
+ acc[quantity] = count
122
+ return acc
123
+ }, {} as Record<number, number>)
124
+ }
125
+
126
+ /**
127
+ * Fetch waitlist products between two dates
128
+ * @param startDate The start date of the range
129
+ * @param endDate The end date of the range
130
+ * @returns Array of waitlist products within the date range
131
+ */
132
+ export async function fetchBetweenDates(
133
+ startDate: Date,
134
+ endDate: Date,
135
+ ): Promise<WaitlistProductJsonResponse[]> {
136
+ const startDateStr = formatDate(startDate)
137
+ const endDateStr = formatDate(endDate)
138
+
139
+ return await db
140
+ .selectFrom('waitlist_products')
141
+ .selectAll()
142
+ .where('created_at', '>=', startDateStr)
143
+ .where('created_at', '<=', endDateStr)
144
+ .execute() as WaitlistProductJsonResponse[]
145
+ }
146
+
147
+ /**
148
+ * Fetch waitlist products that were notified between two dates
149
+ * @param startDate The start date of the range
150
+ * @param endDate The end date of the range
151
+ * @returns Array of waitlist products that were notified within the date range
152
+ */
153
+ export async function fetchNotifiedBetweenDates(
154
+ startDate: Date,
155
+ endDate: Date,
156
+ ): Promise<WaitlistProductJsonResponse[]> {
157
+ const startDateStr = formatDate(startDate)
158
+ const endDateStr = formatDate(endDate)
159
+
160
+ return await db
161
+ .selectFrom('waitlist_products')
162
+ .selectAll()
163
+ .where('notified_at', '>=', startDateStr)
164
+ .where('notified_at', '<=', endDateStr)
165
+ .where('notified_at', 'is not', null)
166
+ .execute() as WaitlistProductJsonResponse[]
167
+ }
168
+
169
+ /**
170
+ * Fetch waitlist products that were purchased between two dates
171
+ * @param startDate The start date of the range
172
+ * @param endDate The end date of the range
173
+ * @returns Array of waitlist products that were purchased within the date range
174
+ */
175
+ export async function fetchPurchasedBetweenDates(
176
+ startDate: Date,
177
+ endDate: Date,
178
+ ): Promise<WaitlistProductJsonResponse[]> {
179
+ const startDateStr = formatDate(startDate)
180
+ const endDateStr = formatDate(endDate)
181
+
182
+ return await db
183
+ .selectFrom('waitlist_products')
184
+ .selectAll()
185
+ .where('purchased_at', '>=', startDateStr)
186
+ .where('purchased_at', '<=', endDateStr)
187
+ .where('purchased_at', 'is not', null)
188
+ .execute() as WaitlistProductJsonResponse[]
189
+ }
190
+
191
+ /**
192
+ * Fetch waitlist products that were cancelled between two dates
193
+ * @param startDate The start date of the range
194
+ * @param endDate The end date of the range
195
+ * @returns Array of waitlist products that were cancelled within the date range
196
+ */
197
+ export async function fetchCancelledBetweenDates(
198
+ startDate: Date,
199
+ endDate: Date,
200
+ ): Promise<WaitlistProductJsonResponse[]> {
201
+ const startDateStr = formatDate(startDate)
202
+ const endDateStr = formatDate(endDate)
203
+
204
+ return await db
205
+ .selectFrom('waitlist_products')
206
+ .selectAll()
207
+ .where('cancelled_at', '>=', startDateStr)
208
+ .where('cancelled_at', '<=', endDateStr)
209
+ .where('cancelled_at', 'is not', null)
210
+ .execute() as WaitlistProductJsonResponse[]
211
+ }
212
+
213
+ /**
214
+ * Fetch all waitlist products with waiting status
215
+ * @returns Array of waitlist products with waiting status
216
+ */
217
+ export async function fetchWaiting(): Promise<WaitlistProductJsonResponse[]> {
218
+ return await db
219
+ .selectFrom('waitlist_products')
220
+ .selectAll()
221
+ .where('status', '=', 'waiting')
222
+ .execute() as WaitlistProductJsonResponse[]
223
+ }
224
+
225
+ /**
226
+ * Fetch the count of waitlist products by status
227
+ * @param status The status to filter by
228
+ * @param startDate Optional start date to filter by
229
+ * @param endDate Optional end date to filter by
230
+ * @returns The count of waitlist products with the specified status
231
+ */
232
+ export async function fetchCountByStatus(
233
+ status: string,
234
+ startDate?: Date,
235
+ _endDate?: Date,
236
+ ): Promise<number> {
237
+ let query = db
238
+ .selectFrom('waitlist_products')
239
+ .select(((eb: StacksExpressionBuilder) => eb.fn.count('id').as('count')) as any)
240
+ .where('status', '=', status) as any
241
+
242
+ if (startDate && _endDate) {
243
+ const startDateStr = formatDate(startDate)
244
+ const endDateStr = formatDate(_endDate)
245
+ query = query
246
+ .where('created_at', '>=', startDateStr)
247
+ .where('created_at', '<=', endDateStr)
248
+ }
249
+
250
+ const result = await query.executeTakeFirst() as { count: number } | undefined
251
+ return result?.count ?? 0
252
+ }
253
+
254
+ /**
255
+ * Fetch conversion rates for waitlist products
256
+ * @param startDate Optional start date to filter by
257
+ * @param endDate Optional end date to filter by
258
+ * @returns Object containing total conversion rate and breakdown by status
259
+ */
260
+ export async function fetchConversionRates(
261
+ _startDate?: Date,
262
+ _endDate?: Date,
263
+ ): Promise<{
264
+ totalConversionRate: number
265
+ statusBreakdown: Record<string, { count: number, percentage: number }>
266
+ }> {
267
+ let query = db
268
+ .selectFrom('waitlist_products')
269
+ .select(['status', (eb: StacksExpressionBuilder) => eb.fn.count('id').as('count')] as any)
270
+ .groupBy('status') as any
271
+
272
+ if (_startDate && _endDate) {
273
+ const startDateStr = formatDate(_startDate)
274
+ const endDateStr = formatDate(_endDate)
275
+ query = query
276
+ .where('created_at', '>=', startDateStr)
277
+ .where('created_at', '<=', endDateStr)
278
+ }
279
+
280
+ const results = await query.execute()
281
+
282
+ // Calculate total count and purchased count
283
+ const totalCount = results.reduce((sum: any, { count }: any) => sum + count, 0)
284
+ const purchasedCount = results.find((r: any) => r.status === 'purchased')?.count ?? 0
285
+
286
+ // Calculate conversion rate
287
+ const totalConversionRate = totalCount > 0 ? (purchasedCount / totalCount) * 100 : 0
288
+
289
+ // Create status breakdown with percentages
290
+ const statusBreakdown = results.reduce((acc: any, { status, count }: any) => {
291
+ acc[status as string] = {
292
+ count,
293
+ percentage: totalCount > 0 ? (count / totalCount) * 100 : 0,
294
+ }
295
+ return acc
296
+ }, {} as Record<string, { count: number, percentage: number }>)
297
+
298
+ return {
299
+ totalConversionRate,
300
+ statusBreakdown,
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Fetch the count of waitlist products between two dates
306
+ * @param startDate The start date of the range
307
+ * @param endDate The end date of the range
308
+ * @returns The count of waitlist products within the date range
309
+ */
310
+ export async function fetchCountBetweenDates(
311
+ startDate: Date,
312
+ endDate: Date,
313
+ ): Promise<number> {
314
+ const startDateStr = formatDate(startDate)
315
+ const endDateStr = formatDate(endDate)
316
+
317
+ const result = await db
318
+ .selectFrom('waitlist_products')
319
+ .select(((eb: StacksExpressionBuilder) => eb.fn.count('id').as('count')) as any)
320
+ .where('created_at', '>=', startDateStr)
321
+ .where('created_at', '<=', endDateStr)
322
+ .executeTakeFirst() as { count: number } | undefined
323
+
324
+ return result?.count ?? 0
325
+ }
326
+
327
+ /**
328
+ * Fetch the count of waitlist products grouped by date
329
+ * @param startDate Optional start date to filter by
330
+ * @param endDate Optional end date to filter by
331
+ * @returns An array of objects containing date and count
332
+ */
333
+ export async function fetchCountByDateGrouped(
334
+ startDate?: Date,
335
+ _endDate?: Date,
336
+ ): Promise<{ date: string, count: number }[]> {
337
+ let query = db
338
+ .selectFrom('waitlist_products')
339
+ .select(['created_at'])
340
+
341
+ if (startDate && _endDate) {
342
+ const startDateStr = formatDate(startDate)
343
+ const endDateStr = formatDate(_endDate)
344
+ query = query
345
+ .where('created_at', '>=', startDateStr)
346
+ .where('created_at', '<=', endDateStr)
347
+ }
348
+
349
+ const results = await query.execute()
350
+
351
+ // Group by date and count
352
+ const dateCounts = results.reduce((acc: any, { created_at }: any) => {
353
+ const date = (created_at || '').split('T')[0] || 'unknown' // Get YYYY-MM-DD
354
+ acc[date] = (acc[date] || 0) + 1
355
+ return acc
356
+ }, {} as Record<string, number>)
357
+
358
+ // Convert to array and sort by date
359
+ return Object.entries(dateCounts)
360
+ .map(([date, count]) => ({ date, count: count as number }))
361
+ .sort((a, b) => a.date.localeCompare(b.date))
362
+ }
@@ -0,0 +1,33 @@
1
+ // Functions from destroy.ts
2
+ export {
3
+ bulkDestroy,
4
+ destroy,
5
+ } from './destroy'
6
+
7
+ export {
8
+ fetchAll,
9
+ fetchById,
10
+ fetchCancelledBetweenDates,
11
+ fetchConversionRates,
12
+ fetchCountBetweenDates,
13
+ fetchCountByAllQuantities,
14
+ fetchCountByDate,
15
+ fetchCountByDateGrouped,
16
+ fetchCountByQuantity,
17
+ fetchCountBySource,
18
+ fetchCountByStatus,
19
+ fetchNotifiedBetweenDates,
20
+ fetchPurchasedBetweenDates,
21
+ fetchWaiting,
22
+ } from './fetch'
23
+
24
+ export {
25
+ bulkStore,
26
+ store,
27
+ } from './store'
28
+
29
+ export {
30
+ update,
31
+ updatePartySize,
32
+ updateStatus,
33
+ } from './update'