@teincfood/core 0.7.7 → 0.7.9
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/adapters/auth.d.ts +2 -0
- package/dist/adapters/auth.js +7 -0
- package/dist/adapters/filesystem.d.ts +22 -0
- package/dist/adapters/filesystem.js +6 -0
- package/dist/adapters/print.d.ts +29 -0
- package/dist/adapters/print.js +12 -0
- package/dist/adapters/services.d.ts +3 -0
- package/dist/adapters/services.js +3 -0
- package/dist/cart/hooks.d.ts +36 -0
- package/dist/cart/hooks.js +45 -0
- package/dist/cart/store.d.ts +33 -0
- package/dist/cart/store.js +122 -0
- package/dist/catalog/hooks.d.ts +25 -0
- package/dist/catalog/hooks.js +160 -0
- package/dist/hooks/orders.hooks.d.ts +17 -0
- package/dist/hooks/orders.hooks.js +117 -0
- package/dist/image-cache/service.d.ts +29 -0
- package/dist/image-cache/service.js +125 -0
- package/dist/index.d.ts +24 -2
- package/dist/index.js +33 -2
- package/dist/printer/hooks.d.ts +32 -0
- package/dist/printer/hooks.js +53 -0
- package/dist/printer/receipt.d.ts +12 -0
- package/dist/printer/receipt.js +315 -0
- package/dist/printer/service.d.ts +31 -0
- package/dist/printer/service.js +119 -0
- package/dist/printer/store.d.ts +24 -0
- package/dist/printer/store.js +94 -0
- package/dist/printer/types.d.ts +12 -0
- package/dist/printer/types.js +1 -0
- package/dist/realtime/events.service.d.ts +18 -0
- package/dist/realtime/events.service.js +76 -0
- package/dist/realtime/order-events.service.d.ts +6 -0
- package/dist/realtime/order-events.service.js +17 -0
- package/dist/realtime/phoenix-socket.d.ts +55 -0
- package/dist/realtime/phoenix-socket.js +240 -0
- package/dist/realtime/useEventsConnection.d.ts +1 -0
- package/dist/realtime/useEventsConnection.js +41 -0
- package/dist/reference/capabilities.js +1 -0
- package/dist/repositories/order.repository.d.ts +1 -0
- package/dist/repositories/order.repository.js +9 -0
- package/dist/sync/command-queue.service.js +34 -2
- package/dist/sync/order-snapshots.service.d.ts +1 -0
- package/dist/sync/order-snapshots.service.js +30 -0
- package/dist/sync/pending-orders.service.js +81 -2
- package/dist/sync/transport-rest.js +12 -5
- package/dist/sync/use-sync-reconciliation.js +43 -15
- package/package.json +1 -1
|
@@ -36,6 +36,7 @@ const QUEUED_STATUS_EVENT_TYPES = new Set([
|
|
|
36
36
|
"delivery.assign_queued",
|
|
37
37
|
"delivery.confirm_handover_queued",
|
|
38
38
|
]);
|
|
39
|
+
const PAYMENT_QUEUED_TYPES = new Set(["payment.completed_queued"]);
|
|
39
40
|
let invalidator = null;
|
|
40
41
|
/**
|
|
41
42
|
* Register a callback used to invalidate TanStack Query caches when a pending
|
|
@@ -99,8 +100,12 @@ export async function hydratePendingOrders() {
|
|
|
99
100
|
export async function applyOrderStateChangeEvent(event, origin = "local") {
|
|
100
101
|
try {
|
|
101
102
|
const isQueued = QUEUED_STATUS_EVENT_TYPES.has(event.event_type);
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
const isPaymentQueued = PAYMENT_QUEUED_TYPES.has(event.event_type);
|
|
104
|
+
if (!ORDER_STATE_EVENT_TYPES.has(event.event_type) && !isQueued && !isPaymentQueued) {
|
|
105
|
+
// Also handle cloud payment.completed for is_paid patch
|
|
106
|
+
if (event.event_type !== "payment.completed")
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
104
109
|
syncLogger.debug("OrderState", "Order state changed", {
|
|
105
110
|
origin,
|
|
106
111
|
event_type: event.event_type,
|
|
@@ -111,6 +116,19 @@ export async function applyOrderStateChangeEvent(event, origin = "local") {
|
|
|
111
116
|
// Queued transitions carry to_status/order_id so they patch instantly offline.
|
|
112
117
|
if (isQueued) {
|
|
113
118
|
await orderSnapshotsService.applyQueuedStatusEvent(event, origin);
|
|
119
|
+
patchPendingStatus(event);
|
|
120
|
+
invalidator?.();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (isPaymentQueued) {
|
|
124
|
+
await orderSnapshotsService.applyPaymentEvent(event, origin);
|
|
125
|
+
patchPendingPaid(event);
|
|
126
|
+
invalidator?.();
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (event.event_type === "payment.completed") {
|
|
130
|
+
await orderSnapshotsService.applyPaymentEvent(event, origin);
|
|
131
|
+
patchPendingPaid(event);
|
|
114
132
|
invalidator?.();
|
|
115
133
|
return;
|
|
116
134
|
}
|
|
@@ -124,6 +142,46 @@ export async function applyOrderStateChangeEvent(event, origin = "local") {
|
|
|
124
142
|
});
|
|
125
143
|
}
|
|
126
144
|
}
|
|
145
|
+
function patchPendingStatus(event) {
|
|
146
|
+
try {
|
|
147
|
+
const payload = event.payload;
|
|
148
|
+
const toStatus = payload.to_status || payload.status;
|
|
149
|
+
const orderId = payload.order_id || event.entity_id;
|
|
150
|
+
if (!toStatus || !orderId)
|
|
151
|
+
return;
|
|
152
|
+
const store = usePendingOrdersStore.getState();
|
|
153
|
+
const target = store.orders.find((o) => o.id === orderId || o.order_number === orderId);
|
|
154
|
+
if (!target)
|
|
155
|
+
return;
|
|
156
|
+
const patched = { ...target, status: toStatus };
|
|
157
|
+
store.addOrder(patched);
|
|
158
|
+
}
|
|
159
|
+
catch { }
|
|
160
|
+
}
|
|
161
|
+
function patchPendingPaid(event) {
|
|
162
|
+
try {
|
|
163
|
+
const payload = event.payload;
|
|
164
|
+
const orderId = payload.order_id || event.entity_id;
|
|
165
|
+
if (!orderId)
|
|
166
|
+
return;
|
|
167
|
+
const now = new Date().toISOString();
|
|
168
|
+
const store = usePendingOrdersStore.getState();
|
|
169
|
+
const target = store.orders.find((o) => o.id === orderId || o.order_number === payload.order_number || o.id === event.entity_id);
|
|
170
|
+
const altTarget = !target ? store.orders.find((o) => o.id === orderId) : null;
|
|
171
|
+
const t = target ?? altTarget;
|
|
172
|
+
if (!t)
|
|
173
|
+
return;
|
|
174
|
+
const paidAt = payload.paid_at || now;
|
|
175
|
+
const patched = {
|
|
176
|
+
...t,
|
|
177
|
+
is_paid: true,
|
|
178
|
+
paid_at: paidAt,
|
|
179
|
+
payment: { ...t.payment, is_paid: true, paid_at: paidAt },
|
|
180
|
+
};
|
|
181
|
+
store.addOrder(patched);
|
|
182
|
+
}
|
|
183
|
+
catch { }
|
|
184
|
+
}
|
|
127
185
|
export async function applyPendingOrderEvent(event, origin = "local") {
|
|
128
186
|
try {
|
|
129
187
|
if (event.event_type === PENDING_EVENT_TYPE) {
|
|
@@ -144,6 +202,27 @@ export async function applyPendingOrderEvent(event, origin = "local") {
|
|
|
144
202
|
if (QUEUED_STATUS_EVENT_TYPES.has(event.event_type)) {
|
|
145
203
|
// Queued transitions: patch the read model so listing reflects new status offline.
|
|
146
204
|
await orderSnapshotsService.applyQueuedStatusEvent(event, origin);
|
|
205
|
+
// Also patch pending store so isPending overlay shows new status immediately.
|
|
206
|
+
try {
|
|
207
|
+
const payload = event.payload;
|
|
208
|
+
const toStatus = payload.to_status || payload.status;
|
|
209
|
+
const orderId = payload.order_id || event.entity_id;
|
|
210
|
+
if (toStatus && orderId) {
|
|
211
|
+
const store = usePendingOrdersStore.getState();
|
|
212
|
+
const target = store.orders.find((o) => o.id === orderId || o.order_number === orderId);
|
|
213
|
+
if (target)
|
|
214
|
+
store.addOrder({ ...target, status: toStatus });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch { }
|
|
218
|
+
const db = await openSyncDatabase();
|
|
219
|
+
await insertEvent(db, { event, origin });
|
|
220
|
+
invalidator?.();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (PAYMENT_QUEUED_TYPES.has(event.event_type) || event.event_type === "payment.completed") {
|
|
224
|
+
await orderSnapshotsService.applyPaymentEvent(event, origin);
|
|
225
|
+
patchPendingPaid(event);
|
|
147
226
|
const db = await openSyncDatabase();
|
|
148
227
|
await insertEvent(db, { event, origin });
|
|
149
228
|
invalidator?.();
|
|
@@ -50,11 +50,12 @@ export class RestTransport {
|
|
|
50
50
|
case "order:create": {
|
|
51
51
|
const posPayload = {
|
|
52
52
|
...payload,
|
|
53
|
+
id: command.command_id,
|
|
53
54
|
command_id: command.command_id,
|
|
54
55
|
local_order_number: payload.order_number,
|
|
55
56
|
};
|
|
56
57
|
const response = await posService.createPOSOrder(business_id, posPayload);
|
|
57
|
-
return this.wrapResponse("order.created", response.data, entity_id, timestamp, command);
|
|
58
|
+
return this.wrapResponse("order.created", response.data, entity_id ?? command.command_id, timestamp, command);
|
|
58
59
|
}
|
|
59
60
|
case "order:accept": {
|
|
60
61
|
const response = await sellerService.acceptOrder(entity_id);
|
|
@@ -89,8 +90,9 @@ export class RestTransport {
|
|
|
89
90
|
return this.wrapResponse("delivery.status_changed", response, entity_id, timestamp, command);
|
|
90
91
|
}
|
|
91
92
|
case "menu:create_item": {
|
|
92
|
-
const
|
|
93
|
-
|
|
93
|
+
const createPayload = { ...payload, id: payload.id ?? command.command_id, command_id: command.command_id };
|
|
94
|
+
const response = await menuService.createMenuItem(business_id, createPayload);
|
|
95
|
+
return this.wrapResponse("menu.item_created", response, command.command_id, timestamp, command);
|
|
94
96
|
}
|
|
95
97
|
case "menu:update_item": {
|
|
96
98
|
const response = await menuService.updateMenuItem(entity_id, payload);
|
|
@@ -101,8 +103,13 @@ export class RestTransport {
|
|
|
101
103
|
return this.wrapResponse("menu.item_deleted", response, entity_id, timestamp, command);
|
|
102
104
|
}
|
|
103
105
|
case "menu:create_category": {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
+
const createPayload = { ...payload, id: payload.id ?? command.command_id, command_id: command.command_id };
|
|
107
|
+
const response = await menuService.createCategory(business_id, createPayload);
|
|
108
|
+
return this.wrapResponse("menu.category_created", response, command.command_id, timestamp, command);
|
|
109
|
+
}
|
|
110
|
+
case "payment:confirm_cash": {
|
|
111
|
+
const response = await sellerService.confirmCashPayment(entity_id);
|
|
112
|
+
return this.wrapResponse("payment.completed", response, entity_id, timestamp, command);
|
|
106
113
|
}
|
|
107
114
|
default:
|
|
108
115
|
throw new Error(`RestTransport: unsupported command type ${command_type}`);
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { useEffect } from "react";
|
|
11
11
|
import { useQueryClient } from "@tanstack/react-query";
|
|
12
12
|
import { syncEngine } from "./engine";
|
|
13
|
+
import { getActiveBusiness } from "../adapters/business";
|
|
13
14
|
import { applyPendingOrderEvent, applyOrderStateChangeEvent, registerPendingOrdersInvalidator, } from "./pending-orders.service";
|
|
14
15
|
import { localNodeService } from "../adapters/local-node";
|
|
15
16
|
import { getSyncDatabase } from "../db/connection";
|
|
@@ -24,16 +25,42 @@ function isLocalOrderEvent(event) {
|
|
|
24
25
|
const localOrderNumber = payload.local_order_number;
|
|
25
26
|
return typeof localOrderNumber === "string" && /^[0-9]{2}-[0-9]{3}-[0-9]{3}$/.test(localOrderNumber);
|
|
26
27
|
}
|
|
28
|
+
const RECONCILE_DEBOUNCE_MS = 400;
|
|
27
29
|
export function useSyncReconciliation() {
|
|
28
30
|
const queryClient = useQueryClient();
|
|
29
31
|
useEffect(() => {
|
|
32
|
+
let timer;
|
|
33
|
+
const pending = new Set();
|
|
34
|
+
const flush = () => {
|
|
35
|
+
timer = undefined;
|
|
36
|
+
if (pending.has("reference")) {
|
|
37
|
+
queryClient.invalidateQueries({ queryKey: MENU_KEYS.all });
|
|
38
|
+
queryClient.invalidateQueries({ queryKey: REFERENCE_KEYS.all });
|
|
39
|
+
queryClient.invalidateQueries({ queryKey: POS_KEYS.all });
|
|
40
|
+
queryClient.invalidateQueries({ queryKey: SELLER_KEYS.all });
|
|
41
|
+
}
|
|
42
|
+
if (pending.has("pending")) {
|
|
43
|
+
queryClient.invalidateQueries({ queryKey: POS_KEYS.all });
|
|
44
|
+
queryClient.invalidateQueries({ queryKey: SELLER_KEYS.all });
|
|
45
|
+
queryClient.invalidateQueries({ queryKey: KIOSK_KEYS.all });
|
|
46
|
+
queryClient.invalidateQueries({ queryKey: SELLER_ORDERS_KEYS.all });
|
|
47
|
+
queryClient.invalidateQueries({ queryKey: BUSINESS_DELIVERIES_KEYS.all });
|
|
48
|
+
}
|
|
49
|
+
if (pending.has("order")) {
|
|
50
|
+
// order.created with local number already covered by pending, but keep fine-grained
|
|
51
|
+
// POS.orders(businessId) is also covered by pending's broad POS.all, no extra needed.
|
|
52
|
+
}
|
|
53
|
+
pending.clear();
|
|
54
|
+
};
|
|
55
|
+
const schedule = (kind) => {
|
|
56
|
+
pending.add(kind);
|
|
57
|
+
if (!timer)
|
|
58
|
+
timer = setTimeout(flush, RECONCILE_DEBOUNCE_MS);
|
|
59
|
+
};
|
|
30
60
|
// Reference changes re-sync offline data and refresh caches on this device
|
|
31
61
|
// and (via the LAN relay) on every connected terminal.
|
|
32
62
|
const unregisterReferenceInvalidator = registerReferenceInvalidator(() => {
|
|
33
|
-
|
|
34
|
-
queryClient.invalidateQueries({ queryKey: REFERENCE_KEYS.all });
|
|
35
|
-
queryClient.invalidateQueries({ queryKey: POS_KEYS.all });
|
|
36
|
-
queryClient.invalidateQueries({ queryKey: SELLER_KEYS.all });
|
|
63
|
+
schedule("reference");
|
|
37
64
|
});
|
|
38
65
|
// Pending-order reconciliation invalidates POS + seller order lists so the
|
|
39
66
|
// server-confirmed order replaces the optimistic one. Invalidate from the
|
|
@@ -42,17 +69,21 @@ export function useSyncReconciliation() {
|
|
|
42
69
|
// keys. Also cover kiosk/seller-orders/business-deliveries so boards
|
|
43
70
|
// refresh even when there is no Local Node (offline queue + Phoenix).
|
|
44
71
|
const unregisterInvalidator = registerPendingOrdersInvalidator(() => {
|
|
45
|
-
|
|
46
|
-
queryClient.invalidateQueries({ queryKey: SELLER_KEYS.all });
|
|
47
|
-
queryClient.invalidateQueries({ queryKey: KIOSK_KEYS.all });
|
|
48
|
-
queryClient.invalidateQueries({ queryKey: SELLER_ORDERS_KEYS.all });
|
|
49
|
-
queryClient.invalidateQueries({ queryKey: BUSINESS_DELIVERIES_KEYS.all });
|
|
72
|
+
schedule("pending");
|
|
50
73
|
});
|
|
51
74
|
// Reference-change events are published through the Sync Engine but are
|
|
52
75
|
// not backed by a command, so the engine has no transport dependency on
|
|
53
76
|
// the reference module. Register the singleton once.
|
|
54
77
|
registerReferenceEventEngine(syncEngine);
|
|
55
78
|
const unsubscribe = syncEngine.subscribe(async (event) => {
|
|
79
|
+
// LAN strict: ignore events for other business on same subnet
|
|
80
|
+
try {
|
|
81
|
+
const activeId = getActiveBusiness()?.id;
|
|
82
|
+
const payloadBid = event.payload?.business_id;
|
|
83
|
+
if (activeId && payloadBid && payloadBid !== activeId)
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
catch { }
|
|
56
87
|
syncLogger.event(event, "received");
|
|
57
88
|
// The node relays engine events (e.g. order.created when a queued
|
|
58
89
|
// command is replayed) to LAN clients so they reconcile their pending
|
|
@@ -90,15 +121,12 @@ export function useSyncReconciliation() {
|
|
|
90
121
|
error: String(error),
|
|
91
122
|
});
|
|
92
123
|
}
|
|
93
|
-
|
|
94
|
-
const businessId = payload.business_id;
|
|
95
|
-
if (businessId) {
|
|
96
|
-
queryClient.invalidateQueries({ queryKey: POS_KEYS.orders(businessId) });
|
|
97
|
-
queryClient.invalidateQueries({ queryKey: SELLER_KEYS.all });
|
|
98
|
-
}
|
|
124
|
+
schedule("pending");
|
|
99
125
|
}
|
|
100
126
|
});
|
|
101
127
|
return () => {
|
|
128
|
+
if (timer)
|
|
129
|
+
clearTimeout(timer);
|
|
102
130
|
unsubscribe();
|
|
103
131
|
unregisterInvalidator();
|
|
104
132
|
unregisterReferenceInvalidator();
|
package/package.json
CHANGED