@teincfood/core 0.7.9 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -21
- package/dist/adapters/auth.d.ts +8 -0
- package/dist/adapters/auth.js +36 -0
- package/dist/adapters/devices.d.ts +3 -1
- package/dist/adapters/devices.js +187 -3
- package/dist/adapters/filesystem.d.ts +4 -0
- package/dist/adapters/kv.d.ts +10 -0
- package/dist/adapters/kv.js +23 -7
- package/dist/adapters/local-node.d.ts +4 -6
- package/dist/adapters/local-node.js +9 -32
- package/dist/adapters/notify.d.ts +12 -0
- package/dist/adapters/notify.js +33 -0
- package/dist/adapters/query-keys.d.ts +4 -0
- package/dist/adapters/query-keys.js +4 -0
- package/dist/adapters/services.d.ts +96 -15
- package/dist/adapters/services.js +241 -4
- package/dist/auth/session.d.ts +66 -0
- package/dist/auth/session.js +172 -0
- package/dist/catalog/hooks.d.ts +10 -0
- package/dist/catalog/hooks.js +82 -11
- package/dist/db/migrations.js +274 -0
- package/dist/db/operations.d.ts +53 -2
- package/dist/db/operations.js +177 -8
- package/dist/domain/business/index.d.ts +5 -0
- package/dist/domain/business/index.js +5 -0
- package/dist/domain/orders/lifecycle.d.ts +6 -0
- package/dist/domain/orders/lifecycle.js +6 -0
- package/dist/domain/orders/service.d.ts +24 -0
- package/dist/domain/orders/service.js +242 -0
- package/dist/domain/payments/index.d.ts +6 -0
- package/dist/domain/payments/index.js +5 -0
- package/dist/domain/reference/index.d.ts +8 -0
- package/dist/domain/reference/index.js +8 -0
- package/dist/e2e/harness.d.ts +1 -0
- package/dist/e2e/harness.js +61 -0
- package/dist/hooks/analytics.hooks.d.ts +25 -0
- package/dist/hooks/analytics.hooks.js +214 -0
- package/dist/hooks/business-hours.hooks.d.ts +8 -0
- package/dist/hooks/business-hours.hooks.js +45 -0
- package/dist/hooks/dashboard.hooks.d.ts +30 -0
- package/dist/hooks/dashboard.hooks.js +132 -0
- package/dist/hooks/delivery.hooks.d.ts +225 -0
- package/dist/hooks/delivery.hooks.js +80 -0
- package/dist/hooks/fleet.hooks.d.ts +37 -0
- package/dist/hooks/fleet.hooks.js +229 -0
- package/dist/hooks/image.hooks.d.ts +24 -0
- package/dist/hooks/image.hooks.js +66 -0
- package/dist/hooks/kiosk.hooks.d.ts +22 -5
- package/dist/hooks/kiosk.hooks.js +74 -5
- package/dist/hooks/members.hooks.d.ts +48 -0
- package/dist/hooks/members.hooks.js +302 -0
- package/dist/hooks/offline-orders.hooks.d.ts +9 -0
- package/dist/hooks/offline-orders.hooks.js +51 -0
- package/dist/hooks/orders.hooks.d.ts +18 -39
- package/dist/hooks/orders.hooks.js +124 -343
- package/dist/hooks/places.hooks.d.ts +21 -0
- package/dist/hooks/places.hooks.js +112 -0
- package/dist/hooks/realtime-stubs.d.ts +28 -0
- package/dist/hooks/realtime-stubs.js +85 -0
- package/dist/hooks/rider.hooks.d.ts +470 -0
- package/dist/hooks/rider.hooks.js +373 -0
- package/dist/hooks/use-infinite-list.d.ts +243 -0
- package/dist/hooks/use-infinite-list.js +58 -0
- package/dist/hooks/wallet.hooks.d.ts +219 -0
- package/dist/hooks/wallet.hooks.js +56 -0
- package/dist/image-cache/service.d.ts +28 -0
- package/dist/image-cache/service.js +51 -3
- package/dist/index.d.ts +56 -6
- package/dist/index.js +73 -6
- package/dist/local-node/adapters.d.ts +87 -0
- package/dist/local-node/adapters.js +4 -0
- package/dist/local-node/index.d.ts +5 -0
- package/dist/local-node/index.js +5 -0
- package/dist/local-node/security.d.ts +25 -0
- package/dist/local-node/security.js +149 -0
- package/dist/local-node/service.d.ts +83 -0
- package/dist/local-node/service.js +706 -0
- package/dist/local-node/store.d.ts +50 -0
- package/dist/local-node/store.js +284 -0
- package/dist/local-node/types.d.ts +92 -0
- package/dist/local-node/types.js +4 -0
- package/dist/printer/receipt.d.ts +4 -0
- package/dist/printer/receipt.js +75 -0
- package/dist/printer/service.js +1 -1
- package/dist/providers/query-client.d.ts +13 -0
- package/dist/providers/query-client.js +58 -0
- package/dist/realtime/phoenix-socket.js +30 -1
- package/dist/realtime/useEventsConnection.js +11 -2
- package/dist/reference/api.d.ts +2 -0
- package/dist/reference/api.js +2 -0
- package/dist/reference/capabilities.d.ts +12 -7
- package/dist/reference/capabilities.js +27 -13
- package/dist/reference/hooks.d.ts +34 -0
- package/dist/reference/hooks.js +124 -0
- package/dist/reference/menu-apply.d.ts +29 -0
- package/dist/reference/menu-apply.js +289 -0
- package/dist/reference/menu-record.d.ts +69 -0
- package/dist/reference/menu-record.js +168 -0
- package/dist/reference/operations.d.ts +3 -0
- package/dist/reference/operations.js +35 -1
- package/dist/reference/service.js +36 -9
- package/dist/reference/types.d.ts +1 -1
- package/dist/repositories/order.repository.d.ts +7 -0
- package/dist/repositories/order.repository.js +15 -2
- package/dist/repositories/pos.repository.d.ts +4 -0
- package/dist/repositories/pos.repository.js +4 -0
- package/dist/sync/backoff.d.ts +21 -0
- package/dist/sync/backoff.js +42 -0
- package/dist/sync/command-builder.js +10 -2
- package/dist/sync/command-queue.service.d.ts +12 -0
- package/dist/sync/command-queue.service.js +81 -16
- package/dist/sync/device-lease.service.d.ts +3 -3
- package/dist/sync/device-lease.service.js +80 -20
- package/dist/sync/engine.d.ts +3 -1
- package/dist/sync/engine.js +44 -4
- package/dist/sync/full-sync.d.ts +6 -0
- package/dist/sync/full-sync.js +72 -0
- package/dist/sync/health-probe.d.ts +24 -0
- package/dist/sync/health-probe.js +138 -0
- package/dist/sync/offline-capable.js +25 -0
- package/dist/sync/online-gates.d.ts +17 -0
- package/dist/sync/online-gates.js +55 -0
- package/dist/sync/order-number.d.ts +4 -4
- package/dist/sync/order-number.js +33 -13
- package/dist/sync/order-snapshots.service.js +18 -3
- package/dist/sync/pending-orders.service.d.ts +18 -0
- package/dist/sync/pending-orders.service.js +67 -1
- package/dist/sync/reconciliation/index.d.ts +5 -0
- package/dist/sync/reconciliation/index.js +5 -0
- package/dist/sync/snapshots/entity.d.ts +6 -0
- package/dist/sync/snapshots/entity.js +6 -0
- package/dist/sync/sync-jobs.service.d.ts +18 -0
- package/dist/sync/sync-jobs.service.js +539 -0
- package/dist/sync/transition-endpoints.d.ts +11 -0
- package/dist/sync/transition-endpoints.js +30 -0
- package/dist/sync/transport-local-node.js +3 -2
- package/dist/sync/transport-rest.d.ts +7 -0
- package/dist/sync/transport-rest.js +84 -26
- package/dist/sync/use-sync-reconciliation.js +15 -1
- package/dist/test-helpers/memory-driver.js +182 -4
- package/dist/test-helpers/seed-hours.d.ts +1 -0
- package/dist/test-helpers/seed-hours.js +23 -0
- package/dist/types/api/business.api.d.ts +42 -0
- package/dist/types/api/menu.api.d.ts +15 -0
- package/dist/types/api/seller.api.d.ts +20 -0
- package/dist/types/places.d.ts +19 -0
- package/dist/types/places.js +2 -0
- package/dist/types/rider.d.ts +67 -0
- package/dist/types/rider.js +2 -0
- package/dist/utils/attempt-stats.d.ts +23 -0
- package/dist/utils/attempt-stats.js +61 -0
- package/dist/utils/business-hours.d.ts +37 -0
- package/dist/utils/business-hours.js +96 -0
- package/dist/utils/debug-log.d.ts +23 -0
- package/dist/utils/debug-log.js +153 -0
- package/dist/utils/order-filter.d.ts +1 -1
- package/dist/utils/order-filter.js +1 -1
- package/dist/utils/order-transitions.d.ts +30 -0
- package/dist/utils/order-transitions.js +185 -0
- package/dist/utils/sync-logger.js +5 -4
- package/dist/validation/offline.d.ts +46 -0
- package/dist/validation/offline.js +79 -0
- package/package.json +6 -1
- package/dist/pricing/engine.test.d.ts +0 -1
- package/dist/pricing/engine.test.js +0 -142
- package/dist/reference/lww.test.d.ts +0 -1
- package/dist/reference/lww.test.js +0 -23
- package/dist/reference/operations.test.d.ts +0 -1
- package/dist/reference/operations.test.js +0 -66
- package/dist/standalone.integration.test.d.ts +0 -1
- package/dist/standalone.integration.test.js +0 -235
- package/dist/sync/device-lease.test.d.ts +0 -1
- package/dist/sync/device-lease.test.js +0 -38
- package/dist/sync/offline-integration.test.d.ts +0 -1
- package/dist/sync/offline-integration.test.js +0 -52
- package/dist/sync/order-number.test.d.ts +0 -1
- package/dist/sync/order-number.test.js +0 -51
- package/dist/sync/perms.test.d.ts +0 -1
- package/dist/sync/perms.test.js +0 -15
- package/dist/utils/currency.test.d.ts +0 -1
- package/dist/utils/currency.test.js +0 -17
- package/dist/utils/hlc.test.d.ts +0 -1
- package/dist/utils/hlc.test.js +0 -29
- package/dist/utils/phone.test.d.ts +0 -1
- package/dist/utils/phone.test.js +0 -19
- package/dist/utils/responsive.d.ts +0 -13
- package/dist/utils/responsive.js +0 -18
- package/dist/utils/uuid.test.d.ts +0 -1
- package/dist/utils/uuid.test.js +0 -13
- package/dist/utils/validation.test.d.ts +0 -1
- package/dist/utils/validation.test.js +0 -23
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
* resulting domain events. This transport is used when the cloud API is
|
|
6
6
|
* reachable.
|
|
7
7
|
*/
|
|
8
|
-
import {
|
|
8
|
+
import { sellerService, kioskService, menuService } from "../adapters/services";
|
|
9
9
|
import { syncLogger } from "../utils/sync-logger";
|
|
10
10
|
import { generateEventId } from "../utils/uuid";
|
|
11
|
+
import { createLocalOrder, transitionLocalOrder } from "../domain/orders/service";
|
|
12
|
+
import { openSyncDatabase } from "../db/connection";
|
|
13
|
+
import { getOrderById, updateOrderStatus } from "../db/operations";
|
|
11
14
|
export class RestTransport {
|
|
12
15
|
constructor() {
|
|
13
16
|
this.name = "rest";
|
|
@@ -45,41 +48,38 @@ export class RestTransport {
|
|
|
45
48
|
}
|
|
46
49
|
async dispatchCommand(command) {
|
|
47
50
|
const { command_type, business_id, entity_id, payload } = command;
|
|
48
|
-
|
|
51
|
+
// Use the command's original timestamp (set at creation time) so the event
|
|
52
|
+
// reflects when the mutation actually happened, not when it was dispatched.
|
|
53
|
+
const timestamp = command.timestamp || new Date().toISOString();
|
|
49
54
|
switch (command_type) {
|
|
55
|
+
// Single create path — createLocalOrder owns SQLite + sync_jobs + the
|
|
56
|
+
// operating-hours gate. No direct-REST fallback: guard rejections
|
|
57
|
+
// (BUSINESS_CLOSED / HOURS_NOT_SYNCED) must stand, never be bypassed.
|
|
50
58
|
case "order:create": {
|
|
51
|
-
const
|
|
52
|
-
...payload,
|
|
53
|
-
id: command.command_id,
|
|
59
|
+
const order = await createLocalOrder(business_id, {
|
|
60
|
+
...(payload ?? {}),
|
|
54
61
|
command_id: command.command_id,
|
|
55
|
-
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
return this.wrapResponse("order.created", response.data, entity_id ?? command.command_id, timestamp, command);
|
|
62
|
+
id: entity_id ?? command.command_id,
|
|
63
|
+
});
|
|
64
|
+
return this.wrapResponse("order.created", order, order.id, timestamp, command);
|
|
59
65
|
}
|
|
60
66
|
case "order:accept": {
|
|
61
|
-
|
|
62
|
-
return this.wrapResponse("order.status_changed", response.data, entity_id, timestamp, command);
|
|
67
|
+
return this.executeOrderTransition(command, "accept", timestamp, () => sellerService.acceptOrder(entity_id));
|
|
63
68
|
}
|
|
64
69
|
case "order:reject": {
|
|
65
|
-
|
|
66
|
-
return this.wrapResponse("order.status_changed", response.data, entity_id, timestamp, command);
|
|
70
|
+
return this.executeOrderTransition(command, "reject", timestamp, () => sellerService.rejectOrder(entity_id));
|
|
67
71
|
}
|
|
68
72
|
case "order:start_preparing": {
|
|
69
|
-
|
|
70
|
-
return this.wrapResponse("order.status_changed", response.data, entity_id, timestamp, command);
|
|
73
|
+
return this.executeOrderTransition(command, "start_preparing", timestamp, () => sellerService.startPreparingOrder(entity_id));
|
|
71
74
|
}
|
|
72
75
|
case "order:mark_ready": {
|
|
73
|
-
|
|
74
|
-
return this.wrapResponse("order.status_changed", response.data, entity_id, timestamp, command);
|
|
76
|
+
return this.executeOrderTransition(command, "mark_ready", timestamp, () => sellerService.markOrderReady(entity_id));
|
|
75
77
|
}
|
|
76
78
|
case "order:complete_pickup": {
|
|
77
|
-
|
|
78
|
-
return this.wrapResponse("order.status_changed", response.data, entity_id, timestamp, command);
|
|
79
|
+
return this.executeOrderTransition(command, "complete_pickup", timestamp, () => sellerService.completeOrderPickup(entity_id));
|
|
79
80
|
}
|
|
80
81
|
case "order:cancel": {
|
|
81
|
-
|
|
82
|
-
return this.wrapResponse("order.status_changed", response.data, entity_id, timestamp, command);
|
|
82
|
+
return this.executeOrderTransition(command, "cancel", timestamp, () => sellerService.cancelOrder(entity_id));
|
|
83
83
|
}
|
|
84
84
|
case "delivery:assign": {
|
|
85
85
|
const response = await kioskService.assignKioskOrderToRider(entity_id, payload.rider_id);
|
|
@@ -90,12 +90,18 @@ export class RestTransport {
|
|
|
90
90
|
return this.wrapResponse("delivery.status_changed", response, entity_id, timestamp, command);
|
|
91
91
|
}
|
|
92
92
|
case "menu:create_item": {
|
|
93
|
-
|
|
93
|
+
// cacheRecord is local-cache only — strip it so the backend receives
|
|
94
|
+
// the flat wire payload it accepts (it owns canonical on converge).
|
|
95
|
+
const { cacheRecord: _omitCreate, ...wireCreate } = payload;
|
|
96
|
+
void _omitCreate;
|
|
97
|
+
const createPayload = { ...wireCreate, id: wireCreate.id ?? command.command_id, command_id: command.command_id };
|
|
94
98
|
const response = await menuService.createMenuItem(business_id, createPayload);
|
|
95
99
|
return this.wrapResponse("menu.item_created", response, command.command_id, timestamp, command);
|
|
96
100
|
}
|
|
97
101
|
case "menu:update_item": {
|
|
98
|
-
const
|
|
102
|
+
const { cacheRecord: _omitUpdate, ...wireUpdate } = payload;
|
|
103
|
+
void _omitUpdate;
|
|
104
|
+
const response = await menuService.updateMenuItem(entity_id, wireUpdate);
|
|
99
105
|
return this.wrapResponse("menu.item_updated", response, entity_id, timestamp, command);
|
|
100
106
|
}
|
|
101
107
|
case "menu:delete_item": {
|
|
@@ -103,18 +109,70 @@ export class RestTransport {
|
|
|
103
109
|
return this.wrapResponse("menu.item_deleted", response, entity_id, timestamp, command);
|
|
104
110
|
}
|
|
105
111
|
case "menu:create_category": {
|
|
106
|
-
const
|
|
112
|
+
const { cacheRecord: _omitCatCreate, ...wireCatCreate } = payload;
|
|
113
|
+
void _omitCatCreate;
|
|
114
|
+
const createPayload = { ...wireCatCreate, id: wireCatCreate.id ?? command.command_id, command_id: command.command_id };
|
|
107
115
|
const response = await menuService.createCategory(business_id, createPayload);
|
|
108
116
|
return this.wrapResponse("menu.category_created", response, command.command_id, timestamp, command);
|
|
109
117
|
}
|
|
118
|
+
case "menu:update_category": {
|
|
119
|
+
const { cacheRecord: _omitCatUpdate, ...wireCatUpdate } = payload;
|
|
120
|
+
void _omitCatUpdate;
|
|
121
|
+
const response = await menuService.updateCategory(entity_id, wireCatUpdate);
|
|
122
|
+
return this.wrapResponse("menu.category_updated", response, entity_id, timestamp, command);
|
|
123
|
+
}
|
|
124
|
+
case "menu:delete_category": {
|
|
125
|
+
const response = await menuService.deleteCategory(entity_id);
|
|
126
|
+
return this.wrapResponse("menu.category_deleted", response, entity_id, timestamp, command);
|
|
127
|
+
}
|
|
110
128
|
case "payment:confirm_cash": {
|
|
111
|
-
|
|
112
|
-
return this.wrapResponse("payment.completed", response, entity_id, timestamp, command);
|
|
129
|
+
return this.executeOrderTransition(command, "confirm_cash", timestamp, () => sellerService.confirmCashPayment(entity_id), "payment.completed");
|
|
113
130
|
}
|
|
114
131
|
default:
|
|
115
132
|
throw new Error(`RestTransport: unsupported command type ${command_type}`);
|
|
116
133
|
}
|
|
117
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Order transitions go through the domain service first (single write path:
|
|
137
|
+
* SQLite + sync_jobs). If this device never saw the order (e.g. a LAN node
|
|
138
|
+
* handling a cloud-created order before its first pull), fall back to the
|
|
139
|
+
* previous direct-REST behavior so availability never regresses.
|
|
140
|
+
*/
|
|
141
|
+
async executeOrderTransition(command, action, timestamp, restFallback, eventType = "order.status_changed") {
|
|
142
|
+
const { business_id, entity_id } = command;
|
|
143
|
+
try {
|
|
144
|
+
const order = await transitionLocalOrder(business_id, entity_id, action);
|
|
145
|
+
const data = { ...order, order_id: order.id };
|
|
146
|
+
return this.wrapResponse(eventType, data, order.id, timestamp, command);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
syncLogger.warn("RestTransport", "domain transition unavailable, REST fallback", {
|
|
150
|
+
command_type: command.command_type,
|
|
151
|
+
entity_id,
|
|
152
|
+
error: error instanceof Error ? error.message : String(error),
|
|
153
|
+
});
|
|
154
|
+
const response = await restFallback();
|
|
155
|
+
const raw = response.data;
|
|
156
|
+
const data = { ...raw };
|
|
157
|
+
const orderId = data.id ?? entity_id;
|
|
158
|
+
if (!data.order_id)
|
|
159
|
+
data.order_id = orderId;
|
|
160
|
+
if (business_id && orderId) {
|
|
161
|
+
try {
|
|
162
|
+
const db = await openSyncDatabase();
|
|
163
|
+
const existing = await getOrderById(db, business_id, orderId);
|
|
164
|
+
if (existing) {
|
|
165
|
+
const newStatus = data.status ?? existing.status;
|
|
166
|
+
if (newStatus !== existing.status) {
|
|
167
|
+
await updateOrderStatus(db, business_id, orderId, newStatus);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
catch { }
|
|
172
|
+
}
|
|
173
|
+
return this.wrapResponse(eventType, data, orderId, timestamp, command);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
118
176
|
wrapResponse(eventType, data, entityId, timestamp, command) {
|
|
119
177
|
return {
|
|
120
178
|
event_id: generateEventId(),
|
|
@@ -16,7 +16,8 @@ import { localNodeService } from "../adapters/local-node";
|
|
|
16
16
|
import { getSyncDatabase } from "../db/connection";
|
|
17
17
|
import { markEventsSynced } from "../db/operations";
|
|
18
18
|
import { syncLogger } from "../utils/sync-logger";
|
|
19
|
-
import {
|
|
19
|
+
import { subscribeDbChanges } from "../db/operations";
|
|
20
|
+
import { POS_KEYS, SELLER_KEYS, KIOSK_KEYS, SELLER_ORDERS_KEYS, BUSINESS_DELIVERIES_KEYS, MENU_KEYS, REFERENCE_KEYS, SELLER_DASHBOARD_KEYS, } from "../adapters/query-keys";
|
|
20
21
|
import { applyReferenceChangeEvent, registerReferenceInvalidator, registerReferenceEventEngine, REFERENCE_CHANGED_EVENT_TYPE, } from "../reference/events";
|
|
21
22
|
function isLocalOrderEvent(event) {
|
|
22
23
|
const payload = event.payload;
|
|
@@ -38,6 +39,7 @@ export function useSyncReconciliation() {
|
|
|
38
39
|
queryClient.invalidateQueries({ queryKey: REFERENCE_KEYS.all });
|
|
39
40
|
queryClient.invalidateQueries({ queryKey: POS_KEYS.all });
|
|
40
41
|
queryClient.invalidateQueries({ queryKey: SELLER_KEYS.all });
|
|
42
|
+
queryClient.invalidateQueries({ queryKey: SELLER_DASHBOARD_KEYS.all });
|
|
41
43
|
}
|
|
42
44
|
if (pending.has("pending")) {
|
|
43
45
|
queryClient.invalidateQueries({ queryKey: POS_KEYS.all });
|
|
@@ -45,6 +47,7 @@ export function useSyncReconciliation() {
|
|
|
45
47
|
queryClient.invalidateQueries({ queryKey: KIOSK_KEYS.all });
|
|
46
48
|
queryClient.invalidateQueries({ queryKey: SELLER_ORDERS_KEYS.all });
|
|
47
49
|
queryClient.invalidateQueries({ queryKey: BUSINESS_DELIVERIES_KEYS.all });
|
|
50
|
+
queryClient.invalidateQueries({ queryKey: SELLER_DASHBOARD_KEYS.all });
|
|
48
51
|
}
|
|
49
52
|
if (pending.has("order")) {
|
|
50
53
|
// order.created with local number already covered by pending, but keep fine-grained
|
|
@@ -105,6 +108,10 @@ export function useSyncReconciliation() {
|
|
|
105
108
|
// Order/delivery state changed: refresh order caches so this terminal
|
|
106
109
|
// shows the same order states as the device that made the change.
|
|
107
110
|
await applyOrderStateChangeEvent(event, "local");
|
|
111
|
+
// Any order/delivery event should refresh dashboard recent orders immediately (fresh on transition)
|
|
112
|
+
if (event.event_type.startsWith("order.") || event.event_type.startsWith("delivery.")) {
|
|
113
|
+
schedule("pending");
|
|
114
|
+
}
|
|
108
115
|
// Route both queued (optimistic) and reconciled (created) order events
|
|
109
116
|
// into the pending-orders store.
|
|
110
117
|
if (event.event_type === "order.create_queued" ||
|
|
@@ -124,12 +131,19 @@ export function useSyncReconciliation() {
|
|
|
124
131
|
schedule("pending");
|
|
125
132
|
}
|
|
126
133
|
});
|
|
134
|
+
const dbUnsub = subscribeDbChanges((info) => {
|
|
135
|
+
if (info.table.startsWith("reference_data"))
|
|
136
|
+
schedule("reference");
|
|
137
|
+
else if (info.table === "orders" || info.table.startsWith("entity_snapshots"))
|
|
138
|
+
schedule("pending");
|
|
139
|
+
});
|
|
127
140
|
return () => {
|
|
128
141
|
if (timer)
|
|
129
142
|
clearTimeout(timer);
|
|
130
143
|
unsubscribe();
|
|
131
144
|
unregisterInvalidator();
|
|
132
145
|
unregisterReferenceInvalidator();
|
|
146
|
+
dbUnsub();
|
|
133
147
|
};
|
|
134
148
|
}, [queryClient]);
|
|
135
149
|
}
|
|
@@ -5,6 +5,8 @@ export function createMemoryDriver() {
|
|
|
5
5
|
entity_snapshots: [],
|
|
6
6
|
commands: [],
|
|
7
7
|
events: [],
|
|
8
|
+
orders: [],
|
|
9
|
+
sync_jobs: [],
|
|
8
10
|
};
|
|
9
11
|
let autoId = { commands: 1, events: 1, migrations: 1 };
|
|
10
12
|
const driver = {
|
|
@@ -93,9 +95,13 @@ export function createMemoryDriver() {
|
|
|
93
95
|
tables.commands.push(row);
|
|
94
96
|
return { lastInsertRowId: row.id, changes: 1 };
|
|
95
97
|
}
|
|
96
|
-
// commands update status
|
|
98
|
+
// commands update status (4- or 5-placeholder variants — always take the
|
|
99
|
+
// LAST arg as command_id; older code assumed 5).
|
|
97
100
|
if (s.includes("UPDATE commands SET status = ?")) {
|
|
98
|
-
const
|
|
101
|
+
const argv = args;
|
|
102
|
+
const status = argv[0];
|
|
103
|
+
const error = argv[1];
|
|
104
|
+
const command_id = argv[argv.length - 1];
|
|
99
105
|
// also handles variants with retry_count increment
|
|
100
106
|
const row = tables.commands.find((r) => r.command_id === command_id);
|
|
101
107
|
if (row) {
|
|
@@ -126,10 +132,17 @@ export function createMemoryDriver() {
|
|
|
126
132
|
return { lastInsertRowId: 0, changes: 1 };
|
|
127
133
|
}
|
|
128
134
|
if (s.includes("UPDATE commands SET payload")) {
|
|
129
|
-
|
|
135
|
+
// handles both old (payload, command_id) and new (payload, error, updated_at, command_id)
|
|
136
|
+
const payload = args[0];
|
|
137
|
+
const command_id = args[args.length - 1];
|
|
130
138
|
const row = tables.commands.find((r) => r.command_id === command_id);
|
|
131
|
-
if (row)
|
|
139
|
+
if (row) {
|
|
132
140
|
row.payload = payload;
|
|
141
|
+
if (args.length === 4) {
|
|
142
|
+
row.error = args[1];
|
|
143
|
+
row.updated_at = args[2];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
133
146
|
return { lastInsertRowId: 0, changes: 1 };
|
|
134
147
|
}
|
|
135
148
|
// events insert
|
|
@@ -162,6 +175,101 @@ export function createMemoryDriver() {
|
|
|
162
175
|
row.synced_to_cloud = 1;
|
|
163
176
|
return { lastInsertRowId: 0, changes: ids.length };
|
|
164
177
|
}
|
|
178
|
+
// orders insert (primary DB)
|
|
179
|
+
if (s.includes("INSERT INTO orders")) {
|
|
180
|
+
const [id, business_id, order_number, callout_number, status, order_type, is_pos, items, subtotal_minor, total_minor, tax_minor, tax_lines, payment, customer_name, customer_phone, pos_payment_method, delivery_id, business, user, inserted_at, hlc] = args;
|
|
181
|
+
const existing = tables.orders.find((r) => r.id === id);
|
|
182
|
+
// LWW check via hlc (if existing and incoming older, skip)
|
|
183
|
+
if (existing) {
|
|
184
|
+
if (hlc && existing.hlc && hlc < existing.hlc)
|
|
185
|
+
return { lastInsertRowId: 0, changes: 0 };
|
|
186
|
+
Object.assign(existing, { business_id, order_number, callout_number, status, order_type, is_pos, items, subtotal_minor, total_minor, tax_minor, tax_lines, payment, customer_name, customer_phone, pos_payment_method, delivery_id, business, user, inserted_at, hlc });
|
|
187
|
+
return { lastInsertRowId: 0, changes: 1 };
|
|
188
|
+
}
|
|
189
|
+
tables.orders.push({ id, business_id, order_number, callout_number, status, order_type, is_pos, items, subtotal_minor, total_minor, tax_minor, tax_lines, payment, customer_name, customer_phone, pos_payment_method, delivery_id, business, user, inserted_at, hlc, deleted_at: null });
|
|
190
|
+
return { lastInsertRowId: 1, changes: 1 };
|
|
191
|
+
}
|
|
192
|
+
// sync_jobs insert
|
|
193
|
+
if (s.includes("INSERT OR IGNORE INTO sync_jobs")) {
|
|
194
|
+
const [id, business_id, command_type, entity_id, payload, created_at, updated_at] = args;
|
|
195
|
+
if (tables.sync_jobs.find((r) => r.id === id))
|
|
196
|
+
return { lastInsertRowId: 0, changes: 0 };
|
|
197
|
+
tables.sync_jobs.push({ id, business_id, command_type, entity_id, payload, status: "pending", attempts: 0, created_at, updated_at });
|
|
198
|
+
return { lastInsertRowId: 1, changes: 1 };
|
|
199
|
+
}
|
|
200
|
+
// sync_jobs update status=completed
|
|
201
|
+
if (s.includes("UPDATE sync_jobs SET status='completed'")) {
|
|
202
|
+
const [updated_at, id] = args;
|
|
203
|
+
const row = tables.sync_jobs.find((r) => r.id === id);
|
|
204
|
+
if (row) {
|
|
205
|
+
row.status = "completed";
|
|
206
|
+
row.updated_at = updated_at;
|
|
207
|
+
}
|
|
208
|
+
return { lastInsertRowId: 0, changes: 1 };
|
|
209
|
+
}
|
|
210
|
+
if (s.includes("UPDATE sync_jobs SET payload=")) {
|
|
211
|
+
const [payload, updated_at, id] = args;
|
|
212
|
+
const row = tables.sync_jobs.find((r) => r.id === id);
|
|
213
|
+
if (row) {
|
|
214
|
+
row.payload = payload;
|
|
215
|
+
row.updated_at = updated_at;
|
|
216
|
+
}
|
|
217
|
+
return { lastInsertRowId: 0, changes: 1 };
|
|
218
|
+
}
|
|
219
|
+
if (s.includes("UPDATE sync_jobs SET attempts = attempts + 1")) {
|
|
220
|
+
const [last_error, updated_at, id] = args;
|
|
221
|
+
const row = tables.sync_jobs.find((r) => r.id === id);
|
|
222
|
+
if (row) {
|
|
223
|
+
row.attempts = (row.attempts ?? 0) + 1;
|
|
224
|
+
row.last_error = last_error;
|
|
225
|
+
row.updated_at = updated_at;
|
|
226
|
+
}
|
|
227
|
+
return { lastInsertRowId: 0, changes: 1 };
|
|
228
|
+
}
|
|
229
|
+
// orders updates
|
|
230
|
+
if (s.includes("UPDATE orders SET status = ?")) {
|
|
231
|
+
const [status, hlc, business_id, id1, id2] = args;
|
|
232
|
+
const row = tables.orders.find((r) => r.business_id === business_id && (r.id === id1 || r.order_number === id2));
|
|
233
|
+
if (row) {
|
|
234
|
+
row.status = status;
|
|
235
|
+
row.hlc = hlc;
|
|
236
|
+
}
|
|
237
|
+
return { lastInsertRowId: 0, changes: 1 };
|
|
238
|
+
}
|
|
239
|
+
if (s.includes("UPDATE orders SET payment = ?")) {
|
|
240
|
+
const [payment, hlc, business_id, id1, id2] = args;
|
|
241
|
+
const row = tables.orders.find((r) => r.business_id === business_id && (r.id === id1 || r.order_number === id2));
|
|
242
|
+
if (row) {
|
|
243
|
+
row.payment = payment;
|
|
244
|
+
row.hlc = hlc;
|
|
245
|
+
}
|
|
246
|
+
return { lastInsertRowId: 0, changes: 1 };
|
|
247
|
+
}
|
|
248
|
+
if (s.includes("UPDATE orders SET order_number=")) {
|
|
249
|
+
const [order_number, hlc, business_id, id] = args;
|
|
250
|
+
const row = tables.orders.find((r) => r.business_id === business_id && r.id === id);
|
|
251
|
+
if (row) {
|
|
252
|
+
row.order_number = order_number;
|
|
253
|
+
row.hlc = hlc;
|
|
254
|
+
}
|
|
255
|
+
return { lastInsertRowId: 0, changes: 1 };
|
|
256
|
+
}
|
|
257
|
+
// cleanup deletes for v7
|
|
258
|
+
if (s.includes("DELETE FROM orders WHERE order_number = '[object Object]'")) {
|
|
259
|
+
const before = tables.orders.length;
|
|
260
|
+
tables.orders = tables.orders.filter((r) => r.order_number !== "[object Object]");
|
|
261
|
+
return { lastInsertRowId: 0, changes: before - tables.orders.length };
|
|
262
|
+
}
|
|
263
|
+
if (s.includes("DELETE FROM sync_jobs WHERE payload LIKE")) {
|
|
264
|
+
const before = tables.sync_jobs.length;
|
|
265
|
+
tables.sync_jobs = tables.sync_jobs.filter((r) => !String(r.payload).includes("[object Object]"));
|
|
266
|
+
return { lastInsertRowId: 0, changes: before - tables.sync_jobs.length };
|
|
267
|
+
}
|
|
268
|
+
if (s.includes("DELETE FROM entity_snapshots WHERE payload LIKE")) {
|
|
269
|
+
const before = tables.entity_snapshots.length;
|
|
270
|
+
tables.entity_snapshots = tables.entity_snapshots.filter((r) => !String(r.payload).includes("[object Object]"));
|
|
271
|
+
return { lastInsertRowId: 0, changes: before - tables.entity_snapshots.length };
|
|
272
|
+
}
|
|
165
273
|
return { lastInsertRowId: 0, changes: 0 };
|
|
166
274
|
},
|
|
167
275
|
async getFirstAsync(sql, ...args) {
|
|
@@ -186,6 +294,11 @@ export function createMemoryDriver() {
|
|
|
186
294
|
const row = tables.entity_snapshots.find((r) => r.entity_type === entity_type && r.entity_id === entity_id);
|
|
187
295
|
return (row ? { last_event_timestamp: row.last_event_timestamp } : null);
|
|
188
296
|
}
|
|
297
|
+
if (s.includes("SELECT * FROM entity_snapshots WHERE entity_type = ? AND entity_id = ? AND business_id =")) {
|
|
298
|
+
const [entity_type, entity_id, business_id] = args;
|
|
299
|
+
const row = tables.entity_snapshots.find((r) => r.entity_type === entity_type && r.entity_id === entity_id && r.business_id === business_id);
|
|
300
|
+
return row ?? null;
|
|
301
|
+
}
|
|
189
302
|
if (s.includes("SELECT * FROM entity_snapshots WHERE entity_type = ? AND entity_id =")) {
|
|
190
303
|
const [entity_type, entity_id] = args;
|
|
191
304
|
const row = tables.entity_snapshots.find((r) => r.entity_type === entity_type && r.entity_id === entity_id);
|
|
@@ -196,6 +309,25 @@ export function createMemoryDriver() {
|
|
|
196
309
|
const row = tables.commands.find((r) => r.id === id);
|
|
197
310
|
return row ?? null;
|
|
198
311
|
}
|
|
312
|
+
// orders count
|
|
313
|
+
if (s.includes("SELECT COUNT(*) as c FROM orders WHERE")) {
|
|
314
|
+
const business_id = args[0];
|
|
315
|
+
const c = tables.orders.filter((r) => r.business_id === business_id).length;
|
|
316
|
+
return { c };
|
|
317
|
+
}
|
|
318
|
+
// orders get by id or order_number
|
|
319
|
+
if (s.includes("SELECT * FROM orders WHERE business_id = ? AND (id = ? OR order_number = ?)")) {
|
|
320
|
+
const [business_id, id1, id2] = args;
|
|
321
|
+
const row = tables.orders.find((r) => r.business_id === business_id && (r.id === id1 || r.order_number === id2));
|
|
322
|
+
return row ?? null;
|
|
323
|
+
}
|
|
324
|
+
// sync_jobs pending single
|
|
325
|
+
if (s.includes("SELECT * FROM sync_jobs WHERE business_id = ? AND entity_id = ? AND status='pending'")) {
|
|
326
|
+
const [business_id, entity_id] = args;
|
|
327
|
+
const row = tables.sync_jobs.find((r) => r.business_id === business_id && r.entity_id === entity_id && r.status === "pending");
|
|
328
|
+
return row ?? null;
|
|
329
|
+
}
|
|
330
|
+
// generic orders exists check for migrations backfill: SELECT * FROM orders WHERE business_id=? (not needed)
|
|
199
331
|
return null;
|
|
200
332
|
},
|
|
201
333
|
async getAllAsync(sql, ...args) {
|
|
@@ -204,6 +336,11 @@ export function createMemoryDriver() {
|
|
|
204
336
|
const [business_id] = args;
|
|
205
337
|
return tables.reference_data.filter((r) => r.business_id === business_id).map((r) => ({ key: r.key }));
|
|
206
338
|
}
|
|
339
|
+
if (s.includes("SELECT * FROM entity_snapshots WHERE entity_type = ? AND business_id = ? ORDER BY last_event_timestamp DESC")) {
|
|
340
|
+
const [entity_type, business_id] = args;
|
|
341
|
+
const rows = tables.entity_snapshots.filter((r) => r.entity_type === entity_type && r.business_id === business_id).sort((a, b) => (a.last_event_timestamp < b.last_event_timestamp ? 1 : -1));
|
|
342
|
+
return rows;
|
|
343
|
+
}
|
|
207
344
|
if (s.includes("SELECT * FROM entity_snapshots WHERE entity_type = ? ORDER BY last_event_timestamp DESC")) {
|
|
208
345
|
const [entity_type] = args;
|
|
209
346
|
const rows = tables.entity_snapshots.filter((r) => r.entity_type === entity_type).sort((a, b) => (a.last_event_timestamp < b.last_event_timestamp ? 1 : -1));
|
|
@@ -230,6 +367,47 @@ export function createMemoryDriver() {
|
|
|
230
367
|
rows = rows.slice(0, limit);
|
|
231
368
|
return rows;
|
|
232
369
|
}
|
|
370
|
+
// orders list with filters (simple business_id filter + pagination)
|
|
371
|
+
if (s.includes("SELECT * FROM orders WHERE")) {
|
|
372
|
+
const business_id = args[0];
|
|
373
|
+
// args includes where-clause params plus LIMIT and OFFSET at end
|
|
374
|
+
// For test, just filter by business_id and apply basic status/q if present in sql string via args slicing?
|
|
375
|
+
// Simpler: return all orders for that business, sorted by inserted_at desc, handle LIMIT/OFFSET manually
|
|
376
|
+
let rows = tables.orders.filter((r) => r.business_id === business_id);
|
|
377
|
+
// handle status filter if sql contains status IN
|
|
378
|
+
if (s.includes("status IN")) {
|
|
379
|
+
// find args that are status values (excluding business_id, limit, offset)
|
|
380
|
+
// Our sql: SELECT * FROM orders WHERE business_id = ? AND status IN (?,?) ... LIMIT ? OFFSET ?
|
|
381
|
+
// So statuses are args[1]..args[?]
|
|
382
|
+
// Count statuses by parsing SQL placeholder count?
|
|
383
|
+
// For test, just extract known statuses from args that look like status strings
|
|
384
|
+
const possibleStatuses = ["pending", "awaiting_payment", "accepted", "preparing", "ready", "waiting_pickup", "on_the_way", "arrived", "delivered", "cancelled", "assigned", "picked_up", "en_route", "failed"];
|
|
385
|
+
const statusArgs = args.filter((v) => possibleStatuses.includes(String(v)));
|
|
386
|
+
if (statusArgs.length)
|
|
387
|
+
rows = rows.filter((r) => statusArgs.includes(r.status));
|
|
388
|
+
}
|
|
389
|
+
rows = rows.sort((a, b) => (a.inserted_at < b.inserted_at ? 1 : -1));
|
|
390
|
+
const limit = args[args.length - 2];
|
|
391
|
+
const offset = args[args.length - 1];
|
|
392
|
+
if (typeof limit === "number" && typeof offset === "number") {
|
|
393
|
+
rows = rows.slice(offset, offset + limit);
|
|
394
|
+
}
|
|
395
|
+
return rows;
|
|
396
|
+
}
|
|
397
|
+
// sync_jobs pending list (generic, handles LIMIT 1/20)
|
|
398
|
+
if (s.includes("SELECT entity_id FROM sync_jobs") && s.includes("status='pending'")) {
|
|
399
|
+
const [business_id] = args;
|
|
400
|
+
const m = s.match(/LIMIT\s+(\d+)/);
|
|
401
|
+
const lim = m ? parseInt(m[1], 10) : 20;
|
|
402
|
+
let rows = tables.sync_jobs.filter((r) => r.business_id === business_id && r.status === "pending").sort((a, b) => (a.created_at < b.created_at ? -1 : 1)).map((r) => ({ entity_id: r.entity_id }));
|
|
403
|
+
rows = rows.slice(0, lim);
|
|
404
|
+
return rows;
|
|
405
|
+
}
|
|
406
|
+
if (s.includes("SELECT * FROM sync_jobs WHERE") || s.includes("SELECT id, command_type, status, created_at FROM sync_jobs")) {
|
|
407
|
+
const [business_id] = args;
|
|
408
|
+
const rows = tables.sync_jobs.filter((r) => r.business_id === business_id);
|
|
409
|
+
return rows;
|
|
410
|
+
}
|
|
233
411
|
return [];
|
|
234
412
|
},
|
|
235
413
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function seedOpenHours(businessId: string): Promise<void>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test helper — seeds 24/7 operating hours into SQLite reference_data.
|
|
3
|
+
* Creation is fail-closed: without cached hours every create throws
|
|
4
|
+
* HOURS_NOT_SYNCED, so tests that create orders must seed first.
|
|
5
|
+
*/
|
|
6
|
+
import { openSyncDatabase } from "../db/connection";
|
|
7
|
+
import { setReferenceData } from "../reference/operations";
|
|
8
|
+
export async function seedOpenHours(businessId) {
|
|
9
|
+
const db = await openSyncDatabase();
|
|
10
|
+
const entries = Array.from({ length: 7 }, (_, dayOfWeek) => ({
|
|
11
|
+
day_of_week: dayOfWeek,
|
|
12
|
+
// "24:00:00" close keeps string-compare open for the whole UTC day.
|
|
13
|
+
open_time: "00:00:00",
|
|
14
|
+
close_time: "24:00:00",
|
|
15
|
+
is_closed: false,
|
|
16
|
+
}));
|
|
17
|
+
await setReferenceData(db, businessId, {
|
|
18
|
+
key: "operating-hours",
|
|
19
|
+
version: "test-open",
|
|
20
|
+
payload: { data: entries },
|
|
21
|
+
downloadedAt: new Date().toISOString(),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -8,6 +8,8 @@ export interface Business {
|
|
|
8
8
|
status: string;
|
|
9
9
|
currency: string;
|
|
10
10
|
is_open: boolean;
|
|
11
|
+
/** Seller-facing open/close toggle (present when backend serializes it). */
|
|
12
|
+
manually_closed?: boolean;
|
|
11
13
|
owner_id: string;
|
|
12
14
|
business_number: string;
|
|
13
15
|
latitude: number;
|
|
@@ -135,3 +137,43 @@ export interface TaxRuleRequest {
|
|
|
135
137
|
display_order?: number;
|
|
136
138
|
};
|
|
137
139
|
}
|
|
140
|
+
export interface BusinessMemberInvitation {
|
|
141
|
+
id: string;
|
|
142
|
+
business_id: string;
|
|
143
|
+
invited_email: string;
|
|
144
|
+
role: string;
|
|
145
|
+
status: string;
|
|
146
|
+
expires_at: string;
|
|
147
|
+
inserted_at: string;
|
|
148
|
+
invited_by?: {
|
|
149
|
+
id: string;
|
|
150
|
+
full_name: string;
|
|
151
|
+
email: string;
|
|
152
|
+
} | null;
|
|
153
|
+
}
|
|
154
|
+
export interface BusinessMemberInvitationListResponse {
|
|
155
|
+
data: BusinessMemberInvitation[];
|
|
156
|
+
}
|
|
157
|
+
export interface UserInvitation {
|
|
158
|
+
id: string;
|
|
159
|
+
type: 'business_owner' | 'fleet_rider' | 'business_member' | 'rider_linked';
|
|
160
|
+
status: string;
|
|
161
|
+
business_name: string | null;
|
|
162
|
+
business_id: string | null;
|
|
163
|
+
code: string | null;
|
|
164
|
+
role?: string;
|
|
165
|
+
is_fleet_managed: boolean;
|
|
166
|
+
created_at: string | null;
|
|
167
|
+
}
|
|
168
|
+
export interface RoleCapabilityItem {
|
|
169
|
+
key: string;
|
|
170
|
+
label: string;
|
|
171
|
+
description?: string;
|
|
172
|
+
}
|
|
173
|
+
export interface RoleCapability {
|
|
174
|
+
label: string;
|
|
175
|
+
capabilities: RoleCapabilityItem[];
|
|
176
|
+
}
|
|
177
|
+
export interface RolesResponse {
|
|
178
|
+
data: Record<string, RoleCapability>;
|
|
179
|
+
}
|
|
@@ -78,13 +78,28 @@ export interface CatalogItemRequest {
|
|
|
78
78
|
currency_code: string;
|
|
79
79
|
dietary_tags?: string[];
|
|
80
80
|
variants: {
|
|
81
|
+
id?: string;
|
|
81
82
|
name: string;
|
|
82
83
|
price_adjustment: number;
|
|
83
84
|
}[];
|
|
84
85
|
extras: {
|
|
86
|
+
id?: string;
|
|
85
87
|
name: string;
|
|
86
88
|
price_minor: number;
|
|
87
89
|
}[];
|
|
88
90
|
available: boolean;
|
|
89
91
|
image_url?: string | null;
|
|
90
92
|
}
|
|
93
|
+
/** menu:create_item / menu:update_item command payload. */
|
|
94
|
+
export interface MenuItemCommandPayload extends CatalogItemRequest {
|
|
95
|
+
cacheRecord: MenuItem;
|
|
96
|
+
}
|
|
97
|
+
/** menu:create_category / menu:update_category command payload. */
|
|
98
|
+
export interface MenuCategoryCommandPayload {
|
|
99
|
+
name: string;
|
|
100
|
+
description?: string;
|
|
101
|
+
sort_order?: number;
|
|
102
|
+
categoryId?: string;
|
|
103
|
+
id?: string;
|
|
104
|
+
cacheRecord: MenuCategory;
|
|
105
|
+
}
|
|
@@ -108,6 +108,7 @@ export interface SellerOrder {
|
|
|
108
108
|
user: OrderUser | null;
|
|
109
109
|
items: OrderItem[];
|
|
110
110
|
inserted_at: string;
|
|
111
|
+
updated_at?: string;
|
|
111
112
|
rider?: {
|
|
112
113
|
id: string;
|
|
113
114
|
name: string | null;
|
|
@@ -170,3 +171,22 @@ export interface BusinessDelivery {
|
|
|
170
171
|
rider: DeliveryRider | null;
|
|
171
172
|
}
|
|
172
173
|
export type BusinessDeliveriesResponse = PaginatedResponse<BusinessDelivery>;
|
|
174
|
+
export interface SellerDashboardMetrics {
|
|
175
|
+
today_orders: number;
|
|
176
|
+
today_revenue_minor: number;
|
|
177
|
+
pending_orders: number;
|
|
178
|
+
average_rating: number;
|
|
179
|
+
active_orders: number;
|
|
180
|
+
}
|
|
181
|
+
export interface SellerDashboardResponse {
|
|
182
|
+
data: {
|
|
183
|
+
metrics: SellerDashboardMetrics;
|
|
184
|
+
recent_orders: Array<{
|
|
185
|
+
id: string;
|
|
186
|
+
order_number: string;
|
|
187
|
+
status: string;
|
|
188
|
+
total_minor: number;
|
|
189
|
+
item_count: number;
|
|
190
|
+
}>;
|
|
191
|
+
};
|
|
192
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Place autocomplete/detail types — backend places payloads. */
|
|
2
|
+
export interface PlaceSuggestion {
|
|
3
|
+
placeId: string;
|
|
4
|
+
description: string;
|
|
5
|
+
mainText: string;
|
|
6
|
+
secondaryText: string;
|
|
7
|
+
}
|
|
8
|
+
export interface PlaceDetails {
|
|
9
|
+
placeId: string;
|
|
10
|
+
formattedAddress: string;
|
|
11
|
+
street: string;
|
|
12
|
+
city: string;
|
|
13
|
+
region: string;
|
|
14
|
+
postalCode: string;
|
|
15
|
+
country: string;
|
|
16
|
+
countryCode?: string;
|
|
17
|
+
latitude: number;
|
|
18
|
+
longitude: number;
|
|
19
|
+
}
|