@fayz-ai/plugin-orders 0.12.28 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/OrderDetailRoute-LTPDQXQU.js +193 -0
- package/dist/OrderDetailRoute-LTPDQXQU.js.map +1 -0
- package/dist/OrdersPage-5326V62H.js +291 -0
- package/dist/OrdersPage-5326V62H.js.map +1 -0
- package/dist/chunk-YVC7R552.js +6 -0
- package/dist/chunk-YVC7R552.js.map +1 -0
- package/dist/context.d.ts +0 -46
- package/dist/context.d.ts.map +1 -1
- package/dist/data/core-supabase.d.ts.map +1 -1
- package/dist/index.d.ts +4 -34
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1108 -934
- package/dist/index.js.map +1 -1
- package/dist/lib/detail-slot.d.ts +13 -0
- package/dist/lib/detail-slot.d.ts.map +1 -0
- package/dist/lib/list-query.d.ts +21 -0
- package/dist/lib/list-query.d.ts.map +1 -0
- package/dist/migrations/index.d.ts +6 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/server/index.d.ts +17 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +48 -0
- package/dist/server/index.js.map +1 -0
- package/dist/types.d.ts +10 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/views/OrderDetailRoute.d.ts +12 -0
- package/dist/views/OrderDetailRoute.d.ts.map +1 -0
- package/dist/views/OrdersPage.d.ts +1 -7
- package/dist/views/OrdersPage.d.ts.map +1 -1
- package/dist/views/OrdersScreen.d.ts.map +1 -1
- package/package.json +12 -9
- package/dist/OrdersPage-QV2YWARF.js +0 -483
- package/dist/OrdersPage-QV2YWARF.js.map +0 -1
- package/dist/chunk-TYS3LCKX.js +0 -57
- package/dist/chunk-TYS3LCKX.js.map +0 -1
- package/dist/lib/floor-plan-id.d.ts +0 -13
- package/dist/lib/floor-plan-id.d.ts.map +0 -1
- package/dist/views/CommercialOrdersScreen.d.ts +0 -3
- package/dist/views/CommercialOrdersScreen.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,428 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
import React2__default from 'react';
|
|
5
|
-
import { fayzConnectorOAuth, OrderDetailPage, orderIsEditable, PageActions, adminNavigateTo, PluginSettingsPanel, dedup, usePermissionOptional, useConnectors, useConnectorsPanelAvailable, openConnectorsPanel, SettingsGroup, ToggleRow, AddConnectorButton, ConnectorRow, AddConnectorPanel, ConnectorDetail, TextRow } from '@fayz-ai/admin';
|
|
6
|
-
import { Pencil, AlertTriangle, Loader2, Check } from 'lucide-react';
|
|
7
|
-
import { createConnectionStore, connectionRuns, connectionSettings, connectionStatus, settingsStatus, getSupabaseClientOptional, usePluginRuntimeOptional, emitDomainEvent, errorMessage, describeError } from '@fayz-ai/core';
|
|
1
|
+
export { ORDER_DETAIL_COMPONENT_ID } from './chunk-YVC7R552.js';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { createConnectionStore, connectionRuns, connectionSettings, connectionStatus, settingsStatus, getSupabaseClientOptional, emitDomainEvent, errorMessage } from '@fayz-ai/core';
|
|
8
4
|
import { createStore } from 'zustand/vanilla';
|
|
5
|
+
import { fayzConnectorOAuth, PluginSettingsPanel, dedup, useConnectors, useConnectorsPanelAvailable, openConnectorsPanel, SettingsGroup, ToggleRow, AddConnectorButton, ConnectorRow, AddConnectorPanel, ConnectorDetail, TextRow } from '@fayz-ai/admin';
|
|
9
6
|
import { toast } from 'sonner';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { jsxs,
|
|
7
|
+
import { Skeleton } from '@fayz-ai/ui';
|
|
8
|
+
import { AlertTriangle, Loader2, Check } from 'lucide-react';
|
|
9
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
13
10
|
import { createFayzClient } from '@fayz-ai/sdk';
|
|
14
11
|
|
|
15
|
-
// src/data/mock.ts
|
|
16
|
-
var uid = 100;
|
|
17
|
-
function nextId(prefix) {
|
|
18
|
-
return prefix + "-" + ++uid;
|
|
19
|
-
}
|
|
20
|
-
var now = (/* @__PURE__ */ new Date()).toISOString();
|
|
21
|
-
function makeItem(orderId, name, quantity, unitPrice, sortOrder, productId) {
|
|
22
|
-
const id = nextId("item");
|
|
23
|
-
return {
|
|
24
|
-
id,
|
|
25
|
-
orderId,
|
|
26
|
-
productId,
|
|
27
|
-
name,
|
|
28
|
-
quantity,
|
|
29
|
-
unitPrice,
|
|
30
|
-
discount: 0,
|
|
31
|
-
total: quantity * unitPrice,
|
|
32
|
-
sortOrder,
|
|
33
|
-
createdAt: now
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
function createSeedOrders() {
|
|
37
|
-
const order1Id = nextId("order");
|
|
38
|
-
const order1Items = [
|
|
39
|
-
makeItem(order1Id, "Classic Burger", 1, 32, 0, "prod-burger-classic"),
|
|
40
|
-
makeItem(order1Id, "Fries", 1, 15, 1, "prod-fries")
|
|
41
|
-
];
|
|
42
|
-
const order1 = {
|
|
43
|
-
id: order1Id,
|
|
44
|
-
referenceNumber: "ORD-0001",
|
|
45
|
-
kind: "dine_in",
|
|
46
|
-
status: "preparing",
|
|
47
|
-
source: "pos",
|
|
48
|
-
tableId: "table-3",
|
|
49
|
-
tableName: "Table 3",
|
|
50
|
-
subtotal: 47,
|
|
51
|
-
discount: 0,
|
|
52
|
-
tax: 0,
|
|
53
|
-
total: 47,
|
|
54
|
-
currency: "BRL",
|
|
55
|
-
tags: [],
|
|
56
|
-
items: order1Items,
|
|
57
|
-
tenantId: "tenant-mock",
|
|
58
|
-
createdAt: now,
|
|
59
|
-
updatedAt: now
|
|
60
|
-
};
|
|
61
|
-
const order2Id = nextId("order");
|
|
62
|
-
const order2Items = [
|
|
63
|
-
makeItem(order2Id, "Cheese Burger", 1, 36, 0, "prod-burger-cheese")
|
|
64
|
-
];
|
|
65
|
-
const order2 = {
|
|
66
|
-
id: order2Id,
|
|
67
|
-
referenceNumber: "ORD-0002",
|
|
68
|
-
kind: "takeout",
|
|
69
|
-
status: "ready",
|
|
70
|
-
source: "pos",
|
|
71
|
-
subtotal: 36,
|
|
72
|
-
discount: 0,
|
|
73
|
-
tax: 0,
|
|
74
|
-
total: 36,
|
|
75
|
-
currency: "BRL",
|
|
76
|
-
tags: [],
|
|
77
|
-
items: order2Items,
|
|
78
|
-
tenantId: "tenant-mock",
|
|
79
|
-
createdAt: now,
|
|
80
|
-
updatedAt: now
|
|
81
|
-
};
|
|
82
|
-
const order3Id = nextId("order");
|
|
83
|
-
const order3Items = [
|
|
84
|
-
makeItem(order3Id, "Margherita Pizza", 1, 38, 0, "prod-pizza-margherita"),
|
|
85
|
-
makeItem(order3Id, "Caesar Salad", 1, 22, 1, "prod-salad-caesar"),
|
|
86
|
-
makeItem(order3Id, "Soda 350ml", 1, 12, 2, "prod-soda")
|
|
87
|
-
];
|
|
88
|
-
const order3 = {
|
|
89
|
-
id: order3Id,
|
|
90
|
-
referenceNumber: "ORD-0003",
|
|
91
|
-
kind: "delivery",
|
|
92
|
-
status: "confirmed",
|
|
93
|
-
source: "ifood",
|
|
94
|
-
subtotal: 72,
|
|
95
|
-
discount: 0,
|
|
96
|
-
tax: 0,
|
|
97
|
-
total: 72,
|
|
98
|
-
currency: "BRL",
|
|
99
|
-
tags: [],
|
|
100
|
-
items: order3Items,
|
|
101
|
-
tenantId: "tenant-mock",
|
|
102
|
-
createdAt: now,
|
|
103
|
-
updatedAt: now
|
|
104
|
-
};
|
|
105
|
-
const order4Id = nextId("order");
|
|
106
|
-
const order4Items = [
|
|
107
|
-
makeItem(order4Id, "BBQ Bacon Burger", 1, 42, 0, "prod-burger-bbq")
|
|
108
|
-
];
|
|
109
|
-
const order4 = {
|
|
110
|
-
id: order4Id,
|
|
111
|
-
referenceNumber: "ORD-0004",
|
|
112
|
-
kind: "dine_in",
|
|
113
|
-
status: "new",
|
|
114
|
-
source: "pos",
|
|
115
|
-
tableId: "table-7",
|
|
116
|
-
tableName: "Table 7",
|
|
117
|
-
subtotal: 42,
|
|
118
|
-
discount: 0,
|
|
119
|
-
tax: 0,
|
|
120
|
-
total: 42,
|
|
121
|
-
currency: "BRL",
|
|
122
|
-
tags: [],
|
|
123
|
-
items: order4Items,
|
|
124
|
-
tenantId: "tenant-mock",
|
|
125
|
-
createdAt: now,
|
|
126
|
-
updatedAt: now
|
|
127
|
-
};
|
|
128
|
-
const order5Id = nextId("order");
|
|
129
|
-
const order5Items = [
|
|
130
|
-
makeItem(order5Id, "Classic Burger", 2, 32, 0, "prod-burger-classic"),
|
|
131
|
-
makeItem(order5Id, "Fries", 1, 15, 1, "prod-fries"),
|
|
132
|
-
makeItem(order5Id, "Onion Rings", 1, 18, 2, "prod-onion-rings"),
|
|
133
|
-
makeItem(order5Id, "Milkshake", 1, 23, 3, "prod-milkshake")
|
|
134
|
-
];
|
|
135
|
-
const order5 = {
|
|
136
|
-
id: order5Id,
|
|
137
|
-
referenceNumber: "ORD-0005",
|
|
138
|
-
kind: "dine_in",
|
|
139
|
-
status: "completed",
|
|
140
|
-
source: "pos",
|
|
141
|
-
tableId: "table-1",
|
|
142
|
-
tableName: "Table 1",
|
|
143
|
-
subtotal: 120,
|
|
144
|
-
discount: 0,
|
|
145
|
-
tax: 0,
|
|
146
|
-
total: 120,
|
|
147
|
-
currency: "BRL",
|
|
148
|
-
tags: [],
|
|
149
|
-
items: order5Items,
|
|
150
|
-
tenantId: "tenant-mock",
|
|
151
|
-
createdAt: now,
|
|
152
|
-
updatedAt: now,
|
|
153
|
-
completedAt: now
|
|
154
|
-
};
|
|
155
|
-
return [order1, order2, order3, order4, order5];
|
|
156
|
-
}
|
|
157
|
-
function recomputeTotals(order) {
|
|
158
|
-
order.subtotal = order.items.reduce((sum, i) => sum + i.total, 0);
|
|
159
|
-
order.total = order.subtotal - order.discount + order.tax;
|
|
160
|
-
}
|
|
161
|
-
var refCounter = 5;
|
|
162
|
-
function generateReferenceNumber() {
|
|
163
|
-
refCounter++;
|
|
164
|
-
return "ORD-" + String(refCounter).padStart(4, "0");
|
|
165
|
-
}
|
|
166
|
-
function toDateStr(d) {
|
|
167
|
-
return d.toISOString().slice(0, 10);
|
|
168
|
-
}
|
|
169
|
-
function createMockOrdersProvider() {
|
|
170
|
-
const orders = createSeedOrders();
|
|
171
|
-
function findOrder(id) {
|
|
172
|
-
return orders.find((o) => o.id === id);
|
|
173
|
-
}
|
|
174
|
-
function findItem(itemId) {
|
|
175
|
-
for (const order of orders) {
|
|
176
|
-
const index = order.items.findIndex((i) => i.id === itemId);
|
|
177
|
-
if (index !== -1) return { order, item: order.items[index], index };
|
|
178
|
-
}
|
|
179
|
-
return void 0;
|
|
180
|
-
}
|
|
181
|
-
const provider = {
|
|
182
|
-
// ------------------------------------------------------------------
|
|
183
|
-
// getOrders
|
|
184
|
-
// ------------------------------------------------------------------
|
|
185
|
-
async getOrders(query) {
|
|
186
|
-
let result = [...orders];
|
|
187
|
-
if (query.kind) {
|
|
188
|
-
const kinds = Array.isArray(query.kind) ? query.kind : [query.kind];
|
|
189
|
-
result = result.filter((o) => kinds.includes(o.kind));
|
|
190
|
-
}
|
|
191
|
-
if (query.status) {
|
|
192
|
-
const statuses = Array.isArray(query.status) ? query.status : [query.status];
|
|
193
|
-
result = result.filter((o) => statuses.includes(o.status));
|
|
194
|
-
}
|
|
195
|
-
if (query.source) {
|
|
196
|
-
result = result.filter((o) => o.source === query.source);
|
|
197
|
-
}
|
|
198
|
-
if (query.tableId) {
|
|
199
|
-
result = result.filter((o) => o.tableId === query.tableId);
|
|
200
|
-
}
|
|
201
|
-
if (query.assigneeId) {
|
|
202
|
-
result = result.filter((o) => o.assigneeId === query.assigneeId);
|
|
203
|
-
}
|
|
204
|
-
if (query.search) {
|
|
205
|
-
const q = query.search.toLowerCase();
|
|
206
|
-
result = result.filter(
|
|
207
|
-
(o) => o.referenceNumber.toLowerCase().includes(q) || o.partyName?.toLowerCase().includes(q) || o.tableName?.toLowerCase().includes(q) || o.items.some((i) => i.name.toLowerCase().includes(q))
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
if (query.dateRange) {
|
|
211
|
-
const from = query.dateRange.from;
|
|
212
|
-
const to = query.dateRange.to;
|
|
213
|
-
result = result.filter((o) => {
|
|
214
|
-
const d = o.createdAt.slice(0, 10);
|
|
215
|
-
return d >= from && d <= to;
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
result.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
219
|
-
const total = result.length;
|
|
220
|
-
const page = query.page ?? 1;
|
|
221
|
-
const pageSize = query.pageSize ?? 20;
|
|
222
|
-
const start = (page - 1) * pageSize;
|
|
223
|
-
const data = result.slice(start, start + pageSize);
|
|
224
|
-
return { data, total };
|
|
225
|
-
},
|
|
226
|
-
// ------------------------------------------------------------------
|
|
227
|
-
// getOrderById
|
|
228
|
-
// ------------------------------------------------------------------
|
|
229
|
-
async getOrderById(id) {
|
|
230
|
-
return findOrder(id) ?? null;
|
|
231
|
-
},
|
|
232
|
-
// ------------------------------------------------------------------
|
|
233
|
-
// createOrder
|
|
234
|
-
// ------------------------------------------------------------------
|
|
235
|
-
async createOrder(input) {
|
|
236
|
-
const orderId = nextId("order");
|
|
237
|
-
const items = input.items.map((item, idx) => ({
|
|
238
|
-
id: nextId("item"),
|
|
239
|
-
orderId,
|
|
240
|
-
productId: item.productId,
|
|
241
|
-
name: item.name,
|
|
242
|
-
quantity: item.quantity,
|
|
243
|
-
unitPrice: item.unitPrice,
|
|
244
|
-
discount: item.discount ?? 0,
|
|
245
|
-
total: item.quantity * item.unitPrice - (item.discount ?? 0),
|
|
246
|
-
sortOrder: idx,
|
|
247
|
-
modifiers: item.modifiers,
|
|
248
|
-
kitchenNotes: item.kitchenNotes,
|
|
249
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
250
|
-
}));
|
|
251
|
-
const subtotal = items.reduce((sum, i) => sum + i.total, 0);
|
|
252
|
-
const order = {
|
|
253
|
-
id: orderId,
|
|
254
|
-
referenceNumber: generateReferenceNumber(),
|
|
255
|
-
kind: input.kind,
|
|
256
|
-
status: "new",
|
|
257
|
-
source: input.source ?? "pos",
|
|
258
|
-
tableId: input.tableId,
|
|
259
|
-
partyId: input.partyId,
|
|
260
|
-
assigneeId: input.assigneeId,
|
|
261
|
-
notes: input.notes,
|
|
262
|
-
estimatedMinutes: input.estimatedMinutes,
|
|
263
|
-
subtotal,
|
|
264
|
-
discount: 0,
|
|
265
|
-
tax: 0,
|
|
266
|
-
total: subtotal,
|
|
267
|
-
currency: "BRL",
|
|
268
|
-
tags: [],
|
|
269
|
-
items,
|
|
270
|
-
tenantId: "tenant-mock",
|
|
271
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
272
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
273
|
-
};
|
|
274
|
-
orders.push(order);
|
|
275
|
-
return order;
|
|
276
|
-
},
|
|
277
|
-
// ------------------------------------------------------------------
|
|
278
|
-
// updateOrderStatus
|
|
279
|
-
// ------------------------------------------------------------------
|
|
280
|
-
async updateOrderStatus(input) {
|
|
281
|
-
const order = findOrder(input.orderId);
|
|
282
|
-
if (!order) throw new Error(`Order not found: ${input.orderId}`);
|
|
283
|
-
if (input.status !== "cancelled") {
|
|
284
|
-
const flow = STATUS_FLOW[order.kind];
|
|
285
|
-
const currentIdx = flow.indexOf(order.status);
|
|
286
|
-
const targetIdx = flow.indexOf(input.status);
|
|
287
|
-
if (targetIdx < 0 || targetIdx !== currentIdx && targetIdx !== currentIdx + 1) {
|
|
288
|
-
const next = getNextStatus(order.kind, order.status);
|
|
289
|
-
throw new Error(
|
|
290
|
-
`Invalid status transition: ${order.status} \u2192 ${input.status}. ` + (next ? `Expected next status: ${next}` : "Order is already in a terminal state.")
|
|
291
|
-
);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
order.status = input.status;
|
|
295
|
-
order.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
296
|
-
if (input.status === "completed") {
|
|
297
|
-
order.completedAt = order.updatedAt;
|
|
298
|
-
}
|
|
299
|
-
return order;
|
|
300
|
-
},
|
|
301
|
-
// ------------------------------------------------------------------
|
|
302
|
-
// updateOrder
|
|
303
|
-
// ------------------------------------------------------------------
|
|
304
|
-
async updateOrder(id, data) {
|
|
305
|
-
const order = findOrder(id);
|
|
306
|
-
if (!order) throw new Error(`Order not found: ${id}`);
|
|
307
|
-
const { id: _id, items: _items, tenantId: _t, createdAt: _c, ...safe } = data;
|
|
308
|
-
Object.assign(order, safe);
|
|
309
|
-
order.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
310
|
-
return order;
|
|
311
|
-
},
|
|
312
|
-
// ------------------------------------------------------------------
|
|
313
|
-
// cancelOrder
|
|
314
|
-
// ------------------------------------------------------------------
|
|
315
|
-
async cancelOrder(id, reason) {
|
|
316
|
-
const order = findOrder(id);
|
|
317
|
-
if (!order) throw new Error(`Order not found: ${id}`);
|
|
318
|
-
order.status = "cancelled";
|
|
319
|
-
order.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
320
|
-
if (reason) {
|
|
321
|
-
order.notes = order.notes ? `${order.notes}
|
|
322
|
-
Cancelled: ${reason}` : `Cancelled: ${reason}`;
|
|
323
|
-
}
|
|
324
|
-
},
|
|
325
|
-
// ------------------------------------------------------------------
|
|
326
|
-
// addOrderItem
|
|
327
|
-
// ------------------------------------------------------------------
|
|
328
|
-
async addOrderItem(orderId, item) {
|
|
329
|
-
const order = findOrder(orderId);
|
|
330
|
-
if (!order) throw new Error(`Order not found: ${orderId}`);
|
|
331
|
-
const newItem = {
|
|
332
|
-
id: nextId("item"),
|
|
333
|
-
orderId,
|
|
334
|
-
productId: item.productId,
|
|
335
|
-
name: item.name,
|
|
336
|
-
quantity: item.quantity,
|
|
337
|
-
unitPrice: item.unitPrice,
|
|
338
|
-
discount: item.discount ?? 0,
|
|
339
|
-
total: item.quantity * item.unitPrice - (item.discount ?? 0),
|
|
340
|
-
sortOrder: order.items.length,
|
|
341
|
-
modifiers: item.modifiers,
|
|
342
|
-
kitchenNotes: item.kitchenNotes,
|
|
343
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
344
|
-
};
|
|
345
|
-
order.items.push(newItem);
|
|
346
|
-
recomputeTotals(order);
|
|
347
|
-
order.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
348
|
-
return newItem;
|
|
349
|
-
},
|
|
350
|
-
// ------------------------------------------------------------------
|
|
351
|
-
// removeOrderItem
|
|
352
|
-
// ------------------------------------------------------------------
|
|
353
|
-
async removeOrderItem(itemId) {
|
|
354
|
-
const found = findItem(itemId);
|
|
355
|
-
if (!found) throw new Error(`Order item not found: ${itemId}`);
|
|
356
|
-
found.order.items.splice(found.index, 1);
|
|
357
|
-
recomputeTotals(found.order);
|
|
358
|
-
found.order.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
359
|
-
},
|
|
360
|
-
// ------------------------------------------------------------------
|
|
361
|
-
// updateOrderItem
|
|
362
|
-
// ------------------------------------------------------------------
|
|
363
|
-
async updateOrderItem(itemId, data) {
|
|
364
|
-
const found = findItem(itemId);
|
|
365
|
-
if (!found) throw new Error(`Order item not found: ${itemId}`);
|
|
366
|
-
const { id: _id, orderId: _oid, createdAt: _c, ...safe } = data;
|
|
367
|
-
Object.assign(found.item, safe);
|
|
368
|
-
found.item.total = found.item.quantity * found.item.unitPrice - found.item.discount;
|
|
369
|
-
recomputeTotals(found.order);
|
|
370
|
-
found.order.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
371
|
-
return found.item;
|
|
372
|
-
},
|
|
373
|
-
// ------------------------------------------------------------------
|
|
374
|
-
// getSummary
|
|
375
|
-
// ------------------------------------------------------------------
|
|
376
|
-
async getSummary(dateRange) {
|
|
377
|
-
const today = toDateStr(/* @__PURE__ */ new Date());
|
|
378
|
-
const from = dateRange?.from ?? today;
|
|
379
|
-
const to = dateRange?.to ?? today;
|
|
380
|
-
const inRange = orders.filter((o) => {
|
|
381
|
-
const d = o.createdAt.slice(0, 10);
|
|
382
|
-
return d >= from && d <= to;
|
|
383
|
-
});
|
|
384
|
-
const completed = inRange.filter((o) => o.status !== "cancelled");
|
|
385
|
-
const revenue = completed.reduce((sum, o) => sum + o.total, 0);
|
|
386
|
-
const activeStatuses = /* @__PURE__ */ new Set(["new", "confirmed", "preparing", "ready", "served", "dispatched"]);
|
|
387
|
-
const active = orders.filter((o) => activeStatuses.has(o.status));
|
|
388
|
-
const ordersByKind = { dine_in: 0, takeout: 0, delivery: 0 };
|
|
389
|
-
const ordersByStatus = {};
|
|
390
|
-
for (const o of inRange) {
|
|
391
|
-
ordersByKind[o.kind] = (ordersByKind[o.kind] || 0) + 1;
|
|
392
|
-
ordersByStatus[o.status] = (ordersByStatus[o.status] || 0) + 1;
|
|
393
|
-
}
|
|
394
|
-
const itemMap = /* @__PURE__ */ new Map();
|
|
395
|
-
for (const o of completed) {
|
|
396
|
-
for (const item of o.items) {
|
|
397
|
-
const existing = itemMap.get(item.name);
|
|
398
|
-
if (existing) {
|
|
399
|
-
existing.quantity += item.quantity;
|
|
400
|
-
existing.revenue += item.total;
|
|
401
|
-
} else {
|
|
402
|
-
itemMap.set(item.name, { name: item.name, quantity: item.quantity, revenue: item.total });
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
const topItems = Array.from(itemMap.values()).sort((a, b) => b.quantity - a.quantity).slice(0, 5);
|
|
407
|
-
return {
|
|
408
|
-
todayOrderCount: inRange.length,
|
|
409
|
-
todayRevenue: revenue,
|
|
410
|
-
averageTicket: completed.length > 0 ? revenue / completed.length : 0,
|
|
411
|
-
activeOrderCount: active.length,
|
|
412
|
-
ordersByKind,
|
|
413
|
-
ordersByStatus,
|
|
414
|
-
topItems
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
};
|
|
418
|
-
return provider;
|
|
419
|
-
}
|
|
420
12
|
var READ_ONLY = "This provider reads the core order archetype; pass `writes` to let the app that owns the flow create and edit them.";
|
|
421
13
|
var DEFAULT_CHANNELS = {
|
|
422
14
|
dine_in: "dine_in",
|
|
423
15
|
delivery: "delivery",
|
|
424
16
|
takeout: "balcao"
|
|
425
17
|
};
|
|
18
|
+
function searchTerm(raw) {
|
|
19
|
+
return (raw ?? "").replace(/[,()%*\\"]/g, " ").trim();
|
|
20
|
+
}
|
|
426
21
|
function createCoreOrdersProvider(options) {
|
|
427
22
|
const db = () => {
|
|
428
23
|
const client = getSupabaseClientOptional();
|
|
@@ -446,11 +41,23 @@ function createCoreOrdersProvider(options) {
|
|
|
446
41
|
if (query?.status) {
|
|
447
42
|
q = Array.isArray(query.status) ? q.in("status", query.status) : q.eq("status", query.status);
|
|
448
43
|
}
|
|
44
|
+
if (query?.channel) q = q.eq("channel", query.channel);
|
|
45
|
+
if (query?.excludeKinds?.length) q = q.not("kind", "in", `(${query.excludeKinds.join(",")})`);
|
|
46
|
+
if (query?.fulfillmentStatus) q = q.eq("fulfillment_status", query.fulfillmentStatus);
|
|
449
47
|
if (query?.tableId) q = q.eq("metadata->>tableId", query.tableId);
|
|
450
48
|
if (query?.assigneeId) q = q.eq("assignee_id", query.assigneeId);
|
|
451
49
|
if (query?.dateRange) {
|
|
452
50
|
q = q.gte("created_at", query.dateRange.from).lte("created_at", query.dateRange.to);
|
|
453
51
|
}
|
|
52
|
+
const term = searchTerm(query?.search);
|
|
53
|
+
if (term) {
|
|
54
|
+
const { data: named } = await client.from("people").select("id").ilike("name", `%${term}%`).limit(50);
|
|
55
|
+
const ids2 = (named ?? []).map((p) => p.id);
|
|
56
|
+
q = q.or([
|
|
57
|
+
`reference_number.ilike.%${term}%`,
|
|
58
|
+
...ids2.length ? [`party_id.in.(${ids2.join(",")})`] : []
|
|
59
|
+
].join(","));
|
|
60
|
+
}
|
|
454
61
|
const pageSize = query?.pageSize ?? 100;
|
|
455
62
|
const from = ((query?.page ?? 1) - 1) * pageSize;
|
|
456
63
|
q = q.range(from, from + pageSize - 1);
|
|
@@ -458,11 +65,17 @@ function createCoreOrdersProvider(options) {
|
|
|
458
65
|
if (error) throw new Error(error.message);
|
|
459
66
|
const rows = data ?? [];
|
|
460
67
|
const ids = rows.map((r) => r.id);
|
|
461
|
-
const
|
|
68
|
+
const withItems = query?.withItems !== false;
|
|
69
|
+
const [{ data: people }, { data: payments }, { data: items }, { data: shipments }] = await Promise.all([
|
|
462
70
|
ids.length ? client.from("people").select("id, name").in("id", [...new Set(rows.flatMap((r) => [r.party_id, r.assignee_id]).filter(Boolean))]) : Promise.resolve({ data: [] }),
|
|
463
71
|
ids.length ? client.from("v_order_payment_status").select("order_id, status, total_amount, paid_amount").in("order_id", ids) : Promise.resolve({ data: [] }),
|
|
464
|
-
ids.length ? client.from("items").select("id, order_id, product_id, name, description, quantity, unit_price, discount, total, sort_order, metadata, created_at").in("order_id", ids).order("sort_order") : Promise.resolve({ data: [] })
|
|
72
|
+
ids.length && withItems ? client.from("items").select("id, order_id, product_id, name, description, quantity, unit_price, discount, total, sort_order, metadata, created_at").in("order_id", ids).order("sort_order") : Promise.resolve({ data: [] }),
|
|
73
|
+
ids.length ? client.from("fulfillments").select("order_id, status").in("order_id", ids).in("kind", ["shipment", "pickup"]) : Promise.resolve({ data: [] })
|
|
465
74
|
]);
|
|
75
|
+
const shipmentsByOrder = /* @__PURE__ */ new Map();
|
|
76
|
+
for (const s of shipments ?? []) {
|
|
77
|
+
shipmentsByOrder.set(s.order_id, [...shipmentsByOrder.get(s.order_id) ?? [], s.status]);
|
|
78
|
+
}
|
|
466
79
|
const nameById = new Map((people ?? []).map((p) => [p.id, p.name]));
|
|
467
80
|
const payById = new Map((payments ?? []).map((p) => [p.order_id, p]));
|
|
468
81
|
const itemsByOrder = /* @__PURE__ */ new Map();
|
|
@@ -506,7 +119,8 @@ function createCoreOrdersProvider(options) {
|
|
|
506
119
|
fulfillmentStatus: r.fulfillment_status ?? null,
|
|
507
120
|
paymentStatus: pay?.status ?? "none",
|
|
508
121
|
invoicedAmount: Number(pay?.total_amount) || 0,
|
|
509
|
-
paidAmount: Number(pay?.paid_amount) || 0
|
|
122
|
+
paidAmount: Number(pay?.paid_amount) || 0,
|
|
123
|
+
shipmentStatuses: shipmentsByOrder.get(r.id) ?? []
|
|
510
124
|
},
|
|
511
125
|
tenantId: r.tenant_id,
|
|
512
126
|
unitId: r.unit_id ?? void 0,
|
|
@@ -727,6 +341,62 @@ function createCoreOrdersProvider(options) {
|
|
|
727
341
|
}
|
|
728
342
|
};
|
|
729
343
|
}
|
|
344
|
+
|
|
345
|
+
// src/types.ts
|
|
346
|
+
var STATUS_FLOW = {
|
|
347
|
+
dine_in: ["draft", "new", "confirmed", "preparing", "ready", "served", "completed"],
|
|
348
|
+
takeout: ["draft", "new", "confirmed", "preparing", "ready", "picked_up", "completed"],
|
|
349
|
+
delivery: ["draft", "new", "confirmed", "preparing", "ready", "dispatched", "delivered", "completed"]
|
|
350
|
+
};
|
|
351
|
+
var OUT_STATUSES = ["served", "picked_up", "dispatched", "delivered"];
|
|
352
|
+
function getNextStatus(kind, current) {
|
|
353
|
+
const flow = STATUS_FLOW[kind];
|
|
354
|
+
let idx = flow.indexOf(current);
|
|
355
|
+
if (idx === -1 && OUT_STATUSES.includes(current)) {
|
|
356
|
+
idx = flow.findIndex((status) => OUT_STATUSES.includes(status));
|
|
357
|
+
}
|
|
358
|
+
return idx >= 0 && idx < flow.length - 1 ? flow[idx + 1] : null;
|
|
359
|
+
}
|
|
360
|
+
var ADVANCE_VERB = {
|
|
361
|
+
draft: "Salvar rascunho",
|
|
362
|
+
new: "Enviar",
|
|
363
|
+
confirmed: "Confirmar",
|
|
364
|
+
preparing: "Preparar",
|
|
365
|
+
ready: "Marcar pronto",
|
|
366
|
+
served: "Servir",
|
|
367
|
+
picked_up: "Entregar",
|
|
368
|
+
dispatched: "Despachar",
|
|
369
|
+
delivered: "Confirmar entrega",
|
|
370
|
+
completed: "Concluir",
|
|
371
|
+
cancelled: "Cancelar"
|
|
372
|
+
};
|
|
373
|
+
function getPreviousStatus(kind, current) {
|
|
374
|
+
const flow = STATUS_FLOW[kind];
|
|
375
|
+
const idx = flow.indexOf(current);
|
|
376
|
+
return idx > 0 ? flow[idx - 1] : null;
|
|
377
|
+
}
|
|
378
|
+
function mapToKanbanColumn(status) {
|
|
379
|
+
if (["served", "picked_up", "dispatched", "delivered"].includes(status)) return "out";
|
|
380
|
+
if (["new", "confirmed", "preparing", "ready", "completed", "cancelled"].includes(status)) {
|
|
381
|
+
return status;
|
|
382
|
+
}
|
|
383
|
+
return "new";
|
|
384
|
+
}
|
|
385
|
+
var KANBAN_COLUMNS = ["new", "confirmed", "preparing", "ready", "out", "completed"];
|
|
386
|
+
var DEFAULT_STATUS_CONFIGS = [
|
|
387
|
+
{ value: "new", label: "New", color: "#6366f1", icon: "PlusCircle" },
|
|
388
|
+
{ value: "confirmed", label: "Confirmed", color: "#3b82f6", icon: "CheckCircle" },
|
|
389
|
+
{ value: "preparing", label: "Preparing", color: "#f59e0b", icon: "ChefHat" },
|
|
390
|
+
{ value: "ready", label: "Ready", color: "#10b981", icon: "Bell" },
|
|
391
|
+
{ value: "served", label: "Served", color: "#06b6d4", icon: "Utensils" },
|
|
392
|
+
{ value: "picked_up", label: "Picked Up", color: "#06b6d4", icon: "ShoppingBag" },
|
|
393
|
+
{ value: "dispatched", label: "Dispatched", color: "#8b5cf6", icon: "Truck" },
|
|
394
|
+
{ value: "delivered", label: "Delivered", color: "#06b6d4", icon: "PackageCheck" },
|
|
395
|
+
{ value: "completed", label: "Completed", color: "#22c55e", icon: "CircleCheck" },
|
|
396
|
+
{ value: "cancelled", label: "Cancelled", color: "#ef4444", icon: "XCircle" }
|
|
397
|
+
];
|
|
398
|
+
|
|
399
|
+
// src/store.ts
|
|
730
400
|
var ACTIVE_STATUSES = [
|
|
731
401
|
"new",
|
|
732
402
|
"confirmed",
|
|
@@ -929,6 +599,214 @@ var ordersRegistries = [
|
|
|
929
599
|
}
|
|
930
600
|
];
|
|
931
601
|
|
|
602
|
+
// src/migrations/index.ts
|
|
603
|
+
var MIGRATION_001_AN_ORDER_ARRIVES_FROM_A_MARKETPLACE = `-- ============================================================================
|
|
604
|
+
-- 001 \u2014 an order arrives from a marketplace
|
|
605
|
+
--
|
|
606
|
+
-- The V2 sink of ADR 0003: the ONE door through which the integration service
|
|
607
|
+
-- hands a canonical delivery order to this product, and the two beside it to
|
|
608
|
+
-- apply a later status and to read the current one. No rule lives in the
|
|
609
|
+
-- service; what an order IS here is decided here, in one transaction.
|
|
610
|
+
--
|
|
611
|
+
-- There is no person behind these calls \u2014 the actor is the service, acting
|
|
612
|
+
-- for the tenant it names, which is why \`p_tenant\` is explicit and no
|
|
613
|
+
-- \`app.has_permission\` is asked (the same reasoning as service_authorize_for).
|
|
614
|
+
-- The functions are therefore closed to every signed-in user and opened only
|
|
615
|
+
-- to the service's own role, \`fayz_integration\`, when that role exists on the
|
|
616
|
+
-- cluster. Creating the role is an operator step; the grant below is guarded.
|
|
617
|
+
--
|
|
618
|
+
-- The order shape is @fayz-ai/contracts' CanonicalOrder v1: money in
|
|
619
|
+
-- centavos, items with options, totals that close. Everything that this
|
|
620
|
+
-- product has no column for travels in orders.metadata.external, so nothing
|
|
621
|
+
-- the marketplace said is lost.
|
|
622
|
+
-- ============================================================================
|
|
623
|
+
|
|
624
|
+
CREATE OR REPLACE FUNCTION public.orders_ingest_external(
|
|
625
|
+
p_tenant uuid,
|
|
626
|
+
p_unit uuid,
|
|
627
|
+
p_order jsonb,
|
|
628
|
+
p_status text
|
|
629
|
+
) RETURNS uuid
|
|
630
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
631
|
+
SET search_path TO ''
|
|
632
|
+
AS $$
|
|
633
|
+
DECLARE
|
|
634
|
+
v_provider text := p_order ->> 'provider';
|
|
635
|
+
v_external text := p_order ->> 'externalId';
|
|
636
|
+
v_ref text;
|
|
637
|
+
v_kind text;
|
|
638
|
+
v_channel text;
|
|
639
|
+
v_order uuid;
|
|
640
|
+
v_item jsonb;
|
|
641
|
+
v_n integer := 0;
|
|
642
|
+
v_qty numeric;
|
|
643
|
+
v_unit numeric;
|
|
644
|
+
v_total numeric;
|
|
645
|
+
v_sub numeric;
|
|
646
|
+
v_disc numeric;
|
|
647
|
+
v_ship numeric;
|
|
648
|
+
BEGIN
|
|
649
|
+
IF p_tenant IS NULL THEN
|
|
650
|
+
RAISE EXCEPTION 'orders_ingest_external: tenant is required' USING ERRCODE = '22023';
|
|
651
|
+
END IF;
|
|
652
|
+
IF v_provider IS NULL OR v_external IS NULL THEN
|
|
653
|
+
RAISE EXCEPTION 'orders_ingest_external: order needs provider and externalId' USING ERRCODE = '22023';
|
|
654
|
+
END IF;
|
|
655
|
+
IF p_unit IS NOT NULL AND NOT EXISTS (
|
|
656
|
+
SELECT 1 FROM public.companies c WHERE c.id = p_unit AND c.tenant_id = p_tenant) THEN
|
|
657
|
+
RAISE EXCEPTION 'orders_ingest_external: unit % is not of tenant %', p_unit, p_tenant USING ERRCODE = '22023';
|
|
658
|
+
END IF;
|
|
659
|
+
|
|
660
|
+
-- Once per (provider, externalId) within the tenant: a replay returns the
|
|
661
|
+
-- order it already made. The key lives in metadata, as order_create's does.
|
|
662
|
+
v_ref := v_provider || ':' || v_external;
|
|
663
|
+
SELECT o.id INTO v_order FROM public.orders o
|
|
664
|
+
WHERE o.tenant_id = p_tenant AND o.metadata ->> 'externalRef' = v_ref
|
|
665
|
+
LIMIT 1;
|
|
666
|
+
IF v_order IS NOT NULL THEN
|
|
667
|
+
RETURN v_order;
|
|
668
|
+
END IF;
|
|
669
|
+
|
|
670
|
+
-- The contract's kinds are this product's kinds; the channel is where the
|
|
671
|
+
-- order came in, in this product's vocabulary.
|
|
672
|
+
v_kind := CASE p_order ->> 'kind'
|
|
673
|
+
WHEN 'delivery' THEN 'delivery'
|
|
674
|
+
WHEN 'takeout' THEN 'takeout'
|
|
675
|
+
WHEN 'dine_in' THEN 'dine_in'
|
|
676
|
+
ELSE 'delivery' END;
|
|
677
|
+
v_channel := CASE v_kind WHEN 'delivery' THEN 'delivery' WHEN 'dine_in' THEN 'dine_in' ELSE 'balcao' END;
|
|
678
|
+
|
|
679
|
+
IF p_status IS NULL OR NOT EXISTS (
|
|
680
|
+
SELECT 1 FROM public.order_kinds k WHERE k.kind = v_kind AND p_status = ANY (k.statuses)) THEN
|
|
681
|
+
RAISE EXCEPTION 'orders_ingest_external: % is not a status of kind %', p_status, v_kind USING ERRCODE = '22023';
|
|
682
|
+
END IF;
|
|
683
|
+
|
|
684
|
+
v_sub := round(coalesce((p_order -> 'totals' ->> 'subtotal')::numeric, 0) / 100, 2);
|
|
685
|
+
v_disc := round(coalesce((p_order -> 'totals' ->> 'discount')::numeric, 0) / 100, 2);
|
|
686
|
+
v_ship := round(coalesce((p_order -> 'totals' ->> 'deliveryFee')::numeric, 0) / 100, 2);
|
|
687
|
+
|
|
688
|
+
INSERT INTO public.orders (tenant_id, unit_id, kind, channel, status, reference_number,
|
|
689
|
+
subtotal, discount, shipping, total, currency, notes, metadata)
|
|
690
|
+
VALUES (p_tenant, p_unit, v_kind, v_channel, p_status,
|
|
691
|
+
nullif(p_order ->> 'displayId', ''),
|
|
692
|
+
v_sub, v_disc, v_ship,
|
|
693
|
+
round(coalesce((p_order -> 'totals' ->> 'total')::numeric, 0) / 100, 2),
|
|
694
|
+
coalesce(p_order -> 'totals' ->> 'currency', 'BRL'),
|
|
695
|
+
nullif(p_order ->> 'notes', ''),
|
|
696
|
+
jsonb_build_object(
|
|
697
|
+
'externalRef', v_ref,
|
|
698
|
+
'idempotencyKey', v_ref,
|
|
699
|
+
'external', jsonb_build_object(
|
|
700
|
+
'provider', v_provider,
|
|
701
|
+
'externalId', v_external,
|
|
702
|
+
'displayId', p_order -> 'displayId',
|
|
703
|
+
'merchantRef', p_order -> 'merchantRef',
|
|
704
|
+
'placedAt', p_order -> 'placedAt',
|
|
705
|
+
'timing', p_order -> 'timing',
|
|
706
|
+
'customer', p_order -> 'customer',
|
|
707
|
+
'payment', p_order -> 'payment',
|
|
708
|
+
'fulfillment', p_order -> 'fulfillment',
|
|
709
|
+
'totals', p_order -> 'totals',
|
|
710
|
+
'lastStatusAt', NULL)))
|
|
711
|
+
RETURNING id INTO v_order;
|
|
712
|
+
|
|
713
|
+
FOR v_item IN SELECT * FROM jsonb_array_elements(coalesce(p_order -> 'items', '[]'::jsonb)) LOOP
|
|
714
|
+
v_n := v_n + 1;
|
|
715
|
+
v_qty := coalesce((v_item ->> 'quantity')::numeric, 1);
|
|
716
|
+
v_unit := round(coalesce((v_item ->> 'unitPrice')::numeric, 0) / 100, 2);
|
|
717
|
+
v_total := round(coalesce((v_item ->> 'total')::numeric, 0) / 100, 2);
|
|
718
|
+
INSERT INTO public.items (tenant_id, unit_id, order_id, name, description, quantity,
|
|
719
|
+
unit_price, discount, total, sort_order, metadata)
|
|
720
|
+
VALUES (p_tenant, p_unit, v_order,
|
|
721
|
+
coalesce(nullif(btrim(coalesce(v_item ->> 'name', '')), ''), 'Item'),
|
|
722
|
+
nullif(v_item ->> 'notes', ''),
|
|
723
|
+
v_qty, v_unit, 0, v_total, v_n - 1,
|
|
724
|
+
jsonb_build_object(
|
|
725
|
+
'externalId', v_item -> 'externalId',
|
|
726
|
+
'externalCode', v_item -> 'externalCode',
|
|
727
|
+
'unit', v_item -> 'unit',
|
|
728
|
+
'options', coalesce(v_item -> 'options', '[]'::jsonb)));
|
|
729
|
+
END LOOP;
|
|
730
|
+
|
|
731
|
+
RETURN v_order;
|
|
732
|
+
END $$;
|
|
733
|
+
|
|
734
|
+
COMMENT ON FUNCTION public.orders_ingest_external(uuid, uuid, jsonb, text) IS
|
|
735
|
+
'The integration service hands a canonical marketplace order to this product, once per (provider, externalId). Actor: the service, for the named tenant.';
|
|
736
|
+
|
|
737
|
+
-- A later status for an order the service ingested. Monotonic on the
|
|
738
|
+
-- service side already; here the only rules are "this order is yours" and
|
|
739
|
+
-- "that status exists for its kind".
|
|
740
|
+
CREATE OR REPLACE FUNCTION public.orders_apply_external_status(
|
|
741
|
+
p_tenant uuid,
|
|
742
|
+
p_order uuid,
|
|
743
|
+
p_status text,
|
|
744
|
+
p_occurred_at timestamptz
|
|
745
|
+
) RETURNS void
|
|
746
|
+
LANGUAGE plpgsql SECURITY DEFINER
|
|
747
|
+
SET search_path TO ''
|
|
748
|
+
AS $$
|
|
749
|
+
DECLARE
|
|
750
|
+
v_kind text;
|
|
751
|
+
BEGIN
|
|
752
|
+
SELECT o.kind INTO v_kind FROM public.orders o
|
|
753
|
+
WHERE o.id = p_order AND o.tenant_id = p_tenant AND o.metadata ? 'externalRef';
|
|
754
|
+
IF v_kind IS NULL THEN
|
|
755
|
+
RAISE EXCEPTION 'orders_apply_external_status: order % is not an external order of tenant %', p_order, p_tenant
|
|
756
|
+
USING ERRCODE = '22023';
|
|
757
|
+
END IF;
|
|
758
|
+
IF NOT EXISTS (SELECT 1 FROM public.order_kinds k WHERE k.kind = v_kind AND p_status = ANY (k.statuses)) THEN
|
|
759
|
+
RAISE EXCEPTION 'orders_apply_external_status: % is not a status of kind %', p_status, v_kind USING ERRCODE = '22023';
|
|
760
|
+
END IF;
|
|
761
|
+
|
|
762
|
+
UPDATE public.orders
|
|
763
|
+
SET status = p_status,
|
|
764
|
+
completed_at = CASE WHEN p_status IN ('completed', 'delivered') THEN coalesce(p_occurred_at, now()) ELSE completed_at END,
|
|
765
|
+
metadata = jsonb_set(metadata, '{external,lastStatusAt}', to_jsonb(coalesce(p_occurred_at, now())), true),
|
|
766
|
+
updated_at = now()
|
|
767
|
+
WHERE id = p_order;
|
|
768
|
+
END $$;
|
|
769
|
+
|
|
770
|
+
COMMENT ON FUNCTION public.orders_apply_external_status(uuid, uuid, text, timestamptz) IS
|
|
771
|
+
'A later status from the marketplace for an order the service ingested. Actor: the service, for the named tenant.';
|
|
772
|
+
|
|
773
|
+
CREATE OR REPLACE FUNCTION public.orders_external_status(p_tenant uuid, p_order uuid)
|
|
774
|
+
RETURNS jsonb
|
|
775
|
+
LANGUAGE sql STABLE SECURITY DEFINER
|
|
776
|
+
SET search_path TO ''
|
|
777
|
+
AS $$
|
|
778
|
+
SELECT jsonb_build_object('status', o.status, 'updatedAt', o.updated_at)
|
|
779
|
+
FROM public.orders o
|
|
780
|
+
WHERE o.id = p_order AND o.tenant_id = p_tenant AND o.metadata ? 'externalRef';
|
|
781
|
+
$$;
|
|
782
|
+
|
|
783
|
+
COMMENT ON FUNCTION public.orders_external_status(uuid, uuid) IS
|
|
784
|
+
'What this product thinks an external order is now. Actor: the service, for the named tenant.';
|
|
785
|
+
|
|
786
|
+
-- Closed to people, open to the service. The pools grant EXECUTE to
|
|
787
|
+
-- \`authenticated\` on every new public function by default; a definer function
|
|
788
|
+
-- that takes the tenant as a parameter must not be reachable by a session.
|
|
789
|
+
REVOKE ALL ON FUNCTION public.orders_ingest_external(uuid, uuid, jsonb, text) FROM PUBLIC, anon, authenticated;
|
|
790
|
+
REVOKE ALL ON FUNCTION public.orders_apply_external_status(uuid, uuid, text, timestamptz) FROM PUBLIC, anon, authenticated;
|
|
791
|
+
REVOKE ALL ON FUNCTION public.orders_external_status(uuid, uuid) FROM PUBLIC, anon, authenticated;
|
|
792
|
+
GRANT EXECUTE ON FUNCTION public.orders_ingest_external(uuid, uuid, jsonb, text) TO service_role;
|
|
793
|
+
GRANT EXECUTE ON FUNCTION public.orders_apply_external_status(uuid, uuid, text, timestamptz) TO service_role;
|
|
794
|
+
GRANT EXECUTE ON FUNCTION public.orders_external_status(uuid, uuid) TO service_role;
|
|
795
|
+
|
|
796
|
+
DO $$
|
|
797
|
+
BEGIN
|
|
798
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'fayz_integration') THEN
|
|
799
|
+
GRANT USAGE ON SCHEMA public TO fayz_integration;
|
|
800
|
+
GRANT EXECUTE ON FUNCTION public.orders_ingest_external(uuid, uuid, jsonb, text) TO fayz_integration;
|
|
801
|
+
GRANT EXECUTE ON FUNCTION public.orders_apply_external_status(uuid, uuid, text, timestamptz) TO fayz_integration;
|
|
802
|
+
GRANT EXECUTE ON FUNCTION public.orders_external_status(uuid, uuid) TO fayz_integration;
|
|
803
|
+
END IF;
|
|
804
|
+
END $$;
|
|
805
|
+
`;
|
|
806
|
+
var MIGRATIONS = [
|
|
807
|
+
{ id: "001_an_order_arrives_from_a_marketplace", sql: MIGRATION_001_AN_ORDER_ARRIVES_FROM_A_MARKETPLACE }
|
|
808
|
+
];
|
|
809
|
+
|
|
932
810
|
// src/locales/en.ts
|
|
933
811
|
var en = {
|
|
934
812
|
"orders.title": "Orders",
|
|
@@ -1540,84 +1418,6 @@ var aiqfomeConnectorDef = {
|
|
|
1540
1418
|
disconnect: () => store6.disconnect(),
|
|
1541
1419
|
disconnectNote: "Desconectar para de trazer pedidos novos. Os pedidos que j\xE1 entraram continuam aqui."
|
|
1542
1420
|
};
|
|
1543
|
-
function toModalInstallment(bill) {
|
|
1544
|
-
return {
|
|
1545
|
-
id: bill.id,
|
|
1546
|
-
amount: Number(bill.amount ?? 0),
|
|
1547
|
-
paidAmount: Number(bill.paid_amount ?? 0),
|
|
1548
|
-
installmentNumber: bill.installment_number ?? 1,
|
|
1549
|
-
direction: bill.direction ?? "credit",
|
|
1550
|
-
dueDate: bill.due_date,
|
|
1551
|
-
invoiceId: bill.financial_invoice_id ?? bill.invoice_id
|
|
1552
|
-
};
|
|
1553
|
-
}
|
|
1554
|
-
function firstOpenBill(receivable) {
|
|
1555
|
-
return receivable?.bills.find(
|
|
1556
|
-
(b) => Number(b.paid_amount ?? 0) < Number(b.amount)
|
|
1557
|
-
);
|
|
1558
|
-
}
|
|
1559
|
-
function OrderReceivableActions({ orderId, orderTotal, receivable, reload }) {
|
|
1560
|
-
const [busy, setBusy] = React2.useState(false);
|
|
1561
|
-
const [paying, setPaying] = React2.useState(null);
|
|
1562
|
-
const can = usePermissionOptional();
|
|
1563
|
-
const runtime = usePluginRuntimeOptional();
|
|
1564
|
-
const hasFinancialRoute = (runtime?.activePlugins ?? []).some(
|
|
1565
|
-
(p) => p.routes?.some((r) => r.path === "/financial")
|
|
1566
|
-
);
|
|
1567
|
-
const canInvoice = can("financial", "create");
|
|
1568
|
-
const balance = receivable ? Number(receivable.balance.balance ?? 0) : 0;
|
|
1569
|
-
const cancelled = receivable?.balance.status === "cancelled";
|
|
1570
|
-
const openBill = firstOpenBill(receivable);
|
|
1571
|
-
const canReceive = receivable ? balance > 0 && !cancelled && Boolean(openBill) : canInvoice && Number(orderTotal) > 0;
|
|
1572
|
-
async function registrarRecebimento() {
|
|
1573
|
-
if (receivable) {
|
|
1574
|
-
const bill = firstOpenBill(receivable);
|
|
1575
|
-
if (bill) setPaying(toModalInstallment(bill));
|
|
1576
|
-
return;
|
|
1577
|
-
}
|
|
1578
|
-
const db = getSupabaseClientOptional();
|
|
1579
|
-
if (!db) return;
|
|
1580
|
-
setBusy(true);
|
|
1581
|
-
try {
|
|
1582
|
-
const { error } = await db.rpc("fn_invoice_from_order", { p_order_id: orderId });
|
|
1583
|
-
if (error) throw new Error(error.message ?? "Falha ao faturar o pedido");
|
|
1584
|
-
const { data } = await db.from("v_financial_bills").select("*").eq("invoice_id", orderId).order("installment_number", { ascending: true });
|
|
1585
|
-
const bill = (data ?? []).find(
|
|
1586
|
-
(b) => Number(b.paid_amount ?? 0) < Number(b.amount ?? 0)
|
|
1587
|
-
);
|
|
1588
|
-
await reload(true);
|
|
1589
|
-
if (bill) setPaying(toModalInstallment(bill));
|
|
1590
|
-
else toast$1.error("Pedido faturado, mas nenhuma parcela em aberto foi encontrada");
|
|
1591
|
-
} catch (err) {
|
|
1592
|
-
toast$1.error(describeError(err, "Falha ao faturar o pedido"));
|
|
1593
|
-
} finally {
|
|
1594
|
-
setBusy(false);
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1598
|
-
canReceive && /* @__PURE__ */ jsx(Button, { onClick: () => void registrarRecebimento(), disabled: busy, "data-testid": "order-register-receipt", children: busy ? "Abrindo\u2026" : "Registrar recebimento" }),
|
|
1599
|
-
hasFinancialRoute && /* @__PURE__ */ jsx(
|
|
1600
|
-
"button",
|
|
1601
|
-
{
|
|
1602
|
-
type: "button",
|
|
1603
|
-
onClick: () => adminNavigateTo("/financial"),
|
|
1604
|
-
className: "rounded-md border px-3 py-1.5 text-sm font-medium transition hover:bg-muted",
|
|
1605
|
-
children: "Abrir no Financeiro"
|
|
1606
|
-
}
|
|
1607
|
-
),
|
|
1608
|
-
paying != null && /* @__PURE__ */ jsx(
|
|
1609
|
-
ReceivePaymentModal,
|
|
1610
|
-
{
|
|
1611
|
-
installment: paying,
|
|
1612
|
-
onClose: () => setPaying(null),
|
|
1613
|
-
onPaid: () => {
|
|
1614
|
-
setPaying(null);
|
|
1615
|
-
void reload(true);
|
|
1616
|
-
}
|
|
1617
|
-
}
|
|
1618
|
-
)
|
|
1619
|
-
] });
|
|
1620
|
-
}
|
|
1621
1421
|
var DEBOUNCE_MS = 400;
|
|
1622
1422
|
var KEYS = {
|
|
1623
1423
|
enabled: "delivery.enabled",
|
|
@@ -1633,14 +1433,14 @@ function message(e) {
|
|
|
1633
1433
|
return String(e);
|
|
1634
1434
|
}
|
|
1635
1435
|
function useDeliverySettings(defaults = {}) {
|
|
1636
|
-
const client =
|
|
1436
|
+
const client = React.useMemo(() => getSupabaseClientOptional(), []);
|
|
1637
1437
|
const supported = !!client;
|
|
1638
|
-
const [stored, setStored] =
|
|
1639
|
-
const [loading, setLoading] =
|
|
1640
|
-
const [saving, setSaving] =
|
|
1641
|
-
const [error, setError] =
|
|
1642
|
-
const [loadError, setLoadError] =
|
|
1643
|
-
|
|
1438
|
+
const [stored, setStored] = React.useState({});
|
|
1439
|
+
const [loading, setLoading] = React.useState(supported);
|
|
1440
|
+
const [saving, setSaving] = React.useState(false);
|
|
1441
|
+
const [error, setError] = React.useState(null);
|
|
1442
|
+
const [loadError, setLoadError] = React.useState(null);
|
|
1443
|
+
React.useEffect(() => {
|
|
1644
1444
|
if (!client) return;
|
|
1645
1445
|
let cancelled = false;
|
|
1646
1446
|
setLoading(true);
|
|
@@ -1667,12 +1467,12 @@ function useDeliverySettings(defaults = {}) {
|
|
|
1667
1467
|
cancelled = true;
|
|
1668
1468
|
};
|
|
1669
1469
|
}, [client]);
|
|
1670
|
-
const pendingRef =
|
|
1671
|
-
const timerRef =
|
|
1672
|
-
|
|
1470
|
+
const pendingRef = React.useRef({});
|
|
1471
|
+
const timerRef = React.useRef(null);
|
|
1472
|
+
React.useEffect(() => () => {
|
|
1673
1473
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
1674
1474
|
}, []);
|
|
1675
|
-
const set =
|
|
1475
|
+
const set = React.useCallback((patch) => {
|
|
1676
1476
|
setStored((current) => ({ ...current, ...patch }));
|
|
1677
1477
|
if (!client) return;
|
|
1678
1478
|
setError(null);
|
|
@@ -1691,7 +1491,7 @@ function useDeliverySettings(defaults = {}) {
|
|
|
1691
1491
|
).catch((e) => setError(message(e))).finally(() => setSaving(false));
|
|
1692
1492
|
}, DEBOUNCE_MS);
|
|
1693
1493
|
}, [client]);
|
|
1694
|
-
const value =
|
|
1494
|
+
const value = React.useMemo(() => ({ ...defaults, ...stored }), [defaults, stored]);
|
|
1695
1495
|
return { value, set, loading, saving, error, loadError, supported };
|
|
1696
1496
|
}
|
|
1697
1497
|
var MARKETPLACE_CATEGORIES = ["marketplace"];
|
|
@@ -1706,9 +1506,9 @@ function amountText(value) {
|
|
|
1706
1506
|
return String(value).replace(".", ",");
|
|
1707
1507
|
}
|
|
1708
1508
|
function AmountRow({ label, description, hint, value, onCommit }) {
|
|
1709
|
-
const [text, setText] =
|
|
1710
|
-
const [invalid, setInvalid] =
|
|
1711
|
-
|
|
1509
|
+
const [text, setText] = React.useState(() => amountText(value));
|
|
1510
|
+
const [invalid, setInvalid] = React.useState(false);
|
|
1511
|
+
React.useEffect(() => {
|
|
1712
1512
|
setText(amountText(value));
|
|
1713
1513
|
}, [value]);
|
|
1714
1514
|
return /* @__PURE__ */ jsx(
|
|
@@ -1740,14 +1540,14 @@ function DeliverySettings() {
|
|
|
1740
1540
|
const settings = useDeliverySettings({ enabled: true, fee: 0, minOrder: 0, etaMinutes: 45, radiusKm: 0 });
|
|
1741
1541
|
const { value, set } = settings;
|
|
1742
1542
|
const { connectors } = useConnectors({ hostPluginId: "orders", includeGrants: false });
|
|
1743
|
-
const [adding, setAdding] =
|
|
1744
|
-
const [selected, setSelected] =
|
|
1543
|
+
const [adding, setAdding] = React.useState(false);
|
|
1544
|
+
const [selected, setSelected] = React.useState(null);
|
|
1745
1545
|
const panelAvailable = useConnectorsPanelAvailable();
|
|
1746
|
-
const openJourney =
|
|
1546
|
+
const openJourney = React.useCallback((connector) => {
|
|
1747
1547
|
if (panelAvailable) openConnectorsPanel(connector);
|
|
1748
1548
|
else setSelected(connector);
|
|
1749
1549
|
}, [panelAvailable]);
|
|
1750
|
-
const marketplaces =
|
|
1550
|
+
const marketplaces = React.useMemo(
|
|
1751
1551
|
() => (connectors ?? []).filter((c) => c.category && MARKETPLACE_CATEGORIES.includes(c.category)),
|
|
1752
1552
|
[connectors]
|
|
1753
1553
|
);
|
|
@@ -1925,409 +1725,816 @@ function numberValue(value) {
|
|
|
1925
1725
|
function dateKey(value) {
|
|
1926
1726
|
return value.slice(0, 10);
|
|
1927
1727
|
}
|
|
1928
|
-
function referenceNumber() {
|
|
1929
|
-
return `ORD-${Date.now().toString().slice(-6)}`;
|
|
1728
|
+
function referenceNumber() {
|
|
1729
|
+
return `ORD-${Date.now().toString().slice(-6)}`;
|
|
1730
|
+
}
|
|
1731
|
+
function objectValue(value) {
|
|
1732
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1733
|
+
}
|
|
1734
|
+
function canonicalStatus(status) {
|
|
1735
|
+
if (status === "new") return "draft";
|
|
1736
|
+
if (status === "completed" || status === "cancelled") return status;
|
|
1737
|
+
return "confirmed";
|
|
1738
|
+
}
|
|
1739
|
+
function restaurantStatus(rowStatus, metadata) {
|
|
1740
|
+
if (rowStatus === "completed" || rowStatus === "cancelled") return rowStatus;
|
|
1741
|
+
const stored = metadata.restaurantStatus;
|
|
1742
|
+
if (typeof stored === "string" && RESTAURANT_STATUSES.has(stored)) return stored;
|
|
1743
|
+
if (rowStatus === "draft") return "new";
|
|
1744
|
+
if (typeof rowStatus === "string" && RESTAURANT_STATUSES.has(rowStatus)) return rowStatus;
|
|
1745
|
+
return "confirmed";
|
|
1746
|
+
}
|
|
1747
|
+
function ordersMetadata(metadata) {
|
|
1748
|
+
return objectValue(metadata?.[ORDERS_METADATA_KEY]);
|
|
1749
|
+
}
|
|
1750
|
+
function mergeOrdersMetadata(metadata, patch) {
|
|
1751
|
+
const base = { ...metadata ?? {} };
|
|
1752
|
+
const current = ordersMetadata(base);
|
|
1753
|
+
const definedPatch = Object.fromEntries(
|
|
1754
|
+
Object.entries(patch).filter(([, value]) => value !== void 0)
|
|
1755
|
+
);
|
|
1756
|
+
const next = { ...current, ...definedPatch };
|
|
1757
|
+
return { ...base, [ORDERS_METADATA_KEY]: next };
|
|
1758
|
+
}
|
|
1759
|
+
function itemToRow(item, orderId, sortOrder, tenantId, unitId) {
|
|
1760
|
+
const discount = item.discount ?? 0;
|
|
1761
|
+
return {
|
|
1762
|
+
order_id: orderId,
|
|
1763
|
+
...tenantId ? { tenant_id: tenantId } : {},
|
|
1764
|
+
...unitId ? { unit_id: unitId } : {},
|
|
1765
|
+
product_id: item.productId ?? null,
|
|
1766
|
+
name: item.name,
|
|
1767
|
+
quantity: item.quantity,
|
|
1768
|
+
unit_price: item.unitPrice,
|
|
1769
|
+
discount,
|
|
1770
|
+
total: item.quantity * item.unitPrice - discount,
|
|
1771
|
+
sort_order: sortOrder,
|
|
1772
|
+
metadata: mergeOrdersMetadata(void 0, {
|
|
1773
|
+
modifiers: item.modifiers ?? null,
|
|
1774
|
+
kitchenNotes: item.kitchenNotes ?? null
|
|
1775
|
+
})
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
function rowToItem(row) {
|
|
1779
|
+
const quantity = numberValue(row.quantity);
|
|
1780
|
+
const unitPrice = numberValue(row.unit_price);
|
|
1781
|
+
const discount = numberValue(row.discount);
|
|
1782
|
+
const restaurant = ordersMetadata(row.metadata);
|
|
1783
|
+
return {
|
|
1784
|
+
id: row.id,
|
|
1785
|
+
orderId: row.order_id ?? "",
|
|
1786
|
+
productId: row.product_id ?? void 0,
|
|
1787
|
+
name: row.name ?? "Item",
|
|
1788
|
+
description: row.description ?? void 0,
|
|
1789
|
+
quantity,
|
|
1790
|
+
unitPrice,
|
|
1791
|
+
discount,
|
|
1792
|
+
total: numberValue(row.total) || quantity * unitPrice - discount,
|
|
1793
|
+
sortOrder: row.sort_order ?? 0,
|
|
1794
|
+
modifiers: row.modifiers ?? (typeof restaurant.modifiers === "string" ? restaurant.modifiers : void 0),
|
|
1795
|
+
kitchenNotes: row.kitchen_notes ?? (typeof restaurant.kitchenNotes === "string" ? restaurant.kitchenNotes : void 0),
|
|
1796
|
+
metadata: row.metadata ?? {},
|
|
1797
|
+
createdAt: row.created_at ?? nowIso()
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
function rowToOrder(row, items = []) {
|
|
1801
|
+
const subtotal = numberValue(row.subtotal) || items.reduce((sum, item) => sum + item.total, 0);
|
|
1802
|
+
const discount = numberValue(row.discount);
|
|
1803
|
+
const tax = numberValue(row.tax);
|
|
1804
|
+
const restaurant = ordersMetadata(row.metadata);
|
|
1805
|
+
return {
|
|
1806
|
+
id: row.id,
|
|
1807
|
+
referenceNumber: row.reference_number ?? row.id,
|
|
1808
|
+
kind: row.kind ?? "dine_in",
|
|
1809
|
+
status: restaurantStatus(row.status, restaurant),
|
|
1810
|
+
partyId: row.party_id ?? void 0,
|
|
1811
|
+
partyName: row.party_name ?? (typeof restaurant.partyName === "string" ? restaurant.partyName : void 0),
|
|
1812
|
+
assigneeId: row.assignee_id ?? void 0,
|
|
1813
|
+
assigneeName: row.assignee_name ?? (typeof restaurant.assigneeName === "string" ? restaurant.assigneeName : void 0),
|
|
1814
|
+
locationId: row.location_id ?? void 0,
|
|
1815
|
+
subtotal,
|
|
1816
|
+
discount,
|
|
1817
|
+
tax,
|
|
1818
|
+
total: numberValue(row.total) || subtotal - discount + tax,
|
|
1819
|
+
currency: row.currency ?? "BRL",
|
|
1820
|
+
notes: row.notes ?? void 0,
|
|
1821
|
+
tags: row.tags ?? [],
|
|
1822
|
+
source: row.source ?? (typeof restaurant.source === "string" ? restaurant.source : "pos"),
|
|
1823
|
+
tableId: row.table_id ?? (typeof restaurant.tableId === "string" ? restaurant.tableId : void 0),
|
|
1824
|
+
tableName: row.table_name ?? (typeof restaurant.tableName === "string" ? restaurant.tableName : void 0),
|
|
1825
|
+
estimatedMinutes: row.estimated_minutes ?? (typeof restaurant.estimatedMinutes === "number" ? restaurant.estimatedMinutes : void 0),
|
|
1826
|
+
items,
|
|
1827
|
+
metadata: row.metadata ?? {},
|
|
1828
|
+
tenantId: row.tenant_id ?? "runtime-tenant",
|
|
1829
|
+
unitId: row.unit_id ?? void 0,
|
|
1830
|
+
createdAt: row.created_at ?? nowIso(),
|
|
1831
|
+
updatedAt: row.updated_at ?? row.created_at ?? nowIso(),
|
|
1832
|
+
completedAt: row.completed_at ?? void 0
|
|
1833
|
+
};
|
|
1834
|
+
}
|
|
1835
|
+
function orderPayload(input) {
|
|
1836
|
+
const subtotal = input.items.reduce((sum, item) => sum + item.quantity * item.unitPrice - (item.discount ?? 0), 0);
|
|
1837
|
+
return {
|
|
1838
|
+
reference_number: referenceNumber(),
|
|
1839
|
+
kind: input.kind,
|
|
1840
|
+
status: "draft",
|
|
1841
|
+
party_id: input.partyId ?? null,
|
|
1842
|
+
assignee_id: input.assigneeId ?? null,
|
|
1843
|
+
notes: input.notes ?? null,
|
|
1844
|
+
subtotal,
|
|
1845
|
+
discount: 0,
|
|
1846
|
+
tax: 0,
|
|
1847
|
+
total: subtotal,
|
|
1848
|
+
currency: "BRL",
|
|
1849
|
+
tags: [],
|
|
1850
|
+
metadata: mergeOrdersMetadata(void 0, {
|
|
1851
|
+
source: input.source ?? "pos",
|
|
1852
|
+
tableId: input.tableId ?? null,
|
|
1853
|
+
estimatedMinutes: input.estimatedMinutes ?? null,
|
|
1854
|
+
restaurantStatus: "new"
|
|
1855
|
+
})
|
|
1856
|
+
};
|
|
1930
1857
|
}
|
|
1931
|
-
function
|
|
1932
|
-
|
|
1858
|
+
function updatePayload(data, existingMetadata) {
|
|
1859
|
+
const payload = {};
|
|
1860
|
+
if (data.referenceNumber !== void 0) payload.reference_number = data.referenceNumber;
|
|
1861
|
+
if (data.kind !== void 0) payload.kind = data.kind;
|
|
1862
|
+
if (data.status !== void 0) payload.status = canonicalStatus(data.status);
|
|
1863
|
+
if (data.partyId !== void 0) payload.party_id = data.partyId;
|
|
1864
|
+
if (data.assigneeId !== void 0) payload.assignee_id = data.assigneeId;
|
|
1865
|
+
if (data.locationId !== void 0) payload.location_id = data.locationId;
|
|
1866
|
+
if (data.subtotal !== void 0) payload.subtotal = data.subtotal;
|
|
1867
|
+
if (data.discount !== void 0) payload.discount = data.discount;
|
|
1868
|
+
if (data.tax !== void 0) payload.tax = data.tax;
|
|
1869
|
+
if (data.total !== void 0) payload.total = data.total;
|
|
1870
|
+
if (data.currency !== void 0) payload.currency = data.currency;
|
|
1871
|
+
if (data.notes !== void 0) payload.notes = data.notes;
|
|
1872
|
+
if (data.tags !== void 0) payload.tags = data.tags;
|
|
1873
|
+
const metadataBase = data.metadata !== void 0 ? { ...existingMetadata ?? {}, ...data.metadata } : existingMetadata;
|
|
1874
|
+
const metadataPatch = {
|
|
1875
|
+
partyName: data.partyName,
|
|
1876
|
+
assigneeName: data.assigneeName,
|
|
1877
|
+
source: data.source,
|
|
1878
|
+
tableId: data.tableId,
|
|
1879
|
+
tableName: data.tableName,
|
|
1880
|
+
estimatedMinutes: data.estimatedMinutes,
|
|
1881
|
+
restaurantStatus: data.status
|
|
1882
|
+
};
|
|
1883
|
+
if (data.metadata !== void 0 || Object.values(metadataPatch).some((value) => value !== void 0)) {
|
|
1884
|
+
payload.metadata = mergeOrdersMetadata(metadataBase, metadataPatch);
|
|
1885
|
+
}
|
|
1886
|
+
if (data.completedAt !== void 0) payload.completed_at = data.completedAt;
|
|
1887
|
+
return payload;
|
|
1933
1888
|
}
|
|
1934
|
-
function
|
|
1935
|
-
|
|
1936
|
-
if (
|
|
1937
|
-
|
|
1889
|
+
function filtersFromQuery(query) {
|
|
1890
|
+
const filters = [];
|
|
1891
|
+
if (query.kind && !Array.isArray(query.kind)) filters.push({ column: "kind", operator: "eq", value: query.kind });
|
|
1892
|
+
if (query.assigneeId) filters.push({ column: "assignee_id", operator: "eq", value: query.assigneeId });
|
|
1893
|
+
return filters;
|
|
1938
1894
|
}
|
|
1939
|
-
function
|
|
1940
|
-
if (
|
|
1941
|
-
|
|
1942
|
-
if (
|
|
1943
|
-
if (
|
|
1944
|
-
if (
|
|
1945
|
-
return
|
|
1895
|
+
function matchesClientQuery(order, query) {
|
|
1896
|
+
if (Array.isArray(query.kind) && !query.kind.includes(order.kind)) return false;
|
|
1897
|
+
if (Array.isArray(query.status) && !query.status.includes(order.status)) return false;
|
|
1898
|
+
if (query.status && !Array.isArray(query.status) && order.status !== query.status) return false;
|
|
1899
|
+
if (query.source && order.source !== query.source) return false;
|
|
1900
|
+
if (query.tableId && order.tableId !== query.tableId) return false;
|
|
1901
|
+
if (query.assigneeId && order.assigneeId !== query.assigneeId) return false;
|
|
1902
|
+
if (query.dateRange) {
|
|
1903
|
+
const created = dateKey(order.createdAt);
|
|
1904
|
+
if (created < query.dateRange.from || created > query.dateRange.to) return false;
|
|
1905
|
+
}
|
|
1906
|
+
if (query.search) {
|
|
1907
|
+
const search = query.search.toLowerCase();
|
|
1908
|
+
return order.referenceNumber.toLowerCase().includes(search) || (order.partyName?.toLowerCase().includes(search) ?? false) || (order.tableName?.toLowerCase().includes(search) ?? false) || order.items.some((item) => item.name.toLowerCase().includes(search));
|
|
1909
|
+
}
|
|
1910
|
+
return true;
|
|
1946
1911
|
}
|
|
1947
|
-
function
|
|
1948
|
-
|
|
1912
|
+
function paginate(items, page, pageSize) {
|
|
1913
|
+
const currentPage = page ?? 1;
|
|
1914
|
+
const limit = pageSize ?? 20;
|
|
1915
|
+
const start = (currentPage - 1) * limit;
|
|
1916
|
+
return { data: items.slice(start, start + limit), total: items.length };
|
|
1949
1917
|
}
|
|
1950
|
-
function
|
|
1951
|
-
const
|
|
1952
|
-
const
|
|
1953
|
-
const
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1918
|
+
function summarize(orders, dateRange) {
|
|
1919
|
+
const today = dateKey(nowIso());
|
|
1920
|
+
const from = dateRange?.from ?? today;
|
|
1921
|
+
const to = dateRange?.to ?? today;
|
|
1922
|
+
const inRange = orders.filter((order) => {
|
|
1923
|
+
const created = dateKey(order.createdAt);
|
|
1924
|
+
return created >= from && created <= to;
|
|
1925
|
+
});
|
|
1926
|
+
const billable = inRange.filter((order) => order.status !== "cancelled");
|
|
1927
|
+
const revenue = billable.reduce((sum, order) => sum + order.total, 0);
|
|
1928
|
+
const ordersByKind = { dine_in: 0, takeout: 0, delivery: 0 };
|
|
1929
|
+
const ordersByStatus = {};
|
|
1930
|
+
const top = /* @__PURE__ */ new Map();
|
|
1931
|
+
for (const order of inRange) {
|
|
1932
|
+
ordersByKind[order.kind] = (ordersByKind[order.kind] ?? 0) + 1;
|
|
1933
|
+
ordersByStatus[order.status] = (ordersByStatus[order.status] ?? 0) + 1;
|
|
1934
|
+
if (order.status === "cancelled") continue;
|
|
1935
|
+
for (const item of order.items) {
|
|
1936
|
+
const current = top.get(item.name) ?? { name: item.name, quantity: 0, revenue: 0 };
|
|
1937
|
+
current.quantity += item.quantity;
|
|
1938
|
+
current.revenue += item.total;
|
|
1939
|
+
top.set(item.name, current);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
return {
|
|
1943
|
+
todayOrderCount: inRange.length,
|
|
1944
|
+
todayRevenue: revenue,
|
|
1945
|
+
averageTicket: billable.length > 0 ? revenue / billable.length : 0,
|
|
1946
|
+
activeOrderCount: orders.filter((order) => ACTIVE_STATUSES2.has(order.status)).length,
|
|
1947
|
+
ordersByKind,
|
|
1948
|
+
ordersByStatus,
|
|
1949
|
+
topItems: [...top.values()].sort((a, b) => b.quantity - a.quantity).slice(0, 5)
|
|
1950
|
+
};
|
|
1958
1951
|
}
|
|
1959
|
-
function
|
|
1960
|
-
const
|
|
1952
|
+
function createFayzOrdersProvider(options = {}) {
|
|
1953
|
+
const client = createFayzClient(options);
|
|
1954
|
+
const ordersTable = options.ordersTable ?? DEFAULT_ORDERS_TABLE;
|
|
1955
|
+
const orderItemsTable = options.orderItemsTable ?? DEFAULT_ORDER_ITEMS_TABLE;
|
|
1956
|
+
const tableOptions = { projectId: options.projectId, schema: options.schema, runtime: options.runtime };
|
|
1957
|
+
function resolveTenant() {
|
|
1958
|
+
const value = typeof options.tenantId === "function" ? options.tenantId() : options.tenantId;
|
|
1959
|
+
return value ?? void 0;
|
|
1960
|
+
}
|
|
1961
|
+
function resolveUnit() {
|
|
1962
|
+
const value = typeof options.unitId === "function" ? options.unitId() : options.unitId;
|
|
1963
|
+
return value ?? void 0;
|
|
1964
|
+
}
|
|
1965
|
+
function withTenant(filters) {
|
|
1966
|
+
const tenantId = resolveTenant();
|
|
1967
|
+
return tenantId ? [{ column: "tenant_id", operator: "eq", value: tenantId }, ...filters] : filters;
|
|
1968
|
+
}
|
|
1969
|
+
async function listItemRows(orderIds) {
|
|
1970
|
+
if (orderIds.length === 0) return [];
|
|
1971
|
+
const rows = [];
|
|
1972
|
+
await Promise.all(orderIds.map(async (orderId) => {
|
|
1973
|
+
const response = await client.data.listRows({
|
|
1974
|
+
...tableOptions,
|
|
1975
|
+
table: orderItemsTable,
|
|
1976
|
+
filters: [{ column: "order_id", operator: "eq", value: orderId }],
|
|
1977
|
+
sortColumn: "sort_order",
|
|
1978
|
+
sortDirection: "asc",
|
|
1979
|
+
limit: 200
|
|
1980
|
+
});
|
|
1981
|
+
rows.push(...response.rows);
|
|
1982
|
+
}));
|
|
1983
|
+
return rows;
|
|
1984
|
+
}
|
|
1985
|
+
async function hydrate(rows) {
|
|
1986
|
+
const itemRows = await listItemRows(rows.map((row) => row.id));
|
|
1987
|
+
const byOrder = /* @__PURE__ */ new Map();
|
|
1988
|
+
for (const itemRow of itemRows) {
|
|
1989
|
+
const item = rowToItem(itemRow);
|
|
1990
|
+
const list = byOrder.get(item.orderId) ?? [];
|
|
1991
|
+
list.push(item);
|
|
1992
|
+
byOrder.set(item.orderId, list);
|
|
1993
|
+
}
|
|
1994
|
+
return rows.map((row) => rowToOrder(row, byOrder.get(row.id) ?? []));
|
|
1995
|
+
}
|
|
1996
|
+
async function listOrders(query = {}) {
|
|
1997
|
+
const response = await client.data.listRows({
|
|
1998
|
+
...tableOptions,
|
|
1999
|
+
table: ordersTable,
|
|
2000
|
+
filters: withTenant(filtersFromQuery(query)),
|
|
2001
|
+
sortColumn: "created_at",
|
|
2002
|
+
sortDirection: "desc",
|
|
2003
|
+
limit: 500
|
|
2004
|
+
});
|
|
2005
|
+
return (await hydrate(response.rows)).filter((order) => matchesClientQuery(order, query));
|
|
2006
|
+
}
|
|
2007
|
+
async function getById(id) {
|
|
2008
|
+
const response = await client.data.listRows({
|
|
2009
|
+
...tableOptions,
|
|
2010
|
+
table: ordersTable,
|
|
2011
|
+
filters: withTenant([{ column: "id", operator: "eq", value: id }]),
|
|
2012
|
+
limit: 1
|
|
2013
|
+
});
|
|
2014
|
+
return (await hydrate(response.rows))[0] ?? null;
|
|
2015
|
+
}
|
|
1961
2016
|
return {
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
2017
|
+
async getOrders(query) {
|
|
2018
|
+
const orders = await listOrders(query);
|
|
2019
|
+
return paginate(orders, query.page, query.pageSize);
|
|
2020
|
+
},
|
|
2021
|
+
async getOrderById(id) {
|
|
2022
|
+
return getById(id);
|
|
2023
|
+
},
|
|
2024
|
+
async createOrder(input) {
|
|
2025
|
+
const tenantId = resolveTenant();
|
|
2026
|
+
const unitId = resolveUnit();
|
|
2027
|
+
const orderRow = await client.data.createRow({
|
|
2028
|
+
...tableOptions,
|
|
2029
|
+
table: ordersTable,
|
|
2030
|
+
row: {
|
|
2031
|
+
...orderPayload(input),
|
|
2032
|
+
...tenantId ? { tenant_id: tenantId } : {},
|
|
2033
|
+
...unitId ? { unit_id: unitId } : {}
|
|
2034
|
+
}
|
|
2035
|
+
});
|
|
2036
|
+
await Promise.all(input.items.map((item, index) => client.data.createRow({
|
|
2037
|
+
...tableOptions,
|
|
2038
|
+
table: orderItemsTable,
|
|
2039
|
+
row: itemToRow(item, orderRow.id, index, tenantId, unitId)
|
|
2040
|
+
})));
|
|
2041
|
+
return await getById(orderRow.id) ?? rowToOrder(orderRow);
|
|
2042
|
+
},
|
|
2043
|
+
async updateOrderStatus(input) {
|
|
2044
|
+
const completedAt = input.status === "completed" ? nowIso() : void 0;
|
|
2045
|
+
return this.updateOrder(input.orderId, { status: input.status, completedAt });
|
|
2046
|
+
},
|
|
2047
|
+
async updateOrder(id, data) {
|
|
2048
|
+
const existing = await getById(id);
|
|
2049
|
+
const row = await client.data.updateRow({
|
|
2050
|
+
...tableOptions,
|
|
2051
|
+
table: ordersTable,
|
|
2052
|
+
primaryKeys: { id },
|
|
2053
|
+
row: updatePayload(data, existing?.metadata)
|
|
2054
|
+
});
|
|
2055
|
+
return await getById(row.id) ?? rowToOrder(row);
|
|
2056
|
+
},
|
|
2057
|
+
async cancelOrder(id, reason) {
|
|
2058
|
+
const order = await getById(id);
|
|
2059
|
+
await this.updateOrder(id, {
|
|
2060
|
+
status: "cancelled",
|
|
2061
|
+
notes: reason ? `${order?.notes ? `${order.notes}
|
|
2062
|
+
` : ""}Cancelled: ${reason}` : order?.notes
|
|
2063
|
+
});
|
|
2064
|
+
},
|
|
2065
|
+
async addOrderItem(orderId, item) {
|
|
2066
|
+
const order = await getById(orderId);
|
|
2067
|
+
const row = await client.data.createRow({
|
|
2068
|
+
...tableOptions,
|
|
2069
|
+
table: orderItemsTable,
|
|
2070
|
+
row: itemToRow(item, orderId, order?.items.length ?? 0, order?.tenantId, order?.unitId ?? resolveUnit())
|
|
2071
|
+
});
|
|
2072
|
+
const created = rowToItem(row);
|
|
2073
|
+
const items = [...order?.items ?? [], created];
|
|
2074
|
+
await this.updateOrder(orderId, {
|
|
2075
|
+
subtotal: items.reduce((sum, orderItem) => sum + orderItem.total, 0),
|
|
2076
|
+
total: items.reduce((sum, orderItem) => sum + orderItem.total, 0) - (order?.discount ?? 0) + (order?.tax ?? 0)
|
|
2077
|
+
});
|
|
2078
|
+
return created;
|
|
2079
|
+
},
|
|
2080
|
+
async removeOrderItem(itemId) {
|
|
2081
|
+
const orders = await listOrders();
|
|
2082
|
+
const order = orders.find((item) => item.items.some((orderItem) => orderItem.id === itemId));
|
|
2083
|
+
await client.data.deleteRows({ ...tableOptions, table: orderItemsTable, rows: [{ id: itemId }] });
|
|
2084
|
+
if (order) {
|
|
2085
|
+
const items = order.items.filter((item) => item.id !== itemId);
|
|
2086
|
+
const subtotal = items.reduce((sum, item) => sum + item.total, 0);
|
|
2087
|
+
await this.updateOrder(order.id, { subtotal, total: subtotal - order.discount + order.tax });
|
|
2088
|
+
}
|
|
2089
|
+
},
|
|
2090
|
+
async updateOrderItem(itemId, data) {
|
|
2091
|
+
const existingResult = await client.data.listRows({
|
|
2092
|
+
...tableOptions,
|
|
2093
|
+
table: orderItemsTable,
|
|
2094
|
+
filters: [{ column: "id", operator: "eq", value: itemId }],
|
|
2095
|
+
limit: 1
|
|
2096
|
+
});
|
|
2097
|
+
const existingMetadata = existingResult.rows[0]?.metadata ?? void 0;
|
|
2098
|
+
const metadataPatch = {
|
|
2099
|
+
modifiers: data.modifiers,
|
|
2100
|
+
kitchenNotes: data.kitchenNotes
|
|
2101
|
+
};
|
|
2102
|
+
const row = await client.data.updateRow({
|
|
2103
|
+
...tableOptions,
|
|
2104
|
+
table: orderItemsTable,
|
|
2105
|
+
primaryKeys: { id: itemId },
|
|
2106
|
+
row: {
|
|
2107
|
+
product_id: data.productId,
|
|
2108
|
+
name: data.name,
|
|
2109
|
+
description: data.description,
|
|
2110
|
+
quantity: data.quantity,
|
|
2111
|
+
unit_price: data.unitPrice,
|
|
2112
|
+
discount: data.discount,
|
|
2113
|
+
total: data.total,
|
|
2114
|
+
sort_order: data.sortOrder,
|
|
2115
|
+
metadata: data.metadata !== void 0 || Object.values(metadataPatch).some((value) => value !== void 0) ? mergeOrdersMetadata(
|
|
2116
|
+
data.metadata !== void 0 ? { ...existingMetadata ?? {}, ...data.metadata } : existingMetadata,
|
|
2117
|
+
metadataPatch
|
|
2118
|
+
) : void 0
|
|
2119
|
+
}
|
|
2120
|
+
});
|
|
2121
|
+
return rowToItem(row);
|
|
2122
|
+
},
|
|
2123
|
+
async getSummary(dateRange) {
|
|
2124
|
+
return summarize(await listOrders({ dateRange }), dateRange);
|
|
2125
|
+
}
|
|
1976
2126
|
};
|
|
1977
2127
|
}
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2128
|
+
|
|
2129
|
+
// src/data/mock.ts
|
|
2130
|
+
var uid = 100;
|
|
2131
|
+
function nextId(prefix) {
|
|
2132
|
+
return prefix + "-" + ++uid;
|
|
2133
|
+
}
|
|
2134
|
+
var now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2135
|
+
function makeItem(orderId, name, quantity, unitPrice, sortOrder, productId) {
|
|
2136
|
+
const id = nextId("item");
|
|
1983
2137
|
return {
|
|
1984
|
-
id
|
|
1985
|
-
orderId
|
|
1986
|
-
productId
|
|
1987
|
-
name
|
|
1988
|
-
description: row.description ?? void 0,
|
|
2138
|
+
id,
|
|
2139
|
+
orderId,
|
|
2140
|
+
productId,
|
|
2141
|
+
name,
|
|
1989
2142
|
quantity,
|
|
1990
2143
|
unitPrice,
|
|
1991
|
-
discount,
|
|
1992
|
-
total:
|
|
1993
|
-
sortOrder
|
|
1994
|
-
|
|
1995
|
-
kitchenNotes: row.kitchen_notes ?? (typeof restaurant.kitchenNotes === "string" ? restaurant.kitchenNotes : void 0),
|
|
1996
|
-
metadata: row.metadata ?? {},
|
|
1997
|
-
createdAt: row.created_at ?? nowIso()
|
|
2144
|
+
discount: 0,
|
|
2145
|
+
total: quantity * unitPrice,
|
|
2146
|
+
sortOrder,
|
|
2147
|
+
createdAt: now
|
|
1998
2148
|
};
|
|
1999
2149
|
}
|
|
2000
|
-
function
|
|
2001
|
-
const
|
|
2002
|
-
const
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
tableName: row.table_name ?? (typeof restaurant.tableName === "string" ? restaurant.tableName : void 0),
|
|
2025
|
-
estimatedMinutes: row.estimated_minutes ?? (typeof restaurant.estimatedMinutes === "number" ? restaurant.estimatedMinutes : void 0),
|
|
2026
|
-
items,
|
|
2027
|
-
metadata: row.metadata ?? {},
|
|
2028
|
-
tenantId: row.tenant_id ?? "runtime-tenant",
|
|
2029
|
-
unitId: row.unit_id ?? void 0,
|
|
2030
|
-
createdAt: row.created_at ?? nowIso(),
|
|
2031
|
-
updatedAt: row.updated_at ?? row.created_at ?? nowIso(),
|
|
2032
|
-
completedAt: row.completed_at ?? void 0
|
|
2150
|
+
function createSeedOrders() {
|
|
2151
|
+
const order1Id = nextId("order");
|
|
2152
|
+
const order1Items = [
|
|
2153
|
+
makeItem(order1Id, "Classic Burger", 1, 32, 0, "prod-burger-classic"),
|
|
2154
|
+
makeItem(order1Id, "Fries", 1, 15, 1, "prod-fries")
|
|
2155
|
+
];
|
|
2156
|
+
const order1 = {
|
|
2157
|
+
id: order1Id,
|
|
2158
|
+
referenceNumber: "ORD-0001",
|
|
2159
|
+
kind: "dine_in",
|
|
2160
|
+
status: "preparing",
|
|
2161
|
+
source: "pos",
|
|
2162
|
+
tableId: "table-3",
|
|
2163
|
+
tableName: "Table 3",
|
|
2164
|
+
subtotal: 47,
|
|
2165
|
+
discount: 0,
|
|
2166
|
+
tax: 0,
|
|
2167
|
+
total: 47,
|
|
2168
|
+
currency: "BRL",
|
|
2169
|
+
tags: [],
|
|
2170
|
+
items: order1Items,
|
|
2171
|
+
tenantId: "tenant-mock",
|
|
2172
|
+
createdAt: now,
|
|
2173
|
+
updatedAt: now
|
|
2033
2174
|
};
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
subtotal,
|
|
2175
|
+
const order2Id = nextId("order");
|
|
2176
|
+
const order2Items = [
|
|
2177
|
+
makeItem(order2Id, "Cheese Burger", 1, 36, 0, "prod-burger-cheese")
|
|
2178
|
+
];
|
|
2179
|
+
const order2 = {
|
|
2180
|
+
id: order2Id,
|
|
2181
|
+
referenceNumber: "ORD-0002",
|
|
2182
|
+
kind: "takeout",
|
|
2183
|
+
status: "ready",
|
|
2184
|
+
source: "pos",
|
|
2185
|
+
subtotal: 36,
|
|
2186
|
+
discount: 0,
|
|
2187
|
+
tax: 0,
|
|
2188
|
+
total: 36,
|
|
2189
|
+
currency: "BRL",
|
|
2190
|
+
tags: [],
|
|
2191
|
+
items: order2Items,
|
|
2192
|
+
tenantId: "tenant-mock",
|
|
2193
|
+
createdAt: now,
|
|
2194
|
+
updatedAt: now
|
|
2195
|
+
};
|
|
2196
|
+
const order3Id = nextId("order");
|
|
2197
|
+
const order3Items = [
|
|
2198
|
+
makeItem(order3Id, "Margherita Pizza", 1, 38, 0, "prod-pizza-margherita"),
|
|
2199
|
+
makeItem(order3Id, "Caesar Salad", 1, 22, 1, "prod-salad-caesar"),
|
|
2200
|
+
makeItem(order3Id, "Soda 350ml", 1, 12, 2, "prod-soda")
|
|
2201
|
+
];
|
|
2202
|
+
const order3 = {
|
|
2203
|
+
id: order3Id,
|
|
2204
|
+
referenceNumber: "ORD-0003",
|
|
2205
|
+
kind: "delivery",
|
|
2206
|
+
status: "confirmed",
|
|
2207
|
+
source: "ifood",
|
|
2208
|
+
subtotal: 72,
|
|
2209
|
+
discount: 0,
|
|
2210
|
+
tax: 0,
|
|
2211
|
+
total: 72,
|
|
2212
|
+
currency: "BRL",
|
|
2213
|
+
tags: [],
|
|
2214
|
+
items: order3Items,
|
|
2215
|
+
tenantId: "tenant-mock",
|
|
2216
|
+
createdAt: now,
|
|
2217
|
+
updatedAt: now
|
|
2218
|
+
};
|
|
2219
|
+
const order4Id = nextId("order");
|
|
2220
|
+
const order4Items = [
|
|
2221
|
+
makeItem(order4Id, "BBQ Bacon Burger", 1, 42, 0, "prod-burger-bbq")
|
|
2222
|
+
];
|
|
2223
|
+
const order4 = {
|
|
2224
|
+
id: order4Id,
|
|
2225
|
+
referenceNumber: "ORD-0004",
|
|
2226
|
+
kind: "dine_in",
|
|
2227
|
+
status: "new",
|
|
2228
|
+
source: "pos",
|
|
2229
|
+
tableId: "table-7",
|
|
2230
|
+
tableName: "Table 7",
|
|
2231
|
+
subtotal: 42,
|
|
2232
|
+
discount: 0,
|
|
2233
|
+
tax: 0,
|
|
2234
|
+
total: 42,
|
|
2235
|
+
currency: "BRL",
|
|
2236
|
+
tags: [],
|
|
2237
|
+
items: order4Items,
|
|
2238
|
+
tenantId: "tenant-mock",
|
|
2239
|
+
createdAt: now,
|
|
2240
|
+
updatedAt: now
|
|
2241
|
+
};
|
|
2242
|
+
const order5Id = nextId("order");
|
|
2243
|
+
const order5Items = [
|
|
2244
|
+
makeItem(order5Id, "Classic Burger", 2, 32, 0, "prod-burger-classic"),
|
|
2245
|
+
makeItem(order5Id, "Fries", 1, 15, 1, "prod-fries"),
|
|
2246
|
+
makeItem(order5Id, "Onion Rings", 1, 18, 2, "prod-onion-rings"),
|
|
2247
|
+
makeItem(order5Id, "Milkshake", 1, 23, 3, "prod-milkshake")
|
|
2248
|
+
];
|
|
2249
|
+
const order5 = {
|
|
2250
|
+
id: order5Id,
|
|
2251
|
+
referenceNumber: "ORD-0005",
|
|
2252
|
+
kind: "dine_in",
|
|
2253
|
+
status: "completed",
|
|
2254
|
+
source: "pos",
|
|
2255
|
+
tableId: "table-1",
|
|
2256
|
+
tableName: "Table 1",
|
|
2257
|
+
subtotal: 120,
|
|
2045
2258
|
discount: 0,
|
|
2046
2259
|
tax: 0,
|
|
2047
|
-
total:
|
|
2260
|
+
total: 120,
|
|
2048
2261
|
currency: "BRL",
|
|
2049
2262
|
tags: [],
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
})
|
|
2056
|
-
};
|
|
2057
|
-
}
|
|
2058
|
-
function updatePayload(data, existingMetadata) {
|
|
2059
|
-
const payload = {};
|
|
2060
|
-
if (data.referenceNumber !== void 0) payload.reference_number = data.referenceNumber;
|
|
2061
|
-
if (data.kind !== void 0) payload.kind = data.kind;
|
|
2062
|
-
if (data.status !== void 0) payload.status = canonicalStatus(data.status);
|
|
2063
|
-
if (data.partyId !== void 0) payload.party_id = data.partyId;
|
|
2064
|
-
if (data.assigneeId !== void 0) payload.assignee_id = data.assigneeId;
|
|
2065
|
-
if (data.locationId !== void 0) payload.location_id = data.locationId;
|
|
2066
|
-
if (data.subtotal !== void 0) payload.subtotal = data.subtotal;
|
|
2067
|
-
if (data.discount !== void 0) payload.discount = data.discount;
|
|
2068
|
-
if (data.tax !== void 0) payload.tax = data.tax;
|
|
2069
|
-
if (data.total !== void 0) payload.total = data.total;
|
|
2070
|
-
if (data.currency !== void 0) payload.currency = data.currency;
|
|
2071
|
-
if (data.notes !== void 0) payload.notes = data.notes;
|
|
2072
|
-
if (data.tags !== void 0) payload.tags = data.tags;
|
|
2073
|
-
const metadataBase = data.metadata !== void 0 ? { ...existingMetadata ?? {}, ...data.metadata } : existingMetadata;
|
|
2074
|
-
const metadataPatch = {
|
|
2075
|
-
partyName: data.partyName,
|
|
2076
|
-
assigneeName: data.assigneeName,
|
|
2077
|
-
source: data.source,
|
|
2078
|
-
tableId: data.tableId,
|
|
2079
|
-
tableName: data.tableName,
|
|
2080
|
-
estimatedMinutes: data.estimatedMinutes,
|
|
2081
|
-
restaurantStatus: data.status
|
|
2263
|
+
items: order5Items,
|
|
2264
|
+
tenantId: "tenant-mock",
|
|
2265
|
+
createdAt: now,
|
|
2266
|
+
updatedAt: now,
|
|
2267
|
+
completedAt: now
|
|
2082
2268
|
};
|
|
2083
|
-
|
|
2084
|
-
payload.metadata = mergeOrdersMetadata(metadataBase, metadataPatch);
|
|
2085
|
-
}
|
|
2086
|
-
if (data.completedAt !== void 0) payload.completed_at = data.completedAt;
|
|
2087
|
-
return payload;
|
|
2088
|
-
}
|
|
2089
|
-
function filtersFromQuery(query) {
|
|
2090
|
-
const filters = [];
|
|
2091
|
-
if (query.kind && !Array.isArray(query.kind)) filters.push({ column: "kind", operator: "eq", value: query.kind });
|
|
2092
|
-
if (query.assigneeId) filters.push({ column: "assignee_id", operator: "eq", value: query.assigneeId });
|
|
2093
|
-
return filters;
|
|
2269
|
+
return [order1, order2, order3, order4, order5];
|
|
2094
2270
|
}
|
|
2095
|
-
function
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
if (query.status && !Array.isArray(query.status) && order.status !== query.status) return false;
|
|
2099
|
-
if (query.source && order.source !== query.source) return false;
|
|
2100
|
-
if (query.tableId && order.tableId !== query.tableId) return false;
|
|
2101
|
-
if (query.assigneeId && order.assigneeId !== query.assigneeId) return false;
|
|
2102
|
-
if (query.dateRange) {
|
|
2103
|
-
const created = dateKey(order.createdAt);
|
|
2104
|
-
if (created < query.dateRange.from || created > query.dateRange.to) return false;
|
|
2105
|
-
}
|
|
2106
|
-
if (query.search) {
|
|
2107
|
-
const search = query.search.toLowerCase();
|
|
2108
|
-
return order.referenceNumber.toLowerCase().includes(search) || (order.partyName?.toLowerCase().includes(search) ?? false) || (order.tableName?.toLowerCase().includes(search) ?? false) || order.items.some((item) => item.name.toLowerCase().includes(search));
|
|
2109
|
-
}
|
|
2110
|
-
return true;
|
|
2271
|
+
function recomputeTotals(order) {
|
|
2272
|
+
order.subtotal = order.items.reduce((sum, i) => sum + i.total, 0);
|
|
2273
|
+
order.total = order.subtotal - order.discount + order.tax;
|
|
2111
2274
|
}
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
return { data: items.slice(start, start + limit), total: items.length };
|
|
2275
|
+
var refCounter = 5;
|
|
2276
|
+
function generateReferenceNumber() {
|
|
2277
|
+
refCounter++;
|
|
2278
|
+
return "ORD-" + String(refCounter).padStart(4, "0");
|
|
2117
2279
|
}
|
|
2118
|
-
function
|
|
2119
|
-
|
|
2120
|
-
const from = dateRange?.from ?? today;
|
|
2121
|
-
const to = dateRange?.to ?? today;
|
|
2122
|
-
const inRange = orders.filter((order) => {
|
|
2123
|
-
const created = dateKey(order.createdAt);
|
|
2124
|
-
return created >= from && created <= to;
|
|
2125
|
-
});
|
|
2126
|
-
const billable = inRange.filter((order) => order.status !== "cancelled");
|
|
2127
|
-
const revenue = billable.reduce((sum, order) => sum + order.total, 0);
|
|
2128
|
-
const ordersByKind = { dine_in: 0, takeout: 0, delivery: 0 };
|
|
2129
|
-
const ordersByStatus = {};
|
|
2130
|
-
const top = /* @__PURE__ */ new Map();
|
|
2131
|
-
for (const order of inRange) {
|
|
2132
|
-
ordersByKind[order.kind] = (ordersByKind[order.kind] ?? 0) + 1;
|
|
2133
|
-
ordersByStatus[order.status] = (ordersByStatus[order.status] ?? 0) + 1;
|
|
2134
|
-
if (order.status === "cancelled") continue;
|
|
2135
|
-
for (const item of order.items) {
|
|
2136
|
-
const current = top.get(item.name) ?? { name: item.name, quantity: 0, revenue: 0 };
|
|
2137
|
-
current.quantity += item.quantity;
|
|
2138
|
-
current.revenue += item.total;
|
|
2139
|
-
top.set(item.name, current);
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
return {
|
|
2143
|
-
todayOrderCount: inRange.length,
|
|
2144
|
-
todayRevenue: revenue,
|
|
2145
|
-
averageTicket: billable.length > 0 ? revenue / billable.length : 0,
|
|
2146
|
-
activeOrderCount: orders.filter((order) => ACTIVE_STATUSES2.has(order.status)).length,
|
|
2147
|
-
ordersByKind,
|
|
2148
|
-
ordersByStatus,
|
|
2149
|
-
topItems: [...top.values()].sort((a, b) => b.quantity - a.quantity).slice(0, 5)
|
|
2150
|
-
};
|
|
2280
|
+
function toDateStr(d) {
|
|
2281
|
+
return d.toISOString().slice(0, 10);
|
|
2151
2282
|
}
|
|
2152
|
-
function
|
|
2153
|
-
const
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
const tableOptions = { projectId: options.projectId, schema: options.schema, runtime: options.runtime };
|
|
2157
|
-
function resolveTenant() {
|
|
2158
|
-
const value = typeof options.tenantId === "function" ? options.tenantId() : options.tenantId;
|
|
2159
|
-
return value ?? void 0;
|
|
2160
|
-
}
|
|
2161
|
-
function resolveUnit() {
|
|
2162
|
-
const value = typeof options.unitId === "function" ? options.unitId() : options.unitId;
|
|
2163
|
-
return value ?? void 0;
|
|
2164
|
-
}
|
|
2165
|
-
function withTenant(filters) {
|
|
2166
|
-
const tenantId = resolveTenant();
|
|
2167
|
-
return tenantId ? [{ column: "tenant_id", operator: "eq", value: tenantId }, ...filters] : filters;
|
|
2168
|
-
}
|
|
2169
|
-
async function listItemRows(orderIds) {
|
|
2170
|
-
if (orderIds.length === 0) return [];
|
|
2171
|
-
const rows = [];
|
|
2172
|
-
await Promise.all(orderIds.map(async (orderId) => {
|
|
2173
|
-
const response = await client.data.listRows({
|
|
2174
|
-
...tableOptions,
|
|
2175
|
-
table: orderItemsTable,
|
|
2176
|
-
filters: [{ column: "order_id", operator: "eq", value: orderId }],
|
|
2177
|
-
sortColumn: "sort_order",
|
|
2178
|
-
sortDirection: "asc",
|
|
2179
|
-
limit: 200
|
|
2180
|
-
});
|
|
2181
|
-
rows.push(...response.rows);
|
|
2182
|
-
}));
|
|
2183
|
-
return rows;
|
|
2283
|
+
function createMockOrdersProvider() {
|
|
2284
|
+
const orders = createSeedOrders();
|
|
2285
|
+
function findOrder(id) {
|
|
2286
|
+
return orders.find((o) => o.id === id);
|
|
2184
2287
|
}
|
|
2185
|
-
|
|
2186
|
-
const
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
const item = rowToItem(itemRow);
|
|
2190
|
-
const list = byOrder.get(item.orderId) ?? [];
|
|
2191
|
-
list.push(item);
|
|
2192
|
-
byOrder.set(item.orderId, list);
|
|
2288
|
+
function findItem(itemId) {
|
|
2289
|
+
for (const order of orders) {
|
|
2290
|
+
const index = order.items.findIndex((i) => i.id === itemId);
|
|
2291
|
+
if (index !== -1) return { order, item: order.items[index], index };
|
|
2193
2292
|
}
|
|
2194
|
-
return
|
|
2195
|
-
}
|
|
2196
|
-
async function listOrders(query = {}) {
|
|
2197
|
-
const response = await client.data.listRows({
|
|
2198
|
-
...tableOptions,
|
|
2199
|
-
table: ordersTable,
|
|
2200
|
-
filters: withTenant(filtersFromQuery(query)),
|
|
2201
|
-
sortColumn: "created_at",
|
|
2202
|
-
sortDirection: "desc",
|
|
2203
|
-
limit: 500
|
|
2204
|
-
});
|
|
2205
|
-
return (await hydrate(response.rows)).filter((order) => matchesClientQuery(order, query));
|
|
2206
|
-
}
|
|
2207
|
-
async function getById(id) {
|
|
2208
|
-
const response = await client.data.listRows({
|
|
2209
|
-
...tableOptions,
|
|
2210
|
-
table: ordersTable,
|
|
2211
|
-
filters: withTenant([{ column: "id", operator: "eq", value: id }]),
|
|
2212
|
-
limit: 1
|
|
2213
|
-
});
|
|
2214
|
-
return (await hydrate(response.rows))[0] ?? null;
|
|
2293
|
+
return void 0;
|
|
2215
2294
|
}
|
|
2216
|
-
|
|
2295
|
+
const provider = {
|
|
2296
|
+
// ------------------------------------------------------------------
|
|
2297
|
+
// getOrders
|
|
2298
|
+
// ------------------------------------------------------------------
|
|
2217
2299
|
async getOrders(query) {
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
}
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2300
|
+
let result = [...orders];
|
|
2301
|
+
if (query.kind) {
|
|
2302
|
+
const kinds = Array.isArray(query.kind) ? query.kind : [query.kind];
|
|
2303
|
+
result = result.filter((o) => kinds.includes(o.kind));
|
|
2304
|
+
}
|
|
2305
|
+
if (query.status) {
|
|
2306
|
+
const statuses = Array.isArray(query.status) ? query.status : [query.status];
|
|
2307
|
+
result = result.filter((o) => statuses.includes(o.status));
|
|
2308
|
+
}
|
|
2309
|
+
if (query.source) {
|
|
2310
|
+
result = result.filter((o) => o.source === query.source);
|
|
2311
|
+
}
|
|
2312
|
+
if (query.tableId) {
|
|
2313
|
+
result = result.filter((o) => o.tableId === query.tableId);
|
|
2314
|
+
}
|
|
2315
|
+
if (query.assigneeId) {
|
|
2316
|
+
result = result.filter((o) => o.assigneeId === query.assigneeId);
|
|
2317
|
+
}
|
|
2318
|
+
if (query.search) {
|
|
2319
|
+
const q = query.search.toLowerCase();
|
|
2320
|
+
result = result.filter(
|
|
2321
|
+
(o) => o.referenceNumber.toLowerCase().includes(q) || o.partyName?.toLowerCase().includes(q) || o.tableName?.toLowerCase().includes(q) || o.items.some((i) => i.name.toLowerCase().includes(q))
|
|
2322
|
+
);
|
|
2323
|
+
}
|
|
2324
|
+
if (query.dateRange) {
|
|
2325
|
+
const from = query.dateRange.from;
|
|
2326
|
+
const to = query.dateRange.to;
|
|
2327
|
+
result = result.filter((o) => {
|
|
2328
|
+
const d = o.createdAt.slice(0, 10);
|
|
2329
|
+
return d >= from && d <= to;
|
|
2330
|
+
});
|
|
2331
|
+
}
|
|
2332
|
+
result.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
|
|
2333
|
+
const total = result.length;
|
|
2334
|
+
const page = query.page ?? 1;
|
|
2335
|
+
const pageSize = query.pageSize ?? 20;
|
|
2336
|
+
const start = (page - 1) * pageSize;
|
|
2337
|
+
const data = result.slice(start, start + pageSize);
|
|
2338
|
+
return { data, total };
|
|
2339
|
+
},
|
|
2340
|
+
// ------------------------------------------------------------------
|
|
2341
|
+
// getOrderById
|
|
2342
|
+
// ------------------------------------------------------------------
|
|
2343
|
+
async getOrderById(id) {
|
|
2344
|
+
return findOrder(id) ?? null;
|
|
2345
|
+
},
|
|
2346
|
+
// ------------------------------------------------------------------
|
|
2347
|
+
// createOrder
|
|
2348
|
+
// ------------------------------------------------------------------
|
|
2349
|
+
async createOrder(input) {
|
|
2350
|
+
const orderId = nextId("order");
|
|
2351
|
+
const items = input.items.map((item, idx) => ({
|
|
2352
|
+
id: nextId("item"),
|
|
2353
|
+
orderId,
|
|
2354
|
+
productId: item.productId,
|
|
2355
|
+
name: item.name,
|
|
2356
|
+
quantity: item.quantity,
|
|
2357
|
+
unitPrice: item.unitPrice,
|
|
2358
|
+
discount: item.discount ?? 0,
|
|
2359
|
+
total: item.quantity * item.unitPrice - (item.discount ?? 0),
|
|
2360
|
+
sortOrder: idx,
|
|
2361
|
+
modifiers: item.modifiers,
|
|
2362
|
+
kitchenNotes: item.kitchenNotes,
|
|
2363
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2364
|
+
}));
|
|
2365
|
+
const subtotal = items.reduce((sum, i) => sum + i.total, 0);
|
|
2366
|
+
const order = {
|
|
2367
|
+
id: orderId,
|
|
2368
|
+
referenceNumber: generateReferenceNumber(),
|
|
2369
|
+
kind: input.kind,
|
|
2370
|
+
status: "new",
|
|
2371
|
+
source: input.source ?? "pos",
|
|
2372
|
+
tableId: input.tableId,
|
|
2373
|
+
partyId: input.partyId,
|
|
2374
|
+
assigneeId: input.assigneeId,
|
|
2375
|
+
notes: input.notes,
|
|
2376
|
+
estimatedMinutes: input.estimatedMinutes,
|
|
2377
|
+
subtotal,
|
|
2378
|
+
discount: 0,
|
|
2379
|
+
tax: 0,
|
|
2380
|
+
total: subtotal,
|
|
2381
|
+
currency: "BRL",
|
|
2382
|
+
tags: [],
|
|
2383
|
+
items,
|
|
2384
|
+
tenantId: "tenant-mock",
|
|
2385
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2386
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2387
|
+
};
|
|
2388
|
+
orders.push(order);
|
|
2389
|
+
return order;
|
|
2242
2390
|
},
|
|
2391
|
+
// ------------------------------------------------------------------
|
|
2392
|
+
// updateOrderStatus
|
|
2393
|
+
// ------------------------------------------------------------------
|
|
2243
2394
|
async updateOrderStatus(input) {
|
|
2244
|
-
const
|
|
2245
|
-
|
|
2395
|
+
const order = findOrder(input.orderId);
|
|
2396
|
+
if (!order) throw new Error(`Order not found: ${input.orderId}`);
|
|
2397
|
+
if (input.status !== "cancelled") {
|
|
2398
|
+
const flow = STATUS_FLOW[order.kind];
|
|
2399
|
+
const currentIdx = flow.indexOf(order.status);
|
|
2400
|
+
const targetIdx = flow.indexOf(input.status);
|
|
2401
|
+
if (targetIdx < 0 || targetIdx !== currentIdx && targetIdx !== currentIdx + 1) {
|
|
2402
|
+
const next = getNextStatus(order.kind, order.status);
|
|
2403
|
+
throw new Error(
|
|
2404
|
+
`Invalid status transition: ${order.status} \u2192 ${input.status}. ` + (next ? `Expected next status: ${next}` : "Order is already in a terminal state.")
|
|
2405
|
+
);
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
order.status = input.status;
|
|
2409
|
+
order.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2410
|
+
if (input.status === "completed") {
|
|
2411
|
+
order.completedAt = order.updatedAt;
|
|
2412
|
+
}
|
|
2413
|
+
return order;
|
|
2246
2414
|
},
|
|
2415
|
+
// ------------------------------------------------------------------
|
|
2416
|
+
// updateOrder
|
|
2417
|
+
// ------------------------------------------------------------------
|
|
2247
2418
|
async updateOrder(id, data) {
|
|
2248
|
-
const
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
});
|
|
2255
|
-
return await getById(row.id) ?? rowToOrder(row);
|
|
2419
|
+
const order = findOrder(id);
|
|
2420
|
+
if (!order) throw new Error(`Order not found: ${id}`);
|
|
2421
|
+
const { id: _id, items: _items, tenantId: _t, createdAt: _c, ...safe } = data;
|
|
2422
|
+
Object.assign(order, safe);
|
|
2423
|
+
order.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2424
|
+
return order;
|
|
2256
2425
|
},
|
|
2426
|
+
// ------------------------------------------------------------------
|
|
2427
|
+
// cancelOrder
|
|
2428
|
+
// ------------------------------------------------------------------
|
|
2257
2429
|
async cancelOrder(id, reason) {
|
|
2258
|
-
const order =
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2430
|
+
const order = findOrder(id);
|
|
2431
|
+
if (!order) throw new Error(`Order not found: ${id}`);
|
|
2432
|
+
order.status = "cancelled";
|
|
2433
|
+
order.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2434
|
+
if (reason) {
|
|
2435
|
+
order.notes = order.notes ? `${order.notes}
|
|
2436
|
+
Cancelled: ${reason}` : `Cancelled: ${reason}`;
|
|
2437
|
+
}
|
|
2264
2438
|
},
|
|
2439
|
+
// ------------------------------------------------------------------
|
|
2440
|
+
// addOrderItem
|
|
2441
|
+
// ------------------------------------------------------------------
|
|
2265
2442
|
async addOrderItem(orderId, item) {
|
|
2266
|
-
const order =
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
total:
|
|
2277
|
-
|
|
2278
|
-
|
|
2443
|
+
const order = findOrder(orderId);
|
|
2444
|
+
if (!order) throw new Error(`Order not found: ${orderId}`);
|
|
2445
|
+
const newItem = {
|
|
2446
|
+
id: nextId("item"),
|
|
2447
|
+
orderId,
|
|
2448
|
+
productId: item.productId,
|
|
2449
|
+
name: item.name,
|
|
2450
|
+
quantity: item.quantity,
|
|
2451
|
+
unitPrice: item.unitPrice,
|
|
2452
|
+
discount: item.discount ?? 0,
|
|
2453
|
+
total: item.quantity * item.unitPrice - (item.discount ?? 0),
|
|
2454
|
+
sortOrder: order.items.length,
|
|
2455
|
+
modifiers: item.modifiers,
|
|
2456
|
+
kitchenNotes: item.kitchenNotes,
|
|
2457
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2458
|
+
};
|
|
2459
|
+
order.items.push(newItem);
|
|
2460
|
+
recomputeTotals(order);
|
|
2461
|
+
order.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2462
|
+
return newItem;
|
|
2279
2463
|
},
|
|
2464
|
+
// ------------------------------------------------------------------
|
|
2465
|
+
// removeOrderItem
|
|
2466
|
+
// ------------------------------------------------------------------
|
|
2280
2467
|
async removeOrderItem(itemId) {
|
|
2281
|
-
const
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
const subtotal = items.reduce((sum, item) => sum + item.total, 0);
|
|
2287
|
-
await this.updateOrder(order.id, { subtotal, total: subtotal - order.discount + order.tax });
|
|
2288
|
-
}
|
|
2468
|
+
const found = findItem(itemId);
|
|
2469
|
+
if (!found) throw new Error(`Order item not found: ${itemId}`);
|
|
2470
|
+
found.order.items.splice(found.index, 1);
|
|
2471
|
+
recomputeTotals(found.order);
|
|
2472
|
+
found.order.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2289
2473
|
},
|
|
2474
|
+
// ------------------------------------------------------------------
|
|
2475
|
+
// updateOrderItem
|
|
2476
|
+
// ------------------------------------------------------------------
|
|
2290
2477
|
async updateOrderItem(itemId, data) {
|
|
2291
|
-
const
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
modifiers: data.modifiers,
|
|
2300
|
-
kitchenNotes: data.kitchenNotes
|
|
2301
|
-
};
|
|
2302
|
-
const row = await client.data.updateRow({
|
|
2303
|
-
...tableOptions,
|
|
2304
|
-
table: orderItemsTable,
|
|
2305
|
-
primaryKeys: { id: itemId },
|
|
2306
|
-
row: {
|
|
2307
|
-
product_id: data.productId,
|
|
2308
|
-
name: data.name,
|
|
2309
|
-
description: data.description,
|
|
2310
|
-
quantity: data.quantity,
|
|
2311
|
-
unit_price: data.unitPrice,
|
|
2312
|
-
discount: data.discount,
|
|
2313
|
-
total: data.total,
|
|
2314
|
-
sort_order: data.sortOrder,
|
|
2315
|
-
metadata: data.metadata !== void 0 || Object.values(metadataPatch).some((value) => value !== void 0) ? mergeOrdersMetadata(
|
|
2316
|
-
data.metadata !== void 0 ? { ...existingMetadata ?? {}, ...data.metadata } : existingMetadata,
|
|
2317
|
-
metadataPatch
|
|
2318
|
-
) : void 0
|
|
2319
|
-
}
|
|
2320
|
-
});
|
|
2321
|
-
return rowToItem(row);
|
|
2478
|
+
const found = findItem(itemId);
|
|
2479
|
+
if (!found) throw new Error(`Order item not found: ${itemId}`);
|
|
2480
|
+
const { id: _id, orderId: _oid, createdAt: _c, ...safe } = data;
|
|
2481
|
+
Object.assign(found.item, safe);
|
|
2482
|
+
found.item.total = found.item.quantity * found.item.unitPrice - found.item.discount;
|
|
2483
|
+
recomputeTotals(found.order);
|
|
2484
|
+
found.order.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2485
|
+
return found.item;
|
|
2322
2486
|
},
|
|
2487
|
+
// ------------------------------------------------------------------
|
|
2488
|
+
// getSummary
|
|
2489
|
+
// ------------------------------------------------------------------
|
|
2323
2490
|
async getSummary(dateRange) {
|
|
2324
|
-
|
|
2491
|
+
const today = toDateStr(/* @__PURE__ */ new Date());
|
|
2492
|
+
const from = dateRange?.from ?? today;
|
|
2493
|
+
const to = dateRange?.to ?? today;
|
|
2494
|
+
const inRange = orders.filter((o) => {
|
|
2495
|
+
const d = o.createdAt.slice(0, 10);
|
|
2496
|
+
return d >= from && d <= to;
|
|
2497
|
+
});
|
|
2498
|
+
const completed = inRange.filter((o) => o.status !== "cancelled");
|
|
2499
|
+
const revenue = completed.reduce((sum, o) => sum + o.total, 0);
|
|
2500
|
+
const activeStatuses = /* @__PURE__ */ new Set(["new", "confirmed", "preparing", "ready", "served", "dispatched"]);
|
|
2501
|
+
const active = orders.filter((o) => activeStatuses.has(o.status));
|
|
2502
|
+
const ordersByKind = { dine_in: 0, takeout: 0, delivery: 0 };
|
|
2503
|
+
const ordersByStatus = {};
|
|
2504
|
+
for (const o of inRange) {
|
|
2505
|
+
ordersByKind[o.kind] = (ordersByKind[o.kind] || 0) + 1;
|
|
2506
|
+
ordersByStatus[o.status] = (ordersByStatus[o.status] || 0) + 1;
|
|
2507
|
+
}
|
|
2508
|
+
const itemMap = /* @__PURE__ */ new Map();
|
|
2509
|
+
for (const o of completed) {
|
|
2510
|
+
for (const item of o.items) {
|
|
2511
|
+
const existing = itemMap.get(item.name);
|
|
2512
|
+
if (existing) {
|
|
2513
|
+
existing.quantity += item.quantity;
|
|
2514
|
+
existing.revenue += item.total;
|
|
2515
|
+
} else {
|
|
2516
|
+
itemMap.set(item.name, { name: item.name, quantity: item.quantity, revenue: item.total });
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
const topItems = Array.from(itemMap.values()).sort((a, b) => b.quantity - a.quantity).slice(0, 5);
|
|
2521
|
+
return {
|
|
2522
|
+
todayOrderCount: inRange.length,
|
|
2523
|
+
todayRevenue: revenue,
|
|
2524
|
+
averageTicket: completed.length > 0 ? revenue / completed.length : 0,
|
|
2525
|
+
activeOrderCount: active.length,
|
|
2526
|
+
ordersByKind,
|
|
2527
|
+
ordersByStatus,
|
|
2528
|
+
topItems
|
|
2529
|
+
};
|
|
2325
2530
|
}
|
|
2326
2531
|
};
|
|
2532
|
+
return provider;
|
|
2327
2533
|
}
|
|
2328
2534
|
|
|
2329
2535
|
// src/index.ts
|
|
2330
|
-
var OrdersPage =
|
|
2536
|
+
var OrdersPage = React.lazy(() => import('./OrdersPage-5326V62H.js').then((m) => ({ default: m.OrdersPage })));
|
|
2537
|
+
var OrderDetailRoute = React.lazy(() => import('./OrderDetailRoute-LTPDQXQU.js').then((m) => ({ default: m.OrderDetailRoute })));
|
|
2331
2538
|
var DEFAULT_LABELS = {
|
|
2332
2539
|
pageTitle: "Orders",
|
|
2333
2540
|
kanban: "Orders",
|
|
@@ -2355,65 +2562,29 @@ function resolveConfig(options) {
|
|
|
2355
2562
|
createRoute: options?.createRoute,
|
|
2356
2563
|
editRoute: options?.editRoute,
|
|
2357
2564
|
canEditOrder: options?.canEditOrder,
|
|
2358
|
-
orderRoute: options?.orderRoute
|
|
2359
|
-
floorView: options?.floorView !== false,
|
|
2360
|
-
boardView: options?.boardView !== false,
|
|
2361
|
-
deliveryRoute: options?.deliveryRoute,
|
|
2362
|
-
detailRail: options?.detailRail ?? "operate"
|
|
2565
|
+
orderRoute: options?.orderRoute
|
|
2363
2566
|
};
|
|
2364
2567
|
}
|
|
2365
2568
|
function createOrdersPlugin(options) {
|
|
2366
2569
|
const config = resolveConfig(options);
|
|
2367
|
-
const provider = options?.dataProvider ??
|
|
2570
|
+
const provider = options?.dataProvider ?? createCoreOrdersProvider();
|
|
2368
2571
|
const store7 = createOrdersStore(provider, {
|
|
2369
2572
|
onOrderCompleted: config.onOrderCompleted,
|
|
2370
2573
|
onOrderCancelled: config.onOrderCancelled
|
|
2371
2574
|
});
|
|
2372
|
-
const
|
|
2373
|
-
|
|
2575
|
+
const PageComponent = () => React.createElement(
|
|
2576
|
+
React.Suspense,
|
|
2374
2577
|
{ fallback: null },
|
|
2375
|
-
|
|
2376
|
-
config: extra ? { ...config, ...extra } : config,
|
|
2377
|
-
provider,
|
|
2378
|
-
store: store7,
|
|
2379
|
-
registries: ordersRegistries,
|
|
2380
|
-
variant: options?.variant ?? "restaurant"
|
|
2381
|
-
})
|
|
2578
|
+
React.createElement(OrdersPage, { config, provider, store: store7, registries: ordersRegistries })
|
|
2382
2579
|
);
|
|
2383
|
-
const
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
// always had the slot; nobody was filling it.
|
|
2390
|
-
actions: (order) => {
|
|
2391
|
-
const edit = options?.editRoute;
|
|
2392
|
-
if (!edit) return null;
|
|
2393
|
-
const allowed = options?.canEditOrder ?? orderIsEditable;
|
|
2394
|
-
if (!allowed(order)) return null;
|
|
2395
|
-
return React2__default.createElement(PageActions, {
|
|
2396
|
-
actions: [{
|
|
2397
|
-
id: "edit",
|
|
2398
|
-
testId: "order-edit",
|
|
2399
|
-
label: "Editar pedido",
|
|
2400
|
-
icon: Pencil,
|
|
2401
|
-
intent: "primary",
|
|
2402
|
-
onSelect: () => adminNavigateTo(edit(order.id))
|
|
2403
|
-
}]
|
|
2404
|
-
});
|
|
2405
|
-
},
|
|
2406
|
-
// Os verbos do dinheiro. O seam existia desde a primeira passada do
|
|
2407
|
-
// archetype e nunca foi preenchido fora da loja — entao a tela sabia
|
|
2408
|
-
// dizer "sem conta a receber" e nao oferecia como criar uma. Ver
|
|
2409
|
-
// ./views/OrderReceivableActions.
|
|
2410
|
-
receivableActions: (order, ctx) => React2__default.createElement(OrderReceivableActions, {
|
|
2411
|
-
orderId: order.id,
|
|
2412
|
-
orderTotal: order.total ?? 0,
|
|
2413
|
-
receivable: ctx.receivable,
|
|
2414
|
-
reload: ctx.reload
|
|
2580
|
+
const DetailComponent = ({ id }) => React.createElement(
|
|
2581
|
+
React.Suspense,
|
|
2582
|
+
{ fallback: null },
|
|
2583
|
+
React.createElement(OrderDetailRoute, {
|
|
2584
|
+
id,
|
|
2585
|
+
options: { editRoute: options?.editRoute, canEditOrder: options?.canEditOrder }
|
|
2415
2586
|
})
|
|
2416
|
-
|
|
2587
|
+
);
|
|
2417
2588
|
return {
|
|
2418
2589
|
id: "orders",
|
|
2419
2590
|
// Como esta conta refaz o módulo com as opções DELA. Ver PluginRebuild:
|
|
@@ -2430,7 +2601,18 @@ function createOrdersPlugin(options) {
|
|
|
2430
2601
|
scope: options?.scope ?? "vertical",
|
|
2431
2602
|
verticalId: options?.verticalId,
|
|
2432
2603
|
defaultEnabled: true,
|
|
2433
|
-
|
|
2604
|
+
// No `menu`: a sale is not a restaurant's, and a dependency lights up by
|
|
2605
|
+
// drag — every account with Orders would grow a Cardápio line.
|
|
2606
|
+
dependencies: [],
|
|
2607
|
+
// What installing this provisions, byte-for-byte the files under
|
|
2608
|
+
// src/migrations (embed barrel). The spine holds the orders tables; this
|
|
2609
|
+
// list holds the doors the plugin adds on top of them.
|
|
2610
|
+
migrations: MIGRATIONS.map((m) => ({
|
|
2611
|
+
id: `orders-${m.id}`,
|
|
2612
|
+
version: "1.0.0",
|
|
2613
|
+
sql: m.sql,
|
|
2614
|
+
description: m.id
|
|
2615
|
+
})),
|
|
2434
2616
|
events: [
|
|
2435
2617
|
{
|
|
2436
2618
|
name: "orders.order_completed",
|
|
@@ -2487,17 +2669,9 @@ function createOrdersPlugin(options) {
|
|
|
2487
2669
|
component: PageComponent,
|
|
2488
2670
|
permission: { feature: "orders", action: "read" }
|
|
2489
2671
|
},
|
|
2490
|
-
...options?.deliveryRoute ? [{
|
|
2491
|
-
path: options.deliveryRoute,
|
|
2492
|
-
component: DeliveryRoute,
|
|
2493
|
-
permission: { feature: "orders", action: "read" }
|
|
2494
|
-
}] : [],
|
|
2495
2672
|
{
|
|
2496
|
-
// The page the rail's "Ver pedido completo" points at. It used to exist
|
|
2497
|
-
// only in plugin-shop, so the restaurant's link opened a blank screen —
|
|
2498
|
-
// the route was never mounted, not the data missing.
|
|
2499
2673
|
path: "/orders/:id",
|
|
2500
|
-
component:
|
|
2674
|
+
component: DetailComponent,
|
|
2501
2675
|
permission: { feature: "orders", action: "read" }
|
|
2502
2676
|
}
|
|
2503
2677
|
],
|
|
@@ -2568,7 +2742,7 @@ function createOrdersPlugin(options) {
|
|
|
2568
2742
|
label: config.labels.pageTitle,
|
|
2569
2743
|
icon: "ClipboardList",
|
|
2570
2744
|
component: (() => {
|
|
2571
|
-
const OrdersSettingsTab = () =>
|
|
2745
|
+
const OrdersSettingsTab = () => React.createElement(PluginSettingsPanel, {
|
|
2572
2746
|
title: "Orders Settings",
|
|
2573
2747
|
registries: ordersRegistries,
|
|
2574
2748
|
routeBase: "/settings/orders"
|
|
@@ -2588,10 +2762,10 @@ function createOrdersPlugin(options) {
|
|
|
2588
2762
|
label: "Delivery",
|
|
2589
2763
|
icon: "Bike",
|
|
2590
2764
|
component: (() => {
|
|
2591
|
-
const DeliverySettingsTab = () =>
|
|
2592
|
-
|
|
2765
|
+
const DeliverySettingsTab = () => React.createElement(
|
|
2766
|
+
React.Suspense,
|
|
2593
2767
|
{ fallback: null },
|
|
2594
|
-
|
|
2768
|
+
React.createElement(DeliverySettings)
|
|
2595
2769
|
);
|
|
2596
2770
|
DeliverySettingsTab.displayName = "DeliverySettingsTab";
|
|
2597
2771
|
return DeliverySettingsTab;
|
|
@@ -2616,6 +2790,6 @@ function createOrdersPlugin(options) {
|
|
|
2616
2790
|
};
|
|
2617
2791
|
}
|
|
2618
2792
|
|
|
2619
|
-
export { AIQFOME_CONNECTOR_ID, AIQFOME_SETTINGS, AUTO_ACCEPT_SETTING, IFOOD_CONNECTOR_ID, IFOOD_SETTINGS, KEETA_CONNECTOR_ID, KEETA_SETTINGS, NINETY_NINE_FOOD_CONNECTOR_ID, NINETY_NINE_FOOD_SETTINGS, PREP_TIME_SETTING, RAPPI_CONNECTOR_ID, RAPPI_SETTINGS, UBER_EATS_CONNECTOR_ID, UBER_EATS_SETTINGS, aiqfomeConnectorDef, createCoreOrdersProvider, createFayzOrdersProvider, createOrdersPlugin, ifoodConnectorDef, keetaConnectorDef, marketplacePermissions, nineNineFoodConnectorDef, rappiConnectorDef, uberEatsConnectorDef };
|
|
2793
|
+
export { ADVANCE_VERB, AIQFOME_CONNECTOR_ID, AIQFOME_SETTINGS, AUTO_ACCEPT_SETTING, DEFAULT_STATUS_CONFIGS, IFOOD_CONNECTOR_ID, IFOOD_SETTINGS, KANBAN_COLUMNS, KEETA_CONNECTOR_ID, KEETA_SETTINGS, NINETY_NINE_FOOD_CONNECTOR_ID, NINETY_NINE_FOOD_SETTINGS, PREP_TIME_SETTING, RAPPI_CONNECTOR_ID, RAPPI_SETTINGS, STATUS_FLOW, UBER_EATS_CONNECTOR_ID, UBER_EATS_SETTINGS, aiqfomeConnectorDef, createCoreOrdersProvider, createFayzOrdersProvider, createMockOrdersProvider, createOrdersPlugin, getNextStatus, getPreviousStatus, ifoodConnectorDef, keetaConnectorDef, mapToKanbanColumn, marketplacePermissions, nineNineFoodConnectorDef, rappiConnectorDef, uberEatsConnectorDef };
|
|
2620
2794
|
//# sourceMappingURL=index.js.map
|
|
2621
2795
|
//# sourceMappingURL=index.js.map
|