@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 +0,0 @@
1
- {"version":3,"file":"registries.d.ts","sourceRoot":"","sources":["../src/registries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAiBtD,eAAO,MAAM,gBAAgB,EAAE,iBAAiB,EAc/C,CAAA"}
@@ -1,47 +0,0 @@
1
- import { createPluginContext, formatCurrency, type CurrencyConfig, type EntityLookup } from '@fayz-ai/saas'
2
- import type { OrdersDataProvider } from './data/types'
3
- import type { OrdersUIState } from './store'
4
- import type { RestaurantOrder } from './types'
5
-
6
- // ---------------------------------------------------------------------------
7
- // Resolved config — fully merged, no optionals
8
- // ---------------------------------------------------------------------------
9
-
10
- export interface OrdersModules {
11
- delivery: boolean
12
- takeout: boolean
13
- }
14
-
15
- export type OrdersCurrency = CurrencyConfig
16
-
17
- export interface OrderSourceOption {
18
- value: string
19
- label: string
20
- icon?: string
21
- }
22
-
23
- export interface OrdersPluginLabels {
24
- pageTitle: string
25
- pageSubtitle: string
26
- orderSingular: string
27
- orderPlural: string
28
- }
29
-
30
- export interface ResolvedOrdersConfig {
31
- modules: OrdersModules
32
- labels: OrdersPluginLabels
33
- currency: OrdersCurrency
34
- orderSources: OrderSourceOption[]
35
- menuItemLookup?: EntityLookup
36
- staffLookup?: EntityLookup
37
- onOrderCompleted?: (order: RestaurantOrder) => Promise<void>
38
- }
39
-
40
- const ctx = createPluginContext<ResolvedOrdersConfig, OrdersDataProvider, OrdersUIState>('OrdersPage')
41
-
42
- export const OrdersContextProvider = ctx.ContextProvider
43
- export const useOrdersConfig = ctx.useConfig
44
- export const useOrdersProvider = ctx.useProvider
45
- export const useOrdersStore = ctx.useStore
46
-
47
- export { formatCurrency }
@@ -1,20 +0,0 @@
1
- import React from 'react'
2
- import type { StoreApi } from 'zustand/vanilla'
3
- import { OrdersContextProvider, type ResolvedOrdersConfig } from './OrdersContext'
4
- import { OrderKanbanView } from './views/OrderKanbanView'
5
- import type { OrdersDataProvider } from './data/types'
6
- import type { OrdersUIState } from './store'
7
- import type { PluginRegistryDef } from '@fayz-ai/core'
8
-
9
- export function OrdersPage({ config, provider, store, registries }: {
10
- config: ResolvedOrdersConfig
11
- provider: OrdersDataProvider
12
- store: StoreApi<OrdersUIState>
13
- registries?: PluginRegistryDef[]
14
- }) {
15
- return (
16
- <OrdersContextProvider config={config} provider={provider} store={store}>
17
- <OrderKanbanView />
18
- </OrdersContextProvider>
19
- )
20
- }
package/src/data/mock.ts DELETED
@@ -1,512 +0,0 @@
1
- import type {
2
- RestaurantOrder, RestaurantOrderItem,
3
- CreateOrderInput, CreateOrderItemInput, UpdateOrderStatusInput,
4
- OrderQuery, PaginatedResult, OrdersSummary,
5
- OrderKind, OrderStatus,
6
- } from '../types'
7
- import { getNextStatus, STATUS_FLOW } from '../types'
8
- import type { OrdersDataProvider } from './types'
9
-
10
- // ---------------------------------------------------------------------------
11
- // ID generator
12
- // ---------------------------------------------------------------------------
13
-
14
- let uid = 100
15
- function nextId(prefix: string) { return prefix + '-' + (++uid) }
16
-
17
- // ---------------------------------------------------------------------------
18
- // Seed helpers
19
- // ---------------------------------------------------------------------------
20
-
21
- const now = new Date().toISOString()
22
-
23
- function makeItem(
24
- orderId: string,
25
- name: string,
26
- quantity: number,
27
- unitPrice: number,
28
- sortOrder: number,
29
- productId?: string,
30
- ): RestaurantOrderItem {
31
- const id = nextId('item')
32
- return {
33
- id,
34
- orderId,
35
- productId,
36
- name,
37
- quantity,
38
- unitPrice,
39
- discount: 0,
40
- total: quantity * unitPrice,
41
- sortOrder,
42
- createdAt: now,
43
- }
44
- }
45
-
46
- // ---------------------------------------------------------------------------
47
- // Seed data
48
- // ---------------------------------------------------------------------------
49
-
50
- function createSeedOrders(): RestaurantOrder[] {
51
- const order1Id = nextId('order')
52
- const order1Items = [
53
- makeItem(order1Id, 'Classic Burger', 1, 32, 0, 'prod-burger-classic'),
54
- makeItem(order1Id, 'Fries', 1, 15, 1, 'prod-fries'),
55
- ]
56
- const order1: RestaurantOrder = {
57
- id: order1Id,
58
- referenceNumber: 'ORD-0001',
59
- kind: 'dine_in',
60
- status: 'preparing',
61
- source: 'pos',
62
- tableId: 'table-3',
63
- tableName: 'Table 3',
64
- subtotal: 47,
65
- discount: 0,
66
- tax: 0,
67
- total: 47,
68
- currency: 'BRL',
69
- tags: [],
70
- items: order1Items,
71
- tenantId: 'tenant-mock',
72
- createdAt: now,
73
- updatedAt: now,
74
- }
75
-
76
- const order2Id = nextId('order')
77
- const order2Items = [
78
- makeItem(order2Id, 'Cheese Burger', 1, 36, 0, 'prod-burger-cheese'),
79
- ]
80
- const order2: RestaurantOrder = {
81
- id: order2Id,
82
- referenceNumber: 'ORD-0002',
83
- kind: 'takeout',
84
- status: 'ready',
85
- source: 'pos',
86
- subtotal: 36,
87
- discount: 0,
88
- tax: 0,
89
- total: 36,
90
- currency: 'BRL',
91
- tags: [],
92
- items: order2Items,
93
- tenantId: 'tenant-mock',
94
- createdAt: now,
95
- updatedAt: now,
96
- }
97
-
98
- const order3Id = nextId('order')
99
- const order3Items = [
100
- makeItem(order3Id, 'Margherita Pizza', 1, 38, 0, 'prod-pizza-margherita'),
101
- makeItem(order3Id, 'Caesar Salad', 1, 22, 1, 'prod-salad-caesar'),
102
- makeItem(order3Id, 'Soda 350ml', 1, 12, 2, 'prod-soda'),
103
- ]
104
- const order3: RestaurantOrder = {
105
- id: order3Id,
106
- referenceNumber: 'ORD-0003',
107
- kind: 'delivery',
108
- status: 'confirmed',
109
- source: 'ifood',
110
- subtotal: 72,
111
- discount: 0,
112
- tax: 0,
113
- total: 72,
114
- currency: 'BRL',
115
- tags: [],
116
- items: order3Items,
117
- tenantId: 'tenant-mock',
118
- createdAt: now,
119
- updatedAt: now,
120
- }
121
-
122
- const order4Id = nextId('order')
123
- const order4Items = [
124
- makeItem(order4Id, 'BBQ Bacon Burger', 1, 42, 0, 'prod-burger-bbq'),
125
- ]
126
- const order4: RestaurantOrder = {
127
- id: order4Id,
128
- referenceNumber: 'ORD-0004',
129
- kind: 'dine_in',
130
- status: 'new',
131
- source: 'pos',
132
- tableId: 'table-7',
133
- tableName: 'Table 7',
134
- subtotal: 42,
135
- discount: 0,
136
- tax: 0,
137
- total: 42,
138
- currency: 'BRL',
139
- tags: [],
140
- items: order4Items,
141
- tenantId: 'tenant-mock',
142
- createdAt: now,
143
- updatedAt: now,
144
- }
145
-
146
- const order5Id = nextId('order')
147
- const order5Items = [
148
- makeItem(order5Id, 'Classic Burger', 2, 32, 0, 'prod-burger-classic'),
149
- makeItem(order5Id, 'Fries', 1, 15, 1, 'prod-fries'),
150
- makeItem(order5Id, 'Onion Rings', 1, 18, 2, 'prod-onion-rings'),
151
- makeItem(order5Id, 'Milkshake', 1, 23, 3, 'prod-milkshake'),
152
- ]
153
- const order5: RestaurantOrder = {
154
- id: order5Id,
155
- referenceNumber: 'ORD-0005',
156
- kind: 'dine_in',
157
- status: 'completed',
158
- source: 'pos',
159
- tableId: 'table-1',
160
- tableName: 'Table 1',
161
- subtotal: 120,
162
- discount: 0,
163
- tax: 0,
164
- total: 120,
165
- currency: 'BRL',
166
- tags: [],
167
- items: order5Items,
168
- tenantId: 'tenant-mock',
169
- createdAt: now,
170
- updatedAt: now,
171
- completedAt: now,
172
- }
173
-
174
- return [order1, order2, order3, order4, order5]
175
- }
176
-
177
- // ---------------------------------------------------------------------------
178
- // Helpers
179
- // ---------------------------------------------------------------------------
180
-
181
- function recomputeTotals(order: RestaurantOrder): void {
182
- order.subtotal = order.items.reduce((sum, i) => sum + i.total, 0)
183
- order.total = order.subtotal - order.discount + order.tax
184
- }
185
-
186
- let refCounter = 5
187
- function generateReferenceNumber(): string {
188
- refCounter++
189
- return 'ORD-' + String(refCounter).padStart(4, '0')
190
- }
191
-
192
- function toDateStr(d: Date): string {
193
- return d.toISOString().slice(0, 10)
194
- }
195
-
196
- // ---------------------------------------------------------------------------
197
- // Provider factory
198
- // ---------------------------------------------------------------------------
199
-
200
- export function createMockOrdersProvider(): OrdersDataProvider {
201
- const orders: RestaurantOrder[] = createSeedOrders()
202
-
203
- function findOrder(id: string): RestaurantOrder | undefined {
204
- return orders.find(o => o.id === id)
205
- }
206
-
207
- function findItem(itemId: string): { order: RestaurantOrder; item: RestaurantOrderItem; index: number } | undefined {
208
- for (const order of orders) {
209
- const index = order.items.findIndex(i => i.id === itemId)
210
- if (index !== -1) return { order, item: order.items[index], index }
211
- }
212
- return undefined
213
- }
214
-
215
- const provider: OrdersDataProvider = {
216
- // ------------------------------------------------------------------
217
- // getOrders
218
- // ------------------------------------------------------------------
219
- async getOrders(query: OrderQuery): Promise<PaginatedResult<RestaurantOrder>> {
220
- let result = [...orders]
221
-
222
- // Filter by kind
223
- if (query.kind) {
224
- const kinds = Array.isArray(query.kind) ? query.kind : [query.kind]
225
- result = result.filter(o => kinds.includes(o.kind))
226
- }
227
-
228
- // Filter by status
229
- if (query.status) {
230
- const statuses = Array.isArray(query.status) ? query.status : [query.status]
231
- result = result.filter(o => (statuses as string[]).includes(o.status))
232
- }
233
-
234
- // Filter by source
235
- if (query.source) {
236
- result = result.filter(o => o.source === query.source)
237
- }
238
-
239
- // Filter by tableId
240
- if (query.tableId) {
241
- result = result.filter(o => o.tableId === query.tableId)
242
- }
243
-
244
- // Filter by assigneeId
245
- if (query.assigneeId) {
246
- result = result.filter(o => o.assigneeId === query.assigneeId)
247
- }
248
-
249
- // Filter by search (reference number, party name, table name)
250
- if (query.search) {
251
- const q = query.search.toLowerCase()
252
- result = result.filter(o =>
253
- o.referenceNumber.toLowerCase().includes(q) ||
254
- o.partyName?.toLowerCase().includes(q) ||
255
- o.tableName?.toLowerCase().includes(q) ||
256
- o.items.some(i => i.name.toLowerCase().includes(q))
257
- )
258
- }
259
-
260
- // Filter by date range
261
- if (query.dateRange) {
262
- const from = query.dateRange.from
263
- const to = query.dateRange.to
264
- result = result.filter(o => {
265
- const d = o.createdAt.slice(0, 10)
266
- return d >= from && d <= to
267
- })
268
- }
269
-
270
- // Sort by createdAt desc
271
- result.sort((a, b) => b.createdAt.localeCompare(a.createdAt))
272
-
273
- const total = result.length
274
- const page = query.page ?? 1
275
- const pageSize = query.pageSize ?? 20
276
- const start = (page - 1) * pageSize
277
- const data = result.slice(start, start + pageSize)
278
-
279
- return { data, total }
280
- },
281
-
282
- // ------------------------------------------------------------------
283
- // getOrderById
284
- // ------------------------------------------------------------------
285
- async getOrderById(id: string): Promise<RestaurantOrder | null> {
286
- return findOrder(id) ?? null
287
- },
288
-
289
- // ------------------------------------------------------------------
290
- // createOrder
291
- // ------------------------------------------------------------------
292
- async createOrder(input: CreateOrderInput): Promise<RestaurantOrder> {
293
- const orderId = nextId('order')
294
- const items: RestaurantOrderItem[] = input.items.map((item, idx) => ({
295
- id: nextId('item'),
296
- orderId,
297
- productId: item.productId,
298
- name: item.name,
299
- quantity: item.quantity,
300
- unitPrice: item.unitPrice,
301
- discount: item.discount ?? 0,
302
- total: item.quantity * item.unitPrice - (item.discount ?? 0),
303
- sortOrder: idx,
304
- modifiers: item.modifiers,
305
- kitchenNotes: item.kitchenNotes,
306
- createdAt: new Date().toISOString(),
307
- }))
308
-
309
- const subtotal = items.reduce((sum, i) => sum + i.total, 0)
310
-
311
- const order: RestaurantOrder = {
312
- id: orderId,
313
- referenceNumber: generateReferenceNumber(),
314
- kind: input.kind,
315
- status: 'new',
316
- source: input.source ?? 'pos',
317
- tableId: input.tableId,
318
- partyId: input.partyId,
319
- assigneeId: input.assigneeId,
320
- notes: input.notes,
321
- estimatedMinutes: input.estimatedMinutes,
322
- subtotal,
323
- discount: 0,
324
- tax: 0,
325
- total: subtotal,
326
- currency: 'BRL',
327
- tags: [],
328
- items,
329
- tenantId: 'tenant-mock',
330
- createdAt: new Date().toISOString(),
331
- updatedAt: new Date().toISOString(),
332
- }
333
-
334
- orders.push(order)
335
- return order
336
- },
337
-
338
- // ------------------------------------------------------------------
339
- // updateOrderStatus
340
- // ------------------------------------------------------------------
341
- async updateOrderStatus(input: UpdateOrderStatusInput): Promise<RestaurantOrder> {
342
- const order = findOrder(input.orderId)
343
- if (!order) throw new Error(`Order not found: ${input.orderId}`)
344
-
345
- // Validate the transition: the target status must be reachable via getNextStatus
346
- // or it must be 'cancelled'
347
- if (input.status !== 'cancelled') {
348
- const flow = STATUS_FLOW[order.kind]
349
- const currentIdx = flow.indexOf(order.status)
350
- const targetIdx = flow.indexOf(input.status)
351
- if (targetIdx < 0 || targetIdx !== currentIdx + 1) {
352
- const next = getNextStatus(order.kind, order.status)
353
- throw new Error(
354
- `Invalid status transition: ${order.status} → ${input.status}. ` +
355
- (next ? `Expected next status: ${next}` : 'Order is already in a terminal state.')
356
- )
357
- }
358
- }
359
-
360
- order.status = input.status
361
- order.updatedAt = new Date().toISOString()
362
-
363
- if (input.status === 'completed') {
364
- order.completedAt = order.updatedAt
365
- }
366
-
367
- return order
368
- },
369
-
370
- // ------------------------------------------------------------------
371
- // updateOrder
372
- // ------------------------------------------------------------------
373
- async updateOrder(id: string, data: Partial<RestaurantOrder>): Promise<RestaurantOrder> {
374
- const order = findOrder(id)
375
- if (!order) throw new Error(`Order not found: ${id}`)
376
-
377
- const { id: _id, items: _items, tenantId: _t, createdAt: _c, ...safe } = data
378
- Object.assign(order, safe)
379
- order.updatedAt = new Date().toISOString()
380
- return order
381
- },
382
-
383
- // ------------------------------------------------------------------
384
- // cancelOrder
385
- // ------------------------------------------------------------------
386
- async cancelOrder(id: string, reason?: string): Promise<void> {
387
- const order = findOrder(id)
388
- if (!order) throw new Error(`Order not found: ${id}`)
389
-
390
- order.status = 'cancelled'
391
- order.updatedAt = new Date().toISOString()
392
- if (reason) {
393
- order.notes = order.notes ? `${order.notes}\nCancelled: ${reason}` : `Cancelled: ${reason}`
394
- }
395
- },
396
-
397
- // ------------------------------------------------------------------
398
- // addOrderItem
399
- // ------------------------------------------------------------------
400
- async addOrderItem(orderId: string, item: CreateOrderItemInput): Promise<RestaurantOrderItem> {
401
- const order = findOrder(orderId)
402
- if (!order) throw new Error(`Order not found: ${orderId}`)
403
-
404
- const newItem: RestaurantOrderItem = {
405
- id: nextId('item'),
406
- orderId,
407
- productId: item.productId,
408
- name: item.name,
409
- quantity: item.quantity,
410
- unitPrice: item.unitPrice,
411
- discount: item.discount ?? 0,
412
- total: item.quantity * item.unitPrice - (item.discount ?? 0),
413
- sortOrder: order.items.length,
414
- modifiers: item.modifiers,
415
- kitchenNotes: item.kitchenNotes,
416
- createdAt: new Date().toISOString(),
417
- }
418
-
419
- order.items.push(newItem)
420
- recomputeTotals(order)
421
- order.updatedAt = new Date().toISOString()
422
- return newItem
423
- },
424
-
425
- // ------------------------------------------------------------------
426
- // removeOrderItem
427
- // ------------------------------------------------------------------
428
- async removeOrderItem(itemId: string): Promise<void> {
429
- const found = findItem(itemId)
430
- if (!found) throw new Error(`Order item not found: ${itemId}`)
431
-
432
- found.order.items.splice(found.index, 1)
433
- recomputeTotals(found.order)
434
- found.order.updatedAt = new Date().toISOString()
435
- },
436
-
437
- // ------------------------------------------------------------------
438
- // updateOrderItem
439
- // ------------------------------------------------------------------
440
- async updateOrderItem(itemId: string, data: Partial<RestaurantOrderItem>): Promise<RestaurantOrderItem> {
441
- const found = findItem(itemId)
442
- if (!found) throw new Error(`Order item not found: ${itemId}`)
443
-
444
- const { id: _id, orderId: _oid, createdAt: _c, ...safe } = data
445
- Object.assign(found.item, safe)
446
-
447
- // Recompute item total if quantity or price changed
448
- found.item.total = found.item.quantity * found.item.unitPrice - found.item.discount
449
-
450
- recomputeTotals(found.order)
451
- found.order.updatedAt = new Date().toISOString()
452
- return found.item
453
- },
454
-
455
- // ------------------------------------------------------------------
456
- // getSummary
457
- // ------------------------------------------------------------------
458
- async getSummary(dateRange?: { from: string; to: string }): Promise<OrdersSummary> {
459
- const today = toDateStr(new Date())
460
- const from = dateRange?.from ?? today
461
- const to = dateRange?.to ?? today
462
-
463
- const inRange = orders.filter(o => {
464
- const d = o.createdAt.slice(0, 10)
465
- return d >= from && d <= to
466
- })
467
-
468
- const completed = inRange.filter(o => o.status !== 'cancelled')
469
- const revenue = completed.reduce((sum, o) => sum + o.total, 0)
470
-
471
- const activeStatuses = new Set(['new', 'confirmed', 'preparing', 'ready', 'served', 'dispatched'])
472
- const active = orders.filter(o => activeStatuses.has(o.status))
473
-
474
- const ordersByKind: Record<OrderKind, number> = { dine_in: 0, takeout: 0, delivery: 0 }
475
- const ordersByStatus: Record<string, number> = {}
476
-
477
- for (const o of inRange) {
478
- ordersByKind[o.kind] = (ordersByKind[o.kind] || 0) + 1
479
- ordersByStatus[o.status] = (ordersByStatus[o.status] || 0) + 1
480
- }
481
-
482
- // Top items
483
- const itemMap = new Map<string, { name: string; quantity: number; revenue: number }>()
484
- for (const o of completed) {
485
- for (const item of o.items) {
486
- const existing = itemMap.get(item.name)
487
- if (existing) {
488
- existing.quantity += item.quantity
489
- existing.revenue += item.total
490
- } else {
491
- itemMap.set(item.name, { name: item.name, quantity: item.quantity, revenue: item.total })
492
- }
493
- }
494
- }
495
- const topItems = Array.from(itemMap.values())
496
- .sort((a, b) => b.quantity - a.quantity)
497
- .slice(0, 5)
498
-
499
- return {
500
- todayOrderCount: inRange.length,
501
- todayRevenue: revenue,
502
- averageTicket: completed.length > 0 ? revenue / completed.length : 0,
503
- activeOrderCount: active.length,
504
- ordersByKind,
505
- ordersByStatus,
506
- topItems,
507
- }
508
- },
509
- }
510
-
511
- return provider
512
- }