@fayz-ai/plugin-orders 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +50 -0
- package/dist/OrdersContext.d.ts +40 -0
- package/dist/OrdersContext.d.ts.map +1 -0
- package/dist/OrdersPage-2IXARHDR.cjs +157 -0
- package/dist/OrdersPage-2IXARHDR.cjs.map +1 -0
- package/dist/OrdersPage-LJIEIZYE.js +155 -0
- package/dist/OrdersPage-LJIEIZYE.js.map +1 -0
- package/dist/OrdersPage.d.ts +13 -0
- package/dist/OrdersPage.d.ts.map +1 -0
- package/dist/chunk-7LFVFZKZ.js +23 -0
- package/dist/chunk-7LFVFZKZ.js.map +1 -0
- package/dist/chunk-WBGECNYU.cjs +28 -0
- package/dist/chunk-WBGECNYU.cjs.map +1 -0
- package/dist/data/fayz.d.ts +18 -0
- package/dist/data/fayz.d.ts.map +1 -0
- package/dist/data/mock.d.ts +3 -0
- package/dist/data/mock.d.ts.map +1 -0
- package/dist/data/types.d.ts +17 -0
- package/dist/data/types.d.ts.map +1 -0
- package/dist/index.cjs +1185 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +42 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1178 -0
- package/dist/index.js.map +1 -0
- package/dist/locales/en.d.ts +2 -0
- package/dist/locales/en.d.ts.map +1 -0
- package/dist/locales/index.d.ts +2 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/pt-BR.d.ts +2 -0
- package/dist/locales/pt-BR.d.ts.map +1 -0
- package/dist/registries.d.ts +3 -0
- package/dist/registries.d.ts.map +1 -0
- package/dist/store.d.ts +29 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/types.d.ts +119 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/views/OrderKanbanView.d.ts +3 -0
- package/dist/views/OrderKanbanView.d.ts.map +1 -0
- package/package.json +54 -0
- package/src/OrdersContext.tsx +47 -0
- package/src/OrdersPage.tsx +20 -0
- package/src/data/fayz.ts +456 -0
- package/src/data/mock.ts +512 -0
- package/src/data/types.ts +23 -0
- package/src/index.ts +217 -0
- package/src/locales/en.ts +53 -0
- package/src/locales/index.ts +4 -0
- package/src/locales/pt-BR.ts +53 -0
- package/src/registries.ts +32 -0
- package/src/store.ts +191 -0
- package/src/types.ts +190 -0
- package/src/views/OrderKanbanView.tsx +240 -0
package/src/data/fayz.ts
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
import { createFayzClient, type FayzClientOptions, type FayzTableFilter } from '@fayz-ai/sdk'
|
|
2
|
+
import type {
|
|
3
|
+
CreateOrderInput,
|
|
4
|
+
CreateOrderItemInput,
|
|
5
|
+
OrderKind,
|
|
6
|
+
OrderQuery,
|
|
7
|
+
OrdersSummary,
|
|
8
|
+
OrderStatus,
|
|
9
|
+
PaginatedResult,
|
|
10
|
+
RestaurantOrder,
|
|
11
|
+
RestaurantOrderItem,
|
|
12
|
+
UpdateOrderStatusInput,
|
|
13
|
+
} from '../types'
|
|
14
|
+
import type { OrdersDataProvider } from './types'
|
|
15
|
+
|
|
16
|
+
export interface FayzOrdersProviderOptions extends FayzClientOptions {
|
|
17
|
+
projectId?: string
|
|
18
|
+
schema?: string
|
|
19
|
+
runtime?: boolean
|
|
20
|
+
ordersTable?: string
|
|
21
|
+
orderItemsTable?: string
|
|
22
|
+
/**
|
|
23
|
+
* Active tenant id (or a getter) used to scope reads and stamp writes on the
|
|
24
|
+
* orders table. When omitted, scoping is left to the runtime/RLS context.
|
|
25
|
+
* Only the orders table carries `tenant_id`; order_items scope via their
|
|
26
|
+
* parent order, so this never touches the order-items table.
|
|
27
|
+
*/
|
|
28
|
+
tenantId?: string | (() => string | undefined | null)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface OrderRow {
|
|
32
|
+
id: string
|
|
33
|
+
tenant_id?: string | null
|
|
34
|
+
reference_number?: string | null
|
|
35
|
+
kind?: OrderKind | null
|
|
36
|
+
status?: OrderStatus | null
|
|
37
|
+
party_id?: string | null
|
|
38
|
+
party_name?: string | null
|
|
39
|
+
assignee_id?: string | null
|
|
40
|
+
assignee_name?: string | null
|
|
41
|
+
location_id?: string | null
|
|
42
|
+
subtotal?: number | string | null
|
|
43
|
+
discount?: number | string | null
|
|
44
|
+
tax?: number | string | null
|
|
45
|
+
total?: number | string | null
|
|
46
|
+
currency?: string | null
|
|
47
|
+
notes?: string | null
|
|
48
|
+
tags?: string[] | null
|
|
49
|
+
source?: string | null
|
|
50
|
+
table_id?: string | null
|
|
51
|
+
table_name?: string | null
|
|
52
|
+
estimated_minutes?: number | null
|
|
53
|
+
metadata?: Record<string, unknown> | null
|
|
54
|
+
created_at?: string | null
|
|
55
|
+
updated_at?: string | null
|
|
56
|
+
completed_at?: string | null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface OrderItemRow {
|
|
60
|
+
id: string
|
|
61
|
+
order_id?: string | null
|
|
62
|
+
product_id?: string | null
|
|
63
|
+
name?: string | null
|
|
64
|
+
description?: string | null
|
|
65
|
+
quantity?: number | string | null
|
|
66
|
+
unit_price?: number | string | null
|
|
67
|
+
discount?: number | string | null
|
|
68
|
+
total?: number | string | null
|
|
69
|
+
sort_order?: number | null
|
|
70
|
+
modifiers?: string | null
|
|
71
|
+
kitchen_notes?: string | null
|
|
72
|
+
metadata?: Record<string, unknown> | null
|
|
73
|
+
created_at?: string | null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const DEFAULT_ORDERS_TABLE = 'orders'
|
|
77
|
+
const DEFAULT_ORDER_ITEMS_TABLE = 'order_items'
|
|
78
|
+
const ACTIVE_STATUSES = new Set<OrderStatus>(['new', 'confirmed', 'preparing', 'ready', 'served', 'dispatched'])
|
|
79
|
+
|
|
80
|
+
function nowIso(): string {
|
|
81
|
+
return new Date().toISOString()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function numberValue(value: number | string | null | undefined): number {
|
|
85
|
+
if (typeof value === 'number') return value
|
|
86
|
+
if (typeof value === 'string') return Number(value) || 0
|
|
87
|
+
return 0
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function dateKey(value: string): string {
|
|
91
|
+
return value.slice(0, 10)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function referenceNumber(): string {
|
|
95
|
+
return `ORD-${Date.now().toString().slice(-6)}`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function itemToRow(item: CreateOrderItemInput, orderId: string, sortOrder: number): Record<string, unknown> {
|
|
99
|
+
const discount = item.discount ?? 0
|
|
100
|
+
return {
|
|
101
|
+
order_id: orderId,
|
|
102
|
+
product_id: item.productId ?? null,
|
|
103
|
+
name: item.name,
|
|
104
|
+
quantity: item.quantity,
|
|
105
|
+
unit_price: item.unitPrice,
|
|
106
|
+
discount,
|
|
107
|
+
total: item.quantity * item.unitPrice - discount,
|
|
108
|
+
sort_order: sortOrder,
|
|
109
|
+
modifiers: item.modifiers ?? null,
|
|
110
|
+
kitchen_notes: item.kitchenNotes ?? null,
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function rowToItem(row: OrderItemRow): RestaurantOrderItem {
|
|
115
|
+
const quantity = numberValue(row.quantity)
|
|
116
|
+
const unitPrice = numberValue(row.unit_price)
|
|
117
|
+
const discount = numberValue(row.discount)
|
|
118
|
+
return {
|
|
119
|
+
id: row.id,
|
|
120
|
+
orderId: row.order_id ?? '',
|
|
121
|
+
productId: row.product_id ?? undefined,
|
|
122
|
+
name: row.name ?? 'Item',
|
|
123
|
+
description: row.description ?? undefined,
|
|
124
|
+
quantity,
|
|
125
|
+
unitPrice,
|
|
126
|
+
discount,
|
|
127
|
+
total: numberValue(row.total) || quantity * unitPrice - discount,
|
|
128
|
+
sortOrder: row.sort_order ?? 0,
|
|
129
|
+
modifiers: row.modifiers ?? undefined,
|
|
130
|
+
kitchenNotes: row.kitchen_notes ?? undefined,
|
|
131
|
+
metadata: row.metadata ?? {},
|
|
132
|
+
createdAt: row.created_at ?? nowIso(),
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function rowToOrder(row: OrderRow, items: RestaurantOrderItem[] = []): RestaurantOrder {
|
|
137
|
+
const subtotal = numberValue(row.subtotal) || items.reduce((sum, item) => sum + item.total, 0)
|
|
138
|
+
const discount = numberValue(row.discount)
|
|
139
|
+
const tax = numberValue(row.tax)
|
|
140
|
+
return {
|
|
141
|
+
id: row.id,
|
|
142
|
+
referenceNumber: row.reference_number ?? row.id,
|
|
143
|
+
kind: row.kind ?? 'dine_in',
|
|
144
|
+
status: row.status ?? 'new',
|
|
145
|
+
partyId: row.party_id ?? undefined,
|
|
146
|
+
partyName: row.party_name ?? undefined,
|
|
147
|
+
assigneeId: row.assignee_id ?? undefined,
|
|
148
|
+
assigneeName: row.assignee_name ?? undefined,
|
|
149
|
+
locationId: row.location_id ?? undefined,
|
|
150
|
+
subtotal,
|
|
151
|
+
discount,
|
|
152
|
+
tax,
|
|
153
|
+
total: numberValue(row.total) || subtotal - discount + tax,
|
|
154
|
+
currency: row.currency ?? 'BRL',
|
|
155
|
+
notes: row.notes ?? undefined,
|
|
156
|
+
tags: row.tags ?? [],
|
|
157
|
+
source: row.source ?? 'pos',
|
|
158
|
+
tableId: row.table_id ?? undefined,
|
|
159
|
+
tableName: row.table_name ?? undefined,
|
|
160
|
+
estimatedMinutes: row.estimated_minutes ?? undefined,
|
|
161
|
+
items,
|
|
162
|
+
metadata: row.metadata ?? {},
|
|
163
|
+
tenantId: row.tenant_id ?? 'runtime-tenant',
|
|
164
|
+
createdAt: row.created_at ?? nowIso(),
|
|
165
|
+
updatedAt: row.updated_at ?? row.created_at ?? nowIso(),
|
|
166
|
+
completedAt: row.completed_at ?? undefined,
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function orderPayload(input: CreateOrderInput): Record<string, unknown> {
|
|
171
|
+
const subtotal = input.items.reduce((sum, item) => sum + item.quantity * item.unitPrice - (item.discount ?? 0), 0)
|
|
172
|
+
return {
|
|
173
|
+
reference_number: referenceNumber(),
|
|
174
|
+
kind: input.kind,
|
|
175
|
+
status: 'new',
|
|
176
|
+
source: input.source ?? 'pos',
|
|
177
|
+
table_id: input.tableId ?? null,
|
|
178
|
+
party_id: input.partyId ?? null,
|
|
179
|
+
assignee_id: input.assigneeId ?? null,
|
|
180
|
+
notes: input.notes ?? null,
|
|
181
|
+
estimated_minutes: input.estimatedMinutes ?? null,
|
|
182
|
+
subtotal,
|
|
183
|
+
discount: 0,
|
|
184
|
+
tax: 0,
|
|
185
|
+
total: subtotal,
|
|
186
|
+
currency: 'BRL',
|
|
187
|
+
tags: [],
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function updatePayload(data: Partial<RestaurantOrder>): Record<string, unknown> {
|
|
192
|
+
const payload: Record<string, unknown> = {}
|
|
193
|
+
if (data.referenceNumber !== undefined) payload.reference_number = data.referenceNumber
|
|
194
|
+
if (data.kind !== undefined) payload.kind = data.kind
|
|
195
|
+
if (data.status !== undefined) payload.status = data.status
|
|
196
|
+
if (data.partyId !== undefined) payload.party_id = data.partyId
|
|
197
|
+
if (data.partyName !== undefined) payload.party_name = data.partyName
|
|
198
|
+
if (data.assigneeId !== undefined) payload.assignee_id = data.assigneeId
|
|
199
|
+
if (data.assigneeName !== undefined) payload.assignee_name = data.assigneeName
|
|
200
|
+
if (data.locationId !== undefined) payload.location_id = data.locationId
|
|
201
|
+
if (data.subtotal !== undefined) payload.subtotal = data.subtotal
|
|
202
|
+
if (data.discount !== undefined) payload.discount = data.discount
|
|
203
|
+
if (data.tax !== undefined) payload.tax = data.tax
|
|
204
|
+
if (data.total !== undefined) payload.total = data.total
|
|
205
|
+
if (data.currency !== undefined) payload.currency = data.currency
|
|
206
|
+
if (data.notes !== undefined) payload.notes = data.notes
|
|
207
|
+
if (data.tags !== undefined) payload.tags = data.tags
|
|
208
|
+
if (data.source !== undefined) payload.source = data.source
|
|
209
|
+
if (data.tableId !== undefined) payload.table_id = data.tableId
|
|
210
|
+
if (data.tableName !== undefined) payload.table_name = data.tableName
|
|
211
|
+
if (data.estimatedMinutes !== undefined) payload.estimated_minutes = data.estimatedMinutes
|
|
212
|
+
if (data.metadata !== undefined) payload.metadata = data.metadata
|
|
213
|
+
if (data.completedAt !== undefined) payload.completed_at = data.completedAt
|
|
214
|
+
return payload
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function filtersFromQuery(query: OrderQuery): FayzTableFilter[] {
|
|
218
|
+
const filters: FayzTableFilter[] = []
|
|
219
|
+
if (query.kind && !Array.isArray(query.kind)) filters.push({ column: 'kind', operator: 'eq', value: query.kind })
|
|
220
|
+
if (query.status && !Array.isArray(query.status)) filters.push({ column: 'status', operator: 'eq', value: query.status })
|
|
221
|
+
if (query.source) filters.push({ column: 'source', operator: 'eq', value: query.source })
|
|
222
|
+
if (query.tableId) filters.push({ column: 'table_id', operator: 'eq', value: query.tableId })
|
|
223
|
+
if (query.assigneeId) filters.push({ column: 'assignee_id', operator: 'eq', value: query.assigneeId })
|
|
224
|
+
return filters
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function matchesClientQuery(order: RestaurantOrder, query: OrderQuery): boolean {
|
|
228
|
+
if (Array.isArray(query.kind) && !query.kind.includes(order.kind)) return false
|
|
229
|
+
if (Array.isArray(query.status) && !query.status.includes(order.status)) return false
|
|
230
|
+
if (query.dateRange) {
|
|
231
|
+
const created = dateKey(order.createdAt)
|
|
232
|
+
if (created < query.dateRange.from || created > query.dateRange.to) return false
|
|
233
|
+
}
|
|
234
|
+
if (query.search) {
|
|
235
|
+
const search = query.search.toLowerCase()
|
|
236
|
+
return order.referenceNumber.toLowerCase().includes(search)
|
|
237
|
+
|| (order.partyName?.toLowerCase().includes(search) ?? false)
|
|
238
|
+
|| (order.tableName?.toLowerCase().includes(search) ?? false)
|
|
239
|
+
|| order.items.some((item) => item.name.toLowerCase().includes(search))
|
|
240
|
+
}
|
|
241
|
+
return true
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function paginate<T>(items: T[], page?: number, pageSize?: number): PaginatedResult<T> {
|
|
245
|
+
const currentPage = page ?? 1
|
|
246
|
+
const limit = pageSize ?? 20
|
|
247
|
+
const start = (currentPage - 1) * limit
|
|
248
|
+
return { data: items.slice(start, start + limit), total: items.length }
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function summarize(orders: RestaurantOrder[], dateRange?: { from: string; to: string }): OrdersSummary {
|
|
252
|
+
const today = dateKey(nowIso())
|
|
253
|
+
const from = dateRange?.from ?? today
|
|
254
|
+
const to = dateRange?.to ?? today
|
|
255
|
+
const inRange = orders.filter((order) => {
|
|
256
|
+
const created = dateKey(order.createdAt)
|
|
257
|
+
return created >= from && created <= to
|
|
258
|
+
})
|
|
259
|
+
const billable = inRange.filter((order) => order.status !== 'cancelled')
|
|
260
|
+
const revenue = billable.reduce((sum, order) => sum + order.total, 0)
|
|
261
|
+
const ordersByKind: Record<OrderKind, number> = { dine_in: 0, takeout: 0, delivery: 0 }
|
|
262
|
+
const ordersByStatus: Record<string, number> = {}
|
|
263
|
+
const top = new Map<string, { name: string; quantity: number; revenue: number }>()
|
|
264
|
+
|
|
265
|
+
for (const order of inRange) {
|
|
266
|
+
ordersByKind[order.kind] = (ordersByKind[order.kind] ?? 0) + 1
|
|
267
|
+
ordersByStatus[order.status] = (ordersByStatus[order.status] ?? 0) + 1
|
|
268
|
+
if (order.status === 'cancelled') continue
|
|
269
|
+
for (const item of order.items) {
|
|
270
|
+
const current = top.get(item.name) ?? { name: item.name, quantity: 0, revenue: 0 }
|
|
271
|
+
current.quantity += item.quantity
|
|
272
|
+
current.revenue += item.total
|
|
273
|
+
top.set(item.name, current)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
todayOrderCount: inRange.length,
|
|
279
|
+
todayRevenue: revenue,
|
|
280
|
+
averageTicket: billable.length > 0 ? revenue / billable.length : 0,
|
|
281
|
+
activeOrderCount: orders.filter((order) => ACTIVE_STATUSES.has(order.status)).length,
|
|
282
|
+
ordersByKind,
|
|
283
|
+
ordersByStatus,
|
|
284
|
+
topItems: [...top.values()].sort((a, b) => b.quantity - a.quantity).slice(0, 5),
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function createFayzOrdersProvider(options: FayzOrdersProviderOptions = {}): OrdersDataProvider {
|
|
289
|
+
const client = createFayzClient(options)
|
|
290
|
+
const ordersTable = options.ordersTable ?? DEFAULT_ORDERS_TABLE
|
|
291
|
+
const orderItemsTable = options.orderItemsTable ?? DEFAULT_ORDER_ITEMS_TABLE
|
|
292
|
+
const tableOptions = { projectId: options.projectId, schema: options.schema, runtime: options.runtime }
|
|
293
|
+
|
|
294
|
+
function resolveTenant(): string | undefined {
|
|
295
|
+
const value = typeof options.tenantId === 'function' ? options.tenantId() : options.tenantId
|
|
296
|
+
return value ?? undefined
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function withTenant(filters: FayzTableFilter[]): FayzTableFilter[] {
|
|
300
|
+
const tenantId = resolveTenant()
|
|
301
|
+
return tenantId ? [{ column: 'tenant_id', operator: 'eq', value: tenantId }, ...filters] : filters
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async function listItemRows(orderIds: string[]): Promise<OrderItemRow[]> {
|
|
305
|
+
if (orderIds.length === 0) return []
|
|
306
|
+
const rows: OrderItemRow[] = []
|
|
307
|
+
await Promise.all(orderIds.map(async (orderId) => {
|
|
308
|
+
const response = await client.data.listRows<OrderItemRow>({
|
|
309
|
+
...tableOptions,
|
|
310
|
+
table: orderItemsTable,
|
|
311
|
+
filters: [{ column: 'order_id', operator: 'eq', value: orderId }],
|
|
312
|
+
sortColumn: 'sort_order',
|
|
313
|
+
sortDirection: 'asc',
|
|
314
|
+
limit: 200,
|
|
315
|
+
})
|
|
316
|
+
rows.push(...response.rows)
|
|
317
|
+
}))
|
|
318
|
+
return rows
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function hydrate(rows: OrderRow[]): Promise<RestaurantOrder[]> {
|
|
322
|
+
const itemRows = await listItemRows(rows.map((row) => row.id))
|
|
323
|
+
const byOrder = new Map<string, RestaurantOrderItem[]>()
|
|
324
|
+
for (const itemRow of itemRows) {
|
|
325
|
+
const item = rowToItem(itemRow)
|
|
326
|
+
const list = byOrder.get(item.orderId) ?? []
|
|
327
|
+
list.push(item)
|
|
328
|
+
byOrder.set(item.orderId, list)
|
|
329
|
+
}
|
|
330
|
+
return rows.map((row) => rowToOrder(row, byOrder.get(row.id) ?? []))
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function listOrders(query: OrderQuery = {}): Promise<RestaurantOrder[]> {
|
|
334
|
+
const response = await client.data.listRows<OrderRow>({
|
|
335
|
+
...tableOptions,
|
|
336
|
+
table: ordersTable,
|
|
337
|
+
filters: withTenant(filtersFromQuery(query)),
|
|
338
|
+
sortColumn: 'created_at',
|
|
339
|
+
sortDirection: 'desc',
|
|
340
|
+
limit: 500,
|
|
341
|
+
})
|
|
342
|
+
return (await hydrate(response.rows)).filter((order) => matchesClientQuery(order, query))
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function getById(id: string): Promise<RestaurantOrder | null> {
|
|
346
|
+
const response = await client.data.listRows<OrderRow>({
|
|
347
|
+
...tableOptions,
|
|
348
|
+
table: ordersTable,
|
|
349
|
+
filters: withTenant([{ column: 'id', operator: 'eq', value: id }]),
|
|
350
|
+
limit: 1,
|
|
351
|
+
})
|
|
352
|
+
return (await hydrate(response.rows))[0] ?? null
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
async getOrders(query) {
|
|
357
|
+
const orders = await listOrders(query)
|
|
358
|
+
return paginate(orders, query.page, query.pageSize)
|
|
359
|
+
},
|
|
360
|
+
|
|
361
|
+
async getOrderById(id) {
|
|
362
|
+
return getById(id)
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
async createOrder(input) {
|
|
366
|
+
const tenantId = resolveTenant()
|
|
367
|
+
const orderRow = await client.data.createRow<OrderRow>({
|
|
368
|
+
...tableOptions,
|
|
369
|
+
table: ordersTable,
|
|
370
|
+
row: tenantId ? { ...orderPayload(input), tenant_id: tenantId } : orderPayload(input),
|
|
371
|
+
})
|
|
372
|
+
await Promise.all(input.items.map((item, index) => client.data.createRow<OrderItemRow>({
|
|
373
|
+
...tableOptions,
|
|
374
|
+
table: orderItemsTable,
|
|
375
|
+
row: itemToRow(item, orderRow.id, index),
|
|
376
|
+
})))
|
|
377
|
+
return (await getById(orderRow.id)) ?? rowToOrder(orderRow)
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
async updateOrderStatus(input: UpdateOrderStatusInput) {
|
|
381
|
+
const completedAt = input.status === 'completed' ? nowIso() : undefined
|
|
382
|
+
return this.updateOrder(input.orderId, { status: input.status, completedAt })
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
async updateOrder(id, data) {
|
|
386
|
+
const row = await client.data.updateRow<OrderRow>({
|
|
387
|
+
...tableOptions,
|
|
388
|
+
table: ordersTable,
|
|
389
|
+
primaryKeys: { id },
|
|
390
|
+
row: updatePayload(data),
|
|
391
|
+
})
|
|
392
|
+
return (await getById(row.id)) ?? rowToOrder(row)
|
|
393
|
+
},
|
|
394
|
+
|
|
395
|
+
async cancelOrder(id, reason) {
|
|
396
|
+
const order = await getById(id)
|
|
397
|
+
await this.updateOrder(id, {
|
|
398
|
+
status: 'cancelled',
|
|
399
|
+
notes: reason ? `${order?.notes ? `${order.notes}\n` : ''}Cancelled: ${reason}` : order?.notes,
|
|
400
|
+
})
|
|
401
|
+
},
|
|
402
|
+
|
|
403
|
+
async addOrderItem(orderId, item) {
|
|
404
|
+
const order = await getById(orderId)
|
|
405
|
+
const row = await client.data.createRow<OrderItemRow>({
|
|
406
|
+
...tableOptions,
|
|
407
|
+
table: orderItemsTable,
|
|
408
|
+
row: itemToRow(item, orderId, order?.items.length ?? 0),
|
|
409
|
+
})
|
|
410
|
+
const created = rowToItem(row)
|
|
411
|
+
const items = [...(order?.items ?? []), created]
|
|
412
|
+
await this.updateOrder(orderId, {
|
|
413
|
+
subtotal: items.reduce((sum, orderItem) => sum + orderItem.total, 0),
|
|
414
|
+
total: items.reduce((sum, orderItem) => sum + orderItem.total, 0) - (order?.discount ?? 0) + (order?.tax ?? 0),
|
|
415
|
+
})
|
|
416
|
+
return created
|
|
417
|
+
},
|
|
418
|
+
|
|
419
|
+
async removeOrderItem(itemId) {
|
|
420
|
+
const orders = await listOrders()
|
|
421
|
+
const order = orders.find((item) => item.items.some((orderItem) => orderItem.id === itemId))
|
|
422
|
+
await client.data.deleteRows({ ...tableOptions, table: orderItemsTable, rows: [{ id: itemId }] })
|
|
423
|
+
if (order) {
|
|
424
|
+
const items = order.items.filter((item) => item.id !== itemId)
|
|
425
|
+
const subtotal = items.reduce((sum, item) => sum + item.total, 0)
|
|
426
|
+
await this.updateOrder(order.id, { subtotal, total: subtotal - order.discount + order.tax })
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
|
|
430
|
+
async updateOrderItem(itemId, data) {
|
|
431
|
+
const row = await client.data.updateRow<OrderItemRow>({
|
|
432
|
+
...tableOptions,
|
|
433
|
+
table: orderItemsTable,
|
|
434
|
+
primaryKeys: { id: itemId },
|
|
435
|
+
row: {
|
|
436
|
+
product_id: data.productId,
|
|
437
|
+
name: data.name,
|
|
438
|
+
description: data.description,
|
|
439
|
+
quantity: data.quantity,
|
|
440
|
+
unit_price: data.unitPrice,
|
|
441
|
+
discount: data.discount,
|
|
442
|
+
total: data.total,
|
|
443
|
+
sort_order: data.sortOrder,
|
|
444
|
+
modifiers: data.modifiers,
|
|
445
|
+
kitchen_notes: data.kitchenNotes,
|
|
446
|
+
metadata: data.metadata,
|
|
447
|
+
},
|
|
448
|
+
})
|
|
449
|
+
return rowToItem(row)
|
|
450
|
+
},
|
|
451
|
+
|
|
452
|
+
async getSummary(dateRange) {
|
|
453
|
+
return summarize(await listOrders({ dateRange }), dateRange)
|
|
454
|
+
},
|
|
455
|
+
}
|
|
456
|
+
}
|