@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.
@@ -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'