@stacksjs/commerce 0.70.54 → 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.
- package/package.json +3 -2
- package/src/carts/destroy.ts +54 -0
- package/src/carts/fetch.ts +20 -0
- package/src/carts/index.ts +13 -0
- package/src/carts/store.ts +75 -0
- package/src/carts/update.ts +78 -0
- package/src/coupons/destroy.ts +49 -0
- package/src/coupons/fetch.ts +510 -0
- package/src/coupons/index.ts +30 -0
- package/src/coupons/store.ts +46 -0
- package/src/coupons/update.ts +147 -0
- package/src/customers/destroy.ts +62 -0
- package/src/customers/fetch.ts +20 -0
- package/src/customers/index.ts +17 -0
- package/src/customers/store.ts +45 -0
- package/src/customers/update.ts +44 -0
- package/src/devices/destroy.ts +54 -0
- package/src/devices/export.ts +98 -0
- package/src/devices/fetch.ts +125 -0
- package/src/devices/index.ts +31 -0
- package/src/devices/store.ts +82 -0
- package/src/devices/update.ts +125 -0
- package/src/errors/destroy.ts +54 -0
- package/src/errors/fetch.ts +197 -0
- package/src/errors/index.ts +28 -0
- package/src/errors/update.ts +116 -0
- package/src/gift-cards/destroy.ts +87 -0
- package/src/gift-cards/fetch.ts +431 -0
- package/src/gift-cards/index.ts +26 -0
- package/src/gift-cards/store.ts +56 -0
- package/src/gift-cards/update.ts +102 -0
- package/src/index.ts +78 -0
- package/src/orders/destroy.ts +111 -0
- package/src/orders/events.ts +121 -0
- package/src/orders/export.ts +158 -0
- package/src/orders/fetch.ts +379 -0
- package/src/orders/guards.ts +261 -0
- package/src/orders/index.ts +91 -0
- package/src/orders/place-order.ts +274 -0
- package/src/orders/store.ts +51 -0
- package/src/orders/totals.ts +190 -0
- package/src/orders/update.ts +164 -0
- package/src/orders/webhook.ts +312 -0
- package/src/payments/destroy.ts +56 -0
- package/src/payments/fetch.ts +288 -0
- package/src/payments/index.ts +16 -0
- package/src/payments/store.ts +63 -0
- package/src/payments/update.ts +52 -0
- package/src/product.ts +0 -0
- package/src/products/categories/destroy.ts +103 -0
- package/src/products/categories/fetch.ts +285 -0
- package/src/products/categories/index.ts +36 -0
- package/src/products/categories/store.ts +87 -0
- package/src/products/categories/update.ts +251 -0
- package/src/products/index.ts +35 -0
- package/src/products/items/destroy.ts +54 -0
- package/src/products/items/fetch.ts +282 -0
- package/src/products/items/index.ts +26 -0
- package/src/products/items/store.ts +84 -0
- package/src/products/items/update.ts +324 -0
- package/src/products/manufacturers/destroy.ts +56 -0
- package/src/products/manufacturers/fetch.ts +117 -0
- package/src/products/manufacturers/index.ts +24 -0
- package/src/products/manufacturers/store.ts +82 -0
- package/src/products/manufacturers/update.ts +113 -0
- package/src/products/reviews/destroy.ts +56 -0
- package/src/products/reviews/fetch.ts +63 -0
- package/src/products/reviews/index.ts +25 -0
- package/src/products/reviews/store.ts +46 -0
- package/src/products/reviews/update.ts +82 -0
- package/src/products/units/destroy.ts +56 -0
- package/src/products/units/fetch.ts +20 -0
- package/src/products/units/index.ts +13 -0
- package/src/products/units/store.ts +162 -0
- package/src/products/units/update.ts +159 -0
- package/src/products/variants/destroy.ts +56 -0
- package/src/products/variants/fetch.ts +20 -0
- package/src/products/variants/index.ts +21 -0
- package/src/products/variants/store.ts +136 -0
- package/src/products/variants/update.ts +104 -0
- package/src/receipts/destroy.ts +54 -0
- package/src/receipts/export.ts +0 -0
- package/src/receipts/fetch.ts +222 -0
- package/src/receipts/index.ts +24 -0
- package/src/receipts/printer.ts +69 -0
- package/src/receipts/store.ts +69 -0
- package/src/receipts/update.ts +124 -0
- package/src/shippings/delivery-routes/destroy.ts +54 -0
- package/src/shippings/delivery-routes/fetch.ts +42 -0
- package/src/shippings/delivery-routes/index.ts +26 -0
- package/src/shippings/delivery-routes/store.ts +69 -0
- package/src/shippings/delivery-routes/update.ts +120 -0
- package/src/shippings/digital-deliveries/destroy.ts +111 -0
- package/src/shippings/digital-deliveries/fetch.ts +20 -0
- package/src/shippings/digital-deliveries/index.ts +26 -0
- package/src/shippings/digital-deliveries/store.ts +76 -0
- package/src/shippings/digital-deliveries/update.ts +129 -0
- package/src/shippings/drivers/destroy.ts +78 -0
- package/src/shippings/drivers/fetch.ts +20 -0
- package/src/shippings/drivers/index.ts +24 -0
- package/src/shippings/drivers/store.ts +69 -0
- package/src/shippings/drivers/update.ts +117 -0
- package/src/shippings/index.ts +39 -0
- package/src/shippings/license-keys/destroy.ts +111 -0
- package/src/shippings/license-keys/fetch.ts +20 -0
- package/src/shippings/license-keys/index.ts +26 -0
- package/src/shippings/license-keys/store.ts +77 -0
- package/src/shippings/license-keys/update.ts +117 -0
- package/src/shippings/shipping-methods/destroy.ts +111 -0
- package/src/shippings/shipping-methods/fetch.ts +62 -0
- package/src/shippings/shipping-methods/index.ts +28 -0
- package/src/shippings/shipping-methods/store.ts +123 -0
- package/src/shippings/shipping-methods/update.ts +120 -0
- package/src/shippings/shipping-rates/destroy.ts +102 -0
- package/src/shippings/shipping-rates/fetch.ts +320 -0
- package/src/shippings/shipping-rates/index.ts +36 -0
- package/src/shippings/shipping-rates/store.ts +102 -0
- package/src/shippings/shipping-rates/update.ts +139 -0
- package/src/shippings/shipping-zones/destroy.ts +111 -0
- package/src/shippings/shipping-zones/fetch.ts +96 -0
- package/src/shippings/shipping-zones/index.ts +26 -0
- package/src/shippings/shipping-zones/store.ts +74 -0
- package/src/shippings/shipping-zones/update.ts +156 -0
- package/src/tax/destroy.ts +54 -0
- package/src/tax/fetch.ts +20 -0
- package/src/tax/index.ts +24 -0
- package/src/tax/store.ts +69 -0
- package/src/tax/update.ts +119 -0
- package/src/tests/coupons.test.ts +90 -0
- package/src/tests/customers.test.ts +39 -0
- package/src/tests/device.test.ts +111 -0
- package/src/tests/digitals.test.ts +31 -0
- package/src/tests/drivers.test.ts +25 -0
- package/src/tests/gift-cards.test.ts +36 -0
- package/src/tests/license.test.ts +31 -0
- package/src/tests/manufacturers.test.ts +80 -0
- package/src/tests/orders.test.ts +25 -0
- package/src/tests/payments.test.ts +75 -0
- package/src/tests/rates.test.ts +367 -0
- package/src/tests/receipts.test.ts +287 -0
- package/src/tests/restaurant.test.ts +522 -0
- package/src/tests/reviews.test.ts +165 -0
- package/src/tests/routes.test.ts +51 -0
- package/src/tests/setup.ts +149 -0
- package/src/tests/shipping.test.ts +88 -0
- package/src/tests/tax.test.ts +25 -0
- package/src/tests/units.test.ts +110 -0
- package/src/tests/variants.test.ts +133 -0
- package/src/tests/waitlist.test.ts +303 -0
- package/src/tests/zones.test.ts +86 -0
- package/src/types.ts +312 -0
- package/src/utils/typed-stats.ts +67 -0
- package/src/waitlists/index.ts +19 -0
- package/src/waitlists/products/destroy.ts +65 -0
- package/src/waitlists/products/export.ts +113 -0
- package/src/waitlists/products/fetch.ts +362 -0
- package/src/waitlists/products/index.ts +33 -0
- package/src/waitlists/products/store.ts +112 -0
- package/src/waitlists/products/update.ts +125 -0
- package/src/waitlists/restaurant/destroy.ts +65 -0
- package/src/waitlists/restaurant/export.ts +107 -0
- package/src/waitlists/restaurant/fetch.ts +538 -0
- package/src/waitlists/restaurant/index.ts +43 -0
- package/src/waitlists/restaurant/store.ts +109 -0
- package/src/waitlists/restaurant/update.ts +198 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { db } from '@stacksjs/database'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Delete an order by ID
|
|
5
|
+
*
|
|
6
|
+
* @param id The ID of the order to delete
|
|
7
|
+
* @returns True if the order was deleted, false otherwise
|
|
8
|
+
*/
|
|
9
|
+
export async function destroy(id: number): Promise<boolean> {
|
|
10
|
+
try {
|
|
11
|
+
const result = await db
|
|
12
|
+
.deleteFrom('orders')
|
|
13
|
+
.where('id', '=', id)
|
|
14
|
+
.executeTakeFirst()
|
|
15
|
+
|
|
16
|
+
return Number(result.numDeletedRows) > 0
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (error instanceof Error) {
|
|
20
|
+
throw new TypeError(`Failed to delete order: ${error.message}`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
throw error
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Soft delete an order by ID (updates status to CANCELED)
|
|
29
|
+
*
|
|
30
|
+
* @param id The ID of the order to soft delete
|
|
31
|
+
* @returns True if the order was soft deleted, false otherwise
|
|
32
|
+
*/
|
|
33
|
+
export async function softDelete(id: number): Promise<boolean> {
|
|
34
|
+
try {
|
|
35
|
+
// Update the status to CANCELED instead of deleting
|
|
36
|
+
const result = await db
|
|
37
|
+
.updateTable('orders')
|
|
38
|
+
.set({ status: 'CANCELED' })
|
|
39
|
+
.where('id', '=', id)
|
|
40
|
+
.executeTakeFirst()
|
|
41
|
+
|
|
42
|
+
// Return true if any row was affected (updated)
|
|
43
|
+
return Number(result.numUpdatedRows) > 0
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (error instanceof Error) {
|
|
47
|
+
throw new TypeError(`Failed to soft delete order: ${error.message}`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
throw error
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Delete multiple orders by ID
|
|
56
|
+
*
|
|
57
|
+
* @param ids Array of order IDs to delete
|
|
58
|
+
* @returns Number of orders deleted
|
|
59
|
+
*/
|
|
60
|
+
export async function bulkDestroy(ids: number[]): Promise<number> {
|
|
61
|
+
if (!ids.length)
|
|
62
|
+
return 0
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// Perform the delete operation
|
|
66
|
+
const result = await db
|
|
67
|
+
.deleteFrom('orders')
|
|
68
|
+
.where('id', 'in', ids)
|
|
69
|
+
.executeTakeFirst()
|
|
70
|
+
|
|
71
|
+
// Return the number of deleted rows
|
|
72
|
+
return Number(result.numDeletedRows)
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
if (error instanceof Error) {
|
|
76
|
+
throw new TypeError(`Failed to delete orders: ${error.message}`)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
throw error
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Soft delete multiple orders by ID (updates status to CANCELED)
|
|
85
|
+
*
|
|
86
|
+
* @param ids Array of order IDs to soft delete
|
|
87
|
+
* @returns Number of orders soft deleted
|
|
88
|
+
*/
|
|
89
|
+
export async function bulkSoftDelete(ids: number[]): Promise<number> {
|
|
90
|
+
if (!ids.length)
|
|
91
|
+
return 0
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
// Update the status to CANCELED instead of deleting
|
|
95
|
+
const result = await db
|
|
96
|
+
.updateTable('orders')
|
|
97
|
+
.set({ status: 'CANCELED' })
|
|
98
|
+
.where('id', 'in', ids)
|
|
99
|
+
.executeTakeFirst()
|
|
100
|
+
|
|
101
|
+
// Return the number of updated rows
|
|
102
|
+
return Number(result.numUpdatedRows)
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (error instanceof Error) {
|
|
106
|
+
throw new TypeError(`Failed to soft delete orders: ${error.message}`)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
throw error
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commerce → events bus integration (stacksjs/stacks#1879 Co-18).
|
|
3
|
+
*
|
|
4
|
+
* Background: orders moved through created → paid → shipped →
|
|
5
|
+
* delivered states with no `events.dispatch(...)` calls. Every
|
|
6
|
+
* downstream service (email confirmation, inventory replenishment,
|
|
7
|
+
* analytics, fraud detection) had to poll the orders table because
|
|
8
|
+
* there was no subscription path. The framework's event bus from
|
|
9
|
+
* `@stacksjs/events` has been ready since #1878 — commerce just
|
|
10
|
+
* wasn't wired into it.
|
|
11
|
+
*
|
|
12
|
+
* This module wraps the dispatch calls so they:
|
|
13
|
+
* - Lazy-import the events package so commerce stays usable in
|
|
14
|
+
* environments that don't load it (CLI scripts, migrations)
|
|
15
|
+
* - Silently no-op when the events package isn't installed
|
|
16
|
+
* - Use a structured payload shape callers can subscribe with type safety
|
|
17
|
+
* - Never throw — emission failures shouldn't abort the order write
|
|
18
|
+
*
|
|
19
|
+
* Status-transition guard (stacksjs/stacks#1879 Co-4) lives here
|
|
20
|
+
* too so the rules are co-located with the events they trigger.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export type OrderStatus = 'PENDING' | 'PROCESSING' | 'SHIPPED' | 'DELIVERED' | 'CANCELLED' | 'REFUNDED'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whitelisted state transitions. Forward-only progressions and
|
|
27
|
+
* branch-to-cancel/refund from any non-terminal state. SHIPPED →
|
|
28
|
+
* PENDING and similar nonsense are rejected.
|
|
29
|
+
*
|
|
30
|
+
* REFUNDED and DELIVERED are terminal — nothing transitions out
|
|
31
|
+
* of them. CANCELLED can be re-entered (rare admin recovery), so
|
|
32
|
+
* we allow CANCELLED → PROCESSING but not the other terminals.
|
|
33
|
+
*/
|
|
34
|
+
const TRANSITIONS: Record<OrderStatus, OrderStatus[]> = {
|
|
35
|
+
PENDING: ['PROCESSING', 'CANCELLED'],
|
|
36
|
+
PROCESSING: ['SHIPPED', 'CANCELLED', 'REFUNDED'],
|
|
37
|
+
SHIPPED: ['DELIVERED', 'REFUNDED'],
|
|
38
|
+
DELIVERED: ['REFUNDED'], // post-delivery refund is the only legal exit
|
|
39
|
+
CANCELLED: ['PROCESSING'], // admin recovery — rare but supported
|
|
40
|
+
REFUNDED: [], // terminal
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Check whether `from → to` is a legal status transition
|
|
45
|
+
* (stacksjs/stacks#1879 Co-4). Use in `updateStatus` to reject
|
|
46
|
+
* illegal jumps like SHIPPED → PENDING before they hit the database.
|
|
47
|
+
*/
|
|
48
|
+
export function canTransition(from: OrderStatus, to: OrderStatus): boolean {
|
|
49
|
+
if (from === to) return true // no-op
|
|
50
|
+
return TRANSITIONS[from]?.includes(to) ?? false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Best-effort event dispatch. Catches the lazy-import miss (events
|
|
55
|
+
* package not installed) and any per-listener throw — order writes
|
|
56
|
+
* MUST NOT depend on the events bus's health.
|
|
57
|
+
*/
|
|
58
|
+
async function emitOrderEvent(eventName: string, payload: Record<string, unknown>): Promise<void> {
|
|
59
|
+
try {
|
|
60
|
+
const mod = await import('@stacksjs/events').catch(() => null)
|
|
61
|
+
if (!mod) return
|
|
62
|
+
const dispatch = (mod as { dispatch?: (t: string, p: unknown) => void }).dispatch
|
|
63
|
+
if (typeof dispatch !== 'function') return
|
|
64
|
+
dispatch(eventName, payload)
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Swallow — the events bus is a notification side-channel, not
|
|
68
|
+
// the source of truth. A listener that throws shouldn't be able
|
|
69
|
+
// to abort an order write. The events package itself logs handler
|
|
70
|
+
// errors via #1876 O-2 / #1878 E-1 paths.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Emit `order:created` after a successful placeOrder / store call. */
|
|
75
|
+
export async function emitOrderCreated(order: Record<string, unknown>): Promise<void> {
|
|
76
|
+
await emitOrderEvent('order:created', { order })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Emit `order:paid` after payment confirmation lands. */
|
|
80
|
+
export async function emitOrderPaid(order: Record<string, unknown>, payment?: Record<string, unknown>): Promise<void> {
|
|
81
|
+
await emitOrderEvent('order:paid', { order, payment })
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Emit `order:shipped` after fulfillment marks the order shipped. */
|
|
85
|
+
export async function emitOrderShipped(order: Record<string, unknown>): Promise<void> {
|
|
86
|
+
await emitOrderEvent('order:shipped', { order })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Emit `order:delivered` after the carrier confirms delivery. */
|
|
90
|
+
export async function emitOrderDelivered(order: Record<string, unknown>): Promise<void> {
|
|
91
|
+
await emitOrderEvent('order:delivered', { order })
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Emit `order:cancelled` on cancellation (pre- or post-payment). */
|
|
95
|
+
export async function emitOrderCancelled(order: Record<string, unknown>, reason?: string): Promise<void> {
|
|
96
|
+
await emitOrderEvent('order:cancelled', { order, reason })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Emit `order:refunded` after a refund settles. */
|
|
100
|
+
export async function emitOrderRefunded(order: Record<string, unknown>, refundAmount?: number): Promise<void> {
|
|
101
|
+
await emitOrderEvent('order:refunded', { order, refundAmount })
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Convenience dispatch keyed by status. Used from `updateStatus`
|
|
106
|
+
* so a single function fires the right event for whichever status
|
|
107
|
+
* we just transitioned into.
|
|
108
|
+
*/
|
|
109
|
+
export async function emitForStatus(status: OrderStatus, order: Record<string, unknown>): Promise<void> {
|
|
110
|
+
switch (status) {
|
|
111
|
+
case 'PROCESSING': return emitOrderPaid(order)
|
|
112
|
+
case 'SHIPPED': return emitOrderShipped(order)
|
|
113
|
+
case 'DELIVERED': return emitOrderDelivered(order)
|
|
114
|
+
case 'CANCELLED': return emitOrderCancelled(order)
|
|
115
|
+
case 'REFUNDED': return emitOrderRefunded(order)
|
|
116
|
+
// PENDING fires no event — the initial state is already covered
|
|
117
|
+
// by `order:created` from the placeOrder path.
|
|
118
|
+
case 'PENDING':
|
|
119
|
+
default:
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { SpreadsheetWrapper } from 'ts-spreadsheets'
|
|
2
|
+
import type { OrderWithTotals } from '../types'
|
|
3
|
+
import { db } from '@stacksjs/database'
|
|
4
|
+
import { createSpreadsheet } from 'ts-spreadsheets'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Represents the structure of an exported order
|
|
8
|
+
*/
|
|
9
|
+
export interface ExportedOrder {
|
|
10
|
+
'Order ID': number
|
|
11
|
+
'Customer': string
|
|
12
|
+
'Date': string
|
|
13
|
+
'Total': number
|
|
14
|
+
'Status': string
|
|
15
|
+
'Items': string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Export orders to a spreadsheet
|
|
20
|
+
* @param format The format of the spreadsheet (default is CSV)
|
|
21
|
+
* @returns Spreadsheet object ready for download or storage
|
|
22
|
+
*/
|
|
23
|
+
export async function exportOrders(format: 'csv' | 'excel' = 'csv'): Promise<SpreadsheetWrapper> {
|
|
24
|
+
// Fetch all orders with their details
|
|
25
|
+
const orders = await fetchAllWithDetails()
|
|
26
|
+
|
|
27
|
+
// Prepare data for spreadsheet
|
|
28
|
+
const spreadsheetData = prepareOrdersForExport(orders)
|
|
29
|
+
|
|
30
|
+
// Create and return spreadsheet
|
|
31
|
+
return createSpreadsheet(spreadsheetData, { type: format })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Fetch all orders with customer names and order items
|
|
36
|
+
*/
|
|
37
|
+
async function fetchAllWithDetails(): Promise<OrderWithTotals[] | []> {
|
|
38
|
+
const ordersWithCustomers = await db
|
|
39
|
+
.selectFrom('orders')
|
|
40
|
+
.leftJoin('customers', 'customers.id', '=', 'orders.customer_id')
|
|
41
|
+
.selectAll()
|
|
42
|
+
.select(['customers.name as customer_name', 'customers.email as customer_email'])
|
|
43
|
+
.execute()
|
|
44
|
+
|
|
45
|
+
const orderIds = ordersWithCustomers.map((order: any) => order.id)
|
|
46
|
+
|
|
47
|
+
if (orderIds.length === 0) {
|
|
48
|
+
return []
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Fetch order items related only to the fetched orders
|
|
52
|
+
const allOrderItems = await db
|
|
53
|
+
.selectFrom('order_items')
|
|
54
|
+
.where('order_items.order_id', 'in', orderIds)
|
|
55
|
+
.select([
|
|
56
|
+
'order_items.order_id',
|
|
57
|
+
'order_items.quantity',
|
|
58
|
+
'order_items.price',
|
|
59
|
+
])
|
|
60
|
+
.execute()
|
|
61
|
+
|
|
62
|
+
const orderItemsMap = allOrderItems.reduce(
|
|
63
|
+
(acc: any, item: any) => {
|
|
64
|
+
if (!item.order_id)
|
|
65
|
+
return acc // Skip items without order_id
|
|
66
|
+
|
|
67
|
+
if (!acc[item.order_id]) {
|
|
68
|
+
acc[item.order_id] = {
|
|
69
|
+
totalItems: 0,
|
|
70
|
+
totalPrice: 0,
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
acc[item.order_id].totalItems += item.quantity
|
|
75
|
+
acc[item.order_id].totalPrice += item.price * item.quantity
|
|
76
|
+
|
|
77
|
+
return acc
|
|
78
|
+
},
|
|
79
|
+
{},
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
const enrichedOrders = ordersWithCustomers.map((order: any) => {
|
|
83
|
+
const orderItemData = orderItemsMap[order.id] || { totalItems: 0, totalPrice: 0 }
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
...order,
|
|
87
|
+
totalItems: orderItemData.totalItems,
|
|
88
|
+
totalPrice: orderItemData.totalPrice,
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
return enrichedOrders as OrderWithTotals[]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Prepare orders data for spreadsheet export
|
|
97
|
+
* @param orders Array of order objects
|
|
98
|
+
* @returns Spreadsheet data structure
|
|
99
|
+
*/
|
|
100
|
+
function prepareOrdersForExport(orders: OrderWithTotals[]) {
|
|
101
|
+
// Define headings
|
|
102
|
+
const headings: (keyof ExportedOrder)[] = [
|
|
103
|
+
'Order ID',
|
|
104
|
+
'Customer',
|
|
105
|
+
'Date',
|
|
106
|
+
'Total',
|
|
107
|
+
'Status',
|
|
108
|
+
'Items',
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
// Transform orders into export format
|
|
112
|
+
const data = orders.map((order: any) => {
|
|
113
|
+
// Convert items to a readable string
|
|
114
|
+
const itemsString = order.order_items
|
|
115
|
+
?.map((item: any) => `${item.product?.name} (Qty: ${item.quantity}, Price: $${item.price})`)
|
|
116
|
+
.join(' | ') || 'No Items'
|
|
117
|
+
|
|
118
|
+
return [
|
|
119
|
+
order.id,
|
|
120
|
+
order.customer?.name || 'N/A',
|
|
121
|
+
order.created_at,
|
|
122
|
+
order.total_amount,
|
|
123
|
+
order.status,
|
|
124
|
+
itemsString,
|
|
125
|
+
]
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
return { headings, data }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Export orders and automatically download
|
|
133
|
+
* @param format The format of the spreadsheet (default is CSV)
|
|
134
|
+
* @param filename Optional filename for the download
|
|
135
|
+
* @returns Download response
|
|
136
|
+
*/
|
|
137
|
+
export async function downloadOrders(format: 'csv' | 'excel' = 'csv', filename?: string): Promise<Response> {
|
|
138
|
+
const spreadsheet = await exportOrders(format)
|
|
139
|
+
|
|
140
|
+
// Use default filename if not provided
|
|
141
|
+
const defaultFilename = `orders_export_${new Date().toISOString().split('T')[0]}.${format}`
|
|
142
|
+
return spreadsheet.download(filename || defaultFilename)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Store orders export to disk
|
|
147
|
+
* @param format The format of the spreadsheet (default is CSV)
|
|
148
|
+
* @param path Optional path to store the file
|
|
149
|
+
* @returns Path where the file is stored
|
|
150
|
+
*/
|
|
151
|
+
export async function storeOrdersExport(format: 'csv' | 'excel' = 'csv', path?: string): Promise<void> {
|
|
152
|
+
const spreadsheet = await exportOrders(format)
|
|
153
|
+
|
|
154
|
+
// Use default path and filename if not provided
|
|
155
|
+
const defaultPath = `orders_export_${new Date().toISOString().split('T')[0]}.${format}`
|
|
156
|
+
|
|
157
|
+
spreadsheet.store(path || defaultPath)
|
|
158
|
+
}
|