@teincfood/core 0.9.25 → 0.9.26
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/catalog/hooks.js +11 -2
- package/dist/domain/orders/service.d.ts +2 -1
- package/dist/domain/orders/service.js +2 -1
- package/dist/hooks/startup-readiness.hooks.js +20 -2
- package/dist/sync/device-lease.service.js +3 -1
- package/dist/sync/startup-readiness.js +29 -16
- package/package.json +1 -1
package/dist/catalog/hooks.js
CHANGED
|
@@ -73,9 +73,14 @@ export function useMenuItemsQuery(filters, enabled = true) {
|
|
|
73
73
|
? networkQuery.data.pages.flatMap((p) => p.data)
|
|
74
74
|
: undefined;
|
|
75
75
|
const data = isOnline && networkData !== undefined ? networkData : enabled ? filtered : undefined;
|
|
76
|
+
// A local snapshot (even an empty list) means the POS has something to
|
|
77
|
+
// render: never gate the whole screen on the in-flight network refresh.
|
|
78
|
+
const hasLocalSnapshot = cachedQuery.data != null;
|
|
79
|
+
const pending = cachedQuery.isPending || (!hasLocalSnapshot && networkQuery.isFetching);
|
|
76
80
|
return {
|
|
77
81
|
data,
|
|
78
|
-
isPending:
|
|
82
|
+
isPending: pending,
|
|
83
|
+
isLoading: pending,
|
|
79
84
|
isFetching: networkQuery.isFetching,
|
|
80
85
|
isError: cachedQuery.isError || networkQuery.isError,
|
|
81
86
|
error: (cachedQuery.error ?? networkQuery.error),
|
|
@@ -117,10 +122,14 @@ export function useMenuCategoriesQuery() {
|
|
|
117
122
|
const usingNetwork = isOnline && networkQuery.data;
|
|
118
123
|
const data = usingNetwork ? networkQuery.data : cachedQuery.data;
|
|
119
124
|
const hasCache = cachedQuery.data != null;
|
|
125
|
+
// Only report "loading" when there is nothing local to show; the network
|
|
126
|
+
// refresh must never hide an already-cached category list.
|
|
127
|
+
const pending = cachedQuery.isPending || (!hasCache && networkQuery.isFetching);
|
|
120
128
|
return {
|
|
121
129
|
...networkQuery,
|
|
122
130
|
data,
|
|
123
|
-
isPending:
|
|
131
|
+
isPending: pending,
|
|
132
|
+
isLoading: pending,
|
|
124
133
|
isError: hasCache ? false : networkQuery.isError,
|
|
125
134
|
error: hasCache ? null : networkQuery.error,
|
|
126
135
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Local Orders Service — SQLite is real DB (like backend Orders).
|
|
3
3
|
* Device-partitioned by UUID PK + DD-RRR-CCC per business.
|
|
4
|
-
*
|
|
4
|
+
* POS orders start in the business's configured flow (staged → accepted,
|
|
5
|
+
* direct → delivered); online payment requires connectivity.
|
|
5
6
|
*/
|
|
6
7
|
import type { SellerOrder } from "../../types/api/seller.api";
|
|
7
8
|
import type { POSOrderCreateRequest } from "../../types/pos.types";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Local Orders Service — SQLite is real DB (like backend Orders).
|
|
3
3
|
* Device-partitioned by UUID PK + DD-RRR-CCC per business.
|
|
4
|
-
*
|
|
4
|
+
* POS orders start in the business's configured flow (staged → accepted,
|
|
5
|
+
* direct → delivered); online payment requires connectivity.
|
|
5
6
|
*/
|
|
6
7
|
import { openSyncDatabase } from "../../db/connection";
|
|
7
8
|
import { insertOrder, getOrderById, listOrders, updateOrderStatus, updateOrderPayment, getEntitySnapshotsByType } from "../../db/operations";
|
|
@@ -25,20 +25,38 @@ export function useStartupReadiness(options = {}) {
|
|
|
25
25
|
const [readiness, setReadiness] = useState(null);
|
|
26
26
|
const [checking, setChecking] = useState(false);
|
|
27
27
|
const runRef = useRef(false);
|
|
28
|
+
// Identifies the latest readiness run so a slow catch-up for a previous
|
|
29
|
+
// business can never overwrite the verdict for the active one.
|
|
30
|
+
const tokenRef = useRef(0);
|
|
31
|
+
const businessRef = useRef(businessId);
|
|
32
|
+
// Switching business invalidates the previous verdict — its cached reference
|
|
33
|
+
// keys and lease belong to the old business.
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (businessRef.current === businessId)
|
|
36
|
+
return;
|
|
37
|
+
businessRef.current = businessId;
|
|
38
|
+
tokenRef.current += 1;
|
|
39
|
+
runRef.current = false;
|
|
40
|
+
setReadiness(null);
|
|
41
|
+
setChecking(false);
|
|
42
|
+
}, [businessId]);
|
|
28
43
|
const check = useCallback(async () => {
|
|
29
44
|
if (!businessId)
|
|
30
45
|
return;
|
|
31
46
|
if (runRef.current)
|
|
32
47
|
return;
|
|
33
48
|
runRef.current = true;
|
|
49
|
+
const token = ++tokenRef.current;
|
|
34
50
|
setChecking(true);
|
|
35
51
|
try {
|
|
36
52
|
const result = await ensureStartupReady(businessId, { timeoutMs });
|
|
37
|
-
|
|
53
|
+
if (tokenRef.current === token)
|
|
54
|
+
setReadiness(result);
|
|
38
55
|
}
|
|
39
56
|
finally {
|
|
40
57
|
runRef.current = false;
|
|
41
|
-
|
|
58
|
+
if (tokenRef.current === token)
|
|
59
|
+
setChecking(false);
|
|
42
60
|
}
|
|
43
61
|
}, [businessId, timeoutMs]);
|
|
44
62
|
// First check when a business is available.
|
|
@@ -28,8 +28,10 @@ export async function ensureLease(businessId) {
|
|
|
28
28
|
try {
|
|
29
29
|
const leasedAt = getSync(`${LEASE_KEY(businessId)}:leased_at`);
|
|
30
30
|
const needsRefresh = !leasedAt || Date.now() - new Date(leasedAt).getTime() > LEASE_TTL_MS - 24 * 60 * 60 * 1000;
|
|
31
|
+
// Never make readiness wait on the refresh — a slow network must not
|
|
32
|
+
// hold POS startup when a valid cached lease already exists.
|
|
31
33
|
if (needsRefresh)
|
|
32
|
-
|
|
34
|
+
void refreshLease(businessId).catch(() => { });
|
|
33
35
|
}
|
|
34
36
|
catch {
|
|
35
37
|
// keep existing on any refresh failure
|
|
@@ -77,25 +77,38 @@ export async function getStartupReadiness(businessId) {
|
|
|
77
77
|
export async function ensureStartupReady(businessId, opts) {
|
|
78
78
|
const timeoutMs = opts?.timeoutMs ?? 15000;
|
|
79
79
|
const { isOnline, isCloudReachable } = getConnectivityState();
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
catch (e) {
|
|
92
|
-
syncLogger.warn("StartupReadiness", "lease catch-up failed", { err: String(e) });
|
|
93
|
-
}
|
|
94
|
-
})();
|
|
95
|
-
await raceWithTimeout(catchUp, timeoutMs);
|
|
80
|
+
const canUseNetwork = isOnline && isCloudReachable !== "unreachable";
|
|
81
|
+
// Evaluate the device first. If everything the POS needs is already cached,
|
|
82
|
+
// the terminal must open immediately — a slow network must never hold the
|
|
83
|
+
// seller on "Preparing offline data…". The catch-up then runs in the
|
|
84
|
+
// background and silently updates the cache.
|
|
85
|
+
const local = await getStartupReadiness(businessId);
|
|
86
|
+
if (local.ready) {
|
|
87
|
+
if (canUseNetwork)
|
|
88
|
+
void runCatchUp(businessId);
|
|
89
|
+
return local;
|
|
96
90
|
}
|
|
91
|
+
// Incomplete cache: one bounded network attempt may complete it. This is the
|
|
92
|
+
// only case where waiting is useful (e.g. first launch on a new device).
|
|
93
|
+
if (canUseNetwork)
|
|
94
|
+
await raceWithTimeout(runCatchUp(businessId), timeoutMs);
|
|
97
95
|
return getStartupReadiness(businessId);
|
|
98
96
|
}
|
|
97
|
+
/** Best-effort catch-up: reference data + device lease. Never throws. */
|
|
98
|
+
async function runCatchUp(businessId) {
|
|
99
|
+
try {
|
|
100
|
+
await referenceSyncService.sync({ businessId, keys: [...REQUIRED_ORDER_KEYS] });
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
syncLogger.warn("StartupReadiness", "reference catch-up failed", { err: String(e) });
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
await ensureLease(businessId);
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
syncLogger.warn("StartupReadiness", "lease catch-up failed", { err: String(e) });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
99
112
|
/** Resolve after either the promise settles or the timeout elapses. */
|
|
100
113
|
async function raceWithTimeout(promise, ms) {
|
|
101
114
|
let timer;
|
package/package.json
CHANGED