@aglyn/plugins-commerce 1.0.0-beta.232 → 1.0.0-beta.234
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/package.json +13 -13
- package/src/lib/components/cart-extras.d.ts +6 -0
- package/src/lib/components/cart-extras.js +4 -1
- package/src/lib/components/cart-extras.js.map +1 -1
- package/src/lib/components/cart-fulfillment.d.ts +117 -0
- package/src/lib/components/cart-fulfillment.js +367 -0
- package/src/lib/components/cart-fulfillment.js.map +1 -0
- package/src/lib/components/cart.js +52 -10
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/commerce-console-page.js +42 -1
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/console/host-orders-card.component.js +38 -10
- package/src/lib/components/console/host-orders-card.component.js.map +1 -1
- package/src/lib/components/console/local-delivery-settings-card.component.d.ts +37 -0
- package/src/lib/components/console/local-delivery-settings-card.component.js +542 -0
- package/src/lib/components/console/local-delivery-settings-card.component.js.map +1 -0
- package/src/lib/components/console/locations-card.component.js +177 -3
- package/src/lib/components/console/locations-card.component.js.map +1 -1
- package/src/lib/components/console/order-detail-dialog.component.js +10 -0
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
- package/src/lib/components/console/order-local-fulfillment-panel.component.d.ts +44 -0
- package/src/lib/components/console/order-local-fulfillment-panel.component.js +265 -0
- package/src/lib/components/console/order-local-fulfillment-panel.component.js.map +1 -0
- package/src/lib/components/console/order-offline-notice.component.d.ts +12 -0
- package/src/lib/components/console/order-offline-notice.component.js +53 -0
- package/src/lib/components/console/order-offline-notice.component.js.map +1 -0
- package/src/lib/components/console/pickup-delivery-queue-card.component.d.ts +35 -0
- package/src/lib/components/console/pickup-delivery-queue-card.component.js +222 -0
- package/src/lib/components/console/pickup-delivery-queue-card.component.js.map +1 -0
- package/src/lib/components/console/pos/pos-api.d.ts +24 -1
- package/src/lib/components/console/pos/pos-api.js +13 -0
- package/src/lib/components/console/pos/pos-api.js.map +1 -1
- package/src/lib/components/console/pos/pos-item-dialog.component.d.ts +12 -0
- package/src/lib/components/console/pos/pos-item-dialog.component.js +10 -8
- package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-kiosk-queue.component.d.ts +22 -0
- package/src/lib/components/console/pos/pos-kiosk-queue.component.js +168 -0
- package/src/lib/components/console/pos/pos-kiosk-queue.component.js.map +1 -0
- package/src/lib/components/console/pos/pos-tender-dialogs.component.d.ts +18 -0
- package/src/lib/components/console/pos/pos-tender-dialogs.component.js +116 -1
- package/src/lib/components/console/pos/pos-tender-dialogs.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-tender-panel.component.d.ts +8 -0
- package/src/lib/components/console/pos/pos-tender-panel.component.js +77 -18
- package/src/lib/components/console/pos/pos-tender-panel.component.js.map +1 -1
- package/src/lib/components/console/pos/use-pos-display.d.ts +5 -2
- package/src/lib/components/console/pos/use-pos-display.js +3 -2
- package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
- package/src/lib/components/console/pos-devices-card.component.js +2 -2
- package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
- package/src/lib/components/console/pos-display/pairing-screen.d.ts +5 -2
- package/src/lib/components/console/pos-display/pairing-screen.js +7 -5
- package/src/lib/components/console/pos-display/pairing-screen.js.map +1 -1
- package/src/lib/components/console/pos-display/pos-display-api.d.ts +7 -1
- package/src/lib/components/console/pos-display/pos-display-api.js +7 -2
- package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -1
- package/src/lib/components/console/pos-kiosk/kiosk-checkout.d.ts +41 -0
- package/src/lib/components/console/pos-kiosk/kiosk-checkout.js +275 -0
- package/src/lib/components/console/pos-kiosk/kiosk-checkout.js.map +1 -0
- package/src/lib/components/console/pos-kiosk/kiosk-menu.d.ts +32 -0
- package/src/lib/components/console/pos-kiosk/kiosk-menu.js +344 -0
- package/src/lib/components/console/pos-kiosk/kiosk-menu.js.map +1 -0
- package/src/lib/components/console/pos-kiosk/kiosk-staff.d.ts +12 -0
- package/src/lib/components/console/pos-kiosk/kiosk-staff.js +257 -0
- package/src/lib/components/console/pos-kiosk/kiosk-staff.js.map +1 -0
- package/src/lib/components/console/pos-kiosk/pos-kiosk-api.d.ts +114 -0
- package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js +189 -0
- package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js.map +1 -0
- package/src/lib/components/console/pos-kiosk/use-kiosk-idle.d.ts +16 -0
- package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js +80 -0
- package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js.map +1 -0
- package/src/lib/components/console/pos-kiosk-page.component.d.ts +28 -0
- package/src/lib/components/console/pos-kiosk-page.component.js +791 -0
- package/src/lib/components/console/pos-kiosk-page.component.js.map +1 -0
- package/src/lib/components/console/pos-offline/pos-offline-api.d.ts +44 -0
- package/src/lib/components/console/pos-offline/pos-offline-api.js +69 -0
- package/src/lib/components/console/pos-offline/pos-offline-api.js.map +1 -0
- package/src/lib/components/console/pos-offline/pos-offline-banner.component.d.ts +7 -0
- package/src/lib/components/console/pos-offline/pos-offline-banner.component.js +135 -0
- package/src/lib/components/console/pos-offline/pos-offline-banner.component.js.map +1 -0
- package/src/lib/components/console/pos-offline/pos-offline-checkout.component.d.ts +32 -0
- package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js +287 -0
- package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js.map +1 -0
- package/src/lib/components/console/pos-offline/pos-offline-store.d.ts +63 -0
- package/src/lib/components/console/pos-offline/pos-offline-store.js +145 -0
- package/src/lib/components/console/pos-offline/pos-offline-store.js.map +1 -0
- package/src/lib/components/console/pos-offline/register-offline.d.ts +24 -0
- package/src/lib/components/console/pos-offline/register-offline.js +24 -0
- package/src/lib/components/console/pos-offline/register-offline.js.map +1 -0
- package/src/lib/components/console/pos-offline/use-pos-offline.d.ts +85 -0
- package/src/lib/components/console/pos-offline/use-pos-offline.js +542 -0
- package/src/lib/components/console/pos-offline/use-pos-offline.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js +72 -17
- package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js.map +1 -1
- package/src/lib/components/console/pos-page.component.js +174 -27
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +35 -10
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/shipping-settings-card.component.js +1 -1
- package/src/lib/components/console/shipping-settings-card.component.js.map +1 -1
- package/src/lib/components/console/stock-movements-card.component.js +48 -16
- package/src/lib/components/console/stock-movements-card.component.js.map +1 -1
- package/src/lib/components/console/store-zones.d.ts +8 -1
- package/src/lib/components/console/store-zones.js +11 -1
- package/src/lib/components/console/store-zones.js.map +1 -1
- package/src/lib/components/order-status.js +66 -0
- package/src/lib/components/order-status.js.map +1 -1
- package/src/lib/components/product-detail.js +42 -15
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/constants/local-fulfillment-list-query.d.ts +48 -0
- package/src/lib/constants/local-fulfillment-list-query.js +84 -0
- package/src/lib/constants/local-fulfillment-list-query.js.map +1 -0
- package/src/lib/constants/orders-list-query.d.ts +7 -2
- package/src/lib/constants/orders-list-query.js +89 -8
- package/src/lib/constants/orders-list-query.js.map +1 -1
- package/src/lib/constants/pos-shift-list-query.d.ts +22 -0
- package/src/lib/constants/pos-shift-list-query.js +115 -0
- package/src/lib/constants/pos-shift-list-query.js.map +1 -0
- package/src/lib/constants/product-list-query.d.ts +6 -1
- package/src/lib/constants/product-list-query.js +77 -8
- package/src/lib/constants/product-list-query.js.map +1 -1
- package/src/lib/constants/stock-movement-query.d.ts +3 -2
- package/src/lib/constants/stock-movement-query.js +52 -8
- package/src/lib/constants/stock-movement-query.js.map +1 -1
- package/src/lib/model/buyer-notifications.d.ts +5 -3
- package/src/lib/model/buyer-notifications.js +25 -4
- package/src/lib/model/buyer-notifications.js.map +1 -1
- package/src/lib/model/commerce-orders.d.ts +59 -0
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce-pos-kiosk.d.ts +175 -0
- package/src/lib/model/commerce-pos-kiosk.js +147 -0
- package/src/lib/model/commerce-pos-kiosk.js.map +1 -0
- package/src/lib/model/commerce-pos-offline.d.ts +234 -0
- package/src/lib/model/commerce-pos-offline.js +301 -0
- package/src/lib/model/commerce-pos-offline.js.map +1 -0
- package/src/lib/model/commerce-pos-ops.d.ts +16 -6
- package/src/lib/model/commerce-pos-ops.js +12 -4
- package/src/lib/model/commerce-pos-ops.js.map +1 -1
- package/src/lib/model/commerce-pos.d.ts +13 -1
- package/src/lib/model/commerce-pos.js +5 -4
- package/src/lib/model/commerce-pos.js.map +1 -1
- package/src/lib/model/commerce-stock-holds.d.ts +24 -3
- package/src/lib/model/commerce-stock-holds.js +26 -8
- package/src/lib/model/commerce-stock-holds.js.map +1 -1
- package/src/lib/model/commerce.d.ts +3 -0
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/index.d.ts +4 -0
- package/src/lib/model/index.js +4 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/local-fulfillment-settings.d.ts +178 -0
- package/src/lib/model/local-fulfillment-settings.js +361 -0
- package/src/lib/model/local-fulfillment-settings.js.map +1 -0
- package/src/lib/model/order-list-fields.d.ts +10 -1
- package/src/lib/model/order-list-fields.js +1 -0
- package/src/lib/model/order-list-fields.js.map +1 -1
- package/src/lib/model/order-local-fulfillment.d.ts +131 -0
- package/src/lib/model/order-local-fulfillment.js +125 -0
- package/src/lib/model/order-local-fulfillment.js.map +1 -0
- package/src/lib/model/order-shipping-export.js +4 -0
- package/src/lib/model/order-shipping-export.js.map +1 -1
- package/src/lib/model/order-status-view.d.ts +20 -1
- package/src/lib/model/order-status-view.js +60 -6
- package/src/lib/model/order-status-view.js.map +1 -1
- package/src/lib/plugin-config.js +6 -2
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +8 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/pos-kiosk-config.d.ts +26 -0
- package/src/lib/pos-kiosk-config.js +59 -0
- package/src/lib/pos-kiosk-config.js.map +1 -0
- package/src/lib/server/api-v1/openapi.js +8 -0
- package/src/lib/server/api-v1/openapi.js.map +1 -1
- package/src/lib/server/api-v1/order-view.d.ts +19 -0
- package/src/lib/server/api-v1/order-view.js +35 -0
- package/src/lib/server/api-v1/order-view.js.map +1 -1
- package/src/lib/server/billing-webhook.js +238 -87
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/cart-checkout.d.ts +2 -0
- package/src/lib/server/cart-checkout.js +173 -13
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/checkout-extras.js +18 -4
- package/src/lib/server/checkout-extras.js.map +1 -1
- package/src/lib/server/checkout.js +71 -14
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/local-fulfillment-status.d.ts +86 -0
- package/src/lib/server/local-fulfillment-status.js +379 -0
- package/src/lib/server/local-fulfillment-status.js.map +1 -0
- package/src/lib/server/local-fulfillment.d.ts +190 -0
- package/src/lib/server/local-fulfillment.js +580 -0
- package/src/lib/server/local-fulfillment.js.map +1 -0
- package/src/lib/server/membership-register.js +3 -1
- package/src/lib/server/membership-register.js.map +1 -1
- package/src/lib/server/order-notifications.js +61 -0
- package/src/lib/server/order-notifications.js.map +1 -1
- package/src/lib/server/pos-display.d.ts +27 -1
- package/src/lib/server/pos-display.js +50 -9
- package/src/lib/server/pos-display.js.map +1 -1
- package/src/lib/server/pos-kiosk-principal.d.ts +29 -0
- package/src/lib/server/pos-kiosk-principal.js +42 -0
- package/src/lib/server/pos-kiosk-principal.js.map +1 -0
- package/src/lib/server/pos-kiosk.d.ts +54 -0
- package/src/lib/server/pos-kiosk.js +972 -0
- package/src/lib/server/pos-kiosk.js.map +1 -0
- package/src/lib/server/pos-offline-sync.d.ts +85 -0
- package/src/lib/server/pos-offline-sync.js +765 -0
- package/src/lib/server/pos-offline-sync.js.map +1 -0
- package/src/lib/server/pos-order.js +22 -4
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/pos-payment.d.ts +6 -0
- package/src/lib/server/pos-payment.js +225 -6
- package/src/lib/server/pos-payment.js.map +1 -1
- package/src/lib/server/pos-print.js +5 -0
- package/src/lib/server/pos-print.js.map +1 -1
- package/src/lib/server/pos-return.js +27 -0
- package/src/lib/server/pos-return.js.map +1 -1
- package/src/lib/server/pos-sale.d.ts +6 -0
- package/src/lib/server/pos-sale.js +2 -0
- package/src/lib/server/pos-sale.js.map +1 -1
- package/src/lib/server/pos-shift.js +8 -1
- package/src/lib/server/pos-shift.js.map +1 -1
- package/src/lib/server/refund.js +3 -0
- package/src/lib/server/refund.js.map +1 -1
- package/src/lib/server/stock-hold.d.ts +9 -1
- package/src/lib/server/stock-hold.js +15 -7
- package/src/lib/server/stock-hold.js.map +1 -1
- package/src/lib/server.js +16 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/tenant-emails.js +161 -0
- package/src/lib/tenant-emails.js.map +1 -1
|
@@ -0,0 +1,175 @@
|
|
|
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
|
+
*/
|
|
17
|
+
import { type PosReceiptChannel } from './commerce-pos';
|
|
18
|
+
import type { ModifierSelection, ProductModifierGroup } from './product-modifiers';
|
|
19
|
+
/** Which kind of paired screen a device token opens. */
|
|
20
|
+
export type PosDeviceMode = 'display' | 'kiosk';
|
|
21
|
+
/** A product as the kiosk grid shows it: no cost, no stock count, no supplier. */
|
|
22
|
+
export interface PosKioskProduct {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
imageUrl?: string;
|
|
27
|
+
categoryIds: string[];
|
|
28
|
+
/** The product's option axes ("Size": S, M, L), for the variant picker. */
|
|
29
|
+
options: Array<{
|
|
30
|
+
name: string;
|
|
31
|
+
values: string[];
|
|
32
|
+
}>;
|
|
33
|
+
variants: PosKioskVariant[];
|
|
34
|
+
modifierGroups: ProductModifierGroup[];
|
|
35
|
+
}
|
|
36
|
+
export interface PosKioskVariant {
|
|
37
|
+
id: string;
|
|
38
|
+
/** Option selections keyed by option name; empty for the default variant. */
|
|
39
|
+
options: Record<string, string>;
|
|
40
|
+
priceCents: number;
|
|
41
|
+
/** A tracked variant at or below zero whose product refuses to oversell. */
|
|
42
|
+
soldOut: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface PosKioskCategory {
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
}
|
|
48
|
+
export interface PosKioskCatalog {
|
|
49
|
+
products: PosKioskProduct[];
|
|
50
|
+
categories: PosKioskCategory[];
|
|
51
|
+
/** The store's currency, lower case (`usd`). */
|
|
52
|
+
currency: string;
|
|
53
|
+
}
|
|
54
|
+
/** What the kiosk offers, resolved on the server for one paired register. */
|
|
55
|
+
export interface PosKioskContext {
|
|
56
|
+
branding: {
|
|
57
|
+
name: string;
|
|
58
|
+
logoUrl: string | null;
|
|
59
|
+
logoDarkUrl: string | null;
|
|
60
|
+
message: string;
|
|
61
|
+
};
|
|
62
|
+
currency: string;
|
|
63
|
+
tipping: {
|
|
64
|
+
enabled: boolean;
|
|
65
|
+
percentages: number[];
|
|
66
|
+
};
|
|
67
|
+
payments: {
|
|
68
|
+
/** A Stripe Terminal smart reader is assigned to this register. */
|
|
69
|
+
reader: boolean;
|
|
70
|
+
/** Card-present payments are switched on for the platform (Tap to Pay needs the native app too). */
|
|
71
|
+
cardPresent: boolean;
|
|
72
|
+
/** "Pay at counter" sends the order to the register's queue. */
|
|
73
|
+
payAtCounter: boolean;
|
|
74
|
+
};
|
|
75
|
+
receipts: PosReceiptChannel[];
|
|
76
|
+
offerMarketing: boolean;
|
|
77
|
+
/** Seconds without a touch before the kiosk asks "Still there?". */
|
|
78
|
+
idleSeconds: number;
|
|
79
|
+
/** Stripe test mode: a test card can be tapped from the payment screen. */
|
|
80
|
+
testMode: boolean;
|
|
81
|
+
}
|
|
82
|
+
/** One cart line as the kiosk sends it: ids and a count, never a price. */
|
|
83
|
+
export interface PosKioskLine {
|
|
84
|
+
productId: string;
|
|
85
|
+
variantId?: string;
|
|
86
|
+
quantity: number;
|
|
87
|
+
modifiers?: ModifierSelection[];
|
|
88
|
+
}
|
|
89
|
+
/** A line of a priced kiosk order, as the customer's screen shows it. */
|
|
90
|
+
export interface PosKioskSaleLine {
|
|
91
|
+
name: string;
|
|
92
|
+
variantLabel?: string;
|
|
93
|
+
quantity: number;
|
|
94
|
+
amountCents: number;
|
|
95
|
+
}
|
|
96
|
+
export type PosKioskSaleStatus =
|
|
97
|
+
/** Priced and waiting for a card at the kiosk. */
|
|
98
|
+
'open'
|
|
99
|
+
/** Sent to the register's queue; a cashier takes payment. */
|
|
100
|
+
| 'queued' | 'paid' | 'voided';
|
|
101
|
+
/** A kiosk order as its own screen reads it back. */
|
|
102
|
+
export interface PosKioskSale {
|
|
103
|
+
orderId: string;
|
|
104
|
+
/** The order number the customer is called by. */
|
|
105
|
+
number: number;
|
|
106
|
+
status: PosKioskSaleStatus;
|
|
107
|
+
lines: PosKioskSaleLine[];
|
|
108
|
+
itemsCents: number;
|
|
109
|
+
discountCents: number;
|
|
110
|
+
taxCents: number;
|
|
111
|
+
totalCents: number;
|
|
112
|
+
tipCents: number;
|
|
113
|
+
paidCents: number;
|
|
114
|
+
dueCents: number;
|
|
115
|
+
/** The card payment in flight, if any. */
|
|
116
|
+
payment: {
|
|
117
|
+
id: string;
|
|
118
|
+
status: string;
|
|
119
|
+
failureMessage?: string;
|
|
120
|
+
} | null;
|
|
121
|
+
}
|
|
122
|
+
/** Seconds without a touch before the kiosk asks "Still there?", by default. */
|
|
123
|
+
export declare const POS_KIOSK_IDLE_SECONDS_DEFAULT = 90;
|
|
124
|
+
export declare const POS_KIOSK_IDLE_SECONDS_MIN = 30;
|
|
125
|
+
export declare const POS_KIOSK_IDLE_SECONDS_MAX = 600;
|
|
126
|
+
/** How long "Still there?" waits before the kiosk clears itself. */
|
|
127
|
+
export declare const POS_KIOSK_IDLE_WARNING_SECONDS = 20;
|
|
128
|
+
/** The order-number screen returns to the start after this long. */
|
|
129
|
+
export declare const POS_KIOSK_DONE_SECONDS = 15;
|
|
130
|
+
/** Most lines one kiosk order carries. */
|
|
131
|
+
export declare const POS_KIOSK_MAX_LINES = 40;
|
|
132
|
+
/** Most of one line a customer may order at a kiosk. */
|
|
133
|
+
export declare const POS_KIOSK_MAX_QUANTITY = 20;
|
|
134
|
+
/** Most products the kiosk grid lists in one category. */
|
|
135
|
+
export declare const POS_KIOSK_CATALOG_LIMIT = 200;
|
|
136
|
+
/** Wrong staff PINs a kiosk takes before it locks its unlock screen. */
|
|
137
|
+
export declare const POS_KIOSK_UNLOCK_MAX_ATTEMPTS = 5;
|
|
138
|
+
/** How long a kiosk's unlock screen stays locked after too many wrong PINs. */
|
|
139
|
+
export declare const POS_KIOSK_UNLOCK_LOCKOUT_MS: number;
|
|
140
|
+
/** How long a staff unlock keeps the kiosk's settings open. */
|
|
141
|
+
export declare const POS_KIOSK_UNLOCK_TTL_MS: number;
|
|
142
|
+
/**
|
|
143
|
+
* The cart as the kiosk may send it, or why not. Ids are shape-checked and
|
|
144
|
+
* quantities bounded here; everything else — whether the product exists, is
|
|
145
|
+
* for sale, what it costs and whether its modifiers are valid — is the sale
|
|
146
|
+
* route's to decide from the product documents.
|
|
147
|
+
*/
|
|
148
|
+
export declare function sanitizePosKioskLines(raw: unknown): {
|
|
149
|
+
ok: true;
|
|
150
|
+
lines: PosKioskLine[];
|
|
151
|
+
} | {
|
|
152
|
+
ok: false;
|
|
153
|
+
error: string;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* The tip a kiosk customer chose, recomputed from the server's own figure
|
|
157
|
+
* for what is being paid: a percentage only from the store's presets, a
|
|
158
|
+
* custom amount bounded like every register tip. Null when the choice is
|
|
159
|
+
* not one the store offers.
|
|
160
|
+
*/
|
|
161
|
+
export declare function posKioskTipCents(choice: unknown, input: {
|
|
162
|
+
baseCents: number;
|
|
163
|
+
enabled: boolean;
|
|
164
|
+
percentages: readonly number[];
|
|
165
|
+
percent?: unknown;
|
|
166
|
+
cents?: unknown;
|
|
167
|
+
}): number | null;
|
|
168
|
+
/** The idle timeout a merchant set, bounded. */
|
|
169
|
+
export declare function posKioskIdleSeconds(raw: unknown): number;
|
|
170
|
+
/**
|
|
171
|
+
* The kiosk's estimate of its cart, from the catalog prices it was given —
|
|
172
|
+
* shown while the customer shops and never sent. The order is priced again,
|
|
173
|
+
* with tax, by the server when they check out.
|
|
174
|
+
*/
|
|
175
|
+
export declare function posKioskEstimateCents(lines: readonly PosKioskLine[], products: readonly PosKioskProduct[]): number;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { posTipFromPercent, posTipProblem } from "./commerce-pos.js";
|
|
18
|
+
/** Seconds without a touch before the kiosk asks "Still there?", by default. */ export const POS_KIOSK_IDLE_SECONDS_DEFAULT = 90;
|
|
19
|
+
export const POS_KIOSK_IDLE_SECONDS_MIN = 30;
|
|
20
|
+
export const POS_KIOSK_IDLE_SECONDS_MAX = 600;
|
|
21
|
+
/** How long "Still there?" waits before the kiosk clears itself. */ export const POS_KIOSK_IDLE_WARNING_SECONDS = 20;
|
|
22
|
+
/** The order-number screen returns to the start after this long. */ export const POS_KIOSK_DONE_SECONDS = 15;
|
|
23
|
+
/** Most lines one kiosk order carries. */ export const POS_KIOSK_MAX_LINES = 40;
|
|
24
|
+
/** Most of one line a customer may order at a kiosk. */ export const POS_KIOSK_MAX_QUANTITY = 20;
|
|
25
|
+
/** Most products the kiosk grid lists in one category. */ export const POS_KIOSK_CATALOG_LIMIT = 200;
|
|
26
|
+
/** Wrong staff PINs a kiosk takes before it locks its unlock screen. */ export const POS_KIOSK_UNLOCK_MAX_ATTEMPTS = 5;
|
|
27
|
+
/** How long a kiosk's unlock screen stays locked after too many wrong PINs. */ export const POS_KIOSK_UNLOCK_LOCKOUT_MS = 15 * 60 * 1000;
|
|
28
|
+
/** How long a staff unlock keeps the kiosk's settings open. */ export const POS_KIOSK_UNLOCK_TTL_MS = 5 * 60 * 1000;
|
|
29
|
+
const ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/;
|
|
30
|
+
/**
|
|
31
|
+
* The cart as the kiosk may send it, or why not. Ids are shape-checked and
|
|
32
|
+
* quantities bounded here; everything else — whether the product exists, is
|
|
33
|
+
* for sale, what it costs and whether its modifiers are valid — is the sale
|
|
34
|
+
* route's to decide from the product documents.
|
|
35
|
+
*/ export function sanitizePosKioskLines(raw) {
|
|
36
|
+
if (!Array.isArray(raw) || raw.length === 0) return {
|
|
37
|
+
ok: false,
|
|
38
|
+
error: 'Your cart is empty.'
|
|
39
|
+
};
|
|
40
|
+
if (raw.length > POS_KIOSK_MAX_LINES) {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
error: `An order can have at most ${POS_KIOSK_MAX_LINES} items.`
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const lines = [];
|
|
47
|
+
for (const entry of raw){
|
|
48
|
+
var _ref;
|
|
49
|
+
const productId = String((_ref = entry == null ? void 0 : entry['productId']) != null ? _ref : '');
|
|
50
|
+
const variantId = (entry == null ? void 0 : entry['variantId']) == null ? '' : String(entry['variantId']);
|
|
51
|
+
const quantity = Number(entry == null ? void 0 : entry['quantity']);
|
|
52
|
+
if (!ID_PATTERN.test(productId) || variantId && !ID_PATTERN.test(variantId)) {
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
error: 'An item in your cart is no longer available.'
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (!Number.isInteger(quantity) || quantity < 1 || quantity > POS_KIOSK_MAX_QUANTITY) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
error: `Order 1 to ${POS_KIOSK_MAX_QUANTITY} of each item.`
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const rawModifiers = Array.isArray(entry == null ? void 0 : entry['modifiers']) ? entry['modifiers'] : [];
|
|
65
|
+
if (rawModifiers.length > 200) return {
|
|
66
|
+
ok: false,
|
|
67
|
+
error: 'Too many choices on one item.'
|
|
68
|
+
};
|
|
69
|
+
const modifiers = [];
|
|
70
|
+
for (const pick of rawModifiers){
|
|
71
|
+
var _ref1, _ref2;
|
|
72
|
+
const groupId = String((_ref1 = pick == null ? void 0 : pick['groupId']) != null ? _ref1 : '');
|
|
73
|
+
const optionId = String((_ref2 = pick == null ? void 0 : pick['optionId']) != null ? _ref2 : '');
|
|
74
|
+
if (!ID_PATTERN.test(groupId) || !ID_PATTERN.test(optionId)) {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
error: 'A choice on an item is no longer offered.'
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
modifiers.push({
|
|
81
|
+
groupId,
|
|
82
|
+
optionId
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
lines.push(_extends({
|
|
86
|
+
productId
|
|
87
|
+
}, variantId ? {
|
|
88
|
+
variantId
|
|
89
|
+
} : {}, {
|
|
90
|
+
quantity
|
|
91
|
+
}, modifiers.length ? {
|
|
92
|
+
modifiers
|
|
93
|
+
} : {}));
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
ok: true,
|
|
97
|
+
lines
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The tip a kiosk customer chose, recomputed from the server's own figure
|
|
102
|
+
* for what is being paid: a percentage only from the store's presets, a
|
|
103
|
+
* custom amount bounded like every register tip. Null when the choice is
|
|
104
|
+
* not one the store offers.
|
|
105
|
+
*/ export function posKioskTipCents(choice, input) {
|
|
106
|
+
if (choice === undefined || choice === null || choice === 'none') return 0;
|
|
107
|
+
if (!input.enabled) return null;
|
|
108
|
+
if (choice === 'percent') {
|
|
109
|
+
const percent = Math.round(Number(input.percent));
|
|
110
|
+
if (!input.percentages.includes(percent)) return null;
|
|
111
|
+
return posTipFromPercent(input.baseCents, percent);
|
|
112
|
+
}
|
|
113
|
+
if (choice === 'custom') {
|
|
114
|
+
const cents = Math.round(Number(input.cents));
|
|
115
|
+
return posTipProblem(cents, input.baseCents) ? null : cents;
|
|
116
|
+
}
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
/** The idle timeout a merchant set, bounded. */ export function posKioskIdleSeconds(raw) {
|
|
120
|
+
const value = Math.round(Number(raw));
|
|
121
|
+
if (!Number.isFinite(value) || value <= 0) return POS_KIOSK_IDLE_SECONDS_DEFAULT;
|
|
122
|
+
return Math.min(POS_KIOSK_IDLE_SECONDS_MAX, Math.max(POS_KIOSK_IDLE_SECONDS_MIN, value));
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The kiosk's estimate of its cart, from the catalog prices it was given —
|
|
126
|
+
* shown while the customer shops and never sent. The order is priced again,
|
|
127
|
+
* with tax, by the server when they check out.
|
|
128
|
+
*/ export function posKioskEstimateCents(lines, products) {
|
|
129
|
+
let total = 0;
|
|
130
|
+
for (const line of lines){
|
|
131
|
+
var _product_variants_find, _line_modifiers;
|
|
132
|
+
const product = products.find((candidate)=>candidate.id === line.productId);
|
|
133
|
+
if (!product) continue;
|
|
134
|
+
const variant = (_product_variants_find = product.variants.find((candidate)=>candidate.id === line.variantId)) != null ? _product_variants_find : product.variants[0];
|
|
135
|
+
if (!variant) continue;
|
|
136
|
+
const extra = ((_line_modifiers = line.modifiers) != null ? _line_modifiers : []).reduce((sum, pick)=>{
|
|
137
|
+
var _ref;
|
|
138
|
+
const group = product.modifierGroups.find((candidate)=>candidate.id === pick.groupId);
|
|
139
|
+
const option = group == null ? void 0 : group.options.find((candidate)=>candidate.id === pick.optionId);
|
|
140
|
+
return sum + ((_ref = option == null ? void 0 : option.priceCents) != null ? _ref : 0);
|
|
141
|
+
}, 0);
|
|
142
|
+
total += (variant.priceCents + extra) * line.quantity;
|
|
143
|
+
}
|
|
144
|
+
return total;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
//# sourceMappingURL=commerce-pos-kiosk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-pos-kiosk.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { posTipFromPercent, posTipProblem, type PosReceiptChannel } from './commerce-pos'\nimport type { ModifierSelection, ProductModifierGroup } from './product-modifiers'\n\n/*==========================================\n * THE SELF-SERVICE KIOSK (AGL-3623).\n *\n * A register's second kind of paired screen: where the customer display\n * (AGL-3608) mirrors the cashier's basket, a kiosk lets the customer build\n * their own order — a product grid with categories and modifiers, a cart, a\n * tip, payment on the register's card reader or Tap to Pay, or \"pay at\n * counter\", which sends the order to the register's queue for a cashier.\n *\n * It pairs exactly like a display (the same six-digit code and the same\n * hashed device token, with `mode: 'kiosk'` on both), and it never holds a\n * staff session: every request carries the device token and the server\n * prices everything. These are the shapes the kiosk route and every client\n * of it — the web kiosk and the native POS app — agree on.\n *=========================================*/\n\n/** Which kind of paired screen a device token opens. */\nexport type PosDeviceMode = 'display' | 'kiosk'\n\n/** A product as the kiosk grid shows it: no cost, no stock count, no supplier. */\nexport interface PosKioskProduct {\n id: string\n name: string\n description?: string\n imageUrl?: string\n categoryIds: string[]\n /** The product's option axes (\"Size\": S, M, L), for the variant picker. */\n options: Array<{ name: string; values: string[] }>\n variants: PosKioskVariant[]\n modifierGroups: ProductModifierGroup[]\n}\n\nexport interface PosKioskVariant {\n id: string\n /** Option selections keyed by option name; empty for the default variant. */\n options: Record<string, string>\n priceCents: number\n /** A tracked variant at or below zero whose product refuses to oversell. */\n soldOut: boolean\n}\n\nexport interface PosKioskCategory {\n id: string\n name: string\n}\n\nexport interface PosKioskCatalog {\n products: PosKioskProduct[]\n categories: PosKioskCategory[]\n /** The store's currency, lower case (`usd`). */\n currency: string\n}\n\n/** What the kiosk offers, resolved on the server for one paired register. */\nexport interface PosKioskContext {\n branding: {\n name: string\n logoUrl: string | null\n logoDarkUrl: string | null\n message: string\n }\n currency: string\n tipping: { enabled: boolean; percentages: number[] }\n payments: {\n /** A Stripe Terminal smart reader is assigned to this register. */\n reader: boolean\n /** Card-present payments are switched on for the platform (Tap to Pay needs the native app too). */\n cardPresent: boolean\n /** \"Pay at counter\" sends the order to the register's queue. */\n payAtCounter: boolean\n }\n receipts: PosReceiptChannel[]\n offerMarketing: boolean\n /** Seconds without a touch before the kiosk asks \"Still there?\". */\n idleSeconds: number\n /** Stripe test mode: a test card can be tapped from the payment screen. */\n testMode: boolean\n}\n\n/** One cart line as the kiosk sends it: ids and a count, never a price. */\nexport interface PosKioskLine {\n productId: string\n variantId?: string\n quantity: number\n modifiers?: ModifierSelection[]\n}\n\n/** A line of a priced kiosk order, as the customer's screen shows it. */\nexport interface PosKioskSaleLine {\n name: string\n variantLabel?: string\n quantity: number\n amountCents: number\n}\n\nexport type PosKioskSaleStatus =\n /** Priced and waiting for a card at the kiosk. */\n | 'open'\n /** Sent to the register's queue; a cashier takes payment. */\n | 'queued'\n | 'paid'\n | 'voided'\n\n/** A kiosk order as its own screen reads it back. */\nexport interface PosKioskSale {\n orderId: string\n /** The order number the customer is called by. */\n number: number\n status: PosKioskSaleStatus\n lines: PosKioskSaleLine[]\n itemsCents: number\n discountCents: number\n taxCents: number\n totalCents: number\n tipCents: number\n paidCents: number\n dueCents: number\n /** The card payment in flight, if any. */\n payment: { id: string; status: string; failureMessage?: string } | null\n}\n\n/** Seconds without a touch before the kiosk asks \"Still there?\", by default. */\nexport const POS_KIOSK_IDLE_SECONDS_DEFAULT = 90\nexport const POS_KIOSK_IDLE_SECONDS_MIN = 30\nexport const POS_KIOSK_IDLE_SECONDS_MAX = 600\n\n/** How long \"Still there?\" waits before the kiosk clears itself. */\nexport const POS_KIOSK_IDLE_WARNING_SECONDS = 20\n\n/** The order-number screen returns to the start after this long. */\nexport const POS_KIOSK_DONE_SECONDS = 15\n\n/** Most lines one kiosk order carries. */\nexport const POS_KIOSK_MAX_LINES = 40\n\n/** Most of one line a customer may order at a kiosk. */\nexport const POS_KIOSK_MAX_QUANTITY = 20\n\n/** Most products the kiosk grid lists in one category. */\nexport const POS_KIOSK_CATALOG_LIMIT = 200\n\n/** Wrong staff PINs a kiosk takes before it locks its unlock screen. */\nexport const POS_KIOSK_UNLOCK_MAX_ATTEMPTS = 5\n\n/** How long a kiosk's unlock screen stays locked after too many wrong PINs. */\nexport const POS_KIOSK_UNLOCK_LOCKOUT_MS = 15 * 60 * 1000\n\n/** How long a staff unlock keeps the kiosk's settings open. */\nexport const POS_KIOSK_UNLOCK_TTL_MS = 5 * 60 * 1000\n\nconst ID_PATTERN = /^[A-Za-z0-9_-]{1,128}$/\n\n/**\n * The cart as the kiosk may send it, or why not. Ids are shape-checked and\n * quantities bounded here; everything else — whether the product exists, is\n * for sale, what it costs and whether its modifiers are valid — is the sale\n * route's to decide from the product documents.\n */\nexport function sanitizePosKioskLines(\n raw: unknown,\n): { ok: true; lines: PosKioskLine[] } | { ok: false; error: string } {\n if (!Array.isArray(raw) || raw.length === 0) return { ok: false, error: 'Your cart is empty.' }\n if (raw.length > POS_KIOSK_MAX_LINES) {\n return { ok: false, error: `An order can have at most ${POS_KIOSK_MAX_LINES} items.` }\n }\n const lines: PosKioskLine[] = []\n for (const entry of raw as Array<Record<string, unknown>>) {\n const productId = String(entry?.['productId'] ?? '')\n const variantId = entry?.['variantId'] == null ? '' : String(entry['variantId'])\n const quantity = Number(entry?.['quantity'])\n if (!ID_PATTERN.test(productId) || (variantId && !ID_PATTERN.test(variantId))) {\n return { ok: false, error: 'An item in your cart is no longer available.' }\n }\n if (!Number.isInteger(quantity) || quantity < 1 || quantity > POS_KIOSK_MAX_QUANTITY) {\n return { ok: false, error: `Order 1 to ${POS_KIOSK_MAX_QUANTITY} of each item.` }\n }\n const rawModifiers = Array.isArray(entry?.['modifiers']) ? (entry['modifiers'] as unknown[]) : []\n if (rawModifiers.length > 200) return { ok: false, error: 'Too many choices on one item.' }\n const modifiers: ModifierSelection[] = []\n for (const pick of rawModifiers as Array<Record<string, unknown>>) {\n const groupId = String(pick?.['groupId'] ?? '')\n const optionId = String(pick?.['optionId'] ?? '')\n if (!ID_PATTERN.test(groupId) || !ID_PATTERN.test(optionId)) {\n return { ok: false, error: 'A choice on an item is no longer offered.' }\n }\n modifiers.push({ groupId, optionId })\n }\n lines.push({\n productId,\n ...(variantId ? { variantId } : {}),\n quantity,\n ...(modifiers.length ? { modifiers } : {}),\n })\n }\n return { ok: true, lines }\n}\n\n/**\n * The tip a kiosk customer chose, recomputed from the server's own figure\n * for what is being paid: a percentage only from the store's presets, a\n * custom amount bounded like every register tip. Null when the choice is\n * not one the store offers.\n */\nexport function posKioskTipCents(\n choice: unknown,\n input: { baseCents: number; enabled: boolean; percentages: readonly number[]; percent?: unknown; cents?: unknown },\n): number | null {\n if (choice === undefined || choice === null || choice === 'none') return 0\n if (!input.enabled) return null\n if (choice === 'percent') {\n const percent = Math.round(Number(input.percent))\n if (!input.percentages.includes(percent)) return null\n return posTipFromPercent(input.baseCents, percent)\n }\n if (choice === 'custom') {\n const cents = Math.round(Number(input.cents))\n return posTipProblem(cents, input.baseCents) ? null : cents\n }\n return null\n}\n\n/** The idle timeout a merchant set, bounded. */\nexport function posKioskIdleSeconds(raw: unknown): number {\n const value = Math.round(Number(raw))\n if (!Number.isFinite(value) || value <= 0) return POS_KIOSK_IDLE_SECONDS_DEFAULT\n return Math.min(POS_KIOSK_IDLE_SECONDS_MAX, Math.max(POS_KIOSK_IDLE_SECONDS_MIN, value))\n}\n\n/**\n * The kiosk's estimate of its cart, from the catalog prices it was given —\n * shown while the customer shops and never sent. The order is priced again,\n * with tax, by the server when they check out.\n */\nexport function posKioskEstimateCents(\n lines: readonly PosKioskLine[],\n products: readonly PosKioskProduct[],\n): number {\n let total = 0\n for (const line of lines) {\n const product = products.find((candidate) => candidate.id === line.productId)\n if (!product) continue\n const variant =\n product.variants.find((candidate) => candidate.id === line.variantId) ?? product.variants[0]\n if (!variant) continue\n const extra = (line.modifiers ?? []).reduce((sum, pick) => {\n const group = product.modifierGroups.find((candidate) => candidate.id === pick.groupId)\n const option = group?.options.find((candidate) => candidate.id === pick.optionId)\n return sum + (option?.priceCents ?? 0)\n }, 0)\n total += (variant.priceCents + extra) * line.quantity\n }\n return total\n}\n"],"names":["posTipFromPercent","posTipProblem","POS_KIOSK_IDLE_SECONDS_DEFAULT","POS_KIOSK_IDLE_SECONDS_MIN","POS_KIOSK_IDLE_SECONDS_MAX","POS_KIOSK_IDLE_WARNING_SECONDS","POS_KIOSK_DONE_SECONDS","POS_KIOSK_MAX_LINES","POS_KIOSK_MAX_QUANTITY","POS_KIOSK_CATALOG_LIMIT","POS_KIOSK_UNLOCK_MAX_ATTEMPTS","POS_KIOSK_UNLOCK_LOCKOUT_MS","POS_KIOSK_UNLOCK_TTL_MS","ID_PATTERN","sanitizePosKioskLines","raw","Array","isArray","length","ok","error","lines","entry","productId","String","variantId","quantity","Number","test","isInteger","rawModifiers","modifiers","pick","groupId","optionId","push","posKioskTipCents","choice","input","undefined","enabled","percent","Math","round","percentages","includes","baseCents","cents","posKioskIdleSeconds","value","isFinite","min","max","posKioskEstimateCents","products","total","line","product","find","candidate","id","variant","variants","extra","reduce","sum","group","modifierGroups","option","options","priceCents"],"mappings":"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"}
|
|
@@ -0,0 +1,234 @@
|
|
|
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
|
+
*/
|
|
17
|
+
import { type OrderTotals } from './commerce-orders';
|
|
18
|
+
import type { ModifierSelection } from './product-modifiers';
|
|
19
|
+
/** The route both register clients sync through. */
|
|
20
|
+
export declare const POS_OFFLINE_SYNC_ROUTE = "/api/commerce/pos-offline-sync";
|
|
21
|
+
/** The most sales one sync request carries; a longer queue sends in batches. */
|
|
22
|
+
export declare const POS_OFFLINE_SYNC_BATCH_MAX = 25;
|
|
23
|
+
/** Lines one offline sale may carry, the same bound the register's basket has. */
|
|
24
|
+
export declare const POS_OFFLINE_SALE_MAX_LINES = 100;
|
|
25
|
+
/** A sale synced later than this after it was rung is flagged `late-sync`. */
|
|
26
|
+
export declare const POS_OFFLINE_LATE_SYNC_MS: number;
|
|
27
|
+
/**
|
|
28
|
+
* How far back a sale's own clock is believed. Older, and the server stamps
|
|
29
|
+
* the sale at this bound and flags it, rather than letting a device clock
|
|
30
|
+
* place a sale anywhere in history.
|
|
31
|
+
*/
|
|
32
|
+
export declare const POS_OFFLINE_MAX_AGE_MS: number;
|
|
33
|
+
/** How far ahead of the server a device clock may run before it is clamped. */
|
|
34
|
+
export declare const POS_OFFLINE_CLOCK_SKEW_MS: number;
|
|
35
|
+
/** The one tender an offline sale takes. */
|
|
36
|
+
export type PosOfflineTender = 'cash';
|
|
37
|
+
/** Every tender the register turns off while offline, and why, in its words. */
|
|
38
|
+
export declare const POS_OFFLINE_DISABLED_TENDERS: Readonly<Record<string, string>>;
|
|
39
|
+
/** One basket line as the register rang it. */
|
|
40
|
+
export interface PosOfflineSaleLine {
|
|
41
|
+
productId: string;
|
|
42
|
+
variantId?: string;
|
|
43
|
+
name: string;
|
|
44
|
+
/** Variant options and modifier choices, as the receipt printed them. */
|
|
45
|
+
variantLabel?: string;
|
|
46
|
+
sku?: string;
|
|
47
|
+
productType?: 'physical' | 'digital' | 'service';
|
|
48
|
+
quantity: number;
|
|
49
|
+
/** Per unit, modifiers included, from the cached catalog. */
|
|
50
|
+
unitAmountCents: number;
|
|
51
|
+
modifiers?: ModifierSelection[];
|
|
52
|
+
}
|
|
53
|
+
/** The customer the register attached: only what the sale and its receipt need. */
|
|
54
|
+
export interface PosOfflineSaleCustomer {
|
|
55
|
+
email?: string;
|
|
56
|
+
name?: string;
|
|
57
|
+
kind?: string;
|
|
58
|
+
id?: string;
|
|
59
|
+
}
|
|
60
|
+
/** One sale rung while offline, exactly as the register queued it. */
|
|
61
|
+
export interface PosOfflineSale {
|
|
62
|
+
v: 1;
|
|
63
|
+
/** The register's key for this sale, minted once; the order's id on the server. */
|
|
64
|
+
saleKey: string;
|
|
65
|
+
hostId: string;
|
|
66
|
+
/** The workspace the register was signed in to. */
|
|
67
|
+
orgId: string;
|
|
68
|
+
/** Who was signed in on the device: the only member who may sync it. */
|
|
69
|
+
signedInUid: string;
|
|
70
|
+
registerId: string;
|
|
71
|
+
/** The shift open on the register when the sale was rung. */
|
|
72
|
+
shiftId?: string | null;
|
|
73
|
+
locationId?: string;
|
|
74
|
+
/** A PIN-switched cashier's assertion, valid when the sale was rung. */
|
|
75
|
+
cashierAssertion?: string;
|
|
76
|
+
/** The device's clock when the cash was taken. */
|
|
77
|
+
soldAtMs: number;
|
|
78
|
+
lines: PosOfflineSaleLine[];
|
|
79
|
+
/** The cashier's discount, a percentage of the goods. */
|
|
80
|
+
discountPct: number;
|
|
81
|
+
/** The totals the receipt printed and the customer paid. */
|
|
82
|
+
totals: OrderTotals;
|
|
83
|
+
cashTenderedCents: number;
|
|
84
|
+
changeCents: number;
|
|
85
|
+
customer?: PosOfflineSaleCustomer;
|
|
86
|
+
}
|
|
87
|
+
/** What does not match, said on the order and back to the register. */
|
|
88
|
+
export type PosOfflineFlag = 'price-changed' | 'unknown-product' | 'tax-differs' | 'totals-restated' | 'discount-over-limit' | 'cash-short' | 'stock-short' | 'shift-closed' | 'no-shift' | 'unknown-register' | 'register-over-cap' | 'late-sync' | 'clock-adjusted' | 'cashier-unverified';
|
|
89
|
+
/** Each flag in the words the order and the register show. */
|
|
90
|
+
export declare const POS_OFFLINE_FLAG_LABELS: Record<PosOfflineFlag, string>;
|
|
91
|
+
/** A line whose stock went short when the sale synced. */
|
|
92
|
+
export interface PosOfflineStockConflict {
|
|
93
|
+
productId: string;
|
|
94
|
+
variantId?: string;
|
|
95
|
+
name: string;
|
|
96
|
+
/** Units sold. */
|
|
97
|
+
requested: number;
|
|
98
|
+
/** Units the shelf actually gave up. */
|
|
99
|
+
applied: number;
|
|
100
|
+
/** Units sold that the shelf did not hold. */
|
|
101
|
+
shortUnits: number;
|
|
102
|
+
}
|
|
103
|
+
/** What the order records about having been rung offline. */
|
|
104
|
+
export interface PosOfflineOrderStamp {
|
|
105
|
+
saleKey: string;
|
|
106
|
+
/** The device's clock when the cash was taken. */
|
|
107
|
+
soldAtMs: number;
|
|
108
|
+
syncedAtMs: number;
|
|
109
|
+
/** Who synced it: always the member who was signed in when it was rung. */
|
|
110
|
+
syncedBy: string;
|
|
111
|
+
flags: PosOfflineFlag[];
|
|
112
|
+
stockConflicts?: PosOfflineStockConflict[];
|
|
113
|
+
/** The server's own price per line where it differed, for the merchant to compare. */
|
|
114
|
+
priceDrift?: Array<{
|
|
115
|
+
index: number;
|
|
116
|
+
rungCents: number;
|
|
117
|
+
currentCents: number;
|
|
118
|
+
}>;
|
|
119
|
+
}
|
|
120
|
+
/** Why a sale was refused, and whether the register should keep trying. */
|
|
121
|
+
export type PosOfflineRefusal = 'wrong-staff' | 'wrong-site' | 'wrong-workspace' | 'invalid' | 'conflict' | 'failed';
|
|
122
|
+
/** What became of one sale the register sent. */
|
|
123
|
+
export type PosOfflineOutcomeStatus = 'recorded' | 'replayed' | 'refused';
|
|
124
|
+
/**
|
|
125
|
+
* One sale's answer, in the order the request sent them. `recorded` and
|
|
126
|
+
* `replayed` carry the order; `refused` carries the reason, the sentence and
|
|
127
|
+
* whether to keep trying. One flat shape, so every register client — the web
|
|
128
|
+
* register and the native apps — decodes it the same way.
|
|
129
|
+
*/
|
|
130
|
+
export interface PosOfflineSaleOutcome {
|
|
131
|
+
saleKey: string;
|
|
132
|
+
status: PosOfflineOutcomeStatus;
|
|
133
|
+
orderId?: string;
|
|
134
|
+
number?: number | null;
|
|
135
|
+
flags?: PosOfflineFlag[];
|
|
136
|
+
stockConflicts?: PosOfflineStockConflict[];
|
|
137
|
+
reason?: PosOfflineRefusal;
|
|
138
|
+
error?: string;
|
|
139
|
+
/** Keep it queued and try again later (a transient failure). */
|
|
140
|
+
retry?: boolean;
|
|
141
|
+
}
|
|
142
|
+
export interface PosOfflineSyncRequest {
|
|
143
|
+
hostId: string;
|
|
144
|
+
sales: PosOfflineSale[];
|
|
145
|
+
}
|
|
146
|
+
export interface PosOfflineSyncResponse {
|
|
147
|
+
results: PosOfflineSaleOutcome[];
|
|
148
|
+
}
|
|
149
|
+
/** One register as the offline kit lists it. */
|
|
150
|
+
export interface PosOfflineKitRegister {
|
|
151
|
+
id: string;
|
|
152
|
+
name: string;
|
|
153
|
+
locationId?: string;
|
|
154
|
+
openShiftId: string | null;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Everything a register needs to sell offline besides the catalog, from
|
|
158
|
+
* `GET /api/commerce/pos-offline-sync?hostId=`: the server's own answers, so
|
|
159
|
+
* the register and the sync agree about the rate and the rules.
|
|
160
|
+
*/
|
|
161
|
+
export interface PosOfflineKit {
|
|
162
|
+
v: 1;
|
|
163
|
+
hostId: string;
|
|
164
|
+
orgId: string;
|
|
165
|
+
issuedAtMs: number;
|
|
166
|
+
/** Offline selling is possible for this site right now. */
|
|
167
|
+
available: boolean;
|
|
168
|
+
/** Why not, in the cashier's words, when it is not. */
|
|
169
|
+
unavailableReason?: string;
|
|
170
|
+
/** The store's own in-person rate; `null` pct is no tax. */
|
|
171
|
+
tax: {
|
|
172
|
+
pct: number | null;
|
|
173
|
+
pricesIncludeTax: boolean;
|
|
174
|
+
};
|
|
175
|
+
maxDiscountPct: number;
|
|
176
|
+
requireOpenShift: boolean;
|
|
177
|
+
registers: PosOfflineKitRegister[];
|
|
178
|
+
/** What the receipt prints at its head and foot. `logo` is the stored value. */
|
|
179
|
+
receipt: {
|
|
180
|
+
name: string;
|
|
181
|
+
logo?: string;
|
|
182
|
+
address?: string;
|
|
183
|
+
footer?: string;
|
|
184
|
+
returnPolicy?: string;
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The totals of an offline sale: the cashier's discount on the goods, then
|
|
189
|
+
* the store's in-person tax on what is left — exactly the arithmetic the
|
|
190
|
+
* online register's sale route uses for the same basket with no promotion.
|
|
191
|
+
* `pricesIncludeTax` adds nothing, as at the online till.
|
|
192
|
+
*/
|
|
193
|
+
export declare function posOfflineSaleTotals(input: {
|
|
194
|
+
lines: ReadonlyArray<Pick<PosOfflineSaleLine, 'unitAmountCents' | 'quantity'>>;
|
|
195
|
+
discountPct: number;
|
|
196
|
+
tax: {
|
|
197
|
+
pct: number | null;
|
|
198
|
+
pricesIncludeTax: boolean;
|
|
199
|
+
};
|
|
200
|
+
}): OrderTotals;
|
|
201
|
+
/** Change for cash handed over, never negative. */
|
|
202
|
+
export declare function posOfflineChangeCents(totalCents: number, tenderedCents: number): number;
|
|
203
|
+
/** A key the register minted: an id the order can take, and nothing else. */
|
|
204
|
+
export declare function posOfflineSaleKeyIsValid(key: unknown): key is string;
|
|
205
|
+
/**
|
|
206
|
+
* The sale's time as the server records it: the device's clock when it is
|
|
207
|
+
* believable, clamped to now when it runs ahead, and to the oldest believable
|
|
208
|
+
* moment when it claims to be older.
|
|
209
|
+
*/
|
|
210
|
+
export declare function posOfflineSoldAt(soldAtMs: number, nowMs: number): {
|
|
211
|
+
atMs: number;
|
|
212
|
+
adjusted: boolean;
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* A queued sale read off the wire, every field typed and bounded — or the
|
|
216
|
+
* sentence that says why it cannot be read at all. Pure, so the server and
|
|
217
|
+
* any client that wants to check its own queue answer the same.
|
|
218
|
+
*/
|
|
219
|
+
export declare function sanitizePosOfflineSale(raw: unknown): {
|
|
220
|
+
ok: true;
|
|
221
|
+
sale: PosOfflineSale;
|
|
222
|
+
} | {
|
|
223
|
+
ok: false;
|
|
224
|
+
error: string;
|
|
225
|
+
};
|
|
226
|
+
/**
|
|
227
|
+
* Whether the register's totals are the arithmetic of its own lines: the
|
|
228
|
+
* goods are the lines' sum, the discount is the percentage of them, and the
|
|
229
|
+
* total is goods less discount plus the tax it rang. The tax itself is the
|
|
230
|
+
* register's, and checked against the store's rate separately.
|
|
231
|
+
*/
|
|
232
|
+
export declare function posOfflineTotalsAddUp(sale: Pick<PosOfflineSale, 'lines' | 'discountPct' | 'totals'>): boolean;
|
|
233
|
+
/** The flags in the order and words the register lists them. */
|
|
234
|
+
export declare function posOfflineFlagLabels(flags: readonly PosOfflineFlag[]): string[];
|