@fayz-ai/plugin-orders 0.9.0 → 0.9.2
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/README.md +3 -3
- package/dist/{OrdersPage-KKQCTFCN.js → OrdersPage-YTSYE573.js} +3 -3
- package/dist/OrdersPage-YTSYE573.js.map +1 -0
- package/dist/{OrdersContext.d.ts → context.d.ts} +2 -2
- package/dist/context.d.ts.map +1 -0
- package/dist/data/registries.d.ts.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/{OrdersPage.d.ts → views/OrdersPage.d.ts} +3 -3
- package/dist/views/OrdersPage.d.ts.map +1 -0
- package/package.json +5 -7
- package/dist/OrdersContext.d.ts.map +0 -1
- package/dist/OrdersPage-KKQCTFCN.js.map +0 -1
- package/dist/OrdersPage.d.ts.map +0 -1
- package/dist/registries.d.ts.map +0 -1
- package/src/OrdersContext.tsx +0 -47
- package/src/OrdersPage.tsx +0 -20
- package/src/data/mock.ts +0 -512
- package/src/data/platform.ts +0 -456
- package/src/data/types.ts +0 -23
- package/src/index.ts +0 -224
- package/src/locales/en.ts +0 -53
- package/src/locales/index.ts +0 -4
- package/src/locales/pt-BR.ts +0 -53
- package/src/registries.ts +0 -32
- package/src/store.ts +0 -191
- package/src/types.ts +0 -190
- package/src/views/OrderKanbanView.tsx +0 -240
- /package/dist/{registries.d.ts → data/registries.d.ts} +0 -0
package/src/data/platform.ts
DELETED
|
@@ -1,456 +0,0 @@
|
|
|
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
|
-
}
|
package/src/data/types.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
RestaurantOrder, RestaurantOrderItem,
|
|
3
|
-
CreateOrderInput, CreateOrderItemInput, UpdateOrderStatusInput,
|
|
4
|
-
OrderQuery, PaginatedResult, OrdersSummary,
|
|
5
|
-
} from '../types'
|
|
6
|
-
|
|
7
|
-
export interface OrdersDataProvider {
|
|
8
|
-
// Orders
|
|
9
|
-
getOrders(query: OrderQuery): Promise<PaginatedResult<RestaurantOrder>>
|
|
10
|
-
getOrderById(id: string): Promise<RestaurantOrder | null>
|
|
11
|
-
createOrder(input: CreateOrderInput): Promise<RestaurantOrder>
|
|
12
|
-
updateOrderStatus(input: UpdateOrderStatusInput): Promise<RestaurantOrder>
|
|
13
|
-
updateOrder(id: string, data: Partial<RestaurantOrder>): Promise<RestaurantOrder>
|
|
14
|
-
cancelOrder(id: string, reason?: string): Promise<void>
|
|
15
|
-
|
|
16
|
-
// Order Items
|
|
17
|
-
addOrderItem(orderId: string, item: CreateOrderItemInput): Promise<RestaurantOrderItem>
|
|
18
|
-
removeOrderItem(itemId: string): Promise<void>
|
|
19
|
-
updateOrderItem(itemId: string, data: Partial<RestaurantOrderItem>): Promise<RestaurantOrderItem>
|
|
20
|
-
|
|
21
|
-
// Summary
|
|
22
|
-
getSummary(dateRange?: { from: string; to: string }): Promise<OrdersSummary>
|
|
23
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import type { PluginManifest, PluginScope, VerticalId } from '@fayz-ai/core'
|
|
3
|
-
import type { EntityLookup } from '@fayz-ai/saas'
|
|
4
|
-
import type { OrdersDataProvider } from './data/types'
|
|
5
|
-
import type { RestaurantOrder } from './types'
|
|
6
|
-
import { createMockOrdersProvider } from './data/mock'
|
|
7
|
-
import { createOrdersStore } from './store'
|
|
8
|
-
import { ordersRegistries } from './registries'
|
|
9
|
-
import { ordersLocales } from './locales'
|
|
10
|
-
import { PluginSettingsPanel } from '@fayz-ai/saas'
|
|
11
|
-
|
|
12
|
-
const OrdersPage = React.lazy(() => import('./OrdersPage').then((m) => ({ default: m.OrdersPage })))
|
|
13
|
-
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
// Labels
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
|
|
18
|
-
export interface OrdersPluginLabels {
|
|
19
|
-
pageTitle: string
|
|
20
|
-
kanban: string
|
|
21
|
-
newOrder: string
|
|
22
|
-
orderList: string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const DEFAULT_LABELS: OrdersPluginLabels = {
|
|
26
|
-
pageTitle: 'Orders',
|
|
27
|
-
kanban: 'Orders',
|
|
28
|
-
newOrder: 'New Order',
|
|
29
|
-
orderList: 'History',
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
// Currency + sources
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
|
|
36
|
-
const DEFAULT_CURRENCY = { code: 'BRL', locale: 'pt-BR', symbol: 'R$' }
|
|
37
|
-
|
|
38
|
-
export interface OrderSourceOption {
|
|
39
|
-
value: string
|
|
40
|
-
label: string
|
|
41
|
-
icon?: string
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const DEFAULT_ORDER_SOURCES: OrderSourceOption[] = [
|
|
45
|
-
{ value: 'dine_in', label: 'Dine-in', icon: '🍽️' },
|
|
46
|
-
{ value: 'takeout', label: 'Takeout', icon: '🥡' },
|
|
47
|
-
]
|
|
48
|
-
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
// Options
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
|
|
53
|
-
export interface OrdersPluginOptions {
|
|
54
|
-
modules?: {
|
|
55
|
-
delivery?: boolean
|
|
56
|
-
takeout?: boolean
|
|
57
|
-
}
|
|
58
|
-
labels?: Partial<OrdersPluginLabels>
|
|
59
|
-
currency?: { code?: string; locale?: string; symbol?: string }
|
|
60
|
-
navPosition?: number
|
|
61
|
-
navSection?: 'main' | 'secondary'
|
|
62
|
-
scope?: PluginScope
|
|
63
|
-
verticalId?: VerticalId
|
|
64
|
-
dataProvider?: OrdersDataProvider
|
|
65
|
-
menuItemLookup?: EntityLookup
|
|
66
|
-
staffLookup?: EntityLookup
|
|
67
|
-
onOrderCompleted?: (order: RestaurantOrder) => Promise<void>
|
|
68
|
-
orderSources?: OrderSourceOption[]
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
// Config resolver
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
74
|
-
|
|
75
|
-
function resolveConfig(options?: OrdersPluginOptions) {
|
|
76
|
-
return {
|
|
77
|
-
modules: {
|
|
78
|
-
delivery: options?.modules?.delivery !== false,
|
|
79
|
-
takeout: options?.modules?.takeout !== false,
|
|
80
|
-
},
|
|
81
|
-
labels: { ...DEFAULT_LABELS, ...options?.labels } as any,
|
|
82
|
-
currency: { ...DEFAULT_CURRENCY, ...options?.currency },
|
|
83
|
-
orderSources: options?.orderSources ?? DEFAULT_ORDER_SOURCES,
|
|
84
|
-
menuItemLookup: options?.menuItemLookup,
|
|
85
|
-
staffLookup: options?.staffLookup,
|
|
86
|
-
onOrderCompleted: options?.onOrderCompleted,
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// ---------------------------------------------------------------------------
|
|
91
|
-
// Factory
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
|
-
|
|
94
|
-
export function createOrdersPlugin(options?: OrdersPluginOptions): PluginManifest {
|
|
95
|
-
const config = resolveConfig(options)
|
|
96
|
-
const provider = options?.dataProvider ?? createMockOrdersProvider()
|
|
97
|
-
const store = createOrdersStore(provider)
|
|
98
|
-
|
|
99
|
-
const PageComponent: React.FC<any> = () =>
|
|
100
|
-
React.createElement(React.Suspense, { fallback: null },
|
|
101
|
-
React.createElement(OrdersPage, { config, provider, store, registries: ordersRegistries })
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
return {
|
|
105
|
-
id: 'orders',
|
|
106
|
-
name: config.labels.pageTitle,
|
|
107
|
-
icon: 'ClipboardList',
|
|
108
|
-
version: '1.0.0',
|
|
109
|
-
scope: options?.scope ?? 'vertical',
|
|
110
|
-
verticalId: options?.verticalId,
|
|
111
|
-
defaultEnabled: true,
|
|
112
|
-
dependencies: ['menu'],
|
|
113
|
-
// Orders/month quota. NOTE: order creation currently flows only through the
|
|
114
|
-
// store/AI-tool (createOrder) with no dedicated UI create handler, so no
|
|
115
|
-
// client-side guard is wired yet — the declaration keeps the cap countable.
|
|
116
|
-
declaredLimits: [
|
|
117
|
-
{ key: 'orders_month', label: 'Orders / month', table: 'orders', kindFilter: 'order', period: 'month' },
|
|
118
|
-
],
|
|
119
|
-
navigation: [
|
|
120
|
-
{
|
|
121
|
-
section: options?.navSection ?? 'main',
|
|
122
|
-
position: options?.navPosition ?? 2,
|
|
123
|
-
label: config.labels.pageTitle,
|
|
124
|
-
route: '/orders',
|
|
125
|
-
icon: 'ClipboardList',
|
|
126
|
-
permission: { feature: 'orders', action: 'read' as const },
|
|
127
|
-
},
|
|
128
|
-
],
|
|
129
|
-
routes: [
|
|
130
|
-
{
|
|
131
|
-
path: '/orders',
|
|
132
|
-
component: PageComponent,
|
|
133
|
-
permission: { feature: 'orders', action: 'read' as const },
|
|
134
|
-
},
|
|
135
|
-
],
|
|
136
|
-
widgets: [],
|
|
137
|
-
aiTools: [
|
|
138
|
-
{
|
|
139
|
-
id: 'orders.today-summary',
|
|
140
|
-
name: 'getOrdersSummary',
|
|
141
|
-
description: 'Returns order count, revenue, and average ticket for today or a given period.',
|
|
142
|
-
icon: 'ClipboardList',
|
|
143
|
-
mode: 'read' as const,
|
|
144
|
-
category: 'Orders',
|
|
145
|
-
parameters: {
|
|
146
|
-
type: 'object' as const,
|
|
147
|
-
properties: {
|
|
148
|
-
period: { type: 'string' as const, description: 'today, week, month' },
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
suggestions: [
|
|
152
|
-
{ label: 'How many orders today?' },
|
|
153
|
-
{ label: "What's the average ticket today?" },
|
|
154
|
-
{ label: "What's the most ordered item?" },
|
|
155
|
-
],
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
id: 'orders.active-orders',
|
|
159
|
-
name: 'getActiveOrders',
|
|
160
|
-
description: 'Lists currently active (non-completed) orders.',
|
|
161
|
-
icon: 'Clock',
|
|
162
|
-
mode: 'read' as const,
|
|
163
|
-
category: 'Orders',
|
|
164
|
-
parameters: {
|
|
165
|
-
type: 'object' as const,
|
|
166
|
-
properties: {
|
|
167
|
-
kind: { type: 'string' as const, enum: ['dine_in', 'takeout', 'delivery'] },
|
|
168
|
-
status: { type: 'string' as const, description: 'Filter by status' },
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
suggestions: [
|
|
172
|
-
{ label: 'What orders are being prepared right now?' },
|
|
173
|
-
{ label: 'Any delivery orders waiting?' },
|
|
174
|
-
],
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
id: 'orders.create-order',
|
|
178
|
-
name: 'createOrder',
|
|
179
|
-
description: 'Creates a new restaurant order with items.',
|
|
180
|
-
icon: 'PlusCircle',
|
|
181
|
-
mode: 'persist' as const,
|
|
182
|
-
limitKey: 'orders_month',
|
|
183
|
-
category: 'Orders',
|
|
184
|
-
parameters: {
|
|
185
|
-
type: 'object' as const,
|
|
186
|
-
properties: {
|
|
187
|
-
kind: { type: 'string' as const, enum: ['dine_in', 'takeout', 'delivery'] },
|
|
188
|
-
tableNumber: { type: 'number' as const, description: 'Table number (for dine-in)' },
|
|
189
|
-
items: { type: 'array' as const, description: 'Item names and quantities' },
|
|
190
|
-
},
|
|
191
|
-
required: ['kind', 'items'],
|
|
192
|
-
},
|
|
193
|
-
permission: { feature: 'orders', action: 'create' as const },
|
|
194
|
-
},
|
|
195
|
-
],
|
|
196
|
-
registries: ordersRegistries,
|
|
197
|
-
settings: [
|
|
198
|
-
{
|
|
199
|
-
id: 'orders',
|
|
200
|
-
label: config.labels.pageTitle,
|
|
201
|
-
icon: 'ClipboardList',
|
|
202
|
-
component: (() => {
|
|
203
|
-
const OrdersSettingsTab: React.ComponentType<unknown> = () =>
|
|
204
|
-
React.createElement(PluginSettingsPanel, {
|
|
205
|
-
title: 'Orders Settings',
|
|
206
|
-
subtitle: 'Order sources and preferences',
|
|
207
|
-
registries: ordersRegistries,
|
|
208
|
-
routeBase: '/settings/orders',
|
|
209
|
-
})
|
|
210
|
-
OrdersSettingsTab.displayName = 'OrdersSettingsTab'
|
|
211
|
-
return OrdersSettingsTab
|
|
212
|
-
})(),
|
|
213
|
-
order: 22,
|
|
214
|
-
permission: { feature: 'orders', action: 'read' as const },
|
|
215
|
-
},
|
|
216
|
-
],
|
|
217
|
-
locales: ordersLocales,
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
export type { OrdersDataProvider } from './data/types'
|
|
222
|
-
export { createFayzOrdersProvider } from './data/platform'
|
|
223
|
-
export type { FayzOrdersProviderOptions } from './data/platform'
|
|
224
|
-
export type { ResolvedOrdersConfig } from './OrdersContext'
|