@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/dist/registries.d.ts.map
DELETED
|
@@ -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"}
|
package/src/OrdersContext.tsx
DELETED
|
@@ -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 }
|
package/src/OrdersPage.tsx
DELETED
|
@@ -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
|
-
}
|