@aglyn/plugins-commerce 1.0.0-beta.232 → 1.0.0-beta.233

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.
Files changed (228) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/cart-extras.d.ts +6 -0
  3. package/src/lib/components/cart-extras.js +4 -1
  4. package/src/lib/components/cart-extras.js.map +1 -1
  5. package/src/lib/components/cart-fulfillment.d.ts +117 -0
  6. package/src/lib/components/cart-fulfillment.js +367 -0
  7. package/src/lib/components/cart-fulfillment.js.map +1 -0
  8. package/src/lib/components/cart.js +52 -10
  9. package/src/lib/components/cart.js.map +1 -1
  10. package/src/lib/components/commerce-console-page.js +42 -1
  11. package/src/lib/components/commerce-console-page.js.map +1 -1
  12. package/src/lib/components/console/host-orders-card.component.js +38 -10
  13. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  14. package/src/lib/components/console/local-delivery-settings-card.component.d.ts +37 -0
  15. package/src/lib/components/console/local-delivery-settings-card.component.js +542 -0
  16. package/src/lib/components/console/local-delivery-settings-card.component.js.map +1 -0
  17. package/src/lib/components/console/locations-card.component.js +177 -3
  18. package/src/lib/components/console/locations-card.component.js.map +1 -1
  19. package/src/lib/components/console/order-detail-dialog.component.js +10 -0
  20. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  21. package/src/lib/components/console/order-local-fulfillment-panel.component.d.ts +44 -0
  22. package/src/lib/components/console/order-local-fulfillment-panel.component.js +265 -0
  23. package/src/lib/components/console/order-local-fulfillment-panel.component.js.map +1 -0
  24. package/src/lib/components/console/order-offline-notice.component.d.ts +12 -0
  25. package/src/lib/components/console/order-offline-notice.component.js +53 -0
  26. package/src/lib/components/console/order-offline-notice.component.js.map +1 -0
  27. package/src/lib/components/console/pickup-delivery-queue-card.component.d.ts +35 -0
  28. package/src/lib/components/console/pickup-delivery-queue-card.component.js +222 -0
  29. package/src/lib/components/console/pickup-delivery-queue-card.component.js.map +1 -0
  30. package/src/lib/components/console/pos/pos-api.d.ts +24 -1
  31. package/src/lib/components/console/pos/pos-api.js +13 -0
  32. package/src/lib/components/console/pos/pos-api.js.map +1 -1
  33. package/src/lib/components/console/pos/pos-item-dialog.component.d.ts +12 -0
  34. package/src/lib/components/console/pos/pos-item-dialog.component.js +10 -8
  35. package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -1
  36. package/src/lib/components/console/pos/pos-kiosk-queue.component.d.ts +22 -0
  37. package/src/lib/components/console/pos/pos-kiosk-queue.component.js +168 -0
  38. package/src/lib/components/console/pos/pos-kiosk-queue.component.js.map +1 -0
  39. package/src/lib/components/console/pos/pos-tender-dialogs.component.d.ts +18 -0
  40. package/src/lib/components/console/pos/pos-tender-dialogs.component.js +116 -1
  41. package/src/lib/components/console/pos/pos-tender-dialogs.component.js.map +1 -1
  42. package/src/lib/components/console/pos/pos-tender-panel.component.d.ts +8 -0
  43. package/src/lib/components/console/pos/pos-tender-panel.component.js +77 -18
  44. package/src/lib/components/console/pos/pos-tender-panel.component.js.map +1 -1
  45. package/src/lib/components/console/pos/use-pos-display.d.ts +5 -2
  46. package/src/lib/components/console/pos/use-pos-display.js +3 -2
  47. package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
  48. package/src/lib/components/console/pos-devices-card.component.js +2 -2
  49. package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
  50. package/src/lib/components/console/pos-display/pairing-screen.d.ts +5 -2
  51. package/src/lib/components/console/pos-display/pairing-screen.js +7 -5
  52. package/src/lib/components/console/pos-display/pairing-screen.js.map +1 -1
  53. package/src/lib/components/console/pos-display/pos-display-api.d.ts +7 -1
  54. package/src/lib/components/console/pos-display/pos-display-api.js +7 -2
  55. package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -1
  56. package/src/lib/components/console/pos-kiosk/kiosk-checkout.d.ts +41 -0
  57. package/src/lib/components/console/pos-kiosk/kiosk-checkout.js +275 -0
  58. package/src/lib/components/console/pos-kiosk/kiosk-checkout.js.map +1 -0
  59. package/src/lib/components/console/pos-kiosk/kiosk-menu.d.ts +32 -0
  60. package/src/lib/components/console/pos-kiosk/kiosk-menu.js +344 -0
  61. package/src/lib/components/console/pos-kiosk/kiosk-menu.js.map +1 -0
  62. package/src/lib/components/console/pos-kiosk/kiosk-staff.d.ts +12 -0
  63. package/src/lib/components/console/pos-kiosk/kiosk-staff.js +257 -0
  64. package/src/lib/components/console/pos-kiosk/kiosk-staff.js.map +1 -0
  65. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.d.ts +114 -0
  66. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js +189 -0
  67. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js.map +1 -0
  68. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.d.ts +16 -0
  69. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js +80 -0
  70. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js.map +1 -0
  71. package/src/lib/components/console/pos-kiosk-page.component.d.ts +28 -0
  72. package/src/lib/components/console/pos-kiosk-page.component.js +791 -0
  73. package/src/lib/components/console/pos-kiosk-page.component.js.map +1 -0
  74. package/src/lib/components/console/pos-offline/pos-offline-api.d.ts +44 -0
  75. package/src/lib/components/console/pos-offline/pos-offline-api.js +69 -0
  76. package/src/lib/components/console/pos-offline/pos-offline-api.js.map +1 -0
  77. package/src/lib/components/console/pos-offline/pos-offline-banner.component.d.ts +7 -0
  78. package/src/lib/components/console/pos-offline/pos-offline-banner.component.js +135 -0
  79. package/src/lib/components/console/pos-offline/pos-offline-banner.component.js.map +1 -0
  80. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.d.ts +32 -0
  81. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js +287 -0
  82. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js.map +1 -0
  83. package/src/lib/components/console/pos-offline/pos-offline-store.d.ts +63 -0
  84. package/src/lib/components/console/pos-offline/pos-offline-store.js +145 -0
  85. package/src/lib/components/console/pos-offline/pos-offline-store.js.map +1 -0
  86. package/src/lib/components/console/pos-offline/register-offline.d.ts +24 -0
  87. package/src/lib/components/console/pos-offline/register-offline.js +24 -0
  88. package/src/lib/components/console/pos-offline/register-offline.js.map +1 -0
  89. package/src/lib/components/console/pos-offline/use-pos-offline.d.ts +85 -0
  90. package/src/lib/components/console/pos-offline/use-pos-offline.js +542 -0
  91. package/src/lib/components/console/pos-offline/use-pos-offline.js.map +1 -0
  92. package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js +72 -17
  93. package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js.map +1 -1
  94. package/src/lib/components/console/pos-page.component.js +174 -27
  95. package/src/lib/components/console/pos-page.component.js.map +1 -1
  96. package/src/lib/components/console/products-hub-card.component.js +35 -10
  97. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  98. package/src/lib/components/console/shipping-settings-card.component.js +1 -1
  99. package/src/lib/components/console/shipping-settings-card.component.js.map +1 -1
  100. package/src/lib/components/console/stock-movements-card.component.js +48 -16
  101. package/src/lib/components/console/stock-movements-card.component.js.map +1 -1
  102. package/src/lib/components/console/store-zones.d.ts +8 -1
  103. package/src/lib/components/console/store-zones.js +11 -1
  104. package/src/lib/components/console/store-zones.js.map +1 -1
  105. package/src/lib/components/order-status.js +66 -0
  106. package/src/lib/components/order-status.js.map +1 -1
  107. package/src/lib/components/product-detail.js +42 -15
  108. package/src/lib/components/product-detail.js.map +1 -1
  109. package/src/lib/constants/local-fulfillment-list-query.d.ts +48 -0
  110. package/src/lib/constants/local-fulfillment-list-query.js +84 -0
  111. package/src/lib/constants/local-fulfillment-list-query.js.map +1 -0
  112. package/src/lib/constants/orders-list-query.d.ts +7 -2
  113. package/src/lib/constants/orders-list-query.js +89 -8
  114. package/src/lib/constants/orders-list-query.js.map +1 -1
  115. package/src/lib/constants/pos-shift-list-query.d.ts +22 -0
  116. package/src/lib/constants/pos-shift-list-query.js +115 -0
  117. package/src/lib/constants/pos-shift-list-query.js.map +1 -0
  118. package/src/lib/constants/product-list-query.d.ts +6 -1
  119. package/src/lib/constants/product-list-query.js +77 -8
  120. package/src/lib/constants/product-list-query.js.map +1 -1
  121. package/src/lib/constants/stock-movement-query.d.ts +3 -2
  122. package/src/lib/constants/stock-movement-query.js +52 -8
  123. package/src/lib/constants/stock-movement-query.js.map +1 -1
  124. package/src/lib/model/buyer-notifications.d.ts +5 -3
  125. package/src/lib/model/buyer-notifications.js +25 -4
  126. package/src/lib/model/buyer-notifications.js.map +1 -1
  127. package/src/lib/model/commerce-orders.d.ts +59 -0
  128. package/src/lib/model/commerce-orders.js.map +1 -1
  129. package/src/lib/model/commerce-pos-kiosk.d.ts +175 -0
  130. package/src/lib/model/commerce-pos-kiosk.js +147 -0
  131. package/src/lib/model/commerce-pos-kiosk.js.map +1 -0
  132. package/src/lib/model/commerce-pos-offline.d.ts +234 -0
  133. package/src/lib/model/commerce-pos-offline.js +301 -0
  134. package/src/lib/model/commerce-pos-offline.js.map +1 -0
  135. package/src/lib/model/commerce-pos-ops.d.ts +16 -6
  136. package/src/lib/model/commerce-pos-ops.js +12 -4
  137. package/src/lib/model/commerce-pos-ops.js.map +1 -1
  138. package/src/lib/model/commerce-pos.d.ts +13 -1
  139. package/src/lib/model/commerce-pos.js +5 -4
  140. package/src/lib/model/commerce-pos.js.map +1 -1
  141. package/src/lib/model/commerce-stock-holds.d.ts +24 -3
  142. package/src/lib/model/commerce-stock-holds.js +26 -8
  143. package/src/lib/model/commerce-stock-holds.js.map +1 -1
  144. package/src/lib/model/commerce.d.ts +3 -0
  145. package/src/lib/model/commerce.js.map +1 -1
  146. package/src/lib/model/index.d.ts +4 -0
  147. package/src/lib/model/index.js +4 -0
  148. package/src/lib/model/index.js.map +1 -1
  149. package/src/lib/model/local-fulfillment-settings.d.ts +178 -0
  150. package/src/lib/model/local-fulfillment-settings.js +361 -0
  151. package/src/lib/model/local-fulfillment-settings.js.map +1 -0
  152. package/src/lib/model/order-list-fields.d.ts +10 -1
  153. package/src/lib/model/order-list-fields.js +1 -0
  154. package/src/lib/model/order-list-fields.js.map +1 -1
  155. package/src/lib/model/order-local-fulfillment.d.ts +131 -0
  156. package/src/lib/model/order-local-fulfillment.js +125 -0
  157. package/src/lib/model/order-local-fulfillment.js.map +1 -0
  158. package/src/lib/model/order-shipping-export.js +4 -0
  159. package/src/lib/model/order-shipping-export.js.map +1 -1
  160. package/src/lib/model/order-status-view.d.ts +20 -1
  161. package/src/lib/model/order-status-view.js +60 -6
  162. package/src/lib/model/order-status-view.js.map +1 -1
  163. package/src/lib/plugin-config.js +6 -2
  164. package/src/lib/plugin-config.js.map +1 -1
  165. package/src/lib/plugin.js +8 -0
  166. package/src/lib/plugin.js.map +1 -1
  167. package/src/lib/pos-kiosk-config.d.ts +26 -0
  168. package/src/lib/pos-kiosk-config.js +59 -0
  169. package/src/lib/pos-kiosk-config.js.map +1 -0
  170. package/src/lib/server/api-v1/openapi.js +8 -0
  171. package/src/lib/server/api-v1/openapi.js.map +1 -1
  172. package/src/lib/server/api-v1/order-view.d.ts +19 -0
  173. package/src/lib/server/api-v1/order-view.js +35 -0
  174. package/src/lib/server/api-v1/order-view.js.map +1 -1
  175. package/src/lib/server/billing-webhook.js +238 -87
  176. package/src/lib/server/billing-webhook.js.map +1 -1
  177. package/src/lib/server/cart-checkout.d.ts +2 -0
  178. package/src/lib/server/cart-checkout.js +173 -13
  179. package/src/lib/server/cart-checkout.js.map +1 -1
  180. package/src/lib/server/checkout-extras.js +18 -4
  181. package/src/lib/server/checkout-extras.js.map +1 -1
  182. package/src/lib/server/checkout.js +71 -14
  183. package/src/lib/server/checkout.js.map +1 -1
  184. package/src/lib/server/local-fulfillment-status.d.ts +86 -0
  185. package/src/lib/server/local-fulfillment-status.js +379 -0
  186. package/src/lib/server/local-fulfillment-status.js.map +1 -0
  187. package/src/lib/server/local-fulfillment.d.ts +190 -0
  188. package/src/lib/server/local-fulfillment.js +580 -0
  189. package/src/lib/server/local-fulfillment.js.map +1 -0
  190. package/src/lib/server/membership-register.js +3 -1
  191. package/src/lib/server/membership-register.js.map +1 -1
  192. package/src/lib/server/order-notifications.js +61 -0
  193. package/src/lib/server/order-notifications.js.map +1 -1
  194. package/src/lib/server/pos-display.d.ts +27 -1
  195. package/src/lib/server/pos-display.js +50 -9
  196. package/src/lib/server/pos-display.js.map +1 -1
  197. package/src/lib/server/pos-kiosk-principal.d.ts +29 -0
  198. package/src/lib/server/pos-kiosk-principal.js +42 -0
  199. package/src/lib/server/pos-kiosk-principal.js.map +1 -0
  200. package/src/lib/server/pos-kiosk.d.ts +54 -0
  201. package/src/lib/server/pos-kiosk.js +972 -0
  202. package/src/lib/server/pos-kiosk.js.map +1 -0
  203. package/src/lib/server/pos-offline-sync.d.ts +85 -0
  204. package/src/lib/server/pos-offline-sync.js +765 -0
  205. package/src/lib/server/pos-offline-sync.js.map +1 -0
  206. package/src/lib/server/pos-order.js +22 -4
  207. package/src/lib/server/pos-order.js.map +1 -1
  208. package/src/lib/server/pos-payment.d.ts +6 -0
  209. package/src/lib/server/pos-payment.js +225 -6
  210. package/src/lib/server/pos-payment.js.map +1 -1
  211. package/src/lib/server/pos-print.js +5 -0
  212. package/src/lib/server/pos-print.js.map +1 -1
  213. package/src/lib/server/pos-return.js +27 -0
  214. package/src/lib/server/pos-return.js.map +1 -1
  215. package/src/lib/server/pos-sale.d.ts +6 -0
  216. package/src/lib/server/pos-sale.js +2 -0
  217. package/src/lib/server/pos-sale.js.map +1 -1
  218. package/src/lib/server/pos-shift.js +8 -1
  219. package/src/lib/server/pos-shift.js.map +1 -1
  220. package/src/lib/server/refund.js +3 -0
  221. package/src/lib/server/refund.js.map +1 -1
  222. package/src/lib/server/stock-hold.d.ts +9 -1
  223. package/src/lib/server/stock-hold.js +15 -7
  224. package/src/lib/server/stock-hold.js.map +1 -1
  225. package/src/lib/server.js +16 -0
  226. package/src/lib/server.js.map +1 -1
  227. package/src/lib/tenant-emails.js +161 -0
  228. package/src/lib/tenant-emails.js.map +1 -1
@@ -0,0 +1,85 @@
1
+ import * as CommerceModel from '../../../model';
2
+ import { type PosOfflineFlag, type PosOfflineKit, type PosOfflineSale, type PosOfflineStockConflict } from '../../../model/commerce-pos-offline';
3
+ import type { RegisterLine } from '../pos/pos-cart-panel.component';
4
+ import { fetchPosOfflineKit } from './pos-offline-api';
5
+ import { openPosOfflineStore, type PosOfflineProduct, type PosOfflineQueuedSale } from './pos-offline-store';
6
+ type User = Parameters<typeof fetchPosOfflineKit>[0];
7
+ /** A sale the server answered for with something to check. */
8
+ export interface PosOfflineSyncNotice {
9
+ saleKey: string;
10
+ orderId: string;
11
+ number: number | null;
12
+ flags: PosOfflineFlag[];
13
+ stockConflicts: PosOfflineStockConflict[];
14
+ }
15
+ /** A cash sale the register just rang offline, for its receipt step. */
16
+ export interface PosOfflineRungSale {
17
+ saleKey: string;
18
+ sale: PosOfflineSale;
19
+ /** The order the receipt prints, shaped as the stored order will be. */
20
+ order: CommerceModel.HostOrder & {
21
+ createdAtMs: number;
22
+ customerName?: string | null;
23
+ };
24
+ }
25
+ export interface PosOfflineState {
26
+ /** The register is offline right now. */
27
+ offline: boolean;
28
+ /** This device can sell offline: storage opened and the kit says yes. */
29
+ ready: boolean;
30
+ /** Why it cannot, in the cashier's words, when it cannot. */
31
+ unavailableReason: string | null;
32
+ kit: PosOfflineKit | null;
33
+ /** Sales on this device that have not synced, oldest first. */
34
+ queue: PosOfflineQueuedSale[];
35
+ syncing: boolean;
36
+ /** Why the last sync could not run, when the server refused it outright. */
37
+ syncError: string | null;
38
+ /** Synced sales the server flagged, until the cashier dismisses them. */
39
+ notices: PosOfflineSyncNotice[];
40
+ /** The products the offline grid shows for a search and a chip. */
41
+ gridProducts(search: string, categoryId: string, quickKeysId: string): PosOfflineProduct[];
42
+ /** A product and variant by barcode or SKU from the cached catalog. */
43
+ findByCode(code: string): {
44
+ product: PosOfflineProduct;
45
+ variant: any;
46
+ } | null;
47
+ /** The totals the basket rings offline, or `null` when it cannot. */
48
+ totalsFor(lines: RegisterLine[], discountPct: number): CommerceModel.OrderTotals | null;
49
+ /** Queues one cash sale; `null` (and nothing taken) when it cannot be kept. */
50
+ ringCashSale(input: {
51
+ lines: RegisterLine[];
52
+ discountPct: number;
53
+ cashTenderedCents: number;
54
+ customer?: {
55
+ email?: string | null;
56
+ name?: string | null;
57
+ kind?: string;
58
+ id?: string;
59
+ } | null;
60
+ }): Promise<PosOfflineRungSale | null>;
61
+ /** A register call failed at the network. */
62
+ reportNetworkFailure(): void;
63
+ syncNow(): Promise<void>;
64
+ dismissNotices(): void;
65
+ /** Removes a refused sale the server will never take, after the cashier confirms. */
66
+ discard(saleKey: string): Promise<void>;
67
+ }
68
+ export interface UsePosOfflineInput {
69
+ hostId: string;
70
+ user: User | null | undefined;
71
+ registerId: string;
72
+ /** The open shift on the register as last seen, if any. */
73
+ openShiftId?: string | null;
74
+ locationId?: string;
75
+ cashierAssertion?: string;
76
+ /** Products the live grid holds; kept for the device as they arrive. */
77
+ liveProducts: ReadonlyArray<Record<string, any>>;
78
+ /** Reads the whole sellable catalog for the device (the grid's own query). */
79
+ loadCatalog?: () => Promise<Array<Record<string, any>>>;
80
+ /** Opens the device store; injected by specs. */
81
+ openStore?: typeof openPosOfflineStore;
82
+ now?: () => number;
83
+ }
84
+ export declare function usePosOffline(input: UsePosOfflineInput): PosOfflineState;
85
+ export {};
@@ -0,0 +1,542 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { _ as _extends } from "@swc/helpers/_/_extends";
18
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
19
+ import * as CommerceModel from "../../../model/index.js";
20
+ import { posOfflineChangeCents, posOfflineSaleTotals, POS_OFFLINE_SYNC_BATCH_MAX } from "../../../model/commerce-pos-offline.js";
21
+ import { fetchPosOfflineKit, newPosOfflineSaleKey, syncPosOfflineSales } from "./pos-offline-api.js";
22
+ import { openPosOfflineStore, posOfflineProduct } from "./pos-offline-store.js";
23
+ /*==========================================
24
+ * THE REGISTER, OFFLINE (AGL-3625).
25
+ *
26
+ * WHEN IS IT OFFLINE: when the browser says so, or when a register call
27
+ * failed at the network — a Wi-Fi that is "connected" to a router with no
28
+ * uplink looks online to `navigator.onLine`. While offline it probes the sync
29
+ * route every few seconds; the first answer brings it back.
30
+ *
31
+ * WHAT IT KEEPS: the kit and the catalog, refreshed while online, and the
32
+ * queue (see `pos-offline-store.ts`).
33
+ *
34
+ * HOW IT SYNCS: on reconnect, on mount, and after every offline sale while
35
+ * online again, in batches, one sync at a time. A sale leaves the device only
36
+ * when the server has answered for it — recorded, or a replay of a recording —
37
+ * so a connection that drops mid-request leaves it queued, and sending it
38
+ * again is safe: the server lands a sale key once.
39
+ *=========================================*/ /** How often the kit is refreshed while online. */ const KIT_REFRESH_MS = 10 * 60 * 1000;
40
+ /** How often the whole catalog is re-read for the device. */ const CATALOG_REFRESH_MS = 30 * 60 * 1000;
41
+ /** How often an offline register asks whether the server answers again. */ const PROBE_MS = 15 * 1000;
42
+ /**
43
+ * The shift open on the register as last seen: the live register document's
44
+ * answer when the page has one (including "none"), the kit's otherwise.
45
+ */ function lastKnownShift(live, kit, registerId) {
46
+ var _ref;
47
+ var _kit_registers_find;
48
+ if (live !== undefined) return live || null;
49
+ return (_ref = (_kit_registers_find = kit.registers.find((register)=>register.id === registerId)) == null ? void 0 : _kit_registers_find.openShiftId) != null ? _ref : null;
50
+ }
51
+ function browserOnline() {
52
+ return typeof navigator === 'undefined' || navigator.onLine !== false;
53
+ }
54
+ export function usePosOffline(input) {
55
+ var _ref, _input_now, _input_openStore, _kit_unavailableReason;
56
+ const { hostId, user, registerId } = input;
57
+ const uid = (_ref = user == null ? void 0 : user.uid) != null ? _ref : '';
58
+ const now = (_input_now = input.now) != null ? _input_now : Date.now;
59
+ const openStore = (_input_openStore = input.openStore) != null ? _input_openStore : openPosOfflineStore;
60
+ const [store, setStore] = useState(null);
61
+ const [storeFailed, setStoreFailed] = useState(false);
62
+ const [kit, setKit] = useState(null);
63
+ const [catalog, setCatalog] = useState([]);
64
+ const [queue, setQueue] = useState([]);
65
+ const [navigatorOnline, setNavigatorOnline] = useState(browserOnline);
66
+ const [networkFailed, setNetworkFailed] = useState(false);
67
+ const [syncing, setSyncing] = useState(false);
68
+ const [syncError, setSyncError] = useState(null);
69
+ const [notices, setNotices] = useState([]);
70
+ const offline = !navigatorOnline || networkFailed;
71
+ const latest = useRef(input);
72
+ latest.current = input;
73
+ const userRef = useRef(user);
74
+ userRef.current = user;
75
+ // The device store, for this member at this site.
76
+ useEffect(()=>{
77
+ let active = true;
78
+ setStore(null);
79
+ setStoreFailed(false);
80
+ setKit(null);
81
+ setCatalog([]);
82
+ setQueue([]);
83
+ if (!uid || !hostId) return undefined;
84
+ void openStore({
85
+ uid,
86
+ hostId
87
+ }).then(async (opened)=>{
88
+ if (!active) return;
89
+ if (!opened) {
90
+ setStoreFailed(true);
91
+ return;
92
+ }
93
+ try {
94
+ var _saved_products;
95
+ const [saved, sales] = await Promise.all([
96
+ opened.loadKit(),
97
+ opened.listSales()
98
+ ]);
99
+ if (!active) return;
100
+ if (saved == null ? void 0 : saved.kit) setKit(saved.kit);
101
+ if (saved == null ? void 0 : (_saved_products = saved.products) == null ? void 0 : _saved_products.length) setCatalog(saved.products);
102
+ setQueue(sales);
103
+ setStore(opened);
104
+ } catch (unused) {
105
+ if (active) setStoreFailed(true);
106
+ }
107
+ });
108
+ return ()=>{
109
+ active = false;
110
+ };
111
+ }, [
112
+ uid,
113
+ hostId,
114
+ openStore
115
+ ]);
116
+ useEffect(()=>{
117
+ const goOnline = ()=>{
118
+ setNavigatorOnline(true);
119
+ setNetworkFailed(false);
120
+ };
121
+ const goOffline = ()=>setNavigatorOnline(false);
122
+ window.addEventListener('online', goOnline);
123
+ window.addEventListener('offline', goOffline);
124
+ return ()=>{
125
+ window.removeEventListener('online', goOnline);
126
+ window.removeEventListener('offline', goOffline);
127
+ };
128
+ }, []);
129
+ // The kit and the catalog, while online.
130
+ const kitSavedAt = useRef(0);
131
+ const catalogSavedAt = useRef(0);
132
+ const refreshKit = useCallback(async ()=>{
133
+ var _ref, _ref1;
134
+ const signedIn = userRef.current;
135
+ if (!store || !signedIn) return;
136
+ const answer = await fetchPosOfflineKit(signedIn, hostId);
137
+ if ('error' in answer) {
138
+ if (answer.status === 0) setNetworkFailed(true);
139
+ return;
140
+ }
141
+ const fresh = answer.body.kit;
142
+ let products = null;
143
+ const load = latest.current.loadCatalog;
144
+ if (load && now() - catalogSavedAt.current > CATALOG_REFRESH_MS) {
145
+ try {
146
+ products = (await load()).map(posOfflineProduct).filter(Boolean);
147
+ catalogSavedAt.current = now();
148
+ } catch (unused) {
149
+ products = null;
150
+ }
151
+ }
152
+ kitSavedAt.current = now();
153
+ setKit(fresh);
154
+ if (products) setCatalog(products);
155
+ const saved = await store.loadKit().catch(()=>null);
156
+ await store.saveKit({
157
+ kit: fresh,
158
+ kitSavedAtMs: kitSavedAt.current,
159
+ products: (_ref = products != null ? products : saved == null ? void 0 : saved.products) != null ? _ref : [],
160
+ catalogSavedAtMs: products ? catalogSavedAt.current : (_ref1 = saved == null ? void 0 : saved.catalogSavedAtMs) != null ? _ref1 : 0
161
+ }).catch(()=>undefined);
162
+ }, [
163
+ store,
164
+ hostId,
165
+ now
166
+ ]);
167
+ useEffect(()=>{
168
+ if (!store || offline || !uid) return undefined;
169
+ void refreshKit();
170
+ const timer = window.setInterval(()=>void refreshKit(), KIT_REFRESH_MS);
171
+ return ()=>window.clearInterval(timer);
172
+ }, [
173
+ store,
174
+ offline,
175
+ uid,
176
+ refreshKit
177
+ ]);
178
+ // Products the live grid shows are merged into the device's catalog, so a
179
+ // product rung up online is sellable offline even past the snapshot.
180
+ const liveKey = input.liveProducts.map((product)=>{
181
+ var _product_$id;
182
+ return String((_product_$id = product['$id']) != null ? _product_$id : '');
183
+ }).join(',');
184
+ useEffect(()=>{
185
+ if (!store || offline || !input.liveProducts.length) return;
186
+ setCatalog((current)=>{
187
+ const byId = new Map(current.map((product)=>[
188
+ product.$id,
189
+ product
190
+ ]));
191
+ let changed = false;
192
+ for (const product of latest.current.liveProducts){
193
+ const trimmed = posOfflineProduct(product);
194
+ if (!trimmed) continue;
195
+ if (JSON.stringify(byId.get(trimmed.$id)) !== JSON.stringify(trimmed)) changed = true;
196
+ byId.set(trimmed.$id, trimmed);
197
+ }
198
+ if (!changed) return current;
199
+ const next = [
200
+ ...byId.values()
201
+ ];
202
+ void store.loadKit().then((saved)=>{
203
+ var _ref, _ref1, _ref2;
204
+ return store.saveKit({
205
+ kit: (_ref = saved == null ? void 0 : saved.kit) != null ? _ref : null,
206
+ kitSavedAtMs: (_ref1 = saved == null ? void 0 : saved.kitSavedAtMs) != null ? _ref1 : 0,
207
+ products: next,
208
+ catalogSavedAtMs: (_ref2 = saved == null ? void 0 : saved.catalogSavedAtMs) != null ? _ref2 : 0
209
+ });
210
+ }).catch(()=>undefined);
211
+ return next;
212
+ });
213
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- keyed on the ids the grid holds
214
+ }, [
215
+ store,
216
+ offline,
217
+ liveKey
218
+ ]);
219
+ // The sync.
220
+ const syncInFlight = useRef(false);
221
+ const syncNow = useCallback(async ()=>{
222
+ const signedIn = userRef.current;
223
+ if (!store || !signedIn || syncInFlight.current) return;
224
+ syncInFlight.current = true;
225
+ setSyncing(true);
226
+ try {
227
+ const all = await store.listSales();
228
+ const waiting = all.filter((entry)=>entry.state === 'queued' && entry.sale.signedInUid === uid);
229
+ if (!waiting.length) {
230
+ // Nothing to send: one empty call says whether the route answers.
231
+ if (networkFailed) {
232
+ const probe = await syncPosOfflineSales(signedIn, hostId, []);
233
+ if (probe.ok || probe.status !== 0) setNetworkFailed(false);
234
+ }
235
+ return;
236
+ }
237
+ const flagged = [];
238
+ for(let at = 0; at < waiting.length; at += POS_OFFLINE_SYNC_BATCH_MAX){
239
+ var _answer_body_results;
240
+ const batch = waiting.slice(at, at + POS_OFFLINE_SYNC_BATCH_MAX);
241
+ const answer = await syncPosOfflineSales(signedIn, hostId, batch.map((entry)=>entry.sale));
242
+ if ('error' in answer) {
243
+ if (answer.status === 0) setNetworkFailed(true);
244
+ else setSyncError(answer.error);
245
+ break;
246
+ }
247
+ setSyncError(null);
248
+ setNetworkFailed(false);
249
+ const byKey = new Map(batch.map((entry)=>[
250
+ entry.saleKey,
251
+ entry
252
+ ]));
253
+ for (const result of (_answer_body_results = answer.body.results) != null ? _answer_body_results : []){
254
+ const entry = byKey.get(result.saleKey);
255
+ if (!entry) continue;
256
+ if (result.status === 'recorded' || result.status === 'replayed') {
257
+ var _result_flags;
258
+ await store.removeSale(entry.saleKey);
259
+ if ((_result_flags = result.flags) == null ? void 0 : _result_flags.length) {
260
+ var _result_orderId, _result_number, _result_stockConflicts;
261
+ flagged.push({
262
+ saleKey: result.saleKey,
263
+ orderId: (_result_orderId = result.orderId) != null ? _result_orderId : result.saleKey,
264
+ number: (_result_number = result.number) != null ? _result_number : null,
265
+ flags: result.flags,
266
+ stockConflicts: (_result_stockConflicts = result.stockConflicts) != null ? _result_stockConflicts : []
267
+ });
268
+ }
269
+ continue;
270
+ }
271
+ // A transient failure stays queued; every other refusal waits for
272
+ // the cashier, still on the device, with the server's sentence.
273
+ await store.putSale(_extends({}, entry, {
274
+ attempts: entry.attempts + 1,
275
+ state: result.reason === 'failed' || result.reason === 'wrong-staff' ? 'queued' : 'refused'
276
+ }, result.error ? {
277
+ error: result.error
278
+ } : {}, result.reason ? {
279
+ reason: result.reason
280
+ } : {}));
281
+ }
282
+ }
283
+ if (flagged.length) setNotices((current)=>[
284
+ ...current,
285
+ ...flagged
286
+ ]);
287
+ } catch (unused) {
288
+ // Storage or the network went away mid-sync; the queue is as it was.
289
+ } finally{
290
+ setQueue(await store.listSales().catch(()=>[]));
291
+ syncInFlight.current = false;
292
+ setSyncing(false);
293
+ }
294
+ }, [
295
+ store,
296
+ uid,
297
+ hostId,
298
+ networkFailed
299
+ ]);
300
+ // Sync on reconnect, on mount, and while offline-by-failure keep probing.
301
+ useEffect(()=>{
302
+ if (!store || offline) return;
303
+ void syncNow();
304
+ }, [
305
+ store,
306
+ offline,
307
+ syncNow
308
+ ]);
309
+ useEffect(()=>{
310
+ if (!store || !networkFailed || !navigatorOnline) return undefined;
311
+ const timer = window.setInterval(()=>void syncNow(), PROBE_MS);
312
+ return ()=>window.clearInterval(timer);
313
+ }, [
314
+ store,
315
+ networkFailed,
316
+ navigatorOnline,
317
+ syncNow
318
+ ]);
319
+ const unavailableReason = storeFailed ? 'This browser is not keeping data for the register (a private window, or storage is blocked), so offline selling is unavailable.' : !store ? 'Getting the register ready to sell offline…' : !kit ? 'Connect once so the register can get ready to sell offline.' : !kit.available ? (_kit_unavailableReason = kit.unavailableReason) != null ? _kit_unavailableReason : 'Offline selling is unavailable for this site.' : !catalog.length ? 'The register has no catalog saved for offline selling yet.' : kit.requireOpenShift && !lastKnownShift(input.openShiftId, kit, registerId) ? 'Open a shift before going offline: this site requires one for every sale.' : null;
320
+ const ready = unavailableReason === null;
321
+ const gridProducts = useCallback((search, categoryId, quickKeysId)=>{
322
+ const typed = search.trim().toLowerCase();
323
+ return catalog.filter((product)=>product['status'] === undefined || product['status'] === 'active').filter((product)=>!product['deletedAt']).filter((product)=>{
324
+ var _product_name;
325
+ // A degraded grid over the device's own copy: there is no query to
326
+ // ask while offline, so the copy is narrowed in memory.
327
+ if (typed) return String((_product_name = product.name) != null ? _product_name : '').toLowerCase().includes(typed);
328
+ if (categoryId === quickKeysId && quickKeysId) return product['posQuickKey'] === true;
329
+ if (categoryId) return Array.isArray(product['categoryIds']) && product['categoryIds'].includes(categoryId);
330
+ return true;
331
+ }).sort((a, b)=>{
332
+ var _a_name, _b_name;
333
+ return String((_a_name = a.name) != null ? _a_name : '').localeCompare(String((_b_name = b.name) != null ? _b_name : ''));
334
+ }).map((product)=>_extends({}, CommerceModel.liftLegacyProduct(product), {
335
+ $id: product.$id
336
+ }));
337
+ }, [
338
+ catalog
339
+ ]);
340
+ const findByCode = useCallback((code)=>{
341
+ const needle = code.trim().toLowerCase();
342
+ if (!needle) return null;
343
+ for (const raw of catalog){
344
+ var _product_variants;
345
+ const product = _extends({}, CommerceModel.liftLegacyProduct(raw), {
346
+ $id: raw.$id
347
+ });
348
+ const variant = ((_product_variants = product['variants']) != null ? _product_variants : []).find((item)=>{
349
+ var _ref, _ref1;
350
+ return String((_ref = item == null ? void 0 : item.barcode) != null ? _ref : '').trim().toLowerCase() === needle || String((_ref1 = item == null ? void 0 : item.sku) != null ? _ref1 : '').trim().toLowerCase() === needle;
351
+ });
352
+ if (variant) return {
353
+ product,
354
+ variant
355
+ };
356
+ }
357
+ return null;
358
+ }, [
359
+ catalog
360
+ ]);
361
+ const totalsFor = useCallback((lines, discountPct)=>{
362
+ if (!(kit == null ? void 0 : kit.available) || !lines.length) return null;
363
+ return posOfflineSaleTotals({
364
+ lines,
365
+ discountPct,
366
+ tax: kit.tax
367
+ });
368
+ }, [
369
+ kit
370
+ ]);
371
+ const ringInFlight = useRef(false);
372
+ const ringCashSale = useCallback(async (sale)=>{
373
+ const context = latest.current;
374
+ if (!store || !(kit == null ? void 0 : kit.available) || !uid || ringInFlight.current) return null;
375
+ if (sale.discountPct > kit.maxDiscountPct) return null;
376
+ const totals = posOfflineSaleTotals({
377
+ lines: sale.lines,
378
+ discountPct: sale.discountPct,
379
+ tax: kit.tax
380
+ });
381
+ if (sale.cashTenderedCents < totals.totalCents) return null;
382
+ ringInFlight.current = true;
383
+ try {
384
+ var _ref;
385
+ const saleKey = newPosOfflineSaleKey();
386
+ const soldAtMs = now();
387
+ const byId = new Map(catalog.map((product)=>[
388
+ product.$id,
389
+ product
390
+ ]));
391
+ const lines = sale.lines.map((line)=>{
392
+ var _ref;
393
+ var _line_modifiers;
394
+ const product = byId.get(line.productId);
395
+ const variant = ((_ref = product == null ? void 0 : product['variants']) != null ? _ref : []).find((item)=>(item == null ? void 0 : item.id) === line.variantId);
396
+ return _extends({
397
+ productId: line.productId
398
+ }, line.variantId ? {
399
+ variantId: line.variantId
400
+ } : {}, {
401
+ name: line.name
402
+ }, line.variantLabel ? {
403
+ variantLabel: line.variantLabel
404
+ } : {}, (variant == null ? void 0 : variant.sku) ? {
405
+ sku: String(variant.sku)
406
+ } : {}, (product == null ? void 0 : product['type']) ? {
407
+ productType: product['type']
408
+ } : {}, {
409
+ quantity: line.quantity,
410
+ unitAmountCents: line.unitAmountCents
411
+ }, ((_line_modifiers = line.modifiers) == null ? void 0 : _line_modifiers.length) ? {
412
+ modifiers: line.modifiers
413
+ } : {});
414
+ });
415
+ const shiftId = lastKnownShift(context.openShiftId, kit, registerId);
416
+ const customer = sale.customer ? _extends({}, sale.customer.email ? {
417
+ email: sale.customer.email
418
+ } : {}, sale.customer.name ? {
419
+ name: sale.customer.name
420
+ } : {}, sale.customer.kind && sale.customer.kind !== 'none' && sale.customer.id ? {
421
+ kind: sale.customer.kind,
422
+ id: sale.customer.id
423
+ } : {}) : undefined;
424
+ const queued = _extends({
425
+ v: 1,
426
+ saleKey,
427
+ hostId,
428
+ orgId: kit.orgId,
429
+ signedInUid: uid,
430
+ registerId,
431
+ shiftId
432
+ }, context.locationId ? {
433
+ locationId: context.locationId
434
+ } : {}, context.cashierAssertion ? {
435
+ cashierAssertion: context.cashierAssertion
436
+ } : {}, {
437
+ soldAtMs,
438
+ lines,
439
+ discountPct: sale.discountPct,
440
+ totals,
441
+ cashTenderedCents: sale.cashTenderedCents,
442
+ changeCents: posOfflineChangeCents(totals.totalCents, sale.cashTenderedCents)
443
+ }, customer && Object.keys(customer).length ? {
444
+ customer
445
+ } : {});
446
+ // The sale is real only once the device holds it: a write that fails
447
+ // answers null, and the cashier is told before taking any cash.
448
+ await store.putSale({
449
+ saleKey,
450
+ sale: queued,
451
+ state: 'queued',
452
+ queuedAtMs: soldAtMs,
453
+ attempts: 0
454
+ });
455
+ setQueue(await store.listSales());
456
+ return {
457
+ saleKey,
458
+ sale: queued,
459
+ order: {
460
+ status: 'paid',
461
+ channel: 'pos',
462
+ registerId,
463
+ lineItems: lines.map((line)=>_extends({}, line, {
464
+ modifiers: undefined
465
+ })),
466
+ totals,
467
+ payments: [
468
+ {
469
+ id: 'pay_cash',
470
+ method: 'cash',
471
+ amountCents: totals.totalCents,
472
+ status: 'succeeded',
473
+ atMs: soldAtMs,
474
+ cashTenderedCents: sale.cashTenderedCents,
475
+ changeCents: queued.changeCents
476
+ }
477
+ ],
478
+ createdAtMs: soldAtMs,
479
+ customerName: (_ref = customer == null ? void 0 : customer.name) != null ? _ref : null
480
+ }
481
+ };
482
+ } catch (unused) {
483
+ return null;
484
+ } finally{
485
+ ringInFlight.current = false;
486
+ }
487
+ }, [
488
+ store,
489
+ kit,
490
+ uid,
491
+ catalog,
492
+ hostId,
493
+ registerId,
494
+ now
495
+ ]);
496
+ const reportNetworkFailure = useCallback(()=>setNetworkFailed(true), []);
497
+ const dismissNotices = useCallback(()=>setNotices([]), []);
498
+ const discard = useCallback(async (saleKey)=>{
499
+ if (!store) return;
500
+ await store.removeSale(saleKey).catch(()=>undefined);
501
+ setQueue(await store.listSales().catch(()=>[]));
502
+ }, [
503
+ store
504
+ ]);
505
+ return useMemo(()=>({
506
+ offline,
507
+ ready,
508
+ unavailableReason,
509
+ kit,
510
+ queue,
511
+ syncing,
512
+ syncError,
513
+ notices,
514
+ gridProducts,
515
+ findByCode,
516
+ totalsFor,
517
+ ringCashSale,
518
+ reportNetworkFailure,
519
+ syncNow,
520
+ dismissNotices,
521
+ discard
522
+ }), [
523
+ offline,
524
+ ready,
525
+ unavailableReason,
526
+ kit,
527
+ queue,
528
+ syncing,
529
+ syncError,
530
+ notices,
531
+ gridProducts,
532
+ findByCode,
533
+ totalsFor,
534
+ ringCashSale,
535
+ reportNetworkFailure,
536
+ syncNow,
537
+ dismissNotices,
538
+ discard
539
+ ]);
540
+ }
541
+
542
+ //# sourceMappingURL=use-pos-offline.js.map