@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.
- 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,765 @@
|
|
|
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 * as Aglyn from "@aglyn/aglyn/server";
|
|
18
|
+
import recordCapturedContact from "@aglyn/aglyn/plugin-manager/record-captured-contact";
|
|
19
|
+
import { FieldValue } from "firebase-admin/firestore";
|
|
20
|
+
import * as CommerceModel from "../model/index.js";
|
|
21
|
+
import { posOfflineFlagLabels, posOfflineSaleTotals, posOfflineSoldAt, posOfflineTotalsAddUp, POS_OFFLINE_LATE_SYNC_MS, POS_OFFLINE_SYNC_BATCH_MAX, sanitizePosOfflineSale } from "../model/commerce-pos-offline.js";
|
|
22
|
+
import { ORDER_PAID_EVENT } from "../model/order-events.js";
|
|
23
|
+
import { posMaxDiscountPct } from "../plugin-config.js";
|
|
24
|
+
import { alertLowStockCrossing } from "./low-stock.js";
|
|
25
|
+
import { notifyOrderBuyer } from "./order-notifications.js";
|
|
26
|
+
import { raiseOrderEvent } from "./order-events.js";
|
|
27
|
+
import { offlineFeeMonthKey } from "./pos-fee-month.js";
|
|
28
|
+
import { authorizePosOps, defaultPosOpsDeps, posOpsBody, posOpsCleanId, resolvePosStaffAssertion } from "./pos-ops-gate.js";
|
|
29
|
+
import { notifyPosSaleCompleted } from "./pos-sale.js";
|
|
30
|
+
import { decrementVariantStock } from "./reserve-stock.js";
|
|
31
|
+
export function defaultPosOfflineSyncDeps() {
|
|
32
|
+
const base = defaultPosOpsDeps();
|
|
33
|
+
return _extends({}, base, {
|
|
34
|
+
feePct: (org, productType)=>Aglyn.resolveTransactionFeePct(org, productType),
|
|
35
|
+
registerWithinCap: (org, hostId, rank)=>Aglyn.checkHostRegisterQuota(org, hostId, rank).allowed,
|
|
36
|
+
decrementStock: async (input)=>{
|
|
37
|
+
const moved = await decrementVariantStock(_extends({
|
|
38
|
+
firestore: base.firestore(),
|
|
39
|
+
hostRef: input.hostRef,
|
|
40
|
+
hostId: input.hostId,
|
|
41
|
+
productId: input.productId,
|
|
42
|
+
variantId: input.variantId,
|
|
43
|
+
quantity: input.quantity
|
|
44
|
+
}, input.locationId ? {
|
|
45
|
+
locationId: input.locationId
|
|
46
|
+
} : {}, {
|
|
47
|
+
ledger: {
|
|
48
|
+
reason: 'sale',
|
|
49
|
+
orderId: input.orderId
|
|
50
|
+
}
|
|
51
|
+
}));
|
|
52
|
+
if (moved.before && moved.after) alertLowStockCrossing(input.hostId, moved.before, moved.after);
|
|
53
|
+
return moved;
|
|
54
|
+
},
|
|
55
|
+
afterSale: async (input)=>{
|
|
56
|
+
const settle = async (label, work)=>{
|
|
57
|
+
try {
|
|
58
|
+
await work();
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(`[pos-offline-sync] ${label} failed`, input.orderId, error);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
if (input.email) {
|
|
64
|
+
await settle('contact', ()=>{
|
|
65
|
+
var _ref, _ref1;
|
|
66
|
+
var _input_order_totals, _input_order_totals1;
|
|
67
|
+
return recordCapturedContact({
|
|
68
|
+
orgId: '',
|
|
69
|
+
hostId: input.hostId,
|
|
70
|
+
identity: _extends({
|
|
71
|
+
email: input.email
|
|
72
|
+
}, input.name ? {
|
|
73
|
+
name: input.name
|
|
74
|
+
} : {}),
|
|
75
|
+
surface: 'relationship',
|
|
76
|
+
lifecycleFloor: 'customer',
|
|
77
|
+
purchaseCents: Number((_ref = (_input_order_totals = input.order.totals) == null ? void 0 : _input_order_totals.totalCents) != null ? _ref : 0),
|
|
78
|
+
interaction: {
|
|
79
|
+
source: 'order',
|
|
80
|
+
refId: input.orderId,
|
|
81
|
+
summary: `In-store purchase ($${(Number((_ref1 = (_input_order_totals1 = input.order.totals) == null ? void 0 : _input_order_totals1.totalCents) != null ? _ref1 : 0) / 100).toFixed(2)})`
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// The sale-completed listeners hear it too, told it was rung offline:
|
|
87
|
+
// the cloud printers must not print a receipt or pop the drawer for a
|
|
88
|
+
// sale the customer walked away with hours ago.
|
|
89
|
+
await settle('sale completed', ()=>notifyPosSaleCompleted({
|
|
90
|
+
hostId: input.hostId,
|
|
91
|
+
orderId: input.orderId,
|
|
92
|
+
order: input.order,
|
|
93
|
+
offline: true
|
|
94
|
+
}));
|
|
95
|
+
// The emailed receipt sends now, on sync (the printed one went offline).
|
|
96
|
+
await settle('receipt', ()=>notifyOrderBuyer({
|
|
97
|
+
hostId: input.hostId,
|
|
98
|
+
orderId: input.orderId
|
|
99
|
+
}, 'receipt', {
|
|
100
|
+
email: input.email
|
|
101
|
+
}));
|
|
102
|
+
await settle('paid event', ()=>raiseOrderEvent(ORDER_PAID_EVENT, {
|
|
103
|
+
hostId: input.hostId,
|
|
104
|
+
orderId: input.orderId,
|
|
105
|
+
key: 'paid'
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const money = (cents)=>`$${(Math.max(0, cents) / 100).toFixed(2)}`;
|
|
111
|
+
/** The store's in-person tax, as every register sale computes it. */ export function posInPersonTax(settings) {
|
|
112
|
+
var _taxSettings_origin;
|
|
113
|
+
const taxSettings = settings != null ? settings : {};
|
|
114
|
+
const decision = CommerceModel.storefrontTaxDecision({
|
|
115
|
+
settings: taxSettings
|
|
116
|
+
});
|
|
117
|
+
if (decision.kind === 'undecided') {
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
reason: CommerceModel.STOREFRONT_TAX_UNDECIDED_MESSAGE
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const misconfigured = CommerceModel.storefrontTaxMisconfiguration(taxSettings, {
|
|
124
|
+
inPerson: true
|
|
125
|
+
});
|
|
126
|
+
if (misconfigured) return {
|
|
127
|
+
ok: false,
|
|
128
|
+
reason: misconfigured
|
|
129
|
+
};
|
|
130
|
+
const rate = decision.kind === 'manual' ? CommerceModel.resolveTaxRate(taxSettings, (_taxSettings_origin = taxSettings.origin) != null ? _taxSettings_origin : {}) : null;
|
|
131
|
+
return {
|
|
132
|
+
ok: true,
|
|
133
|
+
pct: rate && rate.pct > 0 ? rate.pct : null,
|
|
134
|
+
pricesIncludeTax: taxSettings.pricesIncludeTax === true,
|
|
135
|
+
decision
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
async function readRegisters(staff) {
|
|
139
|
+
const docs = (await staff.hostRef.collection('registers').get()).docs.map((doc)=>{
|
|
140
|
+
var _doc_data, _ref, _ref1;
|
|
141
|
+
var _doc_get_toMillis, _doc_get;
|
|
142
|
+
return {
|
|
143
|
+
id: doc.id,
|
|
144
|
+
data: (_doc_data = doc.data()) != null ? _doc_data : {},
|
|
145
|
+
createdAtMs: Number((_ref = (_ref1 = (_doc_get = doc.get('createdAt')) == null ? void 0 : (_doc_get_toMillis = _doc_get.toMillis) == null ? void 0 : _doc_get_toMillis.call(_doc_get)) != null ? _ref1 : doc.get('createdAtMs')) != null ? _ref : 0)
|
|
146
|
+
};
|
|
147
|
+
}).sort((a, b)=>a.createdAtMs - b.createdAtMs || a.id.localeCompare(b.id));
|
|
148
|
+
return new Map(docs.map((doc, rank)=>[
|
|
149
|
+
doc.id,
|
|
150
|
+
{
|
|
151
|
+
rank,
|
|
152
|
+
data: doc.data
|
|
153
|
+
}
|
|
154
|
+
]));
|
|
155
|
+
}
|
|
156
|
+
async function readStoreTax(staff) {
|
|
157
|
+
var _store_get;
|
|
158
|
+
const store = await staff.hostRef.collection('settings').doc('store').get();
|
|
159
|
+
return (_store_get = store.get('tax')) != null ? _store_get : undefined;
|
|
160
|
+
}
|
|
161
|
+
/** The kit a register caches to sell offline. */ export async function buildPosOfflineKit(deps, staff) {
|
|
162
|
+
var _host_get;
|
|
163
|
+
const [registers, taxSettings, config, host, store] = await Promise.all([
|
|
164
|
+
readRegisters(staff),
|
|
165
|
+
readStoreTax(staff),
|
|
166
|
+
staff.orgId ? deps.pluginConfig(staff.orgId, staff.hostId).catch(()=>({})) : Promise.resolve({}),
|
|
167
|
+
staff.hostRef.get(),
|
|
168
|
+
staff.hostRef.collection('settings').doc('store').get()
|
|
169
|
+
]);
|
|
170
|
+
const tax = posInPersonTax(taxSettings);
|
|
171
|
+
return _extends({
|
|
172
|
+
v: 1,
|
|
173
|
+
hostId: staff.hostId,
|
|
174
|
+
orgId: staff.orgId,
|
|
175
|
+
issuedAtMs: deps.now(),
|
|
176
|
+
available: tax.ok
|
|
177
|
+
}, 'reason' in tax ? {
|
|
178
|
+
unavailableReason: tax.reason
|
|
179
|
+
} : {}, {
|
|
180
|
+
tax: 'decision' in tax ? {
|
|
181
|
+
pct: tax.pct,
|
|
182
|
+
pricesIncludeTax: tax.pricesIncludeTax
|
|
183
|
+
} : {
|
|
184
|
+
pct: null,
|
|
185
|
+
pricesIncludeTax: false
|
|
186
|
+
},
|
|
187
|
+
maxDiscountPct: posMaxDiscountPct(config),
|
|
188
|
+
requireOpenShift: staff.settings.requireOpenShift,
|
|
189
|
+
registers: [
|
|
190
|
+
...registers.entries()
|
|
191
|
+
].filter(([, register])=>deps.registerWithinCap(staff.org, staff.hostId, register.rank)).map(([id, register])=>{
|
|
192
|
+
var _register_data_name;
|
|
193
|
+
return _extends({
|
|
194
|
+
id,
|
|
195
|
+
name: String((_register_data_name = register.data['name']) != null ? _register_data_name : 'Register').slice(0, 120)
|
|
196
|
+
}, register.data['locationId'] ? {
|
|
197
|
+
locationId: String(register.data['locationId'])
|
|
198
|
+
} : {}, {
|
|
199
|
+
openShiftId: posOpsCleanId(register.data['openShiftId']) || null
|
|
200
|
+
});
|
|
201
|
+
}),
|
|
202
|
+
receipt: _extends({
|
|
203
|
+
name: String((_host_get = host.get('displayName')) != null ? _host_get : '') || 'Receipt'
|
|
204
|
+
}, typeof host.get('logoUrl') === 'string' && host.get('logoUrl') ? {
|
|
205
|
+
logo: String(host.get('logoUrl'))
|
|
206
|
+
} : {}, staff.settings.receiptAddress ? {
|
|
207
|
+
address: staff.settings.receiptAddress
|
|
208
|
+
} : {}, store.get('receiptFooter') ? {
|
|
209
|
+
footer: String(store.get('receiptFooter')).slice(0, 500)
|
|
210
|
+
} : {}, staff.settings.returnPolicy ? {
|
|
211
|
+
returnPolicy: staff.settings.returnPolicy
|
|
212
|
+
} : {})
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
const refused = (saleKey, reason, error, retry)=>({
|
|
216
|
+
saleKey,
|
|
217
|
+
status: 'refused',
|
|
218
|
+
reason,
|
|
219
|
+
error,
|
|
220
|
+
retry
|
|
221
|
+
});
|
|
222
|
+
const replayOf = (record)=>({
|
|
223
|
+
saleKey: record.saleKey,
|
|
224
|
+
status: 'replayed',
|
|
225
|
+
orderId: record.orderId,
|
|
226
|
+
number: record.number,
|
|
227
|
+
flags: record.flags,
|
|
228
|
+
stockConflicts: record.stockConflicts
|
|
229
|
+
});
|
|
230
|
+
/** The take the plan owes on the goods after the discount (AGL-2111, AGL-2256). */ function takeFeeCents(deps, staff, lines, totals) {
|
|
231
|
+
let applies = false;
|
|
232
|
+
const gross = lines.reduce((sum, line)=>{
|
|
233
|
+
var _line_productType;
|
|
234
|
+
const pct = deps.feePct(staff.org, (_line_productType = line.productType) != null ? _line_productType : 'physical');
|
|
235
|
+
if (pct > 0) applies = true;
|
|
236
|
+
return sum + Math.round(line.unitAmountCents * line.quantity * pct / 100);
|
|
237
|
+
}, 0);
|
|
238
|
+
const charged = Math.max(0, totals.itemsCents - totals.discountCents);
|
|
239
|
+
const scaled = totals.itemsCents > 0 ? Math.round(gross * charged / totals.itemsCents) : 0;
|
|
240
|
+
return applies && charged > 0 ? Math.max(1, scaled) : scaled;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* The steps after the commit, each recorded as it finishes. Run by the sync
|
|
244
|
+
* that recorded the sale, and again by any later sync of it that finds them
|
|
245
|
+
* unfinished.
|
|
246
|
+
*/ async function finishSale(context, recordRef, orderRef, record) {
|
|
247
|
+
var _orderSnapshot_data;
|
|
248
|
+
const { deps, staff } = context;
|
|
249
|
+
const orderSnapshot = await orderRef.get();
|
|
250
|
+
const order = (_orderSnapshot_data = orderSnapshot.data()) != null ? _orderSnapshot_data : {};
|
|
251
|
+
let current = record;
|
|
252
|
+
if (!current.stockDone) {
|
|
253
|
+
var _order_lineItems;
|
|
254
|
+
const conflicts = [
|
|
255
|
+
...current.stockConflicts
|
|
256
|
+
];
|
|
257
|
+
const done = new Set(current.stockLines);
|
|
258
|
+
const lines = (_order_lineItems = order.lineItems) != null ? _order_lineItems : [];
|
|
259
|
+
for (const [index, line] of lines.entries()){
|
|
260
|
+
var _line_variantId;
|
|
261
|
+
if (done.has(index)) continue;
|
|
262
|
+
const moved = await deps.decrementStock(_extends({
|
|
263
|
+
hostRef: staff.hostRef,
|
|
264
|
+
hostId: staff.hostId,
|
|
265
|
+
productId: line.productId,
|
|
266
|
+
variantId: (_line_variantId = line.variantId) != null ? _line_variantId : 'default',
|
|
267
|
+
quantity: line.quantity
|
|
268
|
+
}, order['locationId'] ? {
|
|
269
|
+
locationId: String(order['locationId'])
|
|
270
|
+
} : {}, {
|
|
271
|
+
orderId: orderRef.id
|
|
272
|
+
}));
|
|
273
|
+
// A failed commit leaves the line for the next sync to take, rather than
|
|
274
|
+
// marking stock taken that never moved.
|
|
275
|
+
if (moved.failed) continue;
|
|
276
|
+
// Untracked stock (or a product gone since) moves nothing and is short of nothing.
|
|
277
|
+
const short = moved.before ? moved.applied - moved.requested : 0;
|
|
278
|
+
if (short > 0) {
|
|
279
|
+
conflicts.push(_extends({
|
|
280
|
+
productId: line.productId
|
|
281
|
+
}, line.variantId ? {
|
|
282
|
+
variantId: line.variantId
|
|
283
|
+
} : {}, {
|
|
284
|
+
name: [
|
|
285
|
+
line.name,
|
|
286
|
+
line.variantLabel
|
|
287
|
+
].filter(Boolean).join(' — '),
|
|
288
|
+
requested: Math.abs(moved.requested),
|
|
289
|
+
applied: Math.abs(moved.applied),
|
|
290
|
+
shortUnits: short
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
done.add(index);
|
|
294
|
+
await recordRef.update({
|
|
295
|
+
stockLines: [
|
|
296
|
+
...done
|
|
297
|
+
].sort((a, b)=>a - b),
|
|
298
|
+
stockConflicts: conflicts
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
const stockDone = lines.every((_line, index)=>done.has(index));
|
|
302
|
+
const flags = conflicts.length && !current.flags.includes('stock-short') ? [
|
|
303
|
+
...current.flags,
|
|
304
|
+
'stock-short'
|
|
305
|
+
] : current.flags;
|
|
306
|
+
current = _extends({}, current, {
|
|
307
|
+
stockLines: [
|
|
308
|
+
...done
|
|
309
|
+
],
|
|
310
|
+
stockConflicts: conflicts,
|
|
311
|
+
stockDone,
|
|
312
|
+
flags
|
|
313
|
+
});
|
|
314
|
+
await recordRef.update({
|
|
315
|
+
stockDone,
|
|
316
|
+
flags,
|
|
317
|
+
stockConflicts: conflicts
|
|
318
|
+
});
|
|
319
|
+
if (conflicts.length && conflicts.length !== record.stockConflicts.length) {
|
|
320
|
+
const fresh = (await orderRef.get()).data();
|
|
321
|
+
const stamp = fresh == null ? void 0 : fresh.offline;
|
|
322
|
+
await orderRef.update(_extends({}, stamp ? {
|
|
323
|
+
offline: _extends({}, stamp, {
|
|
324
|
+
flags,
|
|
325
|
+
stockConflicts: conflicts
|
|
326
|
+
})
|
|
327
|
+
} : {}, {
|
|
328
|
+
timeline: CommerceModel.appendOrderEvent(fresh != null ? fresh : {}, 'offline-stock-short', conflicts.map((conflict)=>`${conflict.shortUnits} of ${conflict.requested}× ${conflict.name} not in stock`).join('; ').slice(0, 500), deps.now())
|
|
329
|
+
}));
|
|
330
|
+
}
|
|
331
|
+
if (!stockDone) return current;
|
|
332
|
+
}
|
|
333
|
+
if (!current.notified) {
|
|
334
|
+
var _order_customerEmail, _order_customerName;
|
|
335
|
+
const email = String((_order_customerEmail = order.customerEmail) != null ? _order_customerEmail : '');
|
|
336
|
+
await deps.afterSale({
|
|
337
|
+
hostId: staff.hostId,
|
|
338
|
+
orderId: orderRef.id,
|
|
339
|
+
order: order,
|
|
340
|
+
email,
|
|
341
|
+
name: String((_order_customerName = order.customerName) != null ? _order_customerName : '')
|
|
342
|
+
});
|
|
343
|
+
current = _extends({}, current, {
|
|
344
|
+
notified: true,
|
|
345
|
+
status: 'settled'
|
|
346
|
+
});
|
|
347
|
+
await recordRef.update({
|
|
348
|
+
notified: true,
|
|
349
|
+
status: 'settled'
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
return current;
|
|
353
|
+
}
|
|
354
|
+
async function syncOne(context, raw) {
|
|
355
|
+
var _ref, _sale_customer;
|
|
356
|
+
const { deps, staff } = context;
|
|
357
|
+
const read = sanitizePosOfflineSale(raw);
|
|
358
|
+
const rawKey = String((_ref = raw == null ? void 0 : raw['saleKey']) != null ? _ref : '').slice(0, 64);
|
|
359
|
+
if ('error' in read) return refused(rawKey, 'invalid', read.error, false);
|
|
360
|
+
const sale = read.sale;
|
|
361
|
+
if (sale.hostId !== staff.hostId) {
|
|
362
|
+
return refused(sale.saleKey, 'wrong-site', 'This sale was rung for another site.', false);
|
|
363
|
+
}
|
|
364
|
+
if (sale.signedInUid !== staff.uid) {
|
|
365
|
+
return refused(sale.saleKey, 'wrong-staff', 'This sale was rung while another member was signed in. It syncs when they sign in on this register.', true);
|
|
366
|
+
}
|
|
367
|
+
if (sale.orgId && staff.orgId && sale.orgId !== staff.orgId) {
|
|
368
|
+
return refused(sale.saleKey, 'wrong-workspace', 'This sale was rung in another workspace.', false);
|
|
369
|
+
}
|
|
370
|
+
const firestore = deps.firestore();
|
|
371
|
+
const hostRef = staff.hostRef;
|
|
372
|
+
const recordRef = hostRef.collection('posOfflineSales').doc(sale.saleKey);
|
|
373
|
+
const orderRef = hostRef.collection('orders').doc(sale.saleKey);
|
|
374
|
+
const existing = await recordRef.get();
|
|
375
|
+
if (existing.exists) {
|
|
376
|
+
const record = existing.data();
|
|
377
|
+
if (record.signedInUid !== staff.uid) {
|
|
378
|
+
return refused(sale.saleKey, 'conflict', 'Another sale already used this key.', false);
|
|
379
|
+
}
|
|
380
|
+
return replayOf(record.status === 'settled' ? record : await finishSale(context, recordRef, orderRef, record));
|
|
381
|
+
}
|
|
382
|
+
const now = deps.now();
|
|
383
|
+
const flags = new Set();
|
|
384
|
+
const soldAt = posOfflineSoldAt(sale.soldAtMs, now);
|
|
385
|
+
if (soldAt.adjusted) flags.add('clock-adjusted');
|
|
386
|
+
else if (now - soldAt.atMs > POS_OFFLINE_LATE_SYNC_MS) flags.add('late-sync');
|
|
387
|
+
// The lines as sold, checked against the catalog as it stands now. The
|
|
388
|
+
// price on the order is the one rung — that is what the customer paid —
|
|
389
|
+
// and the catalog's is kept beside it where they differ.
|
|
390
|
+
const productIds = [
|
|
391
|
+
...new Set(sale.lines.map((line)=>line.productId))
|
|
392
|
+
];
|
|
393
|
+
const snapshots = await Promise.all(productIds.map((id)=>hostRef.collection('products').doc(id).get()));
|
|
394
|
+
const products = new Map(snapshots.map((snapshot)=>[
|
|
395
|
+
snapshot.id,
|
|
396
|
+
snapshot.exists ? CommerceModel.liftLegacyProduct(snapshot.data()) : null
|
|
397
|
+
]));
|
|
398
|
+
const priceDrift = [];
|
|
399
|
+
const lineItems = sale.lines.map((line, index)=>{
|
|
400
|
+
var _product_variants_find, _ref, _product_type;
|
|
401
|
+
const product = products.get(line.productId);
|
|
402
|
+
if (!product) {
|
|
403
|
+
var _line_productType;
|
|
404
|
+
flags.add('unknown-product');
|
|
405
|
+
return _extends({
|
|
406
|
+
productId: line.productId
|
|
407
|
+
}, line.variantId ? {
|
|
408
|
+
variantId: line.variantId
|
|
409
|
+
} : {}, {
|
|
410
|
+
name: line.name
|
|
411
|
+
}, line.variantLabel ? {
|
|
412
|
+
variantLabel: line.variantLabel
|
|
413
|
+
} : {}, line.sku ? {
|
|
414
|
+
sku: line.sku
|
|
415
|
+
} : {}, {
|
|
416
|
+
productType: (_line_productType = line.productType) != null ? _line_productType : 'physical',
|
|
417
|
+
quantity: line.quantity,
|
|
418
|
+
unitAmountCents: line.unitAmountCents
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
const variant = (_product_variants_find = product.variants.find((item)=>item.id === line.variantId)) != null ? _product_variants_find : product.variants[0];
|
|
422
|
+
const chosen = CommerceModel.resolveLineModifiers(product, line.modifiers);
|
|
423
|
+
const currentCents = variant && CommerceModel.variantHasPrice(variant) && chosen.ok ? Math.round(Number(variant.priceUsd) * 100) + chosen.extraCents : null;
|
|
424
|
+
if (currentCents !== line.unitAmountCents) {
|
|
425
|
+
flags.add('price-changed');
|
|
426
|
+
priceDrift.push({
|
|
427
|
+
index,
|
|
428
|
+
rungCents: line.unitAmountCents,
|
|
429
|
+
currentCents: currentCents != null ? currentCents : 0
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
return _extends({
|
|
433
|
+
productId: line.productId
|
|
434
|
+
}, variant && variant.id !== 'default' ? {
|
|
435
|
+
variantId: variant.id
|
|
436
|
+
} : {}, {
|
|
437
|
+
name: product.name || line.name
|
|
438
|
+
}, line.variantLabel ? {
|
|
439
|
+
variantLabel: line.variantLabel
|
|
440
|
+
} : {}, (variant == null ? void 0 : variant.sku) ? {
|
|
441
|
+
sku: variant.sku
|
|
442
|
+
} : line.sku ? {
|
|
443
|
+
sku: line.sku
|
|
444
|
+
} : {}, {
|
|
445
|
+
productType: (_ref = (_product_type = product.type) != null ? _product_type : line.productType) != null ? _ref : 'physical',
|
|
446
|
+
quantity: line.quantity,
|
|
447
|
+
unitAmountCents: line.unitAmountCents
|
|
448
|
+
}, chosen.ok && chosen.modifiers.length ? {
|
|
449
|
+
modifiers: chosen.modifiers
|
|
450
|
+
} : {});
|
|
451
|
+
});
|
|
452
|
+
// The money: what the register rang, when its own arithmetic holds; the
|
|
453
|
+
// arithmetic of its lines, with the tax it rang, when it does not.
|
|
454
|
+
let totals;
|
|
455
|
+
if (posOfflineTotalsAddUp(sale)) {
|
|
456
|
+
totals = CommerceModel.computeOrderTotals(lineItems, {
|
|
457
|
+
discountCents: sale.totals.discountCents,
|
|
458
|
+
taxCents: sale.totals.taxCents
|
|
459
|
+
});
|
|
460
|
+
} else {
|
|
461
|
+
flags.add('totals-restated');
|
|
462
|
+
const restated = posOfflineSaleTotals({
|
|
463
|
+
lines: sale.lines,
|
|
464
|
+
discountPct: sale.discountPct,
|
|
465
|
+
tax: {
|
|
466
|
+
pct: null,
|
|
467
|
+
pricesIncludeTax: false
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
totals = CommerceModel.computeOrderTotals(lineItems, {
|
|
471
|
+
discountCents: restated.discountCents,
|
|
472
|
+
taxCents: Math.min(sale.totals.taxCents, restated.itemsCents - restated.discountCents)
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
if ('decision' in context.tax) {
|
|
476
|
+
const expected = posOfflineSaleTotals({
|
|
477
|
+
lines: sale.lines,
|
|
478
|
+
discountPct: sale.discountPct,
|
|
479
|
+
tax: {
|
|
480
|
+
pct: context.tax.pct,
|
|
481
|
+
pricesIncludeTax: context.tax.pricesIncludeTax
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
if (expected.taxCents !== totals.taxCents) flags.add('tax-differs');
|
|
485
|
+
} else if (totals.taxCents > 0) {
|
|
486
|
+
flags.add('tax-differs');
|
|
487
|
+
}
|
|
488
|
+
if (sale.discountPct > posMaxDiscountPct(context.config)) flags.add('discount-over-limit');
|
|
489
|
+
if (sale.cashTenderedCents < totals.totalCents) flags.add('cash-short');
|
|
490
|
+
// Where it was rung.
|
|
491
|
+
const register = context.registers.get(sale.registerId);
|
|
492
|
+
if (!register) flags.add('unknown-register');
|
|
493
|
+
else if (!deps.registerWithinCap(staff.org, staff.hostId, register.rank)) flags.add('register-over-cap');
|
|
494
|
+
let shiftId = '';
|
|
495
|
+
if (sale.shiftId && register) {
|
|
496
|
+
const shift = await hostRef.collection('registers').doc(sale.registerId).collection('shifts').doc(sale.shiftId).get();
|
|
497
|
+
if (shift.exists) {
|
|
498
|
+
shiftId = sale.shiftId;
|
|
499
|
+
if (shift.get('status') !== 'open') flags.add('shift-closed');
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (!shiftId && staff.settings.requireOpenShift) flags.add('no-shift');
|
|
503
|
+
// Who rang it: a PIN-switched cashier's assertion is read as of the moment
|
|
504
|
+
// the cash was taken (it was valid then, and expiring since is the normal
|
|
505
|
+
// case for a sale that waited to sync), and the member is re-checked NOW.
|
|
506
|
+
let cashierId = staff.uid;
|
|
507
|
+
if (sale.cashierAssertion) {
|
|
508
|
+
const memberUid = await resolvePosStaffAssertion(_extends({}, deps, {
|
|
509
|
+
now: ()=>soldAt.atMs
|
|
510
|
+
}), hostRef, sale.cashierAssertion, {
|
|
511
|
+
hostId: staff.hostId,
|
|
512
|
+
registerId: sale.registerId,
|
|
513
|
+
purpose: 'cashier'
|
|
514
|
+
});
|
|
515
|
+
if (memberUid) cashierId = memberUid;
|
|
516
|
+
else flags.add('cashier-unverified');
|
|
517
|
+
}
|
|
518
|
+
const takeFee = takeFeeCents(deps, staff, lineItems, totals);
|
|
519
|
+
totals = _extends({}, totals, {
|
|
520
|
+
feeCents: takeFee
|
|
521
|
+
});
|
|
522
|
+
const changeCents = Math.max(0, sale.cashTenderedCents - totals.totalCents);
|
|
523
|
+
const flagList = [
|
|
524
|
+
...flags
|
|
525
|
+
];
|
|
526
|
+
const stamp = _extends({
|
|
527
|
+
saleKey: sale.saleKey,
|
|
528
|
+
soldAtMs: soldAt.atMs,
|
|
529
|
+
syncedAtMs: now,
|
|
530
|
+
syncedBy: staff.uid,
|
|
531
|
+
flags: flagList
|
|
532
|
+
}, priceDrift.length ? {
|
|
533
|
+
priceDrift
|
|
534
|
+
} : {});
|
|
535
|
+
const customer = (_sale_customer = sale.customer) != null ? _sale_customer : {};
|
|
536
|
+
const feeMonth = offlineFeeMonthKey(new Date(now));
|
|
537
|
+
const feeRef = staff.orgId ? firestore.collection('orgs').doc(staff.orgId).collection('offlineFees').doc(feeMonth) : null;
|
|
538
|
+
const counterRef = hostRef.collection('counters').doc('orders');
|
|
539
|
+
const committed = await firestore.runTransaction(async (transaction)=>{
|
|
540
|
+
var _counter_get;
|
|
541
|
+
const raced = await transaction.get(recordRef);
|
|
542
|
+
if (raced.exists) return {
|
|
543
|
+
raced: raced.data()
|
|
544
|
+
};
|
|
545
|
+
const taken = await transaction.get(orderRef);
|
|
546
|
+
if (taken.exists) return {
|
|
547
|
+
collision: true
|
|
548
|
+
};
|
|
549
|
+
const counter = await transaction.get(counterRef);
|
|
550
|
+
const number = Number((_counter_get = counter.get('next')) != null ? _counter_get : 1);
|
|
551
|
+
transaction.set(counterRef, {
|
|
552
|
+
next: number + 1
|
|
553
|
+
}, {
|
|
554
|
+
merge: true
|
|
555
|
+
});
|
|
556
|
+
const paidDetail = `Cash — received ${money(sale.cashTenderedCents)}, change ${money(changeCents)}. ` + 'Rung while the register was offline.';
|
|
557
|
+
const timeline = [
|
|
558
|
+
{
|
|
559
|
+
atMs: soldAt.atMs,
|
|
560
|
+
event: 'paid',
|
|
561
|
+
detail: paidDetail
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
atMs: now,
|
|
565
|
+
event: 'offline-synced',
|
|
566
|
+
detail: flagList.length ? `Synced. Check: ${posOfflineFlagLabels(flagList).join('; ')}`.slice(0, 1000) : 'Synced.'
|
|
567
|
+
}
|
|
568
|
+
];
|
|
569
|
+
transaction.create(orderRef, CommerceModel.withOrderListFields(orderRef.id, _extends({
|
|
570
|
+
number,
|
|
571
|
+
status: 'paid',
|
|
572
|
+
channel: 'pos',
|
|
573
|
+
registerId: sale.registerId,
|
|
574
|
+
cashierId
|
|
575
|
+
}, shiftId ? {
|
|
576
|
+
shiftId
|
|
577
|
+
} : {}, customer.name ? {
|
|
578
|
+
customerName: customer.name
|
|
579
|
+
} : {}, customer.kind && customer.id ? {
|
|
580
|
+
customerRecord: {
|
|
581
|
+
kind: customer.kind,
|
|
582
|
+
id: customer.id
|
|
583
|
+
}
|
|
584
|
+
} : {}, sale.discountPct > 0 ? {
|
|
585
|
+
discountPct: sale.discountPct,
|
|
586
|
+
discountBy: cashierId
|
|
587
|
+
} : {}, sale.locationId ? {
|
|
588
|
+
locationId: sale.locationId
|
|
589
|
+
} : {}, {
|
|
590
|
+
lineItems,
|
|
591
|
+
totals,
|
|
592
|
+
taxMode: 'decision' in context.tax ? CommerceModel.storefrontTaxModeForDecision(context.tax.decision, totals.taxCents) : totals.taxCents > 0 ? 'manual' : 'none'
|
|
593
|
+
}, takeFee > 0 ? {
|
|
594
|
+
feeCollection: 'invoice'
|
|
595
|
+
} : {}, {
|
|
596
|
+
payments: [
|
|
597
|
+
{
|
|
598
|
+
id: 'pay_cash',
|
|
599
|
+
method: 'cash',
|
|
600
|
+
amountCents: totals.totalCents,
|
|
601
|
+
status: 'succeeded',
|
|
602
|
+
atMs: soldAt.atMs,
|
|
603
|
+
settledAtMs: soldAt.atMs,
|
|
604
|
+
takeFeeCents: takeFee,
|
|
605
|
+
feeCents: 0,
|
|
606
|
+
cashierId,
|
|
607
|
+
cashTenderedCents: sale.cashTenderedCents,
|
|
608
|
+
changeCents
|
|
609
|
+
}
|
|
610
|
+
],
|
|
611
|
+
customerEmail: customer.email || null,
|
|
612
|
+
timeline,
|
|
613
|
+
offline: stamp,
|
|
614
|
+
createdAtMs: soldAt.atMs,
|
|
615
|
+
createdAt: FieldValue.serverTimestamp()
|
|
616
|
+
})));
|
|
617
|
+
const record = {
|
|
618
|
+
saleKey: sale.saleKey,
|
|
619
|
+
orderId: orderRef.id,
|
|
620
|
+
number,
|
|
621
|
+
signedInUid: staff.uid,
|
|
622
|
+
registerId: sale.registerId,
|
|
623
|
+
soldAtMs: soldAt.atMs,
|
|
624
|
+
syncedAtMs: now,
|
|
625
|
+
status: 'recorded',
|
|
626
|
+
flags: flagList,
|
|
627
|
+
stockConflicts: [],
|
|
628
|
+
stockLines: [],
|
|
629
|
+
stockDone: false,
|
|
630
|
+
notified: false
|
|
631
|
+
};
|
|
632
|
+
transaction.create(recordRef, record);
|
|
633
|
+
// The take accrues with the order, in the same transaction (AGL-2111).
|
|
634
|
+
if (takeFee > 0 && feeRef) {
|
|
635
|
+
transaction.set(feeRef, {
|
|
636
|
+
month: feeMonth,
|
|
637
|
+
feeCents: FieldValue.increment(takeFee),
|
|
638
|
+
orders: FieldValue.increment(1)
|
|
639
|
+
}, {
|
|
640
|
+
merge: true
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
return {
|
|
644
|
+
record
|
|
645
|
+
};
|
|
646
|
+
});
|
|
647
|
+
if ('collision' in committed) {
|
|
648
|
+
return refused(sale.saleKey, 'conflict', 'Another order already has this sale’s key.', false);
|
|
649
|
+
}
|
|
650
|
+
if ('raced' in committed) {
|
|
651
|
+
const record = committed.raced;
|
|
652
|
+
if (record.signedInUid !== staff.uid) {
|
|
653
|
+
return refused(sale.saleKey, 'conflict', 'Another sale already used this key.', false);
|
|
654
|
+
}
|
|
655
|
+
return replayOf(record);
|
|
656
|
+
}
|
|
657
|
+
const finished = await finishSale(context, recordRef, orderRef, committed.record);
|
|
658
|
+
return {
|
|
659
|
+
saleKey: sale.saleKey,
|
|
660
|
+
status: 'recorded',
|
|
661
|
+
orderId: orderRef.id,
|
|
662
|
+
number: finished.number,
|
|
663
|
+
flags: finished.flags,
|
|
664
|
+
stockConflicts: finished.stockConflicts
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
export async function handlePosOfflineSync(deps, req) {
|
|
668
|
+
var _req_query;
|
|
669
|
+
if (req.method !== 'GET' && req.method !== 'POST') {
|
|
670
|
+
return {
|
|
671
|
+
status: 405,
|
|
672
|
+
body: {
|
|
673
|
+
error: 'Method not allowed'
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
const body = req.method === 'GET' ? Object.fromEntries(Object.entries((_req_query = req.query) != null ? _req_query : {}).map(([key, value])=>[
|
|
678
|
+
key,
|
|
679
|
+
Array.isArray(value) ? value[0] : value
|
|
680
|
+
])) : posOpsBody(req);
|
|
681
|
+
const gate = await authorizePosOps(deps, req, body['hostId']);
|
|
682
|
+
if ('error' in gate) return {
|
|
683
|
+
status: gate.status,
|
|
684
|
+
body: {
|
|
685
|
+
error: gate.error
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
const staff = gate.staff;
|
|
689
|
+
if (req.method === 'GET') {
|
|
690
|
+
return {
|
|
691
|
+
status: 200,
|
|
692
|
+
body: {
|
|
693
|
+
kit: await buildPosOfflineKit(deps, staff)
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
const sales = Array.isArray(body['sales']) ? body['sales'] : null;
|
|
698
|
+
if (!sales) return {
|
|
699
|
+
status: 400,
|
|
700
|
+
body: {
|
|
701
|
+
error: 'Missing sales'
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
if (sales.length > POS_OFFLINE_SYNC_BATCH_MAX) {
|
|
705
|
+
return {
|
|
706
|
+
status: 400,
|
|
707
|
+
body: {
|
|
708
|
+
error: `Send at most ${POS_OFFLINE_SYNC_BATCH_MAX} sales at a time`
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
if (sales.length === 0) return {
|
|
713
|
+
status: 200,
|
|
714
|
+
body: {
|
|
715
|
+
results: []
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
const [registers, taxSettings, config] = await Promise.all([
|
|
719
|
+
readRegisters(staff),
|
|
720
|
+
readStoreTax(staff),
|
|
721
|
+
staff.orgId ? deps.pluginConfig(staff.orgId, staff.hostId).catch(()=>({})) : Promise.resolve({})
|
|
722
|
+
]);
|
|
723
|
+
const context = {
|
|
724
|
+
deps,
|
|
725
|
+
staff,
|
|
726
|
+
config,
|
|
727
|
+
tax: posInPersonTax(taxSettings),
|
|
728
|
+
registers
|
|
729
|
+
};
|
|
730
|
+
const results = [];
|
|
731
|
+
// One at a time, in the order they were rung, so order numbers follow the
|
|
732
|
+
// till and one sale's failure cannot take the rest with it.
|
|
733
|
+
for (const sale of sales){
|
|
734
|
+
try {
|
|
735
|
+
results.push(await syncOne(context, sale));
|
|
736
|
+
} catch (error) {
|
|
737
|
+
var _ref;
|
|
738
|
+
console.error('[pos-offline-sync] sale failed', error);
|
|
739
|
+
const key = String((_ref = sale == null ? void 0 : sale['saleKey']) != null ? _ref : '').slice(0, 64);
|
|
740
|
+
results.push(refused(key, 'failed', 'The sale could not be synced yet. It stays on this register.', true));
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return {
|
|
744
|
+
status: 200,
|
|
745
|
+
body: {
|
|
746
|
+
results
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
export function createPosOfflineSyncHandler(deps = defaultPosOfflineSyncDeps) {
|
|
751
|
+
return async (req, res)=>{
|
|
752
|
+
try {
|
|
753
|
+
const outcome = await handlePosOfflineSync(deps(), req);
|
|
754
|
+
return res.status(outcome.status).json(outcome.body);
|
|
755
|
+
} catch (error) {
|
|
756
|
+
console.error('[pos-offline-sync] failed', error);
|
|
757
|
+
return res.status(500).json({
|
|
758
|
+
error: 'The offline sales could not be synced'
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
export const posOfflineSyncHandler = createPosOfflineSyncHandler();
|
|
764
|
+
|
|
765
|
+
//# sourceMappingURL=pos-offline-sync.js.map
|