@aglyn/plugins-commerce 1.0.0-beta.277 → 1.0.0-beta.282

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/package.json +17 -14
  2. package/src/lib/components/commerce-console-page.js +33 -1
  3. package/src/lib/components/commerce-console-page.js.map +1 -1
  4. package/src/lib/components/console/payments-settings-card.component.js +1 -1
  5. package/src/lib/components/console/payments-settings-card.component.js.map +1 -1
  6. package/src/lib/components/console/pos/pos-hardware-api.d.ts +122 -0
  7. package/src/lib/components/console/pos/pos-hardware-api.js +90 -0
  8. package/src/lib/components/console/pos/pos-hardware-api.js.map +1 -0
  9. package/src/lib/components/console/pos/pos-reader-splash-api.d.ts +58 -0
  10. package/src/lib/components/console/pos/pos-reader-splash-api.js +101 -0
  11. package/src/lib/components/console/pos/pos-reader-splash-api.js.map +1 -0
  12. package/src/lib/components/console/pos-devices-card.component.js +5 -0
  13. package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
  14. package/src/lib/components/console/pos-hardware-card.component.d.ts +26 -0
  15. package/src/lib/components/console/pos-hardware-card.component.js +456 -0
  16. package/src/lib/components/console/pos-hardware-card.component.js.map +1 -0
  17. package/src/lib/components/console/pos-hardware-orders-staff-page.component.d.ts +14 -0
  18. package/src/lib/components/console/pos-hardware-orders-staff-page.component.js +519 -0
  19. package/src/lib/components/console/pos-hardware-orders-staff-page.component.js.map +1 -0
  20. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.d.ts +27 -0
  21. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js +444 -0
  22. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js.map +1 -0
  23. package/src/lib/components/console/pos-ops/pos-check-in-zone.component.d.ts +9 -0
  24. package/src/lib/components/console/pos-ops/pos-check-in-zone.component.js +37 -0
  25. package/src/lib/components/console/pos-ops/pos-check-in-zone.component.js.map +1 -0
  26. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.d.ts +24 -0
  27. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +346 -0
  28. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -0
  29. package/src/lib/components/console/pos-ops/pos-customer-lookup.component.d.ts +5 -1
  30. package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js +83 -4
  31. package/src/lib/components/console/pos-ops/pos-customer-lookup.component.js.map +1 -1
  32. package/src/lib/components/console/pos-ops/pos-day-totals-card.component.d.ts +35 -0
  33. package/src/lib/components/console/pos-ops/pos-day-totals-card.component.js +546 -0
  34. package/src/lib/components/console/pos-ops/pos-day-totals-card.component.js.map +1 -0
  35. package/src/lib/components/console/pos-ops/pos-operations-bar.component.d.ts +2 -1
  36. package/src/lib/components/console/pos-ops/pos-operations-bar.component.js +20 -1
  37. package/src/lib/components/console/pos-ops/pos-operations-bar.component.js.map +1 -1
  38. package/src/lib/components/console/pos-ops/pos-ops-api.d.ts +1 -1
  39. package/src/lib/components/console/pos-ops/pos-ops-api.js.map +1 -1
  40. package/src/lib/components/console/pos-page.component.js +6 -3
  41. package/src/lib/components/console/pos-page.component.js.map +1 -1
  42. package/src/lib/components/console/pos-reader-splash-setting.component.d.ts +22 -0
  43. package/src/lib/components/console/pos-reader-splash-setting.component.js +275 -0
  44. package/src/lib/components/console/pos-reader-splash-setting.component.js.map +1 -0
  45. package/src/lib/components/console/pos-reader-splash-staff-page.component.d.ts +14 -0
  46. package/src/lib/components/console/pos-reader-splash-staff-page.component.js +301 -0
  47. package/src/lib/components/console/pos-reader-splash-staff-page.component.js.map +1 -0
  48. package/src/lib/components/console/pos-zones.d.ts +37 -3
  49. package/src/lib/components/console/pos-zones.js +11 -1
  50. package/src/lib/components/console/pos-zones.js.map +1 -1
  51. package/src/lib/components/console/product-check-in-fields.component.d.ts +20 -0
  52. package/src/lib/components/console/product-check-in-fields.component.js +172 -0
  53. package/src/lib/components/console/product-check-in-fields.component.js.map +1 -0
  54. package/src/lib/components/console/product-editor-dialog.component.js +48 -9
  55. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  56. package/src/lib/components/console/product-register-fields.component.d.ts +12 -0
  57. package/src/lib/components/console/product-register-fields.component.js +39 -1
  58. package/src/lib/components/console/product-register-fields.component.js.map +1 -1
  59. package/src/lib/components/console/products-hub-card.component.js +21 -6
  60. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  61. package/src/lib/components/console/reservations-card.component.js +239 -70
  62. package/src/lib/components/console/reservations-card.component.js.map +1 -1
  63. package/src/lib/components/reservation-widget.js +16 -0
  64. package/src/lib/components/reservation-widget.js.map +1 -1
  65. package/src/lib/constants/operator-alerts.d.ts +6 -0
  66. package/src/lib/constants/operator-alerts.js +20 -1
  67. package/src/lib/constants/operator-alerts.js.map +1 -1
  68. package/src/lib/declarations.console-server.js +7 -0
  69. package/src/lib/declarations.console-server.js.map +1 -1
  70. package/src/lib/declarations.d.ts +26 -0
  71. package/src/lib/declarations.js +34 -0
  72. package/src/lib/declarations.js.map +1 -0
  73. package/src/lib/model/commerce-check-in-product.d.ts +37 -0
  74. package/src/lib/model/commerce-check-in-product.js +49 -0
  75. package/src/lib/model/commerce-check-in-product.js.map +1 -0
  76. package/src/lib/model/commerce-check-in.d.ts +187 -0
  77. package/src/lib/model/commerce-check-in.js +468 -0
  78. package/src/lib/model/commerce-check-in.js.map +1 -0
  79. package/src/lib/model/commerce-pos-day-report.d.ts +209 -0
  80. package/src/lib/model/commerce-pos-day-report.js +572 -0
  81. package/src/lib/model/commerce-pos-day-report.js.map +1 -0
  82. package/src/lib/model/commerce-pos-offline.d.ts +65 -3
  83. package/src/lib/model/commerce-pos-offline.js +104 -7
  84. package/src/lib/model/commerce-pos-offline.js.map +1 -1
  85. package/src/lib/model/commerce-pos-ops.d.ts +3 -0
  86. package/src/lib/model/commerce-pos-ops.js +1 -0
  87. package/src/lib/model/commerce-pos-ops.js.map +1 -1
  88. package/src/lib/model/commerce-pos.d.ts +43 -3
  89. package/src/lib/model/commerce-pos.js +73 -5
  90. package/src/lib/model/commerce-pos.js.map +1 -1
  91. package/src/lib/model/commerce-reservations.d.ts +73 -7
  92. package/src/lib/model/commerce-reservations.js +90 -10
  93. package/src/lib/model/commerce-reservations.js.map +1 -1
  94. package/src/lib/model/commerce.d.ts +40 -0
  95. package/src/lib/model/commerce.js +22 -4
  96. package/src/lib/model/commerce.js.map +1 -1
  97. package/src/lib/model/index.d.ts +1 -0
  98. package/src/lib/model/index.js +1 -0
  99. package/src/lib/model/index.js.map +1 -1
  100. package/src/lib/model/plugin-permissions.d.ts +1 -1
  101. package/src/lib/model/plugin-permissions.js +2 -1
  102. package/src/lib/model/plugin-permissions.js.map +1 -1
  103. package/src/lib/model/pos-hardware.d.ts +288 -0
  104. package/src/lib/model/pos-hardware.js +499 -0
  105. package/src/lib/model/pos-hardware.js.map +1 -0
  106. package/src/lib/model/pos-offline-card-limits.d.ts +20 -0
  107. package/src/lib/model/pos-offline-card-limits.js +28 -0
  108. package/src/lib/model/pos-offline-card-limits.js.map +1 -0
  109. package/src/lib/model/pos-reader-splash.d.ts +114 -0
  110. package/src/lib/model/pos-reader-splash.js +141 -0
  111. package/src/lib/model/pos-reader-splash.js.map +1 -0
  112. package/src/lib/model/product-write.d.ts +6 -0
  113. package/src/lib/plugin-config.d.ts +10 -0
  114. package/src/lib/plugin-config.js +40 -0
  115. package/src/lib/plugin-config.js.map +1 -1
  116. package/src/lib/plugin.js +28 -0
  117. package/src/lib/plugin.js.map +1 -1
  118. package/src/lib/server/cart-checkout.js +3 -1
  119. package/src/lib/server/cart-checkout.js.map +1 -1
  120. package/src/lib/server/cart.js +12 -1
  121. package/src/lib/server/cart.js.map +1 -1
  122. package/src/lib/server/catalog.js +49 -12
  123. package/src/lib/server/catalog.js.map +1 -1
  124. package/src/lib/server/checkout-extras.js +1 -1
  125. package/src/lib/server/checkout-extras.js.map +1 -1
  126. package/src/lib/server/checkout.js +7 -0
  127. package/src/lib/server/checkout.js.map +1 -1
  128. package/src/lib/server/person-eraser.d.ts +8 -1
  129. package/src/lib/server/person-eraser.js +23 -1
  130. package/src/lib/server/person-eraser.js.map +1 -1
  131. package/src/lib/server/pos-check-in.d.ts +49 -0
  132. package/src/lib/server/pos-check-in.js +652 -0
  133. package/src/lib/server/pos-check-in.js.map +1 -0
  134. package/src/lib/server/pos-day-report.d.ts +46 -0
  135. package/src/lib/server/pos-day-report.js +299 -0
  136. package/src/lib/server/pos-day-report.js.map +1 -0
  137. package/src/lib/server/pos-hardware-admin.d.ts +50 -0
  138. package/src/lib/server/pos-hardware-admin.js +366 -0
  139. package/src/lib/server/pos-hardware-admin.js.map +1 -0
  140. package/src/lib/server/pos-hardware-notify.d.ts +27 -0
  141. package/src/lib/server/pos-hardware-notify.js +142 -0
  142. package/src/lib/server/pos-hardware-notify.js.map +1 -0
  143. package/src/lib/server/pos-hardware-user-eraser.d.ts +26 -0
  144. package/src/lib/server/pos-hardware-user-eraser.js +67 -0
  145. package/src/lib/server/pos-hardware-user-eraser.js.map +1 -0
  146. package/src/lib/server/pos-hardware-webhook.d.ts +31 -0
  147. package/src/lib/server/pos-hardware-webhook.js +176 -0
  148. package/src/lib/server/pos-hardware-webhook.js.map +1 -0
  149. package/src/lib/server/pos-hardware.d.ts +58 -0
  150. package/src/lib/server/pos-hardware.js +234 -0
  151. package/src/lib/server/pos-hardware.js.map +1 -0
  152. package/src/lib/server/pos-offline-card-reconcile.d.ts +38 -0
  153. package/src/lib/server/pos-offline-card-reconcile.js +58 -0
  154. package/src/lib/server/pos-offline-card-reconcile.js.map +1 -0
  155. package/src/lib/server/pos-offline-sync.d.ts +32 -0
  156. package/src/lib/server/pos-offline-sync.js +191 -15
  157. package/src/lib/server/pos-offline-sync.js.map +1 -1
  158. package/src/lib/server/pos-ops-routes.d.ts +4 -2
  159. package/src/lib/server/pos-ops-routes.js +14 -2
  160. package/src/lib/server/pos-ops-routes.js.map +1 -1
  161. package/src/lib/server/pos-order.js +39 -51
  162. package/src/lib/server/pos-order.js.map +1 -1
  163. package/src/lib/server/pos-payment.js +8 -4
  164. package/src/lib/server/pos-payment.js.map +1 -1
  165. package/src/lib/server/pos-reader-splash-admin.d.ts +36 -0
  166. package/src/lib/server/pos-reader-splash-admin.js +295 -0
  167. package/src/lib/server/pos-reader-splash-admin.js.map +1 -0
  168. package/src/lib/server/pos-reader-splash-render.d.ts +40 -0
  169. package/src/lib/server/pos-reader-splash-render.js +138 -0
  170. package/src/lib/server/pos-reader-splash-render.js.map +1 -0
  171. package/src/lib/server/pos-reader-splash-server.d.ts +32 -0
  172. package/src/lib/server/pos-reader-splash-server.js +32 -0
  173. package/src/lib/server/pos-reader-splash-server.js.map +1 -0
  174. package/src/lib/server/pos-reader-splash.d.ts +105 -0
  175. package/src/lib/server/pos-reader-splash.js +548 -0
  176. package/src/lib/server/pos-reader-splash.js.map +1 -0
  177. package/src/lib/server/pos-sale.d.ts +2 -1
  178. package/src/lib/server/pos-sale.js +3 -1
  179. package/src/lib/server/pos-sale.js.map +1 -1
  180. package/src/lib/server/pos-terminal.d.ts +67 -4
  181. package/src/lib/server/pos-terminal.js +239 -32
  182. package/src/lib/server/pos-terminal.js.map +1 -1
  183. package/src/lib/server/process-restock.js +4 -0
  184. package/src/lib/server/process-restock.js.map +1 -1
  185. package/src/lib/server/product-card.js +2 -2
  186. package/src/lib/server/product-card.js.map +1 -1
  187. package/src/lib/server/product-catalog.d.ts +2 -0
  188. package/src/lib/server/product-catalog.js +11 -4
  189. package/src/lib/server/product-catalog.js.map +1 -1
  190. package/src/lib/server/product.js +3 -1
  191. package/src/lib/server/product.js.map +1 -1
  192. package/src/lib/server/related.js +8 -1
  193. package/src/lib/server/related.js.map +1 -1
  194. package/src/lib/server/reservation-availability-query.d.ts +35 -0
  195. package/src/lib/server/reservation-availability-query.js +34 -0
  196. package/src/lib/server/reservation-availability-query.js.map +1 -0
  197. package/src/lib/server/reservation-availability.d.ts +21 -0
  198. package/src/lib/server/reservation-availability.js +64 -10
  199. package/src/lib/server/reservation-availability.js.map +1 -1
  200. package/src/lib/server/reservation-walk-in.d.ts +28 -0
  201. package/src/lib/server/reservation-walk-in.js +166 -0
  202. package/src/lib/server/reservation-walk-in.js.map +1 -0
  203. package/src/lib/server/reserve.js +13 -10
  204. package/src/lib/server/reserve.js.map +1 -1
  205. package/src/lib/server/reviews.js +3 -1
  206. package/src/lib/server/reviews.js.map +1 -1
  207. package/src/lib/server/site-page-resolver.js +5 -2
  208. package/src/lib/server/site-page-resolver.js.map +1 -1
  209. package/src/lib/server.js +26 -0
  210. package/src/lib/server.js.map +1 -1
  211. package/src/lib/testing/fake-firestore.js +72 -0
  212. 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