@aglyn/plugins-commerce 1.0.0-beta.278 → 1.0.0-beta.283
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 +17 -14
- package/src/lib/components/commerce-console-page.js +33 -1
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/console/payments-settings-card.component.js +1 -1
- package/src/lib/components/console/payments-settings-card.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-hardware-api.d.ts +122 -0
- package/src/lib/components/console/pos/pos-hardware-api.js +90 -0
- package/src/lib/components/console/pos/pos-hardware-api.js.map +1 -0
- package/src/lib/components/console/pos/pos-reader-splash-api.d.ts +58 -0
- package/src/lib/components/console/pos/pos-reader-splash-api.js +101 -0
- package/src/lib/components/console/pos/pos-reader-splash-api.js.map +1 -0
- package/src/lib/components/console/pos-devices-card.component.js +5 -0
- package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
- package/src/lib/components/console/pos-hardware-card.component.d.ts +26 -0
- package/src/lib/components/console/pos-hardware-card.component.js +456 -0
- package/src/lib/components/console/pos-hardware-card.component.js.map +1 -0
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.d.ts +14 -0
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.js +519 -0
- package/src/lib/components/console/pos-hardware-orders-staff-page.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.d.ts +27 -0
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js +444 -0
- package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-check-in-zone.component.d.ts +9 -0
- package/src/lib/components/console/pos-ops/pos-check-in-zone.component.js +37 -0
- package/src/lib/components/console/pos-ops/pos-check-in-zone.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.d.ts +24 -0
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +346 -0
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.d.ts +5 -1
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js +83 -4
- package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-day-totals-card.component.d.ts +35 -0
- package/src/lib/components/console/pos-ops/pos-day-totals-card.component.js +546 -0
- package/src/lib/components/console/pos-ops/pos-day-totals-card.component.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.d.ts +2 -1
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.js +20 -1
- package/src/lib/components/console/pos-ops/pos-operations-bar.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-ops-api.d.ts +1 -1
- package/src/lib/components/console/pos-ops/pos-ops-api.js.map +1 -1
- package/src/lib/components/console/pos-page.component.js +6 -3
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/pos-reader-splash-setting.component.d.ts +22 -0
- package/src/lib/components/console/pos-reader-splash-setting.component.js +275 -0
- package/src/lib/components/console/pos-reader-splash-setting.component.js.map +1 -0
- package/src/lib/components/console/pos-reader-splash-staff-page.component.d.ts +14 -0
- package/src/lib/components/console/pos-reader-splash-staff-page.component.js +301 -0
- package/src/lib/components/console/pos-reader-splash-staff-page.component.js.map +1 -0
- package/src/lib/components/console/pos-zones.d.ts +37 -3
- package/src/lib/components/console/pos-zones.js +11 -1
- package/src/lib/components/console/pos-zones.js.map +1 -1
- package/src/lib/components/console/product-check-in-fields.component.d.ts +20 -0
- package/src/lib/components/console/product-check-in-fields.component.js +172 -0
- package/src/lib/components/console/product-check-in-fields.component.js.map +1 -0
- package/src/lib/components/console/product-editor-dialog.component.js +48 -9
- package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
- package/src/lib/components/console/product-register-fields.component.d.ts +12 -0
- package/src/lib/components/console/product-register-fields.component.js +39 -1
- package/src/lib/components/console/product-register-fields.component.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +21 -6
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/reservations-card.component.js +239 -70
- package/src/lib/components/console/reservations-card.component.js.map +1 -1
- package/src/lib/components/reservation-widget.js +16 -0
- package/src/lib/components/reservation-widget.js.map +1 -1
- package/src/lib/constants/operator-alerts.d.ts +6 -0
- package/src/lib/constants/operator-alerts.js +20 -1
- package/src/lib/constants/operator-alerts.js.map +1 -1
- package/src/lib/declarations.console-server.js +7 -0
- package/src/lib/declarations.console-server.js.map +1 -1
- package/src/lib/declarations.d.ts +26 -0
- package/src/lib/declarations.js +34 -0
- package/src/lib/declarations.js.map +1 -0
- package/src/lib/model/commerce-check-in-product.d.ts +37 -0
- package/src/lib/model/commerce-check-in-product.js +49 -0
- package/src/lib/model/commerce-check-in-product.js.map +1 -0
- package/src/lib/model/commerce-check-in.d.ts +187 -0
- package/src/lib/model/commerce-check-in.js +468 -0
- package/src/lib/model/commerce-check-in.js.map +1 -0
- package/src/lib/model/commerce-pos-day-report.d.ts +209 -0
- package/src/lib/model/commerce-pos-day-report.js +572 -0
- package/src/lib/model/commerce-pos-day-report.js.map +1 -0
- package/src/lib/model/commerce-pos-offline.d.ts +65 -3
- package/src/lib/model/commerce-pos-offline.js +104 -7
- package/src/lib/model/commerce-pos-offline.js.map +1 -1
- package/src/lib/model/commerce-pos-ops.d.ts +3 -0
- package/src/lib/model/commerce-pos-ops.js +1 -0
- package/src/lib/model/commerce-pos-ops.js.map +1 -1
- package/src/lib/model/commerce-pos.d.ts +43 -3
- package/src/lib/model/commerce-pos.js +73 -5
- package/src/lib/model/commerce-pos.js.map +1 -1
- package/src/lib/model/commerce-reservations.d.ts +73 -7
- package/src/lib/model/commerce-reservations.js +90 -10
- package/src/lib/model/commerce-reservations.js.map +1 -1
- package/src/lib/model/commerce.d.ts +40 -0
- package/src/lib/model/commerce.js +22 -4
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/index.d.ts +1 -0
- package/src/lib/model/index.js +1 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/plugin-permissions.d.ts +1 -1
- package/src/lib/model/plugin-permissions.js +2 -1
- package/src/lib/model/plugin-permissions.js.map +1 -1
- package/src/lib/model/pos-hardware.d.ts +288 -0
- package/src/lib/model/pos-hardware.js +499 -0
- package/src/lib/model/pos-hardware.js.map +1 -0
- package/src/lib/model/pos-offline-card-limits.d.ts +20 -0
- package/src/lib/model/pos-offline-card-limits.js +28 -0
- package/src/lib/model/pos-offline-card-limits.js.map +1 -0
- package/src/lib/model/pos-reader-splash.d.ts +114 -0
- package/src/lib/model/pos-reader-splash.js +141 -0
- package/src/lib/model/pos-reader-splash.js.map +1 -0
- package/src/lib/model/product-write.d.ts +6 -0
- package/src/lib/plugin-config.d.ts +10 -0
- package/src/lib/plugin-config.js +40 -0
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +28 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/cart-checkout.js +3 -1
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/cart.js +12 -1
- package/src/lib/server/cart.js.map +1 -1
- package/src/lib/server/catalog.js +49 -12
- package/src/lib/server/catalog.js.map +1 -1
- package/src/lib/server/checkout-extras.js +1 -1
- package/src/lib/server/checkout-extras.js.map +1 -1
- package/src/lib/server/checkout.js +7 -0
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/person-eraser.d.ts +8 -1
- package/src/lib/server/person-eraser.js +23 -1
- package/src/lib/server/person-eraser.js.map +1 -1
- package/src/lib/server/pos-check-in.d.ts +49 -0
- package/src/lib/server/pos-check-in.js +652 -0
- package/src/lib/server/pos-check-in.js.map +1 -0
- package/src/lib/server/pos-day-report.d.ts +46 -0
- package/src/lib/server/pos-day-report.js +299 -0
- package/src/lib/server/pos-day-report.js.map +1 -0
- package/src/lib/server/pos-hardware-admin.d.ts +50 -0
- package/src/lib/server/pos-hardware-admin.js +366 -0
- package/src/lib/server/pos-hardware-admin.js.map +1 -0
- package/src/lib/server/pos-hardware-notify.d.ts +27 -0
- package/src/lib/server/pos-hardware-notify.js +142 -0
- package/src/lib/server/pos-hardware-notify.js.map +1 -0
- package/src/lib/server/pos-hardware-user-eraser.d.ts +26 -0
- package/src/lib/server/pos-hardware-user-eraser.js +67 -0
- package/src/lib/server/pos-hardware-user-eraser.js.map +1 -0
- package/src/lib/server/pos-hardware-webhook.d.ts +31 -0
- package/src/lib/server/pos-hardware-webhook.js +176 -0
- package/src/lib/server/pos-hardware-webhook.js.map +1 -0
- package/src/lib/server/pos-hardware.d.ts +58 -0
- package/src/lib/server/pos-hardware.js +234 -0
- package/src/lib/server/pos-hardware.js.map +1 -0
- package/src/lib/server/pos-offline-card-reconcile.d.ts +38 -0
- package/src/lib/server/pos-offline-card-reconcile.js +58 -0
- package/src/lib/server/pos-offline-card-reconcile.js.map +1 -0
- package/src/lib/server/pos-offline-sync.d.ts +32 -0
- package/src/lib/server/pos-offline-sync.js +191 -15
- package/src/lib/server/pos-offline-sync.js.map +1 -1
- package/src/lib/server/pos-ops-routes.d.ts +4 -2
- package/src/lib/server/pos-ops-routes.js +14 -2
- package/src/lib/server/pos-ops-routes.js.map +1 -1
- package/src/lib/server/pos-order.js +39 -51
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/pos-payment.js +8 -4
- package/src/lib/server/pos-payment.js.map +1 -1
- package/src/lib/server/pos-reader-splash-admin.d.ts +36 -0
- package/src/lib/server/pos-reader-splash-admin.js +295 -0
- package/src/lib/server/pos-reader-splash-admin.js.map +1 -0
- package/src/lib/server/pos-reader-splash-render.d.ts +40 -0
- package/src/lib/server/pos-reader-splash-render.js +138 -0
- package/src/lib/server/pos-reader-splash-render.js.map +1 -0
- package/src/lib/server/pos-reader-splash-server.d.ts +32 -0
- package/src/lib/server/pos-reader-splash-server.js +32 -0
- package/src/lib/server/pos-reader-splash-server.js.map +1 -0
- package/src/lib/server/pos-reader-splash.d.ts +105 -0
- package/src/lib/server/pos-reader-splash.js +548 -0
- package/src/lib/server/pos-reader-splash.js.map +1 -0
- package/src/lib/server/pos-sale.d.ts +2 -1
- package/src/lib/server/pos-sale.js +3 -1
- package/src/lib/server/pos-sale.js.map +1 -1
- package/src/lib/server/pos-terminal.d.ts +67 -4
- package/src/lib/server/pos-terminal.js +239 -32
- package/src/lib/server/pos-terminal.js.map +1 -1
- package/src/lib/server/process-restock.js +4 -0
- package/src/lib/server/process-restock.js.map +1 -1
- package/src/lib/server/product-card.js +2 -2
- package/src/lib/server/product-card.js.map +1 -1
- package/src/lib/server/product-catalog.d.ts +2 -0
- package/src/lib/server/product-catalog.js +11 -4
- package/src/lib/server/product-catalog.js.map +1 -1
- package/src/lib/server/product.js +3 -1
- package/src/lib/server/product.js.map +1 -1
- package/src/lib/server/related.js +8 -1
- package/src/lib/server/related.js.map +1 -1
- package/src/lib/server/reservation-availability-query.d.ts +35 -0
- package/src/lib/server/reservation-availability-query.js +34 -0
- package/src/lib/server/reservation-availability-query.js.map +1 -0
- package/src/lib/server/reservation-availability.d.ts +21 -0
- package/src/lib/server/reservation-availability.js +64 -10
- package/src/lib/server/reservation-availability.js.map +1 -1
- package/src/lib/server/reservation-walk-in.d.ts +28 -0
- package/src/lib/server/reservation-walk-in.js +166 -0
- package/src/lib/server/reservation-walk-in.js.map +1 -0
- package/src/lib/server/reserve.js +13 -10
- package/src/lib/server/reserve.js.map +1 -1
- package/src/lib/server/reviews.js +3 -1
- package/src/lib/server/reviews.js.map +1 -1
- package/src/lib/server/site-page-resolver.js +5 -2
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/server.js +26 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/fake-firestore.js +72 -0
- package/src/lib/testing/fake-firestore.js.map +1 -1
|
@@ -0,0 +1,652 @@
|
|
|
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 { resolveSiteTimeZone } from "@aglyn/aglyn/app-utils/collection-entry-date";
|
|
18
|
+
import { zonedDayKey } from "@aglyn/shared-util-timestamp/zoned-time";
|
|
19
|
+
import { changedCheckInCustomerFields, checkInCustomerKey, checkInCustomers, checkInDayCsv, checkInDayMs, checkInDayTotals, checkInSearchToken, CHECK_IN_BATCH_MAX, CHECK_IN_DAY_CEILING, CHECK_IN_SEARCH_CUSTOMERS, CHECK_IN_SEARCH_ROWS, dueCheckInsForOrder, dueCheckInsForReservation, newCheckInDocument, normalizeWristband, sortCheckInRows } from "../model/commerce-check-in.js";
|
|
20
|
+
import { posDayReportRange } from "../model/commerce-pos-day-report.js";
|
|
21
|
+
import { authorizePosOps, defaultPosOpsDeps, posOpsBody, posOpsCleanId, posOpsIdempotencyKey, resolvePosCashier } from "./pos-ops-gate.js";
|
|
22
|
+
import { onPosSaleCompleted } from "./pos-sale.js";
|
|
23
|
+
/*==========================================
|
|
24
|
+
* CHECK-IN AT THE WINDOW (AGL-4043): `/api/commerce/pos-check-in`.
|
|
25
|
+
*
|
|
26
|
+
* POST { hostId, action: 'search', text, day? }
|
|
27
|
+
* POST { hostId, action: 'customer', customerKey | email | contactId, day?, reconcile?: false | 'if-used' }
|
|
28
|
+
* POST { hostId, action: 'check_in', checkIns: [{ id, wristband? }], registerId?, cashierAssertion? }
|
|
29
|
+
* POST { hostId, action: 'undo', id }
|
|
30
|
+
* POST { hostId, action: 'wristband', id, wristband }
|
|
31
|
+
* GET ?hostId=…&day=YYYY-MM-DD[&format=csv] the day's list
|
|
32
|
+
*
|
|
33
|
+
* `day` is today in the site's time zone when absent. Who may use it is who
|
|
34
|
+
* may work the register (`authorizePosOps`). A check-in is idempotent on the
|
|
35
|
+
* `Idempotency-Key` header: a retried tap answers the check-in it made, not
|
|
36
|
+
* "already checked in".
|
|
37
|
+
*
|
|
38
|
+
* WHAT IS DUE is written down, not filtered at read: each due person is a
|
|
39
|
+
* `checkIns` document (see `model/commerce-check-in.ts`), so a search is a
|
|
40
|
+
* query on `dayKey ==` and `searchTokens array-contains` with an index. The
|
|
41
|
+
* documents are written from their sources:
|
|
42
|
+
*
|
|
43
|
+
* - a register sale, the moment it completes (the sale-completed seam);
|
|
44
|
+
* - and every source, by {@link reconcileCheckInDay}: the day's orders
|
|
45
|
+
* (`createdAtMs` in the day, the automatic single-field index) and the
|
|
46
|
+
* stays that began within a year before it (`checkInDayMs`, likewise),
|
|
47
|
+
* at most every {@link CHECK_IN_RECONCILE_MS} per day, on the next search
|
|
48
|
+
* or list. So an online order paid a minute ago is found on the next
|
|
49
|
+
* search after that.
|
|
50
|
+
*
|
|
51
|
+
* Reconciling only ever CREATES what is missing, refreshes a waiting
|
|
52
|
+
* person's name and address, and removes a waiting person whose order was
|
|
53
|
+
* refunded or cancelled. A checked-in person is never touched: each change
|
|
54
|
+
* to an existing document is a transaction that re-reads it first.
|
|
55
|
+
*=========================================*/ /** How stale a day's reconcile may be before a search or the list redoes it. */ export const CHECK_IN_RECONCILE_MS = 30000;
|
|
56
|
+
/** How far back a stay may have begun and still cover the day. */ const STAY_LOOKBACK_MS = 366 * 86400000;
|
|
57
|
+
const queryValue = (value)=>{
|
|
58
|
+
var _value_;
|
|
59
|
+
return String(Array.isArray(value) ? (_value_ = value[0]) != null ? _value_ : '' : value != null ? value : '').trim();
|
|
60
|
+
};
|
|
61
|
+
/** A check-in id as the routes accept one. */ function cleanCheckInId(value) {
|
|
62
|
+
const id = String(value != null ? value : '').trim();
|
|
63
|
+
return /^[or]_[A-Za-z0-9_-]{1,300}$/.test(id) ? id : '';
|
|
64
|
+
}
|
|
65
|
+
/** The day asked for, in the site's zone; today there when absent. */ export async function readCheckInDay(deps, hostRef, org, rawDay) {
|
|
66
|
+
var _host_data;
|
|
67
|
+
const host = await hostRef.get();
|
|
68
|
+
const timeZone = resolveSiteTimeZone(org, (_host_data = host.data()) != null ? _host_data : null);
|
|
69
|
+
const day = String(rawDay != null ? rawDay : '').trim();
|
|
70
|
+
const range = posDayReportRange({
|
|
71
|
+
from: day,
|
|
72
|
+
to: day,
|
|
73
|
+
nowMs: deps.now(),
|
|
74
|
+
timeZone
|
|
75
|
+
});
|
|
76
|
+
if ('error' in range) return {
|
|
77
|
+
ok: false,
|
|
78
|
+
error: 'Choose the day as YYYY-MM-DD.'
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
ok: true,
|
|
82
|
+
dayKey: range.from,
|
|
83
|
+
startMs: range.startMs,
|
|
84
|
+
endMs: range.endMs,
|
|
85
|
+
timeZone
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** Reads documents by reference, a few hundred at a time. */ async function readAll(firestore, refs) {
|
|
89
|
+
const out = [];
|
|
90
|
+
for(let at = 0; at < refs.length; at += 300){
|
|
91
|
+
out.push(...await firestore.getAll(...refs.slice(at, at + 300)));
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
/** The products an order set names, as stored now. */ async function productsFor(deps, hostRef, orders) {
|
|
96
|
+
const ids = new Set();
|
|
97
|
+
for (const order of orders){
|
|
98
|
+
for (const line of Array.isArray(order.lineItems) ? order.lineItems : []){
|
|
99
|
+
if ((line == null ? void 0 : line.productId) && posOpsCleanId(line.productId)) ids.add(line.productId);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const snapshots = await readAll(deps.firestore(), [
|
|
103
|
+
...ids
|
|
104
|
+
].map((id)=>hostRef.collection('products').doc(id)));
|
|
105
|
+
const products = new Map();
|
|
106
|
+
for (const snapshot of snapshots){
|
|
107
|
+
var _snapshot_data;
|
|
108
|
+
if (snapshot.exists) products.set(snapshot.id, (_snapshot_data = snapshot.data()) != null ? _snapshot_data : {});
|
|
109
|
+
}
|
|
110
|
+
return products;
|
|
111
|
+
}
|
|
112
|
+
const isAlreadyExists = (error)=>{
|
|
113
|
+
const code = error == null ? void 0 : error.code;
|
|
114
|
+
return code === 6 || code === 'already-exists' || /ALREADY_EXISTS/.test(String(error == null ? void 0 : error.message));
|
|
115
|
+
};
|
|
116
|
+
/** Creates the due check-ins that are missing; an existing one is left exactly as it is. */ async function createMissing(checkIns, due, existing, nowMs) {
|
|
117
|
+
const missing = [
|
|
118
|
+
...due
|
|
119
|
+
].filter(([id])=>!existing.has(id));
|
|
120
|
+
let created = 0;
|
|
121
|
+
for(let at = 0; at < missing.length; at += 100){
|
|
122
|
+
await Promise.all(missing.slice(at, at + 100).map(async ([id, entry])=>{
|
|
123
|
+
try {
|
|
124
|
+
await checkIns.doc(id).create(newCheckInDocument(entry, nowMs));
|
|
125
|
+
created += 1;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
// Another device wrote it first, which is the same document.
|
|
128
|
+
if (!isAlreadyExists(error)) throw error;
|
|
129
|
+
}
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
return created;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Brings a day's check-ins in line with its orders and stays, at most every
|
|
136
|
+
* {@link CHECK_IN_RECONCILE_MS} unless `force`. Answers whether a source
|
|
137
|
+
* held more than one day reads.
|
|
138
|
+
*/ export async function reconcileCheckInDay(deps, hostRef, day, options = {}) {
|
|
139
|
+
var _checkInDayMs;
|
|
140
|
+
const nowMs = deps.now();
|
|
141
|
+
const markerRef = hostRef.collection('checkInDays').doc(day.dayKey);
|
|
142
|
+
if (!options.force) {
|
|
143
|
+
var _marker_get;
|
|
144
|
+
const marker = await markerRef.get();
|
|
145
|
+
// A day nobody has checked anyone in on yet is left alone by a caller
|
|
146
|
+
// that only asks in passing: a store that never checks anybody in pays
|
|
147
|
+
// nothing for the register's customer panel.
|
|
148
|
+
if (!marker.exists && options.onlyIfUsed) return {
|
|
149
|
+
truncated: false
|
|
150
|
+
};
|
|
151
|
+
if (marker.exists && nowMs - Number((_marker_get = marker.get('reconciledAtMs')) != null ? _marker_get : 0) < CHECK_IN_RECONCILE_MS) {
|
|
152
|
+
return {
|
|
153
|
+
truncated: marker.get('truncated') === true
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const dayMs = (_checkInDayMs = checkInDayMs(day.dayKey)) != null ? _checkInDayMs : 0;
|
|
158
|
+
const checkIns = hostRef.collection('checkIns');
|
|
159
|
+
const [ordersSnapshot, staysSnapshot, existingSnapshot] = await Promise.all([
|
|
160
|
+
hostRef.collection('orders').where('createdAtMs', '>=', day.startMs).where('createdAtMs', '<', day.endMs).orderBy('createdAtMs').limit(CHECK_IN_DAY_CEILING + 1).get(),
|
|
161
|
+
hostRef.collection('reservations').where('checkInDayMs', '<=', dayMs).where('checkInDayMs', '>', dayMs - STAY_LOOKBACK_MS).orderBy('checkInDayMs', 'desc').limit(CHECK_IN_DAY_CEILING + 1).get(),
|
|
162
|
+
checkIns.where('dayKey', '==', day.dayKey).limit(CHECK_IN_DAY_CEILING + 1).get()
|
|
163
|
+
]);
|
|
164
|
+
const truncated = ordersSnapshot.docs.length > CHECK_IN_DAY_CEILING || staysSnapshot.docs.length > CHECK_IN_DAY_CEILING || existingSnapshot.docs.length > CHECK_IN_DAY_CEILING;
|
|
165
|
+
const orders = ordersSnapshot.docs.slice(0, CHECK_IN_DAY_CEILING);
|
|
166
|
+
const stays = staysSnapshot.docs.slice(0, CHECK_IN_DAY_CEILING);
|
|
167
|
+
const products = await productsFor(deps, hostRef, orders.map((doc)=>doc.data()));
|
|
168
|
+
const resourceIds = [
|
|
169
|
+
...new Set(stays.map((doc)=>{
|
|
170
|
+
var _doc_get;
|
|
171
|
+
return String((_doc_get = doc.get('resourceId')) != null ? _doc_get : '');
|
|
172
|
+
}).filter((id)=>posOpsCleanId(id)))
|
|
173
|
+
];
|
|
174
|
+
const resourceNames = new Map();
|
|
175
|
+
for (const snapshot of (await readAll(deps.firestore(), resourceIds.map((id)=>hostRef.collection('resources').doc(id))))){
|
|
176
|
+
var _snapshot_get;
|
|
177
|
+
if (snapshot.exists) resourceNames.set(snapshot.id, String((_snapshot_get = snapshot.get('name')) != null ? _snapshot_get : ''));
|
|
178
|
+
}
|
|
179
|
+
const due = new Map();
|
|
180
|
+
const readOrders = new Set();
|
|
181
|
+
const readStays = new Set();
|
|
182
|
+
for (const doc of orders){
|
|
183
|
+
readOrders.add(doc.id);
|
|
184
|
+
for (const [id, entry] of dueCheckInsForOrder({
|
|
185
|
+
orderId: doc.id,
|
|
186
|
+
order: doc.data(),
|
|
187
|
+
products,
|
|
188
|
+
dayKey: day.dayKey
|
|
189
|
+
})){
|
|
190
|
+
due.set(id, entry);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
for (const doc of stays){
|
|
194
|
+
var _resourceNames_get, _doc_get;
|
|
195
|
+
readStays.add(doc.id);
|
|
196
|
+
for (const [id, entry] of dueCheckInsForReservation({
|
|
197
|
+
reservationId: doc.id,
|
|
198
|
+
reservation: doc.data(),
|
|
199
|
+
resourceName: (_resourceNames_get = resourceNames.get(String((_doc_get = doc.get('resourceId')) != null ? _doc_get : ''))) != null ? _resourceNames_get : null,
|
|
200
|
+
dayKey: day.dayKey
|
|
201
|
+
})){
|
|
202
|
+
due.set(id, entry);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const existing = new Set(existingSnapshot.docs.map((doc)=>doc.id));
|
|
206
|
+
await createMissing(checkIns, due, existing, nowMs);
|
|
207
|
+
// A waiting person whose source this pass READ and no longer finds due
|
|
208
|
+
// goes; one whose name or address changed is refreshed. Each re-reads its
|
|
209
|
+
// document in a transaction, so a check-in made meanwhile stands.
|
|
210
|
+
const changes = existingSnapshot.docs.slice(0, CHECK_IN_DAY_CEILING).flatMap((doc)=>{
|
|
211
|
+
const stored = doc.data();
|
|
212
|
+
if (stored.status !== 'expected') return [];
|
|
213
|
+
const wanted = due.get(doc.id);
|
|
214
|
+
if (!wanted) {
|
|
215
|
+
const sourceRead = stored.source === 'order' && stored.orderId && readOrders.has(stored.orderId) || stored.source === 'reservation' && stored.reservationId && readStays.has(stored.reservationId);
|
|
216
|
+
return sourceRead ? [
|
|
217
|
+
{
|
|
218
|
+
ref: doc.ref,
|
|
219
|
+
patch: null
|
|
220
|
+
}
|
|
221
|
+
] : [];
|
|
222
|
+
}
|
|
223
|
+
const patch = changedCheckInCustomerFields(stored, wanted);
|
|
224
|
+
return patch ? [
|
|
225
|
+
{
|
|
226
|
+
ref: doc.ref,
|
|
227
|
+
patch
|
|
228
|
+
}
|
|
229
|
+
] : [];
|
|
230
|
+
});
|
|
231
|
+
const firestore = deps.firestore();
|
|
232
|
+
for (const change of changes){
|
|
233
|
+
await firestore.runTransaction(async (transaction)=>{
|
|
234
|
+
const fresh = await transaction.get(change.ref);
|
|
235
|
+
if (!fresh.exists || fresh.get('status') !== 'expected') return;
|
|
236
|
+
if (change.patch) transaction.update(change.ref, _extends({}, change.patch, {
|
|
237
|
+
updatedAtMs: nowMs
|
|
238
|
+
}));
|
|
239
|
+
else transaction.delete(change.ref);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
await markerRef.set({
|
|
243
|
+
dayKey: day.dayKey,
|
|
244
|
+
reconciledAtMs: nowMs,
|
|
245
|
+
truncated
|
|
246
|
+
});
|
|
247
|
+
return {
|
|
248
|
+
truncated
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* The check-ins a register sale makes, written the moment it completes so
|
|
253
|
+
* the person who just paid at the window is found on the next search. The
|
|
254
|
+
* day is the sale's own, in the site's zone.
|
|
255
|
+
*/ export async function recordSaleCheckIns(deps, event) {
|
|
256
|
+
var _ref, _host_data, _event_order_createdAtMs;
|
|
257
|
+
var _event_order;
|
|
258
|
+
const hostId = posOpsCleanId(event.hostId);
|
|
259
|
+
const orderId = posOpsCleanId(event.orderId);
|
|
260
|
+
if (!hostId || !orderId) return 0;
|
|
261
|
+
const lines = Array.isArray((_event_order = event.order) == null ? void 0 : _event_order.lineItems) ? event.order.lineItems : [];
|
|
262
|
+
if (!lines.length) return 0;
|
|
263
|
+
const hostRef = deps.firestore().collection('hosts').doc(hostId);
|
|
264
|
+
const products = await productsFor(deps, hostRef, [
|
|
265
|
+
event.order
|
|
266
|
+
]);
|
|
267
|
+
if (![
|
|
268
|
+
...products.values()
|
|
269
|
+
].length) return 0;
|
|
270
|
+
const [host, owner] = await Promise.all([
|
|
271
|
+
hostRef.get(),
|
|
272
|
+
deps.orgForHost(hostId)
|
|
273
|
+
]);
|
|
274
|
+
const timeZone = resolveSiteTimeZone((_ref = owner == null ? void 0 : owner.org) != null ? _ref : null, (_host_data = host.data()) != null ? _host_data : null);
|
|
275
|
+
const dayKey = zonedDayKey(Number((_event_order_createdAtMs = event.order.createdAtMs) != null ? _event_order_createdAtMs : deps.now()), timeZone);
|
|
276
|
+
const due = dueCheckInsForOrder({
|
|
277
|
+
orderId,
|
|
278
|
+
order: event.order,
|
|
279
|
+
products,
|
|
280
|
+
dayKey
|
|
281
|
+
});
|
|
282
|
+
if (!due.size) return 0;
|
|
283
|
+
return createMissing(hostRef.collection('checkIns'), due, new Set(), deps.now());
|
|
284
|
+
}
|
|
285
|
+
let subscribed = false;
|
|
286
|
+
/** Writes a register sale's check-ins as it completes. Never throws into the sale. */ export function registerPosSaleCheckIns() {
|
|
287
|
+
if (subscribed) return;
|
|
288
|
+
subscribed = true;
|
|
289
|
+
onPosSaleCompleted(async (event)=>{
|
|
290
|
+
try {
|
|
291
|
+
await recordSaleCheckIns(defaultPosOpsDeps(), event);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error('[pos-check-in] sale check-ins not written', event.orderId, error);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
/*==========================================
|
|
298
|
+
* THE ROUTE
|
|
299
|
+
*=========================================*/ const rowOf = (doc)=>_extends({}, doc.data(), {
|
|
300
|
+
id: doc.id
|
|
301
|
+
});
|
|
302
|
+
/** Who checks people in: the member a PIN put at the register, else the signed-in one. */ async function checkerOf(deps, staff, body) {
|
|
303
|
+
const registerId = posOpsCleanId(body['registerId']);
|
|
304
|
+
const uid = registerId ? (await resolvePosCashier(deps, staff, registerId, body['cashierAssertion'])).cashierId : staff.uid;
|
|
305
|
+
return {
|
|
306
|
+
uid,
|
|
307
|
+
name: await deps.memberName(uid)
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
export async function handlePosCheckIn(deps, req) {
|
|
311
|
+
var _body_action;
|
|
312
|
+
if (req.method === 'GET') return handleDayList(deps, req);
|
|
313
|
+
if (req.method !== 'POST') return {
|
|
314
|
+
status: 405,
|
|
315
|
+
body: {
|
|
316
|
+
error: 'Method not allowed'
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
const body = posOpsBody(req);
|
|
320
|
+
const gate = await authorizePosOps(deps, req, body['hostId']);
|
|
321
|
+
if ('error' in gate) return {
|
|
322
|
+
status: gate.status,
|
|
323
|
+
body: {
|
|
324
|
+
error: gate.error
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
const staff = gate.staff;
|
|
328
|
+
const checkIns = staff.hostRef.collection('checkIns');
|
|
329
|
+
const action = String((_body_action = body['action']) != null ? _body_action : '');
|
|
330
|
+
switch(action){
|
|
331
|
+
case 'search':
|
|
332
|
+
{
|
|
333
|
+
var _body_text;
|
|
334
|
+
const day = await readCheckInDay(deps, staff.hostRef, staff.org, body['day']);
|
|
335
|
+
if ('error' in day) return {
|
|
336
|
+
status: 400,
|
|
337
|
+
body: {
|
|
338
|
+
error: day.error
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
const text = String((_body_text = body['text']) != null ? _body_text : '').trim().slice(0, 120);
|
|
342
|
+
const token = checkInSearchToken(text);
|
|
343
|
+
if (text.length < 2 || !token) {
|
|
344
|
+
return {
|
|
345
|
+
status: 200,
|
|
346
|
+
body: {
|
|
347
|
+
dayKey: day.dayKey,
|
|
348
|
+
timeZone: day.timeZone,
|
|
349
|
+
customers: []
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
await reconcileCheckInDay(deps, staff.hostRef, day);
|
|
354
|
+
const snapshot = await checkIns.where('dayKey', '==', day.dayKey).where('searchTokens', 'array-contains', token).limit(CHECK_IN_SEARCH_ROWS).get();
|
|
355
|
+
return {
|
|
356
|
+
status: 200,
|
|
357
|
+
body: {
|
|
358
|
+
dayKey: day.dayKey,
|
|
359
|
+
timeZone: day.timeZone,
|
|
360
|
+
customers: checkInCustomers(snapshot.docs.map(rowOf)).slice(0, CHECK_IN_SEARCH_CUSTOMERS)
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
case 'customer':
|
|
365
|
+
{
|
|
366
|
+
var _body_customerKey, _body_email, _checkInCustomers_;
|
|
367
|
+
const day = await readCheckInDay(deps, staff.hostRef, staff.org, body['day']);
|
|
368
|
+
if ('error' in day) return {
|
|
369
|
+
status: 400,
|
|
370
|
+
body: {
|
|
371
|
+
error: day.error
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
const typedKey = String((_body_customerKey = body['customerKey']) != null ? _body_customerKey : '').trim().slice(0, 300);
|
|
375
|
+
const customerKey = typedKey || checkInCustomerKey({
|
|
376
|
+
email: String((_body_email = body['email']) != null ? _body_email : '').slice(0, 200),
|
|
377
|
+
contactId: posOpsCleanId(body['contactId']) || null,
|
|
378
|
+
fallback: ''
|
|
379
|
+
});
|
|
380
|
+
if (!customerKey) {
|
|
381
|
+
return {
|
|
382
|
+
status: 400,
|
|
383
|
+
body: {
|
|
384
|
+
error: 'Choose a customer.'
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
// The register's customer panel asks on every customer it attaches
|
|
389
|
+
// (`reconcile: 'if-used'`), so it brings the day in line only on a day
|
|
390
|
+
// the window is already checking people in.
|
|
391
|
+
if (body['reconcile'] !== false) {
|
|
392
|
+
await reconcileCheckInDay(deps, staff.hostRef, day, {
|
|
393
|
+
onlyIfUsed: body['reconcile'] === 'if-used'
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
const snapshot = await checkIns.where('dayKey', '==', day.dayKey).where('customerKey', '==', customerKey).limit(CHECK_IN_DAY_CEILING).get();
|
|
397
|
+
const rows = sortCheckInRows(snapshot.docs.map(rowOf));
|
|
398
|
+
return {
|
|
399
|
+
status: 200,
|
|
400
|
+
body: {
|
|
401
|
+
dayKey: day.dayKey,
|
|
402
|
+
timeZone: day.timeZone,
|
|
403
|
+
customer: (_checkInCustomers_ = checkInCustomers(rows)[0]) != null ? _checkInCustomers_ : null,
|
|
404
|
+
checkIns: rows
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
case 'check_in':
|
|
409
|
+
{
|
|
410
|
+
const entries = Array.isArray(body['checkIns']) ? body['checkIns'] : [];
|
|
411
|
+
if (!entries.length) return {
|
|
412
|
+
status: 400,
|
|
413
|
+
body: {
|
|
414
|
+
error: 'Choose who to check in.'
|
|
415
|
+
}
|
|
416
|
+
};
|
|
417
|
+
if (entries.length > CHECK_IN_BATCH_MAX) {
|
|
418
|
+
return {
|
|
419
|
+
status: 400,
|
|
420
|
+
body: {
|
|
421
|
+
error: `Check in ${CHECK_IN_BATCH_MAX} people or fewer at a time.`
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
const wanted = new Map();
|
|
426
|
+
for (const entry of entries){
|
|
427
|
+
const id = cleanCheckInId(entry == null ? void 0 : entry['id']);
|
|
428
|
+
if (!id) return {
|
|
429
|
+
status: 400,
|
|
430
|
+
body: {
|
|
431
|
+
error: 'That is not a check-in.'
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
const wristband = normalizeWristband(entry['wristband']);
|
|
435
|
+
if ('error' in wristband) return {
|
|
436
|
+
status: 400,
|
|
437
|
+
body: {
|
|
438
|
+
error: wristband.error
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
wanted.set(id, wristband.wristband);
|
|
442
|
+
}
|
|
443
|
+
const attempt = posOpsIdempotencyKey(req) || null;
|
|
444
|
+
const checker = await checkerOf(deps, staff, body);
|
|
445
|
+
const nowMs = deps.now();
|
|
446
|
+
const outcome = await deps.firestore().runTransaction(async (transaction)=>{
|
|
447
|
+
const done = [];
|
|
448
|
+
const already = [];
|
|
449
|
+
const missing = [];
|
|
450
|
+
const writes = [];
|
|
451
|
+
for (const [id, wristband] of wanted){
|
|
452
|
+
const ref = checkIns.doc(id);
|
|
453
|
+
const snapshot = await transaction.get(ref);
|
|
454
|
+
if (!snapshot.exists) {
|
|
455
|
+
missing.push(id);
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
const row = rowOf(snapshot);
|
|
459
|
+
if (row.status === 'checked_in') {
|
|
460
|
+
if (attempt && row.checkInAttempt === attempt) done.push(row);
|
|
461
|
+
else already.push(row);
|
|
462
|
+
continue;
|
|
463
|
+
}
|
|
464
|
+
const patch = {
|
|
465
|
+
status: 'checked_in',
|
|
466
|
+
checkedInAtMs: nowMs,
|
|
467
|
+
checkedInBy: checker.uid,
|
|
468
|
+
checkedInByName: checker.name,
|
|
469
|
+
wristband,
|
|
470
|
+
checkInAttempt: attempt,
|
|
471
|
+
updatedAtMs: nowMs
|
|
472
|
+
};
|
|
473
|
+
writes.push({
|
|
474
|
+
ref,
|
|
475
|
+
patch
|
|
476
|
+
});
|
|
477
|
+
done.push(_extends({}, row, patch));
|
|
478
|
+
}
|
|
479
|
+
for (const write of writes)transaction.update(write.ref, write.patch);
|
|
480
|
+
return {
|
|
481
|
+
done,
|
|
482
|
+
already,
|
|
483
|
+
missing
|
|
484
|
+
};
|
|
485
|
+
});
|
|
486
|
+
if (outcome.missing.length && !outcome.done.length && !outcome.already.length) {
|
|
487
|
+
return {
|
|
488
|
+
status: 404,
|
|
489
|
+
body: {
|
|
490
|
+
error: 'That check-in is no longer due.'
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
status: 200,
|
|
496
|
+
body: {
|
|
497
|
+
checkedIn: outcome.done,
|
|
498
|
+
alreadyCheckedIn: outcome.already,
|
|
499
|
+
missing: outcome.missing
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
case 'undo':
|
|
504
|
+
case 'wristband':
|
|
505
|
+
{
|
|
506
|
+
const id = cleanCheckInId(body['id']);
|
|
507
|
+
if (!id) return {
|
|
508
|
+
status: 400,
|
|
509
|
+
body: {
|
|
510
|
+
error: 'That is not a check-in.'
|
|
511
|
+
}
|
|
512
|
+
};
|
|
513
|
+
let wristband = null;
|
|
514
|
+
if (action === 'wristband') {
|
|
515
|
+
const parsed = normalizeWristband(body['wristband']);
|
|
516
|
+
if ('error' in parsed) return {
|
|
517
|
+
status: 400,
|
|
518
|
+
body: {
|
|
519
|
+
error: parsed.error
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
wristband = parsed.wristband;
|
|
523
|
+
}
|
|
524
|
+
const checker = action === 'undo' ? await checkerOf(deps, staff, body) : null;
|
|
525
|
+
const nowMs = deps.now();
|
|
526
|
+
const ref = checkIns.doc(id);
|
|
527
|
+
const outcome = await deps.firestore().runTransaction(async (transaction)=>{
|
|
528
|
+
var _ref;
|
|
529
|
+
const snapshot = await transaction.get(ref);
|
|
530
|
+
if (!snapshot.exists) return {
|
|
531
|
+
status: 404,
|
|
532
|
+
error: 'That check-in is no longer due.'
|
|
533
|
+
};
|
|
534
|
+
const row = rowOf(snapshot);
|
|
535
|
+
if (row.status !== 'checked_in') {
|
|
536
|
+
return {
|
|
537
|
+
status: 409,
|
|
538
|
+
error: 'They are not checked in yet.'
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
const patch = action === 'undo' ? {
|
|
542
|
+
status: 'expected',
|
|
543
|
+
checkedInAtMs: null,
|
|
544
|
+
checkedInBy: null,
|
|
545
|
+
checkedInByName: null,
|
|
546
|
+
wristband: null,
|
|
547
|
+
checkInAttempt: null,
|
|
548
|
+
undoneAtMs: nowMs,
|
|
549
|
+
undoneBy: (_ref = checker == null ? void 0 : checker.uid) != null ? _ref : staff.uid,
|
|
550
|
+
updatedAtMs: nowMs
|
|
551
|
+
} : {
|
|
552
|
+
wristband,
|
|
553
|
+
updatedAtMs: nowMs
|
|
554
|
+
};
|
|
555
|
+
transaction.update(ref, patch);
|
|
556
|
+
return {
|
|
557
|
+
status: 200,
|
|
558
|
+
row: _extends({}, row, patch)
|
|
559
|
+
};
|
|
560
|
+
});
|
|
561
|
+
if ('error' in outcome) return {
|
|
562
|
+
status: outcome.status,
|
|
563
|
+
body: {
|
|
564
|
+
error: outcome.error
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
return {
|
|
568
|
+
status: 200,
|
|
569
|
+
body: {
|
|
570
|
+
checkIn: outcome.row
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
default:
|
|
575
|
+
return {
|
|
576
|
+
status: 400,
|
|
577
|
+
body: {
|
|
578
|
+
error: 'Unknown action'
|
|
579
|
+
}
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
async function handleDayList(deps, req) {
|
|
584
|
+
var _req_query;
|
|
585
|
+
const query = (_req_query = req.query) != null ? _req_query : {};
|
|
586
|
+
const gate = await authorizePosOps(deps, req, queryValue(query['hostId']));
|
|
587
|
+
if ('error' in gate) return {
|
|
588
|
+
status: gate.status,
|
|
589
|
+
body: {
|
|
590
|
+
error: gate.error
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
const format = queryValue(query['format']).toLowerCase();
|
|
594
|
+
if (format && format !== 'json' && format !== 'csv') {
|
|
595
|
+
return {
|
|
596
|
+
status: 400,
|
|
597
|
+
body: {
|
|
598
|
+
error: 'Choose json or csv.'
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
const { staff } = gate;
|
|
603
|
+
const day = await readCheckInDay(deps, staff.hostRef, staff.org, queryValue(query['day']));
|
|
604
|
+
if ('error' in day) return {
|
|
605
|
+
status: 400,
|
|
606
|
+
body: {
|
|
607
|
+
error: day.error
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
const { truncated } = await reconcileCheckInDay(deps, staff.hostRef, day);
|
|
611
|
+
const snapshot = await staff.hostRef.collection('checkIns').where('dayKey', '==', day.dayKey).limit(CHECK_IN_DAY_CEILING + 1).get();
|
|
612
|
+
const rows = sortCheckInRows(snapshot.docs.slice(0, CHECK_IN_DAY_CEILING).map(rowOf));
|
|
613
|
+
if (format === 'csv') {
|
|
614
|
+
return {
|
|
615
|
+
status: 200,
|
|
616
|
+
csv: checkInDayCsv(rows, day.timeZone),
|
|
617
|
+
filename: `check-ins-${day.dayKey}.csv`
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
return {
|
|
621
|
+
status: 200,
|
|
622
|
+
body: {
|
|
623
|
+
dayKey: day.dayKey,
|
|
624
|
+
timeZone: day.timeZone,
|
|
625
|
+
totals: checkInDayTotals(rows),
|
|
626
|
+
checkIns: rows,
|
|
627
|
+
truncated: truncated || snapshot.docs.length > CHECK_IN_DAY_CEILING
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
export function createPosCheckInHandler(deps = defaultPosOpsDeps) {
|
|
632
|
+
return async (req, res)=>{
|
|
633
|
+
try {
|
|
634
|
+
const outcome = await handlePosCheckIn(deps(), req);
|
|
635
|
+
res.setHeader('Cache-Control', 'private, no-store');
|
|
636
|
+
if ('csv' in outcome) {
|
|
637
|
+
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
638
|
+
res.setHeader('Content-Disposition', `attachment; filename="${outcome.filename}"`);
|
|
639
|
+
return res.status(200).send(outcome.csv);
|
|
640
|
+
}
|
|
641
|
+
return res.status(outcome.status).json(outcome.body);
|
|
642
|
+
} catch (error) {
|
|
643
|
+
console.error('[pos-check-in] failed', error);
|
|
644
|
+
return res.status(500).json({
|
|
645
|
+
error: 'Check-in failed. Try again.'
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
export const posCheckInHandler = createPosCheckInHandler();
|
|
651
|
+
|
|
652
|
+
//# sourceMappingURL=pos-check-in.js.map
|