@teincfood/core 0.7.4 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,20 @@
1
1
  /**
2
- * Business store stub.
2
+ * Business store adapter — host app injects active business; core hooks subscribe.
3
3
  */
4
4
  export declare function setActiveBusiness(b: {
5
5
  id: string;
6
6
  } | null): void;
7
+ export declare function getActiveBusiness(): {
8
+ id: string;
9
+ } | null;
10
+ export declare function subscribeBusiness(listener: (b: {
11
+ id: string;
12
+ } | null) => void): () => void;
7
13
  export declare const useBusinessStore: {
8
14
  getState: () => {
9
15
  activeBusiness: {
10
16
  id: string;
11
17
  } | null;
12
18
  };
19
+ subscribe: typeof subscribeBusiness;
13
20
  };
@@ -1,10 +1,20 @@
1
1
  /**
2
- * Business store stub.
2
+ * Business store adapter — host app injects active business; core hooks subscribe.
3
3
  */
4
4
  let activeBusiness = null;
5
+ const listeners = new Set();
5
6
  export function setActiveBusiness(b) {
6
7
  activeBusiness = b;
8
+ listeners.forEach((l) => l(b));
9
+ }
10
+ export function getActiveBusiness() {
11
+ return activeBusiness;
12
+ }
13
+ export function subscribeBusiness(listener) {
14
+ listeners.add(listener);
15
+ return () => listeners.delete(listener);
7
16
  }
8
17
  export const useBusinessStore = {
9
18
  getState: () => ({ activeBusiness }),
19
+ subscribe: subscribeBusiness,
10
20
  };
@@ -1,17 +1,26 @@
1
1
  /**
2
- * Pending orders store stub.
2
+ * Pending orders store — holds locally queued (offline) order:create commands
3
+ * as optimistic SellerOrders so they remain visible on every terminal until
4
+ * the cloud acknowledges them. Observable so React hooks can subscribe.
3
5
  */
4
6
  import type { SellerOrder } from "../types/api/seller.api";
7
+ export declare function subscribePendingOrders(listener: () => void): () => void;
8
+ export declare function getPendingOrdersSnapshot(): {
9
+ orders: SellerOrder[];
10
+ hydrated: boolean;
11
+ };
5
12
  export declare const usePendingOrdersStore: {
6
13
  getState: () => {
7
14
  hydrated: boolean;
8
15
  orders: SellerOrder[];
9
16
  addOrder: (order: SellerOrder) => void;
10
17
  removeByOrderNumber: (orderNumber: string) => void;
18
+ removeByOrderId: (orderId: string) => void;
11
19
  setHydrated: (v: boolean) => void;
12
20
  };
13
21
  setState: (patch: Partial<{
14
22
  orders: SellerOrder[];
15
23
  hydrated: boolean;
16
24
  }>) => void;
25
+ subscribe: typeof subscribePendingOrders;
17
26
  };
@@ -1,28 +1,64 @@
1
1
  /**
2
- * Pending orders store stub.
2
+ * Pending orders store — holds locally queued (offline) order:create commands
3
+ * as optimistic SellerOrders so they remain visible on every terminal until
4
+ * the cloud acknowledges them. Observable so React hooks can subscribe.
3
5
  */
4
6
  let orders = [];
5
7
  let hydrated = false;
8
+ const listeners = new Set();
9
+ function notify() {
10
+ listeners.forEach((l) => l());
11
+ }
12
+ export function subscribePendingOrders(listener) {
13
+ listeners.add(listener);
14
+ return () => listeners.delete(listener);
15
+ }
16
+ export function getPendingOrdersSnapshot() {
17
+ return { orders: [...orders], hydrated };
18
+ }
6
19
  export const usePendingOrdersStore = {
7
20
  getState: () => ({
8
21
  hydrated,
9
- orders,
22
+ orders: [...orders],
10
23
  addOrder: (order) => {
11
24
  if (!orders.find((o) => o.order_number === order.order_number)) {
12
- orders.push(order);
25
+ orders = [...orders, order];
26
+ notify();
13
27
  }
14
28
  },
15
29
  removeByOrderNumber: (orderNumber) => {
16
- orders = orders.filter((o) => o.order_number !== orderNumber);
30
+ const next = orders.filter((o) => o.order_number !== orderNumber);
31
+ if (next.length !== orders.length) {
32
+ orders = next;
33
+ notify();
34
+ }
35
+ },
36
+ removeByOrderId: (orderId) => {
37
+ const next = orders.filter((o) => o.id !== orderId);
38
+ if (next.length !== orders.length) {
39
+ orders = next;
40
+ notify();
41
+ }
17
42
  },
18
43
  setHydrated: (v) => {
19
- hydrated = v;
44
+ if (hydrated !== v) {
45
+ hydrated = v;
46
+ notify();
47
+ }
20
48
  },
21
49
  }),
22
50
  setState: (patch) => {
23
- if (patch.orders !== undefined)
24
- orders = patch.orders;
25
- if (patch.hydrated !== undefined)
51
+ let changed = false;
52
+ if (patch.orders !== undefined) {
53
+ orders = [...patch.orders];
54
+ changed = true;
55
+ }
56
+ if (patch.hydrated !== undefined && hydrated !== patch.hydrated) {
26
57
  hydrated = patch.hydrated;
58
+ changed = true;
59
+ }
60
+ if (changed)
61
+ notify();
27
62
  },
63
+ subscribe: subscribePendingOrders,
28
64
  };
@@ -10,6 +10,17 @@ export declare const posService: {
10
10
  fetchBusinessOrders: (_businessId: string, _params?: unknown) => Promise<unknown>;
11
11
  };
12
12
  export declare const sellerService: {
13
+ fetchBusinessOrders: (_businessId: string, _params?: Record<string, unknown>) => Promise<{
14
+ data: unknown[];
15
+ pagination: unknown;
16
+ }>;
17
+ fetchSellerOrder: (_orderId: string) => Promise<{
18
+ data: unknown;
19
+ }>;
20
+ fetchBusinessDeliveries: (_businessId: string, _params?: Record<string, unknown>) => Promise<{
21
+ data: unknown[];
22
+ pagination: unknown;
23
+ }>;
13
24
  acceptOrder: (_orderId: string) => Promise<{
14
25
  data: unknown;
15
26
  }>;
@@ -17,8 +17,17 @@ export const posService = {
17
17
  notImpl("posService.fetchBusinessOrders");
18
18
  },
19
19
  };
20
- // seller.service
20
+ // seller.service — also order listing used by POS/seller order lifecycle
21
21
  export const sellerService = {
22
+ fetchBusinessOrders: async (_businessId, _params) => {
23
+ notImpl("sellerService.fetchBusinessOrders");
24
+ },
25
+ fetchSellerOrder: async (_orderId) => {
26
+ notImpl("sellerService.fetchSellerOrder");
27
+ },
28
+ fetchBusinessDeliveries: async (_businessId, _params) => {
29
+ notImpl("sellerService.fetchBusinessDeliveries");
30
+ },
22
31
  acceptOrder: async (_orderId) => {
23
32
  notImpl("sellerService.acceptOrder");
24
33
  },
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Order lifecycle hooks — offline-first, device-owned outbox.
3
+ *
4
+ * Device owns its partition: when offline it serves the durable
5
+ * entity_snapshots + pending overlay immediately; the SyncEngine replays
6
+ * queued commands when back online. No hook ever blocks on a cloud timeout
7
+ * when offline.
8
+ *
9
+ * Host apps just re-export from "@teincfood/core" — no app-local duplication.
10
+ */
11
+ import type { POSOrderCreateRequest, POSOrderPreviewResponse } from "../types/pos.types";
12
+ import type { POSCartItem, POSOrderType } from "../types/pos.types";
13
+ import type { SellerOrder } from "../types/api/seller.api";
14
+ export declare const POS_KEYS: {
15
+ readonly all: readonly ["pos"];
16
+ readonly orders: (businessId?: string) => readonly ["pos", "orders", string | undefined];
17
+ readonly preview: (businessId?: string) => readonly ["pos", "preview", string | undefined];
18
+ };
19
+ export declare const SELLER_KEYS: {
20
+ readonly all: readonly ["seller"];
21
+ readonly orders: (businessId?: string, filters?: Record<string, unknown>) => readonly unknown[];
22
+ readonly deliveries: (businessId?: string, filters?: Record<string, unknown>) => readonly unknown[];
23
+ readonly order: (id: string, businessId?: string) => readonly ["seller", "order", string, string | undefined];
24
+ };
25
+ export declare function usePOSOrdersQuery(filters?: {
26
+ status?: string;
27
+ order_type?: string;
28
+ is_pos?: string;
29
+ date_from?: string;
30
+ date_to?: string;
31
+ q?: string;
32
+ search?: string;
33
+ }, enabled?: boolean): import("@tanstack/react-query").UseInfiniteQueryResult<SellerOrder[], Error> & {
34
+ data: SellerOrder[] | undefined;
35
+ };
36
+ export declare function useSellerOrdersQuery(enabled?: boolean, filters?: {
37
+ status?: string;
38
+ q?: string;
39
+ search?: string;
40
+ date_from?: string;
41
+ date_to?: string;
42
+ }): import("@tanstack/react-query").UseInfiniteQueryResult<SellerOrder[], Error> & {
43
+ data: SellerOrder[] | undefined;
44
+ };
45
+ export declare function usePendingOrders(businessId?: string): SellerOrder[];
46
+ export declare function useCreatePOSOrderMutation(): import("@tanstack/react-query").UseMutationResult<unknown, Error, POSOrderCreateRequest, unknown>;
47
+ export declare function usePOSOrderPreviewQuery(items: POSCartItem[], orderType: POSOrderType, customerName: string): import("@tanstack/react-query").UseQueryResult<POSOrderPreviewResponse, Error>;
48
+ export declare const useAcceptOrderMutation: () => import("@tanstack/react-query").UseMutationResult<unknown, Error, string, unknown>;
49
+ export declare const useRejectOrderMutation: () => import("@tanstack/react-query").UseMutationResult<unknown, Error, string, unknown>;
50
+ export declare const useStartPreparingOrderMutation: () => import("@tanstack/react-query").UseMutationResult<unknown, Error, string, unknown>;
51
+ export declare const useMarkOrderReadyMutation: () => import("@tanstack/react-query").UseMutationResult<unknown, Error, string, unknown>;
52
+ export declare const useCompletePickupMutation: () => import("@tanstack/react-query").UseMutationResult<unknown, Error, string, unknown>;
53
+ export declare const useCancelOrderMutation: () => import("@tanstack/react-query").UseMutationResult<unknown, Error, string, unknown>;
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Order lifecycle hooks — offline-first, device-owned outbox.
3
+ *
4
+ * Device owns its partition: when offline it serves the durable
5
+ * entity_snapshots + pending overlay immediately; the SyncEngine replays
6
+ * queued commands when back online. No hook ever blocks on a cloud timeout
7
+ * when offline.
8
+ *
9
+ * Host apps just re-export from "@teincfood/core" — no app-local duplication.
10
+ */
11
+ import { useEffect, useMemo, useSyncExternalStore } from "react";
12
+ import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
13
+ import { posRepository } from "../repositories/pos.repository";
14
+ import { orderRepository } from "../repositories/order.repository";
15
+ import { sellerService } from "../adapters/services";
16
+ import { getActiveBusiness, subscribeBusiness } from "../adapters/business";
17
+ import { getConnectivityState, subscribeConnectivity } from "../adapters/connectivity";
18
+ import { getPendingOrdersSnapshot, subscribePendingOrders, } from "../adapters/pending-orders";
19
+ import { orderSnapshotsService } from "../sync/order-snapshots.service";
20
+ import { hydratePendingOrders } from "../sync/pending-orders.service";
21
+ import { isLocalOrderNumber } from "../sync/order-number";
22
+ import { filterOrdersOffline, paginateOffline } from "../utils/order-filter";
23
+ // ─── Keys (host apps should import from core, not define their own) ─────
24
+ export const POS_KEYS = {
25
+ all: ["pos"],
26
+ orders: (businessId) => ["pos", "orders", businessId],
27
+ preview: (businessId) => ["pos", "preview", businessId],
28
+ };
29
+ export const SELLER_KEYS = {
30
+ all: ["seller"],
31
+ orders: (businessId, filters) => [...SELLER_KEYS.all, "orders", businessId, filters].filter((x) => x !== undefined),
32
+ deliveries: (businessId, filters) => [...SELLER_KEYS.all, "deliveries", businessId, filters].filter((x) => x !== undefined),
33
+ order: (id, businessId) => [...SELLER_KEYS.all, "order", id, businessId],
34
+ };
35
+ // ─── Helpers ───────────────────────────────────────────────────────────────
36
+ function useBusinessId() {
37
+ const id = useSyncExternalStore(subscribeBusiness, () => getActiveBusiness()?.id ?? null, () => null);
38
+ return id ?? undefined;
39
+ }
40
+ function useConnectivity() {
41
+ const state = useSyncExternalStore(subscribeConnectivity, getConnectivityState, getConnectivityState);
42
+ return state;
43
+ }
44
+ function useCorePendingOrders(businessId) {
45
+ const snap = useSyncExternalStore(subscribePendingOrders, getPendingOrdersSnapshot, getPendingOrdersSnapshot);
46
+ useEffect(() => {
47
+ if (snap.hydrated)
48
+ return;
49
+ hydratePendingOrders().catch(() => { });
50
+ }, [snap.hydrated]);
51
+ return useMemo(() => (businessId ? snap.orders.filter((o) => o.business_id === businessId) : snap.orders), [snap.orders, businessId]);
52
+ }
53
+ function mergePendingOrders(pending, serverOrders) {
54
+ if (pending.length === 0)
55
+ return serverOrders ?? [];
56
+ const seen = new Set();
57
+ const merged = [];
58
+ for (const order of pending) {
59
+ seen.add(order.order_number);
60
+ merged.push(order);
61
+ }
62
+ for (const order of serverOrders ?? []) {
63
+ if (!seen.has(order.order_number))
64
+ merged.push(order);
65
+ }
66
+ return merged.sort((a, b) => (b.inserted_at ?? "").localeCompare(a.inserted_at ?? ""));
67
+ }
68
+ function isOfflineState(s) {
69
+ return !s.isOnline || s.isCloudReachable === "unreachable";
70
+ }
71
+ // ─── POS Orders Query (offline-first) ─────────────────────────────────────
72
+ export function usePOSOrdersQuery(filters, enabled = true) {
73
+ const businessId = useBusinessId();
74
+ const pendingOrders = useCorePendingOrders(businessId);
75
+ const connectivity = useConnectivity();
76
+ const offline = isOfflineState(connectivity);
77
+ const query = useInfiniteQuery({
78
+ queryKey: [...POS_KEYS.orders(businessId), filters],
79
+ queryFn: async ({ pageParam = 1 }) => {
80
+ const params = { page: String(pageParam) };
81
+ if (filters?.status)
82
+ params.status = filters.status;
83
+ if (filters?.order_type)
84
+ params.order_type = filters.order_type;
85
+ if (filters?.is_pos)
86
+ params.is_pos = filters.is_pos;
87
+ if (filters?.date_from)
88
+ params.date_from = filters.date_from;
89
+ if (filters?.date_to)
90
+ params.date_to = filters.date_to;
91
+ if (filters?.q)
92
+ params.q = filters.q;
93
+ if (filters?.search)
94
+ params.search = filters.search;
95
+ if (offline) {
96
+ const offlineList = businessId ? await orderSnapshotsService.getSellerList(businessId, businessId) : null;
97
+ const base = offlineList ?? [];
98
+ const filtered = filterOrdersOffline(base, filters);
99
+ // date_from/to are handled inside filterSellerOrders via inserted_at; apply if present
100
+ const paged = paginateOffline(filtered, pageParam, 20);
101
+ return paged;
102
+ }
103
+ try {
104
+ const result = (await posRepository.fetchOrders(businessId, params));
105
+ if (pageParam === 1 && businessId && Array.isArray(result.data)) {
106
+ orderSnapshotsService.persistSellerOrders(businessId, businessId, result.data).catch(() => { });
107
+ }
108
+ return result;
109
+ }
110
+ catch (e) {
111
+ const offlineList = businessId ? await orderSnapshotsService.getSellerList(businessId, businessId) : null;
112
+ if (offlineList) {
113
+ const filtered = filterOrdersOffline(offlineList, filters);
114
+ const paged = paginateOffline(filtered, pageParam, 20);
115
+ return paged;
116
+ }
117
+ throw e;
118
+ }
119
+ },
120
+ initialPageParam: 1,
121
+ getNextPageParam: (lastPage) => {
122
+ const p = lastPage.pagination;
123
+ return p && p.page < p.total_pages ? p.page + 1 : undefined;
124
+ },
125
+ enabled: enabled && !!businessId,
126
+ staleTime: 30 * 1000,
127
+ retry: false,
128
+ networkMode: "offlineFirst",
129
+ select: (data) => data.pages.flatMap((p) => p.data),
130
+ });
131
+ const data = useMemo(() => mergePendingOrders(pendingOrders, query.data), [pendingOrders, query.data]);
132
+ return { ...query, data };
133
+ }
134
+ // ─── Seller Orders Query (offline-first, shares same snapshot) ────────────
135
+ export function useSellerOrdersQuery(enabled = true, filters) {
136
+ const businessId = useBusinessId();
137
+ const pendingOrders = useCorePendingOrders(businessId);
138
+ const connectivity = useConnectivity();
139
+ const offline = isOfflineState(connectivity);
140
+ const query = useInfiniteQuery({
141
+ queryKey: SELLER_KEYS.orders(businessId, filters),
142
+ queryFn: async ({ pageParam = 1 }) => {
143
+ const params = { page: pageParam, page_size: 20 };
144
+ if (filters?.status)
145
+ params.status = filters.status;
146
+ if (filters?.q)
147
+ params.q = filters.q;
148
+ if (filters?.search)
149
+ params.search = filters.search;
150
+ if (filters?.date_from)
151
+ params.date_from = filters.date_from;
152
+ if (filters?.date_to)
153
+ params.date_to = filters.date_to;
154
+ if (offline) {
155
+ const offlineList = businessId ? await orderSnapshotsService.getSellerList(businessId, businessId) : null;
156
+ const base = offlineList ?? [];
157
+ const filtered = filterOrdersOffline(base, filters);
158
+ const paged = paginateOffline(filtered, pageParam, 20);
159
+ return paged;
160
+ }
161
+ try {
162
+ const result = (await sellerService.fetchBusinessOrders(businessId, params));
163
+ if (pageParam === 1 && businessId && Array.isArray(result.data)) {
164
+ orderSnapshotsService.persistSellerOrders(businessId, businessId, result.data).catch(() => { });
165
+ }
166
+ return result;
167
+ }
168
+ catch (e) {
169
+ const offlineList = businessId ? await orderSnapshotsService.getSellerList(businessId, businessId) : null;
170
+ if (offlineList) {
171
+ const filtered = filterOrdersOffline(offlineList, filters);
172
+ const paged = paginateOffline(filtered, pageParam, 20);
173
+ return paged;
174
+ }
175
+ throw e;
176
+ }
177
+ },
178
+ initialPageParam: 1,
179
+ getNextPageParam: (lastPage) => {
180
+ const p = lastPage.pagination;
181
+ return p && p.page < p.total_pages ? p.page + 1 : undefined;
182
+ },
183
+ enabled: enabled && !!businessId,
184
+ staleTime: 30 * 1000,
185
+ retry: false,
186
+ networkMode: "offlineFirst",
187
+ select: (data) => data.pages.flatMap((p) => p.data),
188
+ });
189
+ const data = useMemo(() => mergePendingOrders(pendingOrders, query.data), [pendingOrders, query.data]);
190
+ return { ...query, data };
191
+ }
192
+ // ─── Pending orders hook (for screens that just need the overlay) ─────────
193
+ export function usePendingOrders(businessId) {
194
+ return useCorePendingOrders(businessId);
195
+ }
196
+ // ─── Create POS Order (device-lease aware, via SyncEngine) ────────────────
197
+ export function useCreatePOSOrderMutation() {
198
+ const businessId = useBusinessId();
199
+ const queryClient = useQueryClient();
200
+ return useMutation({
201
+ mutationFn: async (data) => {
202
+ const { isOnline, isCloudReachable } = getConnectivityState();
203
+ if (!businessId)
204
+ throw new Error("Missing business — select a business before creating orders");
205
+ try {
206
+ const m = await import("../sync/device-lease.service");
207
+ const dn = m.getDeviceNumber?.(businessId) ?? null;
208
+ if (!dn) {
209
+ const leased = await m.ensureDeviceLease?.(businessId);
210
+ if (!leased && (!isOnline || isCloudReachable === "unreachable")) {
211
+ throw new Error("No device number leased (99 devices max) — cannot create offline orders");
212
+ }
213
+ }
214
+ }
215
+ catch (e) {
216
+ if (e?.message?.includes("device number") || e?.message?.includes("No device lease"))
217
+ throw e;
218
+ }
219
+ return posRepository.createOrder(businessId, data);
220
+ },
221
+ onSuccess: (result) => {
222
+ const r = result;
223
+ const orderNumber = r?.order_number ?? "";
224
+ const queued = orderNumber ? isLocalOrderNumber(orderNumber) && r?.status === "pending" : false;
225
+ queryClient.invalidateQueries({ queryKey: POS_KEYS.orders(businessId) });
226
+ queryClient.invalidateQueries({ queryKey: SELLER_KEYS.orders(businessId) });
227
+ queryClient.invalidateQueries({ queryKey: SELLER_KEYS.all });
228
+ // Toast is host concern — core just invalidates; host can show queued vs created.
229
+ void queued;
230
+ },
231
+ });
232
+ }
233
+ // ─── Preview (offline via tax-rules reference) ────────────────────────────
234
+ function cartHash(items) {
235
+ if (items.length === 0)
236
+ return "";
237
+ return items
238
+ .map((i) => `${i.menu_item.id}:${i.quantity}:${i.selected_variant?.id || ""}:${(i.selected_addons || []).map((a) => a.addon_id).sort().join(",")}`)
239
+ .join("|");
240
+ }
241
+ export function usePOSOrderPreviewQuery(items, orderType, customerName) {
242
+ const businessId = useBusinessId();
243
+ const hash = useMemo(() => cartHash(items), [items]);
244
+ return useQuery({
245
+ queryKey: [...POS_KEYS.preview(businessId), hash, orderType, customerName],
246
+ queryFn: async () => {
247
+ const orderItems = items.map((i) => ({
248
+ menu_item_id: i.menu_item.id,
249
+ quantity: i.quantity,
250
+ variant_id: i.selected_variant?.id,
251
+ addon_ids: i.selected_addons.length > 0 ? i.selected_addons.map((a) => a.addon_id) : undefined,
252
+ }));
253
+ try {
254
+ return await posRepository.fetchPreview({ businessId: businessId, items: orderItems, orderType, customerName: customerName || undefined });
255
+ }
256
+ catch (e) {
257
+ const s = getConnectivityState();
258
+ const isOffline = !s.isOnline || s.isCloudReachable === "unreachable";
259
+ if (!isOffline)
260
+ throw e;
261
+ const { openSyncDatabase } = await import("../db/connection");
262
+ const { getReferenceData } = await import("../reference/operations");
263
+ const { calculatePricing } = await import("../pricing/engine");
264
+ const db = await openSyncDatabase();
265
+ const taxRec = await getReferenceData(db, businessId, "tax-rules");
266
+ const taxRules = taxRec?.payload?.data ?? [];
267
+ const pricing = calculatePricing({ items: items, taxRules: taxRules });
268
+ return { data: { subtotal_minor: pricing.subtotal_minor, discount_minor: pricing.discount_minor, delivery_fee_minor: pricing.delivery_fee_minor, tax_minor: pricing.tax_minor, tip_minor: pricing.tip_minor, total_minor: pricing.total_minor, tax_lines: pricing.tax_lines, currency: pricing.currency_code } };
269
+ }
270
+ },
271
+ enabled: !!businessId && items.length > 0,
272
+ staleTime: 30 * 1000,
273
+ });
274
+ }
275
+ // ─── Order status mutations via SyncEngine (all go through outbox) ────────
276
+ function makeOrderActionMutation(build) {
277
+ return function useOrderActionMutation() {
278
+ const businessId = useBusinessId();
279
+ const queryClient = useQueryClient();
280
+ return useMutation({
281
+ mutationFn: (orderId) => {
282
+ if (!businessId)
283
+ throw new Error("No active business");
284
+ return build({ orderId, businessId });
285
+ },
286
+ onSettled: (_d, _e, orderId) => {
287
+ queryClient.invalidateQueries({ queryKey: SELLER_KEYS.orders() });
288
+ queryClient.invalidateQueries({ queryKey: POS_KEYS.orders(businessId) });
289
+ if (orderId)
290
+ queryClient.invalidateQueries({ queryKey: SELLER_KEYS.order(orderId) });
291
+ },
292
+ });
293
+ };
294
+ }
295
+ export const useAcceptOrderMutation = makeOrderActionMutation(({ orderId, businessId }) => orderRepository.acceptOrder({ orderId, businessId }));
296
+ export const useRejectOrderMutation = makeOrderActionMutation(({ orderId, businessId }) => orderRepository.rejectOrder({ orderId, businessId }));
297
+ export const useStartPreparingOrderMutation = makeOrderActionMutation(({ orderId, businessId }) => orderRepository.startPreparingOrder({ orderId, businessId }));
298
+ export const useMarkOrderReadyMutation = makeOrderActionMutation(({ orderId, businessId }) => orderRepository.markOrderReady({ orderId, businessId }));
299
+ export const useCompletePickupMutation = makeOrderActionMutation(({ orderId, businessId }) => orderRepository.completePickup({ orderId, businessId }));
300
+ export const useCancelOrderMutation = makeOrderActionMutation(({ orderId, businessId }) => orderRepository.cancelOrder({ orderId, businessId }));
package/dist/index.d.ts CHANGED
@@ -52,6 +52,7 @@ export { orderRepository } from "./repositories/order.repository";
52
52
  export type { CreateOrderInput, AssignRiderInput, OrderActionInput } from "./repositories/order.repository";
53
53
  export { posRepository } from "./repositories/pos.repository";
54
54
  export type { POSOrderPreviewInput } from "./repositories/pos.repository";
55
+ export * from "./hooks/orders.hooks";
55
56
  export * from "./reference/types";
56
57
  export * from "./reference/operations";
57
58
  export { fetchReferenceVersions, fetchReferenceData, fetchReferenceChanges, setReferenceLocalNodeFetcher } from "./reference/api";
@@ -69,6 +70,9 @@ export * from "./adapters/local-node";
69
70
  export * from "./adapters/services";
70
71
  export * from "./adapters/devices";
71
72
  export * from "./adapters/image-cache";
73
+ export * from "./adapters/business";
74
+ export * from "./adapters/pending-orders";
75
+ export { KIOSK_KEYS, SELLER_ORDERS_KEYS, BUSINESS_DELIVERIES_KEYS, MENU_KEYS, REFERENCE_KEYS } from "./adapters/query-keys";
72
76
  import { type SqliteDriver } from "./adapters/sqlite";
73
77
  import { type KVDriver } from "./adapters/kv";
74
78
  import { type HttpClient } from "./adapters/http";
package/dist/index.js CHANGED
@@ -53,6 +53,8 @@ export { runOfflineCapableMutation } from "./sync/offline-capable";
53
53
  // ── Repositories ──
54
54
  export { orderRepository } from "./repositories/order.repository";
55
55
  export { posRepository } from "./repositories/pos.repository";
56
+ // ── Order lifecycle hooks (offline-first, device-owned outbox) ──
57
+ export * from "./hooks/orders.hooks";
56
58
  // ── Reference ──
57
59
  export * from "./reference/types";
58
60
  export * from "./reference/operations";
@@ -71,6 +73,10 @@ export * from "./adapters/local-node";
71
73
  export * from "./adapters/services";
72
74
  export * from "./adapters/devices";
73
75
  export * from "./adapters/image-cache";
76
+ export * from "./adapters/business";
77
+ export * from "./adapters/pending-orders";
78
+ export { KIOSK_KEYS, SELLER_ORDERS_KEYS, BUSINESS_DELIVERIES_KEYS, MENU_KEYS, REFERENCE_KEYS } from "./adapters/query-keys";
79
+ // POS_KEYS / SELLER_KEYS are exported from hooks/orders.hooks (offline-first source of truth)
74
80
  // ── Init helper ──
75
81
  import { setSqliteDriver } from "./adapters/sqlite";
76
82
  import { setKVDriver } from "./adapters/kv";
@@ -9,6 +9,8 @@ export declare function filterOrdersOffline(orders: SellerOrder[], filters?: {
9
9
  is_pos?: string;
10
10
  q?: string;
11
11
  search?: string;
12
+ date_from?: string;
13
+ date_to?: string;
12
14
  }): SellerOrder[];
13
15
  export declare function filterSellerOrdersOffline(orders: SellerOrder[], filters?: {
14
16
  status?: string;
@@ -19,6 +19,14 @@ export function filterOrdersOffline(orders, filters) {
19
19
  filtered = filtered.filter((o) => o.order_number.toLowerCase().includes(q) ||
20
20
  matchesCustomerSearch(o, q));
21
21
  }
22
+ if (filters?.date_from) {
23
+ const from = filters.date_from.slice(0, 10);
24
+ filtered = filtered.filter((o) => (o.inserted_at ?? "").slice(0, 10) >= from);
25
+ }
26
+ if (filters?.date_to) {
27
+ const to = filters.date_to.slice(0, 10);
28
+ filtered = filtered.filter((o) => (o.inserted_at ?? "").slice(0, 10) <= to);
29
+ }
22
30
  return filtered;
23
31
  }
24
32
  export function filterSellerOrdersOffline(orders, filters) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teincfood/core",
3
- "version": "0.7.4",
3
+ "version": "0.7.5",
4
4
  "description": "TeincFood shared offline-first core — types, sync engine, local DB, reference data, and repositories for mobile + desktop",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",