@teincfood/core 0.10.22 → 0.10.24
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/domain/orders/service.d.ts +1 -1
- package/dist/domain/orders/service.js +1 -1
- package/dist/local-node/adapters.d.ts +2 -0
- package/dist/local-node/service.d.ts +5 -0
- package/dist/local-node/service.js +24 -0
- package/dist/printer/receipt.js +1 -1
- package/dist/printer/service.d.ts +1 -1
- package/dist/printer/service.js +2 -2
- package/dist/repositories/order.repository.d.ts +1 -1
- package/dist/repositories/order.repository.js +1 -1
- package/dist/sync/command-queue.service.js +1 -1
- package/dist/sync/offline-capable.js +28 -6
- package/dist/sync/startup-readiness.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Local Orders Service — SQLite is real DB (like backend Orders).
|
|
3
|
-
* Device-partitioned by UUID PK + DD-
|
|
3
|
+
* Device-partitioned by UUID PK + DD-NNNN-MMM per business.
|
|
4
4
|
* POS orders start in the business's configured flow (staged → accepted,
|
|
5
5
|
* direct → delivered); online payment requires connectivity.
|
|
6
6
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Local Orders Service — SQLite is real DB (like backend Orders).
|
|
3
|
-
* Device-partitioned by UUID PK + DD-
|
|
3
|
+
* Device-partitioned by UUID PK + DD-NNNN-MMM per business.
|
|
4
4
|
* POS orders start in the business's configured flow (staged → accepted,
|
|
5
5
|
* direct → delivered); online payment requires connectivity.
|
|
6
6
|
*/
|
|
@@ -43,6 +43,8 @@ export interface LocalNodeClient {
|
|
|
43
43
|
contentType?: string | null;
|
|
44
44
|
error?: string | null;
|
|
45
45
|
}>;
|
|
46
|
+
/** Mirror locally cached image bytes to a connected node. */
|
|
47
|
+
putAsset?(businessId: string, url: string, base64: string): Promise<void>;
|
|
46
48
|
publishEvent?(event: DomainEvent): Promise<void> | void;
|
|
47
49
|
}
|
|
48
50
|
export interface TransportAdapter {
|
|
@@ -24,6 +24,11 @@ declare class LocalNodeServiceCore {
|
|
|
24
24
|
init(deps: LocalNodeDeps): void;
|
|
25
25
|
private requestReferenceFromPeers;
|
|
26
26
|
private requestAssetFromPeers;
|
|
27
|
+
/**
|
|
28
|
+
* Push locally cached image bytes to every connected node so terminals that
|
|
29
|
+
* cannot reach S3 (dark LAN) can fetch the same offline image.
|
|
30
|
+
*/
|
|
31
|
+
putAsset(businessId: string, url: string, base64: string): Promise<boolean>;
|
|
27
32
|
/**
|
|
28
33
|
* Catch a newly connected peer up on orders created while the mesh was not
|
|
29
34
|
* connected (e.g. a sale rung up before Local Node was enabled).
|
|
@@ -101,6 +101,30 @@ class LocalNodeServiceCore {
|
|
|
101
101
|
}
|
|
102
102
|
return null;
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Push locally cached image bytes to every connected node so terminals that
|
|
106
|
+
* cannot reach S3 (dark LAN) can fetch the same offline image.
|
|
107
|
+
*/
|
|
108
|
+
async putAsset(businessId, url, base64) {
|
|
109
|
+
if (!businessId || !url || !base64)
|
|
110
|
+
return false;
|
|
111
|
+
let sent = false;
|
|
112
|
+
for (const peer of this.peers.values()) {
|
|
113
|
+
if (!peer.isConnected() || !peer.putAsset)
|
|
114
|
+
continue;
|
|
115
|
+
try {
|
|
116
|
+
await peer.putAsset(businessId, url, base64);
|
|
117
|
+
sent = true;
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
syncLogger.debug("LocalNodeService", "asset put failed", {
|
|
121
|
+
url,
|
|
122
|
+
error: String(error),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return sent;
|
|
127
|
+
}
|
|
104
128
|
async publishOrderCatchup(peerDeviceId) {
|
|
105
129
|
if (!this.businessId)
|
|
106
130
|
return;
|
package/dist/printer/receipt.js
CHANGED
|
@@ -181,7 +181,7 @@ export function calloutNumber(order) {
|
|
|
181
181
|
const num = order.order_number || "";
|
|
182
182
|
if (!num)
|
|
183
183
|
return "----";
|
|
184
|
-
// Strip non-digits first: local DD-
|
|
184
|
+
// Strip non-digits first: local DD-NNNN-MMM numbers end in "-MMM" and
|
|
185
185
|
// parseInt("-005") would give -5 (the old "-5" bug).
|
|
186
186
|
const digits = num.slice(-4).replace(/\D/g, "");
|
|
187
187
|
if (!digits)
|
|
@@ -7,7 +7,7 @@ import { generateEscpos, generateHtml } from "./receipt";
|
|
|
7
7
|
import type { SellerOrder } from "../types/api/seller.api";
|
|
8
8
|
declare function fetchReceipt(orderId: string, format?: "html" | "escpos"): Promise<string>;
|
|
9
9
|
/**
|
|
10
|
-
* Generate receipt locally without network — used for offline queued DD-
|
|
10
|
+
* Generate receipt locally without network — used for offline queued DD-NNNN-MMM orders.
|
|
11
11
|
* Pass a base64 logo raster (see resolveLogoRaster) to include the thermal header logo.
|
|
12
12
|
*/
|
|
13
13
|
export declare function generateLocalReceipt(order: SellerOrder, format?: "html" | "escpos", logoDataUri?: string | null, logoRasterB64?: string | null): string;
|
package/dist/printer/service.js
CHANGED
|
@@ -31,7 +31,7 @@ async function fetchReceipt(orderId, format = "html") {
|
|
|
31
31
|
return resp.text();
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
* Generate receipt locally without network — used for offline queued DD-
|
|
34
|
+
* Generate receipt locally without network — used for offline queued DD-NNNN-MMM orders.
|
|
35
35
|
* Pass a base64 logo raster (see resolveLogoRaster) to include the thermal header logo.
|
|
36
36
|
*/
|
|
37
37
|
export function generateLocalReceipt(order, format = "escpos", logoDataUri, logoRasterB64) {
|
|
@@ -62,7 +62,7 @@ export async function printOrderReceipt(order, opts) {
|
|
|
62
62
|
let html = null;
|
|
63
63
|
if (isTcp) {
|
|
64
64
|
try {
|
|
65
|
-
// Prefer backend escpos when we have a real UUID (not
|
|
65
|
+
// Prefer backend escpos when we have a real UUID (not a local order number)
|
|
66
66
|
const isLocal = /^[0-9]{2}-[0-9]{3,4}-[0-9]{3}$/.test(order.order_number) || /^[0-9A-F-]{36}$/.test(order.id) === false && order.id === order.order_number;
|
|
67
67
|
if (!isLocal || order.id) {
|
|
68
68
|
try {
|
|
@@ -28,7 +28,7 @@ export declare const orderRepository: {
|
|
|
28
28
|
* Single create path: build an `order:create` command and route it through
|
|
29
29
|
* the SyncEngine (cloud → LAN node → queue). Every path returns an
|
|
30
30
|
* order-shaped payload: cloud/LAN execute the real create, queue returns
|
|
31
|
-
* the optimistic order (same UUID + DD-
|
|
31
|
+
* the optimistic order (same UUID + DD-NNNN-MMM, replayed idempotently).
|
|
32
32
|
* Direct `createLocalOrder` bypasses sync — never call it from UI flows.
|
|
33
33
|
*/
|
|
34
34
|
createOrder(input: CreateOrderInput): Promise<SellerOrder>;
|
|
@@ -42,7 +42,7 @@ export const orderRepository = {
|
|
|
42
42
|
* Single create path: build an `order:create` command and route it through
|
|
43
43
|
* the SyncEngine (cloud → LAN node → queue). Every path returns an
|
|
44
44
|
* order-shaped payload: cloud/LAN execute the real create, queue returns
|
|
45
|
-
* the optimistic order (same UUID + DD-
|
|
45
|
+
* the optimistic order (same UUID + DD-NNNN-MMM, replayed idempotently).
|
|
46
46
|
* Direct `createLocalOrder` bypasses sync — never call it from UI flows.
|
|
47
47
|
*/
|
|
48
48
|
async createOrder(input) {
|
|
@@ -194,7 +194,7 @@ function injectLocalOrderNumber(command) {
|
|
|
194
194
|
const payload = command.payload;
|
|
195
195
|
if (payload.order_number)
|
|
196
196
|
return command;
|
|
197
|
-
//
|
|
197
|
+
// Business-scoped DD-NNNN-MMM; requires a leased device_number
|
|
198
198
|
const orderNumber = generateOrderNumberForBusiness(command.business_id);
|
|
199
199
|
return {
|
|
200
200
|
...command,
|
|
@@ -19,6 +19,10 @@ import { getConnectivityState } from "../adapters/connectivity";
|
|
|
19
19
|
import { CAPABILITY_BY_COMMAND } from "../reference/capabilities";
|
|
20
20
|
import { openSyncDatabase } from "../db/connection";
|
|
21
21
|
import { getReferenceData } from "../reference/operations";
|
|
22
|
+
import { commandQueueService } from "./command-queue.service";
|
|
23
|
+
import { applyMenuCommandLocal } from "../reference/menu-apply";
|
|
24
|
+
import { emitReferenceChanged } from "../reference/events";
|
|
25
|
+
import { isOnlineCapable } from "./online-gates";
|
|
22
26
|
export async function runOfflineCapableMutation(options) {
|
|
23
27
|
// Per-command capability guard before even attempting direct call
|
|
24
28
|
const required = CAPABILITY_BY_COMMAND[options.commandType];
|
|
@@ -26,10 +30,10 @@ export async function runOfflineCapableMutation(options) {
|
|
|
26
30
|
// Online-required gate first: these commands can never succeed offline —
|
|
27
31
|
// throw loud immediately instead of queuing what no transport can fulfill.
|
|
28
32
|
assertOnlineFor(options.commandType, options.payload);
|
|
29
|
-
// Local-first for engine-routed domains (menu:*):
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
+
// Local-first for engine-routed domains (menu:*): persist to SQLite and the
|
|
34
|
+
// durable outbox immediately, update the local reference cache, and let
|
|
35
|
+
// replay upload to the cloud/LAN in the background. A slow or absent
|
|
36
|
+
// network must never hold the form open.
|
|
33
37
|
if (options.commandType.startsWith("menu:")) {
|
|
34
38
|
const command = buildCommand({
|
|
35
39
|
commandType: options.commandType,
|
|
@@ -37,8 +41,26 @@ export async function runOfflineCapableMutation(options) {
|
|
|
37
41
|
entityId: options.entityId,
|
|
38
42
|
payload: options.payload,
|
|
39
43
|
});
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
try {
|
|
45
|
+
const { keys } = await applyMenuCommandLocal(options.businessId, command);
|
|
46
|
+
if (keys.length)
|
|
47
|
+
emitReferenceChanged(options.businessId, keys);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
syncLogger.debug("OfflineCapable", "menu local apply failed", {
|
|
51
|
+
error: String(error),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const event = await commandQueueService.enqueue(command);
|
|
55
|
+
if (isOnlineCapable()) {
|
|
56
|
+
// Background only: save must return now.
|
|
57
|
+
void syncEngine.replayQueuedCommands().catch((error) => {
|
|
58
|
+
syncLogger.debug("OfflineCapable", "menu background replay failed", {
|
|
59
|
+
error: String(error),
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return event.payload;
|
|
42
64
|
}
|
|
43
65
|
try {
|
|
44
66
|
const db = await openSyncDatabase();
|
|
@@ -25,7 +25,7 @@ export interface StartupReadiness {
|
|
|
25
25
|
ready: boolean;
|
|
26
26
|
/** Required keys with no cached payload. */
|
|
27
27
|
missingKeys: ReferenceDataKey[];
|
|
28
|
-
/** True when no DD-
|
|
28
|
+
/** True when no DD-NNNN-MMM device number is available (needs one online lease). */
|
|
29
29
|
needsLease: boolean;
|
|
30
30
|
/**
|
|
31
31
|
* True when an entitlement envelope is absent or does not evaluate as
|
package/package.json
CHANGED