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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/cart-extras.d.ts +6 -0
  3. package/src/lib/components/cart-extras.js +4 -1
  4. package/src/lib/components/cart-extras.js.map +1 -1
  5. package/src/lib/components/cart-fulfillment.d.ts +117 -0
  6. package/src/lib/components/cart-fulfillment.js +367 -0
  7. package/src/lib/components/cart-fulfillment.js.map +1 -0
  8. package/src/lib/components/cart.js +52 -10
  9. package/src/lib/components/cart.js.map +1 -1
  10. package/src/lib/components/commerce-console-page.js +42 -1
  11. package/src/lib/components/commerce-console-page.js.map +1 -1
  12. package/src/lib/components/console/catalog-organization-card.component.js +1 -0
  13. package/src/lib/components/console/catalog-organization-card.component.js.map +1 -1
  14. package/src/lib/components/console/host-orders-card.component.js +42 -11
  15. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  16. package/src/lib/components/console/local-delivery-settings-card.component.d.ts +37 -0
  17. package/src/lib/components/console/local-delivery-settings-card.component.js +542 -0
  18. package/src/lib/components/console/local-delivery-settings-card.component.js.map +1 -0
  19. package/src/lib/components/console/locations-card.component.js +177 -3
  20. package/src/lib/components/console/locations-card.component.js.map +1 -1
  21. package/src/lib/components/console/order-detail-dialog.component.js +10 -0
  22. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  23. package/src/lib/components/console/order-local-fulfillment-panel.component.d.ts +44 -0
  24. package/src/lib/components/console/order-local-fulfillment-panel.component.js +265 -0
  25. package/src/lib/components/console/order-local-fulfillment-panel.component.js.map +1 -0
  26. package/src/lib/components/console/order-offline-notice.component.d.ts +12 -0
  27. package/src/lib/components/console/order-offline-notice.component.js +53 -0
  28. package/src/lib/components/console/order-offline-notice.component.js.map +1 -0
  29. package/src/lib/components/console/pickup-delivery-queue-card.component.d.ts +35 -0
  30. package/src/lib/components/console/pickup-delivery-queue-card.component.js +222 -0
  31. package/src/lib/components/console/pickup-delivery-queue-card.component.js.map +1 -0
  32. package/src/lib/components/console/pos/pos-api.d.ts +24 -1
  33. package/src/lib/components/console/pos/pos-api.js +13 -0
  34. package/src/lib/components/console/pos/pos-api.js.map +1 -1
  35. package/src/lib/components/console/pos/pos-item-dialog.component.d.ts +12 -0
  36. package/src/lib/components/console/pos/pos-item-dialog.component.js +10 -8
  37. package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -1
  38. package/src/lib/components/console/pos/pos-kiosk-queue.component.d.ts +22 -0
  39. package/src/lib/components/console/pos/pos-kiosk-queue.component.js +168 -0
  40. package/src/lib/components/console/pos/pos-kiosk-queue.component.js.map +1 -0
  41. package/src/lib/components/console/pos/pos-tender-dialogs.component.d.ts +18 -0
  42. package/src/lib/components/console/pos/pos-tender-dialogs.component.js +116 -1
  43. package/src/lib/components/console/pos/pos-tender-dialogs.component.js.map +1 -1
  44. package/src/lib/components/console/pos/pos-tender-panel.component.d.ts +8 -0
  45. package/src/lib/components/console/pos/pos-tender-panel.component.js +77 -18
  46. package/src/lib/components/console/pos/pos-tender-panel.component.js.map +1 -1
  47. package/src/lib/components/console/pos/use-pos-display.d.ts +5 -2
  48. package/src/lib/components/console/pos/use-pos-display.js +3 -2
  49. package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
  50. package/src/lib/components/console/pos-devices-card.component.js +2 -2
  51. package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
  52. package/src/lib/components/console/pos-display/pairing-screen.d.ts +5 -2
  53. package/src/lib/components/console/pos-display/pairing-screen.js +7 -5
  54. package/src/lib/components/console/pos-display/pairing-screen.js.map +1 -1
  55. package/src/lib/components/console/pos-display/pos-display-api.d.ts +7 -1
  56. package/src/lib/components/console/pos-display/pos-display-api.js +7 -2
  57. package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -1
  58. package/src/lib/components/console/pos-kiosk/kiosk-checkout.d.ts +41 -0
  59. package/src/lib/components/console/pos-kiosk/kiosk-checkout.js +275 -0
  60. package/src/lib/components/console/pos-kiosk/kiosk-checkout.js.map +1 -0
  61. package/src/lib/components/console/pos-kiosk/kiosk-menu.d.ts +32 -0
  62. package/src/lib/components/console/pos-kiosk/kiosk-menu.js +344 -0
  63. package/src/lib/components/console/pos-kiosk/kiosk-menu.js.map +1 -0
  64. package/src/lib/components/console/pos-kiosk/kiosk-staff.d.ts +12 -0
  65. package/src/lib/components/console/pos-kiosk/kiosk-staff.js +257 -0
  66. package/src/lib/components/console/pos-kiosk/kiosk-staff.js.map +1 -0
  67. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.d.ts +114 -0
  68. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js +189 -0
  69. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js.map +1 -0
  70. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.d.ts +16 -0
  71. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js +80 -0
  72. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js.map +1 -0
  73. package/src/lib/components/console/pos-kiosk-page.component.d.ts +28 -0
  74. package/src/lib/components/console/pos-kiosk-page.component.js +791 -0
  75. package/src/lib/components/console/pos-kiosk-page.component.js.map +1 -0
  76. package/src/lib/components/console/pos-offline/pos-offline-api.d.ts +44 -0
  77. package/src/lib/components/console/pos-offline/pos-offline-api.js +69 -0
  78. package/src/lib/components/console/pos-offline/pos-offline-api.js.map +1 -0
  79. package/src/lib/components/console/pos-offline/pos-offline-banner.component.d.ts +7 -0
  80. package/src/lib/components/console/pos-offline/pos-offline-banner.component.js +135 -0
  81. package/src/lib/components/console/pos-offline/pos-offline-banner.component.js.map +1 -0
  82. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.d.ts +32 -0
  83. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js +287 -0
  84. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js.map +1 -0
  85. package/src/lib/components/console/pos-offline/pos-offline-store.d.ts +63 -0
  86. package/src/lib/components/console/pos-offline/pos-offline-store.js +145 -0
  87. package/src/lib/components/console/pos-offline/pos-offline-store.js.map +1 -0
  88. package/src/lib/components/console/pos-offline/register-offline.d.ts +24 -0
  89. package/src/lib/components/console/pos-offline/register-offline.js +24 -0
  90. package/src/lib/components/console/pos-offline/register-offline.js.map +1 -0
  91. package/src/lib/components/console/pos-offline/use-pos-offline.d.ts +85 -0
  92. package/src/lib/components/console/pos-offline/use-pos-offline.js +542 -0
  93. package/src/lib/components/console/pos-offline/use-pos-offline.js.map +1 -0
  94. package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js +72 -17
  95. package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js.map +1 -1
  96. package/src/lib/components/console/pos-page.component.js +228 -51
  97. package/src/lib/components/console/pos-page.component.js.map +1 -1
  98. package/src/lib/components/console/pos-zones.d.ts +23 -0
  99. package/src/lib/components/console/pos-zones.js +31 -0
  100. package/src/lib/components/console/pos-zones.js.map +1 -0
  101. package/src/lib/components/console/product-editor-dialog.component.js +10 -64
  102. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  103. package/src/lib/components/console/products-hub-card.component.js +46 -30
  104. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  105. package/src/lib/components/console/shipping-settings-card.component.js +1 -1
  106. package/src/lib/components/console/shipping-settings-card.component.js.map +1 -1
  107. package/src/lib/components/console/stock-movements-card.component.js +48 -16
  108. package/src/lib/components/console/stock-movements-card.component.js.map +1 -1
  109. package/src/lib/components/console/store-zones.d.ts +8 -1
  110. package/src/lib/components/console/store-zones.js +11 -1
  111. package/src/lib/components/console/store-zones.js.map +1 -1
  112. package/src/lib/components/console/storefront-tax-summary-card.component.js +1 -0
  113. package/src/lib/components/console/storefront-tax-summary-card.component.js.map +1 -1
  114. package/src/lib/components/order-status.js +66 -0
  115. package/src/lib/components/order-status.js.map +1 -1
  116. package/src/lib/components/product-detail.js +42 -15
  117. package/src/lib/components/product-detail.js.map +1 -1
  118. package/src/lib/constants/local-fulfillment-list-query.d.ts +48 -0
  119. package/src/lib/constants/local-fulfillment-list-query.js +84 -0
  120. package/src/lib/constants/local-fulfillment-list-query.js.map +1 -0
  121. package/src/lib/constants/orders-list-query.d.ts +7 -2
  122. package/src/lib/constants/orders-list-query.js +89 -8
  123. package/src/lib/constants/orders-list-query.js.map +1 -1
  124. package/src/lib/constants/pos-shift-list-query.d.ts +22 -0
  125. package/src/lib/constants/pos-shift-list-query.js +115 -0
  126. package/src/lib/constants/pos-shift-list-query.js.map +1 -0
  127. package/src/lib/constants/product-list-query.d.ts +6 -1
  128. package/src/lib/constants/product-list-query.js +77 -8
  129. package/src/lib/constants/product-list-query.js.map +1 -1
  130. package/src/lib/constants/stock-movement-query.d.ts +3 -2
  131. package/src/lib/constants/stock-movement-query.js +52 -8
  132. package/src/lib/constants/stock-movement-query.js.map +1 -1
  133. package/src/lib/declarations.server.js +21 -0
  134. package/src/lib/declarations.server.js.map +1 -1
  135. package/src/lib/model/buyer-notifications.d.ts +5 -3
  136. package/src/lib/model/buyer-notifications.js +25 -4
  137. package/src/lib/model/buyer-notifications.js.map +1 -1
  138. package/src/lib/model/commerce-orders.d.ts +103 -1
  139. package/src/lib/model/commerce-orders.js +2 -1
  140. package/src/lib/model/commerce-orders.js.map +1 -1
  141. package/src/lib/model/commerce-pos-kiosk.d.ts +175 -0
  142. package/src/lib/model/commerce-pos-kiosk.js +147 -0
  143. package/src/lib/model/commerce-pos-kiosk.js.map +1 -0
  144. package/src/lib/model/commerce-pos-offline.d.ts +234 -0
  145. package/src/lib/model/commerce-pos-offline.js +301 -0
  146. package/src/lib/model/commerce-pos-offline.js.map +1 -0
  147. package/src/lib/model/commerce-pos-ops.d.ts +16 -6
  148. package/src/lib/model/commerce-pos-ops.js +12 -4
  149. package/src/lib/model/commerce-pos-ops.js.map +1 -1
  150. package/src/lib/model/commerce-pos.d.ts +13 -1
  151. package/src/lib/model/commerce-pos.js +5 -4
  152. package/src/lib/model/commerce-pos.js.map +1 -1
  153. package/src/lib/model/commerce-stock-holds.d.ts +24 -3
  154. package/src/lib/model/commerce-stock-holds.js +26 -8
  155. package/src/lib/model/commerce-stock-holds.js.map +1 -1
  156. package/src/lib/model/commerce.d.ts +7 -1
  157. package/src/lib/model/commerce.js.map +1 -1
  158. package/src/lib/model/index.d.ts +5 -0
  159. package/src/lib/model/index.js +5 -0
  160. package/src/lib/model/index.js.map +1 -1
  161. package/src/lib/model/local-fulfillment-settings.d.ts +178 -0
  162. package/src/lib/model/local-fulfillment-settings.js +361 -0
  163. package/src/lib/model/local-fulfillment-settings.js.map +1 -0
  164. package/src/lib/model/order-list-fields.d.ts +10 -1
  165. package/src/lib/model/order-list-fields.js +1 -0
  166. package/src/lib/model/order-list-fields.js.map +1 -1
  167. package/src/lib/model/order-local-fulfillment.d.ts +131 -0
  168. package/src/lib/model/order-local-fulfillment.js +125 -0
  169. package/src/lib/model/order-local-fulfillment.js.map +1 -0
  170. package/src/lib/model/order-shipping-export.js +4 -0
  171. package/src/lib/model/order-shipping-export.js.map +1 -1
  172. package/src/lib/model/order-status-view.d.ts +20 -1
  173. package/src/lib/model/order-status-view.js +60 -6
  174. package/src/lib/model/order-status-view.js.map +1 -1
  175. package/src/lib/model/product-write.d.ts +186 -0
  176. package/src/lib/model/product-write.js +116 -0
  177. package/src/lib/model/product-write.js.map +1 -0
  178. package/src/lib/plugin-config.js +6 -2
  179. package/src/lib/plugin-config.js.map +1 -1
  180. package/src/lib/plugin.js +11 -0
  181. package/src/lib/plugin.js.map +1 -1
  182. package/src/lib/pos-kiosk-config.d.ts +26 -0
  183. package/src/lib/pos-kiosk-config.js +59 -0
  184. package/src/lib/pos-kiosk-config.js.map +1 -0
  185. package/src/lib/server/api-v1/openapi.js +8 -0
  186. package/src/lib/server/api-v1/openapi.js.map +1 -1
  187. package/src/lib/server/api-v1/order-view.d.ts +19 -0
  188. package/src/lib/server/api-v1/order-view.js +35 -0
  189. package/src/lib/server/api-v1/order-view.js.map +1 -1
  190. package/src/lib/server/billing-webhook.js +238 -87
  191. package/src/lib/server/billing-webhook.js.map +1 -1
  192. package/src/lib/server/cart-checkout.d.ts +2 -0
  193. package/src/lib/server/cart-checkout.js +173 -13
  194. package/src/lib/server/cart-checkout.js.map +1 -1
  195. package/src/lib/server/channel-order-updates.d.ts +50 -0
  196. package/src/lib/server/channel-order-updates.js +290 -0
  197. package/src/lib/server/channel-order-updates.js.map +1 -0
  198. package/src/lib/server/channel-orders.d.ts +85 -0
  199. package/src/lib/server/channel-orders.js +543 -0
  200. package/src/lib/server/channel-orders.js.map +1 -0
  201. package/src/lib/server/checkout-extras.js +18 -4
  202. package/src/lib/server/checkout-extras.js.map +1 -1
  203. package/src/lib/server/checkout.js +71 -14
  204. package/src/lib/server/checkout.js.map +1 -1
  205. package/src/lib/server/local-fulfillment-status.d.ts +86 -0
  206. package/src/lib/server/local-fulfillment-status.js +379 -0
  207. package/src/lib/server/local-fulfillment-status.js.map +1 -0
  208. package/src/lib/server/local-fulfillment.d.ts +190 -0
  209. package/src/lib/server/local-fulfillment.js +580 -0
  210. package/src/lib/server/local-fulfillment.js.map +1 -0
  211. package/src/lib/server/membership-register.js +3 -1
  212. package/src/lib/server/membership-register.js.map +1 -1
  213. package/src/lib/server/order-notifications.js +61 -0
  214. package/src/lib/server/order-notifications.js.map +1 -1
  215. package/src/lib/server/pos-display.d.ts +27 -1
  216. package/src/lib/server/pos-display.js +50 -9
  217. package/src/lib/server/pos-display.js.map +1 -1
  218. package/src/lib/server/pos-kiosk-principal.d.ts +29 -0
  219. package/src/lib/server/pos-kiosk-principal.js +42 -0
  220. package/src/lib/server/pos-kiosk-principal.js.map +1 -0
  221. package/src/lib/server/pos-kiosk.d.ts +54 -0
  222. package/src/lib/server/pos-kiosk.js +972 -0
  223. package/src/lib/server/pos-kiosk.js.map +1 -0
  224. package/src/lib/server/pos-offline-sync.d.ts +85 -0
  225. package/src/lib/server/pos-offline-sync.js +765 -0
  226. package/src/lib/server/pos-offline-sync.js.map +1 -0
  227. package/src/lib/server/pos-order.js +22 -4
  228. package/src/lib/server/pos-order.js.map +1 -1
  229. package/src/lib/server/pos-payment.d.ts +6 -0
  230. package/src/lib/server/pos-payment.js +225 -6
  231. package/src/lib/server/pos-payment.js.map +1 -1
  232. package/src/lib/server/pos-print.js +5 -0
  233. package/src/lib/server/pos-print.js.map +1 -1
  234. package/src/lib/server/pos-return.js +27 -0
  235. package/src/lib/server/pos-return.js.map +1 -1
  236. package/src/lib/server/pos-sale.d.ts +6 -0
  237. package/src/lib/server/pos-sale.js +2 -0
  238. package/src/lib/server/pos-sale.js.map +1 -1
  239. package/src/lib/server/pos-shift.js +8 -1
  240. package/src/lib/server/pos-shift.js.map +1 -1
  241. package/src/lib/server/product-catalog.js +5 -1
  242. package/src/lib/server/product-catalog.js.map +1 -1
  243. package/src/lib/server/product-drafts.d.ts +5 -1
  244. package/src/lib/server/product-drafts.js +3 -3
  245. package/src/lib/server/product-drafts.js.map +1 -1
  246. package/src/lib/server/product-writer.d.ts +19 -0
  247. package/src/lib/server/product-writer.js +261 -0
  248. package/src/lib/server/product-writer.js.map +1 -0
  249. package/src/lib/server/products-write.d.ts +61 -0
  250. package/src/lib/server/products-write.js +375 -0
  251. package/src/lib/server/products-write.js.map +1 -0
  252. package/src/lib/server/refund.js +3 -0
  253. package/src/lib/server/refund.js.map +1 -1
  254. package/src/lib/server/stock-hold.d.ts +9 -1
  255. package/src/lib/server/stock-hold.js +15 -7
  256. package/src/lib/server/stock-hold.js.map +1 -1
  257. package/src/lib/server.js +21 -0
  258. package/src/lib/server.js.map +1 -1
  259. package/src/lib/tenant-emails.js +161 -0
  260. package/src/lib/tenant-emails.js.map +1 -1
  261. package/src/lib/testing/memory-firestore.d.ts +16 -3
  262. package/src/lib/testing/memory-firestore.js +11 -3
  263. package/src/lib/testing/memory-firestore.js.map +1 -1
  264. package/src/lib/transfer/products.server.d.ts +5 -0
  265. package/src/lib/transfer/products.server.js +2 -2
  266. package/src/lib/transfer/products.server.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-pos-ops.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { apportionCents, type HostOrder } from './commerce-orders'\nimport { orderPayments } from './commerce-pos'\nimport type { PrintReport } from './commerce-printers'\nimport { receiptDataFromOrder } from './commerce-receipt'\n\n/*==========================================\n * RUNNING THE REGISTER (AGL-3609): shifts and the cash drawer, register\n * returns and the receipt. Pure and client-safe: the routes, the register\n * and the reports all compute from these, so a drawer's expected cash is one\n * formula wherever it is shown.\n *\n * Where each thing lives:\n *\n * hosts/{hostId}/registers/{registerId}\n * `openShiftId` — the one open shift, claimed in a transaction\n * hosts/{hostId}/registers/{registerId}/shifts/{shiftId} {@link PosShift}\n * hosts/{hostId}/registers/{registerId}/returns/{returnId} {@link PosRegisterReturn}\n * hosts/{hostId}/posStaffPins/{uid} server-only\n *\n * Everything under `registers` is written by the Admin SDK only (the\n * register pool is billed), and every member of the site may read it.\n *=========================================*/\n\n/** How one payment toward a sale was taken, in the register's tender words. */\nexport type PosTenderMethod =\n | 'cash'\n | 'card_present'\n | 'card_keyed'\n | 'card_link'\n | 'gift_card'\n | 'folio'\n\nexport const POS_TENDER_METHODS: readonly PosTenderMethod[] = [\n 'cash',\n 'card_present',\n 'card_keyed',\n 'card_link',\n 'gift_card',\n 'folio',\n]\n\n/** What each tender is called on a report, a receipt and a refund. */\nexport const POS_TENDER_LABELS: Readonly<Record<PosTenderMethod, string>> = {\n cash: 'Cash',\n card_present: 'Card',\n card_keyed: 'Card (keyed)',\n card_link: 'Card (payment link)',\n gift_card: 'Gift card',\n folio: 'Room charge',\n}\n\n/** The tenders whose money moved through Stripe and goes back through it. */\nexport const POS_STRIPE_TENDERS: ReadonlySet<PosTenderMethod> = new Set([\n 'card_present',\n 'card_keyed',\n 'card_link',\n])\n\n/**\n * One settled payment toward a sale, as the shift report and a register\n * return read it.\n */\nexport interface PosOrderTender {\n /** The payment's id on the order, or `legacy` for an order before the tender ledger. */\n id: string\n method: PosTenderMethod\n /** Cents put toward the sale; never the tip, never the change. */\n amountCents: number\n tipCents: number\n paymentIntentId?: string\n giftCardId?: string\n reservationId?: string\n cardBrand?: string\n last4?: string\n cashTenderedCents?: number\n changeCents?: number\n}\n\nconst wholeCents = (value: unknown): number => {\n const number = Math.round(Number(value))\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n\nconst isTenderMethod = (value: unknown): value is PosTenderMethod =>\n typeof value === 'string' && (POS_TENDER_METHODS as readonly string[]).includes(value)\n\n/** The order fields the tender reader looks at. */\nexport interface PosTenderSource {\n payments?: unknown\n channel?: string\n status?: string\n totals?: { totalCents?: number; tipCents?: number } | null\n amountCents?: number\n reservationId?: string\n checkoutSessionId?: string\n paymentIntentId?: string\n}\n\n/**\n * THE ONE PLACE THE TENDER LEDGER IS READ (AGL-3609).\n *\n * A register sale may carry `payments[]` — one entry per tender, the ledger\n * the split-tender register writes (AGL-3607) — and only the payments that\n * SUCCEEDED count: a declined card or a voided gift card took nothing. An\n * order written before that ledger is one payment, inferred the way the\n * ledger's own reader infers it: a folio when it names a reservation, a card\n * link when Stripe saw it (or it came through the storefront), cash otherwise.\n *\n * The ledger's own reader (`orderPayments`) does both; this narrows it to the\n * settled payments and the fields a report or a refund reads.\n */\nexport function posOrderTenders(order: PosTenderSource | null | undefined): PosOrderTender[] {\n if (!order) return []\n return orderPayments(order as Parameters<typeof orderPayments>[0])\n .filter((payment) => payment.status === 'succeeded' && isTenderMethod(payment.method))\n .map((payment) => ({\n id: payment.id,\n method: payment.method as PosTenderMethod,\n amountCents: wholeCents(payment.amountCents),\n tipCents: wholeCents(payment.tipCents ?? (payment.id === 'legacy' ? order.totals?.tipCents : 0)),\n ...(payment.paymentIntentId ? { paymentIntentId: payment.paymentIntentId } : {}),\n ...(payment.giftCardId ? { giftCardId: payment.giftCardId } : {}),\n ...(payment.reservationId ? { reservationId: payment.reservationId } : {}),\n ...(payment.cardBrand ? { cardBrand: payment.cardBrand } : {}),\n ...(payment.last4 ? { last4: payment.last4 } : {}),\n ...(payment.cashTenderedCents != null ? { cashTenderedCents: wholeCents(payment.cashTenderedCents) } : {}),\n ...(payment.changeCents != null ? { changeCents: wholeCents(payment.changeCents) } : {}),\n }))\n}\n\n/** A tender as one line of a receipt: `Card •• 4242`. */\nexport function posTenderLabel(tender: Pick<PosOrderTender, 'method' | 'cardBrand' | 'last4'>): string {\n const base = POS_TENDER_LABELS[tender.method] ?? tender.method\n if (tender.last4) {\n return `${tender.cardBrand ? capitalize(tender.cardBrand) : base} •• ${tender.last4}`\n }\n return base\n}\n\nfunction capitalize(value: string): string {\n return value ? value.charAt(0).toUpperCase() + value.slice(1) : value\n}\n\n/*==========================================\n * SHIFTS AND THE CASH DRAWER\n *=========================================*/\n\n/**\n * Cash that moved through the drawer without a sale: money put in, money\n * paid out for a reason (a delivery, petty cash), a drop to the safe, and\n * a register return paid out in cash.\n */\nexport type PosCashEventType = 'paid_in' | 'paid_out' | 'drop' | 'refund'\n\nexport const POS_CASH_EVENT_LABELS: Readonly<Record<PosCashEventType, string>> = {\n paid_in: 'Paid in',\n paid_out: 'Paid out',\n drop: 'Safe drop',\n refund: 'Cash refund',\n}\n\nexport interface PosCashEvent {\n /** The register's attempt id, so a retried tap records one event. */\n id: string\n type: PosCashEventType\n amountCents: number\n reason: string\n /** The member who moved the cash. */\n by: string\n atMs: number\n /** The order a `refund` paid out against. */\n orderId?: string\n}\n\n/** Sales, refunds and cash movements over one shift. */\nexport interface PosShiftReport {\n orderCount: number\n /** What customers paid for the goods: order totals, tax in, tips out. */\n grossSalesCents: number\n discountsCents: number\n taxCents: number\n tipsCents: number\n salesByTender: Partial<Record<PosTenderMethod, number>>\n refundCount: number\n refundsCents: number\n refundsByTender: Partial<Record<PosTenderMethod, number>>\n /** Gross sales less refunds. */\n netSalesCents: number\n openingFloatCents: number\n cashSalesCents: number\n paidInCents: number\n paidOutCents: number\n dropsCents: number\n cashRefundsCents: number\n /** float + cash sales + paid in − paid out − drops − cash refunds. */\n expectedCashCents: number\n /** More sales were stamped with the shift than the report could read. */\n truncated?: boolean\n}\n\n/** `hosts/{hostId}/registers/{registerId}/shifts/{shiftId}`. */\nexport interface PosShift {\n hostId: string\n registerId: string\n status: 'open' | 'closed'\n openedBy: string\n /** The member's name when the shift opened, for the history and the export. */\n openedByName?: string\n openedAtMs: number\n openingFloatCents: number\n cashEvents: PosCashEvent[]\n closedBy?: string\n closedByName?: string\n closedAtMs?: number\n /** What the closing cashier counted in the drawer. */\n countedCashCents?: number\n expectedCashCents?: number\n /** counted − expected: positive is over, negative is short. */\n varianceCents?: number\n closingNote?: string\n /** The Z report, frozen when the shift closed. */\n report?: PosShiftReport\n}\n\n/** The cash-event sums one formula reads. */\nexport function posCashEventTotals(events: readonly PosCashEvent[] | null | undefined): {\n paidInCents: number\n paidOutCents: number\n dropsCents: number\n cashRefundsCents: number\n} {\n const sum = (type: PosCashEventType) =>\n (events ?? [])\n .filter((event) => event?.type === type)\n .reduce((total, event) => total + wholeCents(event.amountCents), 0)\n return {\n paidInCents: sum('paid_in'),\n paidOutCents: sum('paid_out'),\n dropsCents: sum('drop'),\n cashRefundsCents: sum('refund'),\n }\n}\n\n/**\n * THE DRAWER'S EXPECTED CASH: what should be in it when it is counted.\n *\n * opening float + cash sales + paid in − paid out − safe drops − cash refunds\n *\n * Cash SALES are the cash tender's `amountCents`, which is what the sale took\n * and never what the customer handed over: the change went straight back out\n * of the same drawer. A tip left in cash is in the drawer too, so it counts.\n */\nexport function posExpectedCashCents(input: {\n openingFloatCents: number\n cashSalesCents: number\n paidInCents: number\n paidOutCents: number\n dropsCents: number\n cashRefundsCents: number\n}): number {\n return (\n wholeCents(input.openingFloatCents) +\n wholeCents(input.cashSalesCents) +\n wholeCents(input.paidInCents) -\n wholeCents(input.paidOutCents) -\n wholeCents(input.dropsCents) -\n wholeCents(input.cashRefundsCents)\n )\n}\n\n/** A sale as the shift report reads it. */\nexport interface PosShiftSale extends PosTenderSource {\n totals?: {\n totalCents?: number\n discountCents?: number\n taxCents?: number\n tipCents?: number\n } | null\n}\n\n/** A register return as the shift report reads it. */\nexport interface PosShiftRefund {\n refundedCents: number\n tenders: Array<{ method: PosTenderMethod; amountCents: number }>\n}\n\n/**\n * The X report (mid-shift) and the Z report (at close) are this one function\n * over the sales stamped with the shift, the returns rung at the register\n * during it and the shift's own cash events. A pending sale took no money and\n * a cancelled one gave it back, so neither counts; a sale refunded LATER\n * still counts as a sale, and its refund is counted where it was paid out.\n */\nexport function computePosShiftReport(input: {\n shift: Pick<PosShift, 'openingFloatCents' | 'cashEvents'>\n sales: readonly PosShiftSale[]\n refunds: readonly PosShiftRefund[]\n truncated?: boolean\n}): PosShiftReport {\n const salesByTender: Partial<Record<PosTenderMethod, number>> = {}\n const refundsByTender: Partial<Record<PosTenderMethod, number>> = {}\n let orderCount = 0\n let grossSalesCents = 0\n let discountsCents = 0\n let taxCents = 0\n let tipsCents = 0\n let cashSalesCents = 0\n for (const sale of input.sales) {\n if (sale.status === 'pending' || sale.status === 'cancelled') continue\n const tenders = posOrderTenders(sale)\n orderCount += 1\n grossSalesCents += wholeCents(sale.totals?.totalCents ?? sale.amountCents)\n discountsCents += wholeCents(sale.totals?.discountCents)\n taxCents += wholeCents(sale.totals?.taxCents)\n const tenderTips = tenders.reduce((sum, tender) => sum + tender.tipCents, 0)\n tipsCents += Math.max(wholeCents(sale.totals?.tipCents), tenderTips)\n for (const tender of tenders) {\n salesByTender[tender.method] = (salesByTender[tender.method] ?? 0) + tender.amountCents\n if (tender.method === 'cash') cashSalesCents += tender.amountCents + tender.tipCents\n }\n }\n let refundsCents = 0\n for (const refund of input.refunds) {\n refundsCents += wholeCents(refund.refundedCents)\n for (const tender of refund.tenders ?? []) {\n if (!isTenderMethod(tender.method)) continue\n refundsByTender[tender.method] =\n (refundsByTender[tender.method] ?? 0) + wholeCents(tender.amountCents)\n }\n }\n const cash = posCashEventTotals(input.shift.cashEvents)\n const openingFloatCents = wholeCents(input.shift.openingFloatCents)\n return {\n orderCount,\n grossSalesCents,\n discountsCents,\n taxCents,\n tipsCents,\n salesByTender,\n refundCount: input.refunds.length,\n refundsCents,\n refundsByTender,\n netSalesCents: grossSalesCents - refundsCents,\n openingFloatCents,\n cashSalesCents,\n ...cash,\n expectedCashCents: posExpectedCashCents({ openingFloatCents, cashSalesCents, ...cash }),\n ...(input.truncated ? { truncated: true } : {}),\n }\n}\n\n/** counted − expected; positive is over, negative is short. */\nexport function posCashVarianceCents(countedCashCents: number, expectedCashCents: number): number {\n return Math.round(Number(countedCashCents) || 0) - Math.round(Number(expectedCashCents) || 0)\n}\n\n/** `$12.34`, `-$0.50` — the register's money format, store currency USD. */\nexport function posMoney(cents: number): string {\n const value = Math.round(Number(cents) || 0)\n return `${value < 0 ? '-' : ''}$${(Math.abs(value) / 100).toFixed(2)}`\n}\n\n/** One CSV cell, quoted when it needs to be and never read as a formula. */\nfunction csvCell(value: unknown): string {\n let cell = value == null ? '' : String(value)\n if (/^[=+\\-@\\t\\r]/.test(cell) && !/^-?\\d+(\\.\\d+)?$/.test(cell)) cell = `'${cell}`\n return /[\",\\r\\n]/.test(cell) ? `\"${cell.replace(/\"/g, '\"\"')}\"` : cell\n}\n\nconst dollars = (cents: number | undefined): string =>\n cents == null ? '' : (Math.round(cents) / 100).toFixed(2)\n\n/** The shift history as a spreadsheet: one row per shift, money in dollars. */\nexport function posShiftsCsv(\n shifts: ReadonlyArray<PosShift & { id: string; registerName?: string }>,\n memberName: (uid: string) => string = (uid) => uid,\n): string {\n const header = [\n 'Shift',\n 'Register',\n 'Opened',\n 'Opened by',\n 'Closed',\n 'Closed by',\n 'Orders',\n 'Gross sales',\n 'Refunds',\n 'Net sales',\n 'Discounts',\n 'Tax',\n 'Tips',\n 'Cash sales',\n 'Card sales',\n 'Gift card sales',\n 'Room charges',\n 'Opening float',\n 'Paid in',\n 'Paid out',\n 'Safe drops',\n 'Cash refunds',\n 'Expected cash',\n 'Counted cash',\n 'Variance',\n ]\n const iso = (ms: number | undefined) => (ms ? new Date(ms).toISOString() : '')\n const rows = shifts.map((shift) => {\n const report = shift.report\n const card = report\n ? (report.salesByTender.card_present ?? 0) +\n (report.salesByTender.card_keyed ?? 0) +\n (report.salesByTender.card_link ?? 0)\n : undefined\n return [\n shift.id,\n shift.registerName ?? shift.registerId,\n iso(shift.openedAtMs),\n shift.openedByName ?? memberName(shift.openedBy),\n iso(shift.closedAtMs),\n shift.closedBy ? (shift.closedByName ?? memberName(shift.closedBy)) : '',\n report?.orderCount ?? '',\n dollars(report?.grossSalesCents),\n dollars(report?.refundsCents),\n dollars(report?.netSalesCents),\n dollars(report?.discountsCents),\n dollars(report?.taxCents),\n dollars(report?.tipsCents),\n dollars(report?.cashSalesCents),\n dollars(card),\n dollars(report ? (report.salesByTender.gift_card ?? 0) : undefined),\n dollars(report ? (report.salesByTender.folio ?? 0) : undefined),\n dollars(shift.openingFloatCents),\n dollars(report?.paidInCents),\n dollars(report?.paidOutCents),\n dollars(report?.dropsCents),\n dollars(report?.cashRefundsCents),\n dollars(shift.expectedCashCents ?? report?.expectedCashCents),\n dollars(shift.countedCashCents),\n dollars(shift.varianceCents),\n ]\n })\n return [header, ...rows].map((row) => row.map(csvCell).join(',')).join('\\r\\n')\n}\n\n/*==========================================\n * RETURNS AT THE REGISTER\n *=========================================*/\n\n/** One line picked for return: its index on the order and how many units. */\nexport interface PosReturnPick {\n index: number\n quantity: number\n}\n\n/** The order fields a return reads. */\nexport interface PosReturnSource extends PosTenderSource {\n lineItems?: Array<{ unitAmountCents?: number; quantity?: number; name?: string }> | null\n totals?: {\n totalCents?: number\n discountCents?: number\n taxCents?: number\n shippingCents?: number\n tipCents?: number\n } | null\n refundedCents?: number\n refundedLineItemIds?: number[]\n /** Units of each line already returned at a register, by line index. */\n returnedQuantities?: Record<string, number>\n /** Cents already refunded to each payment by a register return. */\n posPaymentRefunds?: Record<string, number>\n}\n\n/**\n * What each line is worth back, whole: its price less its share of the\n * order's discount, plus its share of the order's tax — the same\n * apportionment `orderLineRefundCents` uses for the discount, extended to the\n * tax a register sale charged on top. Shipping and tips are not a line's.\n */\nexport function posReturnLineValues(order: PosReturnSource): number[] {\n const lines = order.lineItems ?? []\n const gross = lines.map(\n (line) =>\n Math.max(0, Math.round(Number(line?.unitAmountCents ?? 0))) *\n Math.max(1, Math.round(Number(line?.quantity ?? 1))),\n )\n const discountShares = apportionCents(gross, wholeCents(order.totals?.discountCents))\n const net = gross.map((cents, index) => Math.max(0, cents - (discountShares[index] ?? 0)))\n const taxShares = apportionCents(net, wholeCents(order.totals?.taxCents))\n return net.map((cents, index) => cents + (taxShares[index] ?? 0))\n}\n\n/** Units of each line already returned, a line refunded whole counting as all of it. */\nexport function posReturnedQuantities(order: PosReturnSource): number[] {\n const lines = order.lineItems ?? []\n const wholeLines = new Set(order.refundedLineItemIds ?? [])\n return lines.map((line, index) => {\n const quantity = Math.max(1, Math.round(Number(line?.quantity ?? 1)))\n if (wholeLines.has(index)) return quantity\n const returned = Math.round(Number(order.returnedQuantities?.[String(index)] ?? 0))\n return Math.min(quantity, Math.max(0, Number.isFinite(returned) ? returned : 0))\n })\n}\n\nexport type PosReturnPlan =\n | {\n ok: true\n refundCents: number\n /** Cents per picked line, in the order picked. */\n lineCents: number[]\n /** The order's per-line returned quantities after this return. */\n returnedQuantities: Record<string, number>\n /** Lines this return finishes, for `refundedLineItemIds`. */\n completedLines: number[]\n }\n | { ok: false; error: string }\n\n/**\n * What a return of these picks refunds, to the cent.\n *\n * Each line's value is split across its units CUMULATIVELY: returning `k` of\n * `n` units already returned `r` refunds `round(V·(r+k)/n) − round(V·r/n)`,\n * so returning a line one unit at a time refunds exactly what returning it\n * whole would have, and the rounding never strands or invents a cent. The\n * sum is capped at what is left to refund on the order — a refund given\n * from the order dialog already took its share.\n *\n * `heldElsewhere` is, per line, the units an online return (the returns\n * flow) already holds or took back. They cap what the register may take, but\n * they are not written into `returnedQuantities`, which counts only what the\n * register itself took.\n */\nexport function planPosReturn(\n order: PosReturnSource,\n picks: readonly PosReturnPick[],\n heldElsewhere: readonly number[] = [],\n): PosReturnPlan {\n const lines = order.lineItems ?? []\n if (!picks.length) return { ok: false, error: 'Pick at least one item to return.' }\n const values = posReturnLineValues(order)\n const returned = posReturnedQuantities(order)\n const seen = new Set<number>()\n const lineCents: number[] = []\n const nextReturned: Record<string, number> = {}\n for (const [index, count] of returned.entries()) if (count > 0) nextReturned[String(index)] = count\n const completedLines: number[] = []\n for (const pick of picks) {\n const index = Number(pick.index)\n const quantity = Number(pick.quantity)\n if (!Number.isInteger(index) || index < 0 || index >= lines.length) {\n return { ok: false, error: `Line ${pick.index} is not on this order.` }\n }\n if (seen.has(index)) return { ok: false, error: 'Each line can be picked once.' }\n seen.add(index)\n if (!Number.isInteger(quantity) || quantity < 1) {\n return { ok: false, error: 'Return at least one of each line you pick.' }\n }\n const total = Math.max(1, Math.round(Number(lines[index]?.quantity ?? 1)))\n const already = returned[index] ?? 0\n const held = Math.max(0, Math.round(Number(heldElsewhere[index] ?? 0)))\n if (already + held + quantity > total) {\n const left = Math.max(0, total - already - held)\n return {\n ok: false,\n error:\n left > 0\n ? `Only ${left} of \"${lines[index]?.name ?? 'that item'}\" can still be returned.`\n : held > 0\n ? `\"${lines[index]?.name ?? 'That item'}\" is already in a return. Finish it under Returns.`\n : `\"${lines[index]?.name ?? 'That item'}\" has already been returned.`,\n }\n }\n const value = values[index] ?? 0\n lineCents.push(\n Math.round((value * (already + quantity)) / total) - Math.round((value * already) / total),\n )\n nextReturned[String(index)] = already + quantity\n if (already + quantity === total) completedLines.push(index)\n }\n const remaining = Math.max(\n 0,\n wholeCents(order.totals?.totalCents) - wholeCents(order.refundedCents),\n )\n const asked = lineCents.reduce((sum, cents) => sum + cents, 0)\n const refundCents = Math.min(asked, remaining)\n if (!(refundCents > 0)) return { ok: false, error: 'Nothing is left to refund on this order.' }\n return { ok: true, refundCents, lineCents, returnedQuantities: nextReturned, completedLines }\n}\n\n/** One slice of a refund, sent back to one of the sale's payments. */\nexport interface PosRefundAllocation {\n paymentId: string\n method: PosTenderMethod\n amountCents: number\n}\n\n/**\n * What each payment can still take back: what it put toward the sale, less\n * what register returns already sent to it. A refund given from the order\n * dialog refunds through Stripe without naming a payment, so whatever it\n * took that no payment accounts for comes off the Stripe payments first.\n */\nexport function posRefundableByTender(order: PosReturnSource): Array<PosOrderTender & { refundableCents: number }> {\n const tenders = posOrderTenders(order)\n const attributed = Object.values(order.posPaymentRefunds ?? {}).reduce(\n (sum, cents) => sum + wholeCents(cents),\n 0,\n )\n let unattributed = Math.max(0, wholeCents(order.refundedCents) - attributed)\n const ordered = [...tenders].sort(\n (a, b) => Number(POS_STRIPE_TENDERS.has(b.method)) - Number(POS_STRIPE_TENDERS.has(a.method)),\n )\n const refundable = new Map<string, number>()\n for (const tender of ordered) {\n let left = Math.max(0, tender.amountCents - wholeCents(order.posPaymentRefunds?.[tender.id]))\n if (POS_STRIPE_TENDERS.has(tender.method) && unattributed > 0) {\n const taken = Math.min(left, unattributed)\n left -= taken\n unattributed -= taken\n }\n refundable.set(tender.id, left)\n }\n return tenders.map((tender) => ({ ...tender, refundableCents: refundable.get(tender.id) ?? 0 }))\n}\n\n/**\n * The order a refund goes back in, when the cashier does not split it by\n * hand: the cards first, then store credit, then the room, and the drawer\n * LAST — so cash leaves the till only for what the customer paid in cash.\n */\nconst REFUND_ORDER: readonly PosTenderMethod[] = [\n 'card_present',\n 'card_keyed',\n 'card_link',\n 'gift_card',\n 'folio',\n 'cash',\n]\n\nexport type PosRefundSplit =\n | { ok: true; allocations: PosRefundAllocation[] }\n | { ok: false; error: string }\n\n/**\n * Sends `amountCents` back to the sale's own payments.\n *\n * With no `requested` split, each payment takes what it can in\n * {@link REFUND_ORDER}. With one, every slice must name a payment of this\n * sale and fit what that payment can still take, and the slices must add up\n * to the refund exactly — a refund is never sent to a tender the sale was\n * not paid with.\n */\nexport function splitPosRefund(\n order: PosReturnSource,\n amountCents: number,\n requested?: ReadonlyArray<{ paymentId: string; amountCents: number }> | null,\n): PosRefundSplit {\n const amount = wholeCents(amountCents)\n const tenders = posRefundableByTender(order)\n if (!tenders.length) return { ok: false, error: 'This order has no payment to refund to.' }\n if (requested && requested.length) {\n const allocations: PosRefundAllocation[] = []\n const used = new Set<string>()\n for (const slice of requested) {\n const tender = tenders.find((candidate) => candidate.id === slice.paymentId)\n if (!tender) return { ok: false, error: 'That payment is not on this order.' }\n if (used.has(tender.id)) return { ok: false, error: 'Each payment can be named once.' }\n used.add(tender.id)\n const cents = Math.round(Number(slice.amountCents))\n if (!Number.isFinite(cents) || cents < 0) return { ok: false, error: 'Refund amounts must be positive.' }\n if (cents === 0) continue\n if (cents > tender.refundableCents) {\n return {\n ok: false,\n error: `${posTenderLabel(tender)} can take back at most ${posMoney(tender.refundableCents)}.`,\n }\n }\n allocations.push({ paymentId: tender.id, method: tender.method, amountCents: cents })\n }\n const total = allocations.reduce((sum, slice) => sum + slice.amountCents, 0)\n if (total !== amount) {\n return {\n ok: false,\n error: `The refund is ${posMoney(amount)}; the split adds up to ${posMoney(total)}.`,\n }\n }\n return { ok: true, allocations }\n }\n const ordered = [...tenders].sort(\n (a, b) => REFUND_ORDER.indexOf(a.method) - REFUND_ORDER.indexOf(b.method),\n )\n const allocations: PosRefundAllocation[] = []\n let left = amount\n for (const tender of ordered) {\n if (!(left > 0)) break\n const cents = Math.min(left, tender.refundableCents)\n if (!(cents > 0)) continue\n allocations.push({ paymentId: tender.id, method: tender.method, amountCents: cents })\n left -= cents\n }\n if (left > 0) {\n return {\n ok: false,\n error: `Only ${posMoney(amount - left)} can go back to this sale's payments.`,\n }\n }\n return { ok: true, allocations }\n}\n\n/** `hosts/{hostId}/registers/{registerId}/returns/{returnId}`. */\nexport interface PosRegisterReturn {\n hostId: string\n registerId: string\n orderId: string\n orderNumber?: number\n /** The shift the drawer was in when the return was rung, if one was open. */\n shiftId?: string\n lines: Array<{ index: number; quantity: number; name: string; refundCents: number }>\n refundedCents: number\n tenders: Array<PosRefundAllocation & { status: 'refunded' | 'failed'; refundId?: string; error?: string }>\n restocked: boolean\n locationId?: string\n /** The member at the register (by PIN or by sign-in). */\n cashierId: string\n /** The manager whose PIN allowed a refund above the cashier's limit. */\n approvedBy?: string\n reason?: string\n status: 'refunded' | 'partial'\n atMs: number\n}\n\n/*==========================================\n * STAFF PINS\n *=========================================*/\n\n/** How long a cashier PIN switch lasts before the register asks again. */\nexport const POS_CASHIER_ASSERTION_TTL_MS = 15 * 60 * 1000\n/** A manager's approval is for the one refund in front of them. */\nexport const POS_MANAGER_ASSERTION_TTL_MS = 2 * 60 * 1000\n/** Wrong PINs in a row before the member is locked out. */\nexport const POS_PIN_MAX_ATTEMPTS = 5\n/** How long a lockout lasts. */\nexport const POS_PIN_LOCKOUT_MS = 15 * 60 * 1000\n\n/**\n * Why a PIN cannot be used, or `null` when it can: 4 to 6 digits, and not\n * one a stranger tries first — every digit the same, or a straight run.\n */\nexport function posPinProblem(pin: unknown): string | null {\n const value = typeof pin === 'string' ? pin : ''\n if (!/^\\d{4,6}$/.test(value)) return 'A PIN is 4 to 6 digits.'\n if (/^(\\d)\\1+$/.test(value)) return 'Pick a PIN that is not one digit repeated.'\n const digits = [...value].map(Number)\n const step = (digits[1] ?? 0) - (digits[0] ?? 0)\n if (\n (step === 1 || step === -1) &&\n digits.every((digit, index) => index === 0 || digit - (digits[index - 1] ?? 0) === step)\n ) {\n return 'Pick a PIN that is not a straight run like 1234.'\n }\n return null\n}\n\n/*==========================================\n * THE THERMAL RECEIPT\n *=========================================*/\n\nexport interface PosReceiptLine {\n name: string\n variantLabel?: string\n quantity: number\n unitAmountCents: number\n lineCents: number\n}\n\nexport interface PosReceipt {\n storeName: string\n logoUrl?: string\n addressLines: string[]\n orderNumber: string\n /** The digits the barcode encodes and the register's return lookup reads. */\n barcodeValue: string\n dateLabel: string\n cashierName?: string\n registerName?: string\n customerName?: string\n lines: PosReceiptLine[]\n subtotalCents: number\n discountCents: number\n taxCents: number\n totalCents: number\n tipCents: number\n tenders: Array<{ label: string; amountCents: number }>\n changeCents: number\n refundedCents: number\n footer?: string\n returnPolicy?: string\n /** A gift receipt: the items and nothing about what they cost. */\n gift: boolean\n}\n\n/** The order fields a receipt prints. */\nexport interface PosReceiptOrder extends PosReturnSource {\n number?: number\n createdAtMs?: number\n customerName?: string | null\n lineItems?: Array<{\n name?: string\n variantLabel?: string\n unitAmountCents?: number\n quantity?: number\n }> | null\n}\n\n/**\n * The receipt for one sale, from the order alone plus what the store prints\n * on every receipt. The sale's lines, totals, tenders and change are the\n * receipt model's (`receiptDataFromOrder`, the one the cloud printers print\n * from), so a browser print and a cloud print never disagree; this adds the\n * logo, the address, the return policy and the gift receipt. A gift receipt\n * keeps the lines and the barcode — the recipient's way to return — and\n * drops every price, total and tender.\n */\nexport function buildPosReceipt(input: {\n orderId: string\n order: PosReceiptOrder\n store: {\n name: string\n logoUrl?: string\n address?: string\n footer?: string\n returnPolicy?: string\n }\n cashierName?: string\n registerName?: string\n gift?: boolean\n /** How the date prints; the register's own locale by default. */\n formatDate?: (ms: number) => string\n}): PosReceipt {\n const { order } = input\n const gift = input.gift === true\n const data = receiptDataFromOrder(input.orderId, order as unknown as HostOrder & Record<string, any>, {\n storeName: input.store.name,\n })\n const lines: PosReceiptLine[] = data.lines.map((line) => ({\n name: line.name,\n ...(line.detail ? { variantLabel: line.detail } : {}),\n quantity: line.quantity,\n unitAmountCents: gift ? 0 : line.unitCents,\n lineCents: gift ? 0 : line.totalCents,\n }))\n const number = Number(order.number)\n const digits = Number.isInteger(number) && number > 0 ? String(number) : ''\n const createdAtMs = Number(order.createdAtMs) || 0\n const formatDate =\n input.formatDate ?? ((ms: number) => (ms ? new Date(ms).toLocaleString() : ''))\n const money = (cents: number | undefined) => (gift ? 0 : wholeCents(cents))\n return {\n storeName: input.store.name,\n ...(input.store.logoUrl ? { logoUrl: input.store.logoUrl } : {}),\n addressLines: String(input.store.address ?? '')\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter(Boolean),\n orderNumber: digits ? `#${digits}` : `#${input.orderId.slice(0, 8)}`,\n barcodeValue: digits || input.orderId,\n dateLabel: formatDate(createdAtMs),\n ...(input.cashierName ? { cashierName: input.cashierName } : {}),\n ...(input.registerName ? { registerName: input.registerName } : {}),\n ...(order.customerName ? { customerName: String(order.customerName) } : {}),\n lines,\n subtotalCents: money(data.subtotalCents),\n discountCents: money(data.discountCents),\n taxCents: money(data.taxCents),\n totalCents: money(data.totalCents),\n tipCents: money(data.tipCents),\n tenders: gift ? [] : (data.tenders ?? []),\n changeCents: money(data.changeCents),\n refundedCents: money(data.refundedCents),\n ...(input.store.footer ? { footer: input.store.footer } : {}),\n ...(input.store.returnPolicy ? { returnPolicy: input.store.returnPolicy } : {}),\n gift,\n }\n}\n\n/** One titled block of an X or Z report: what the screen, the browser print and a cloud printer all show. */\nexport interface PosShiftReportSection {\n section: string\n rows: Array<{ label: string; value: string; strong?: boolean }>\n}\n\n/** The rows of an X or Z report, in the order a manager reads them. */\nexport function posShiftReportRows(\n report: PosShiftReport,\n shift?: Pick<PosShift, 'countedCashCents' | 'varianceCents'>,\n): PosShiftReportSection[] {\n const tenders = POS_TENDER_METHODS.filter(\n (method) => (report.salesByTender[method] ?? 0) > 0 || (report.refundsByTender[method] ?? 0) > 0,\n )\n return [\n {\n section: 'Sales',\n rows: [\n { label: 'Orders', value: String(report.orderCount) },\n { label: 'Gross sales', value: posMoney(report.grossSalesCents) },\n { label: 'Discounts', value: posMoney(report.discountsCents) },\n { label: 'Tax', value: posMoney(report.taxCents) },\n { label: 'Tips', value: posMoney(report.tipsCents) },\n { label: `Refunds (${report.refundCount})`, value: `-${posMoney(report.refundsCents)}` },\n { label: 'Net sales', value: posMoney(report.netSalesCents), strong: true },\n ],\n },\n {\n section: 'By tender',\n rows: tenders.length\n ? tenders.map((method) => ({\n label: POS_TENDER_LABELS[method],\n value:\n posMoney(report.salesByTender[method] ?? 0) +\n ((report.refundsByTender[method] ?? 0) > 0\n ? ` (−${posMoney(report.refundsByTender[method] ?? 0)})`\n : ''),\n }))\n : [{ label: 'No sales yet', value: '' }],\n },\n {\n section: 'Cash drawer',\n rows: [\n { label: 'Starting cash', value: posMoney(report.openingFloatCents) },\n { label: 'Cash sales', value: posMoney(report.cashSalesCents) },\n { label: 'Paid in', value: posMoney(report.paidInCents) },\n { label: 'Paid out', value: `-${posMoney(report.paidOutCents)}` },\n { label: 'Safe drops', value: `-${posMoney(report.dropsCents)}` },\n { label: 'Cash refunds', value: `-${posMoney(report.cashRefundsCents)}` },\n { label: 'Expected in drawer', value: posMoney(report.expectedCashCents), strong: true },\n ...(shift?.countedCashCents != null\n ? [\n { label: 'Counted', value: posMoney(shift.countedCashCents) },\n {\n label: (shift.varianceCents ?? 0) < 0 ? 'Short' : (shift.varianceCents ?? 0) > 0 ? 'Over' : 'Balanced',\n value: posMoney(shift.varianceCents ?? 0),\n strong: true,\n },\n ]\n : []),\n ],\n },\n ]\n}\n\n/** A shift's X or Z report as the receipt printer prints it. */\nexport function posShiftPrintReport(input: {\n title: 'X REPORT' | 'Z REPORT'\n storeName: string\n registerName?: string\n report: PosShiftReport\n shift?: Pick<PosShift, 'countedCashCents' | 'varianceCents'>\n subtitle?: string\n}): PrintReport {\n return {\n title: input.title,\n storeName: input.storeName,\n ...(input.subtitle || input.registerName\n ? { subtitle: [input.registerName, input.subtitle].filter(Boolean).join(' - ') }\n : {}),\n sections: posShiftReportRows(input.report, input.shift),\n }\n}\n"],"names":["apportionCents","orderPayments","receiptDataFromOrder","POS_TENDER_METHODS","POS_TENDER_LABELS","cash","card_present","card_keyed","card_link","gift_card","folio","POS_STRIPE_TENDERS","Set","wholeCents","value","number","Math","round","Number","isFinite","isTenderMethod","includes","posOrderTenders","order","filter","payment","status","method","map","id","amountCents","tipCents","totals","paymentIntentId","giftCardId","reservationId","cardBrand","last4","cashTenderedCents","changeCents","posTenderLabel","tender","base","capitalize","charAt","toUpperCase","slice","POS_CASH_EVENT_LABELS","paid_in","paid_out","drop","refund","posCashEventTotals","events","sum","type","event","reduce","total","paidInCents","paidOutCents","dropsCents","cashRefundsCents","posExpectedCashCents","input","openingFloatCents","cashSalesCents","computePosShiftReport","salesByTender","refundsByTender","orderCount","grossSalesCents","discountsCents","taxCents","tipsCents","sale","sales","tenders","totalCents","discountCents","tenderTips","max","refundsCents","refunds","refundedCents","shift","cashEvents","refundCount","length","netSalesCents","expectedCashCents","truncated","posCashVarianceCents","countedCashCents","posMoney","cents","abs","toFixed","csvCell","cell","String","test","replace","dollars","posShiftsCsv","shifts","memberName","uid","header","iso","ms","Date","toISOString","rows","report","card","undefined","registerName","registerId","openedAtMs","openedByName","openedBy","closedAtMs","closedBy","closedByName","varianceCents","row","join","posReturnLineValues","lines","lineItems","gross","line","unitAmountCents","quantity","discountShares","net","index","taxShares","posReturnedQuantities","wholeLines","refundedLineItemIds","has","returned","returnedQuantities","min","planPosReturn","picks","heldElsewhere","ok","error","values","seen","lineCents","nextReturned","count","entries","completedLines","pick","isInteger","add","already","held","left","name","push","remaining","asked","refundCents","posRefundableByTender","attributed","Object","posPaymentRefunds","unattributed","ordered","sort","a","b","refundable","Map","taken","set","refundableCents","get","REFUND_ORDER","splitPosRefund","requested","amount","allocations","used","find","candidate","paymentId","indexOf","POS_CASHIER_ASSERTION_TTL_MS","POS_MANAGER_ASSERTION_TTL_MS","POS_PIN_MAX_ATTEMPTS","POS_PIN_LOCKOUT_MS","posPinProblem","pin","digits","step","every","digit","buildPosReceipt","data","gift","orderId","storeName","store","detail","variantLabel","unitCents","createdAtMs","formatDate","toLocaleString","money","logoUrl","addressLines","address","split","trim","Boolean","orderNumber","barcodeValue","dateLabel","cashierName","customerName","subtotalCents","footer","returnPolicy","posShiftReportRows","section","label","strong","posShiftPrintReport","title","subtitle","sections"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-pos-ops.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { apportionCents, type HostOrder } from './commerce-orders'\nimport { orderPayments } from './commerce-pos'\nimport type { PrintReport } from './commerce-printers'\nimport { receiptDataFromOrder } from './commerce-receipt'\n\n/*==========================================\n * RUNNING THE REGISTER (AGL-3609): shifts and the cash drawer, register\n * returns and the receipt. Pure and client-safe: the routes, the register\n * and the reports all compute from these, so a drawer's expected cash is one\n * formula wherever it is shown.\n *\n * Where each thing lives:\n *\n * hosts/{hostId}/registers/{registerId}\n * `openShiftId` — the one open shift, claimed in a transaction\n * hosts/{hostId}/registers/{registerId}/shifts/{shiftId} {@link PosShift}\n * hosts/{hostId}/registers/{registerId}/returns/{returnId} {@link PosRegisterReturn}\n * hosts/{hostId}/posStaffPins/{uid} server-only\n *\n * Everything under `registers` is written by the Admin SDK only (the\n * register pool is billed), and every member of the site may read it.\n *=========================================*/\n\n/** How one payment toward a sale was taken, in the register's tender words. */\nexport type PosTenderMethod =\n | 'cash'\n | 'card_present'\n | 'card_keyed'\n | 'card_link'\n | 'gift_card'\n | 'folio'\n /** Store credit another plugin keeps (AGL-3640). */\n | 'credit'\n\nexport const POS_TENDER_METHODS: readonly PosTenderMethod[] = [\n 'cash',\n 'card_present',\n 'card_keyed',\n 'card_link',\n 'gift_card',\n 'folio',\n 'credit',\n]\n\n/** What each tender is called on a report, a receipt and a refund. */\nexport const POS_TENDER_LABELS: Readonly<Record<PosTenderMethod, string>> = {\n cash: 'Cash',\n card_present: 'Card',\n card_keyed: 'Card (keyed)',\n card_link: 'Card (payment link)',\n gift_card: 'Gift card',\n folio: 'Room charge',\n credit: 'Store credit',\n}\n\n/** The tenders whose money moved through Stripe and goes back through it. */\nexport const POS_STRIPE_TENDERS: ReadonlySet<PosTenderMethod> = new Set([\n 'card_present',\n 'card_keyed',\n 'card_link',\n])\n\n/**\n * One settled payment toward a sale, as the shift report and a register\n * return read it.\n */\nexport interface PosOrderTender {\n /** The payment's id on the order, or `legacy` for an order before the tender ledger. */\n id: string\n method: PosTenderMethod\n /** Cents put toward the sale; never the tip, never the change. */\n amountCents: number\n tipCents: number\n paymentIntentId?: string\n giftCardId?: string\n reservationId?: string\n /** For `credit` (AGL-3640): the provider, its account handle, and its name. */\n creditProviderId?: string\n creditReference?: string\n creditLabel?: string\n cardBrand?: string\n last4?: string\n cashTenderedCents?: number\n changeCents?: number\n}\n\nconst wholeCents = (value: unknown): number => {\n const number = Math.round(Number(value))\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n\nconst isTenderMethod = (value: unknown): value is PosTenderMethod =>\n typeof value === 'string' && (POS_TENDER_METHODS as readonly string[]).includes(value)\n\n/** The order fields the tender reader looks at. */\nexport interface PosTenderSource {\n payments?: unknown\n channel?: string\n status?: string\n totals?: { totalCents?: number; tipCents?: number } | null\n amountCents?: number\n reservationId?: string\n checkoutSessionId?: string\n paymentIntentId?: string\n}\n\n/**\n * THE ONE PLACE THE TENDER LEDGER IS READ (AGL-3609).\n *\n * A register sale may carry `payments[]` — one entry per tender, the ledger\n * the split-tender register writes (AGL-3607) — and only the payments that\n * SUCCEEDED count: a declined card or a voided gift card took nothing. An\n * order written before that ledger is one payment, inferred the way the\n * ledger's own reader infers it: a folio when it names a reservation, a card\n * link when Stripe saw it (or it came through the storefront), cash otherwise.\n *\n * The ledger's own reader (`orderPayments`) does both; this narrows it to the\n * settled payments and the fields a report or a refund reads.\n */\nexport function posOrderTenders(order: PosTenderSource | null | undefined): PosOrderTender[] {\n if (!order) return []\n return orderPayments(order as Parameters<typeof orderPayments>[0])\n .filter((payment) => payment.status === 'succeeded' && isTenderMethod(payment.method))\n .map((payment) => ({\n id: payment.id,\n method: payment.method as PosTenderMethod,\n amountCents: wholeCents(payment.amountCents),\n tipCents: wholeCents(payment.tipCents ?? (payment.id === 'legacy' ? order.totals?.tipCents : 0)),\n ...(payment.paymentIntentId ? { paymentIntentId: payment.paymentIntentId } : {}),\n ...(payment.giftCardId ? { giftCardId: payment.giftCardId } : {}),\n ...(payment.reservationId ? { reservationId: payment.reservationId } : {}),\n ...(payment.creditProviderId ? { creditProviderId: payment.creditProviderId } : {}),\n ...(payment.creditReference ? { creditReference: payment.creditReference } : {}),\n ...(payment.creditLabel ? { creditLabel: payment.creditLabel } : {}),\n ...(payment.cardBrand ? { cardBrand: payment.cardBrand } : {}),\n ...(payment.last4 ? { last4: payment.last4 } : {}),\n ...(payment.cashTenderedCents != null ? { cashTenderedCents: wholeCents(payment.cashTenderedCents) } : {}),\n ...(payment.changeCents != null ? { changeCents: wholeCents(payment.changeCents) } : {}),\n }))\n}\n\n/** A tender as one line of a receipt: `Card •• 4242`. */\nexport function posTenderLabel(\n tender: Pick<PosOrderTender, 'method' | 'cardBrand' | 'last4'> & { creditLabel?: string },\n): string {\n const base = (tender.method === 'credit' && tender.creditLabel) || POS_TENDER_LABELS[tender.method] || tender.method\n if (tender.last4) {\n return `${tender.cardBrand ? capitalize(tender.cardBrand) : base} •• ${tender.last4}`\n }\n return base\n}\n\nfunction capitalize(value: string): string {\n return value ? value.charAt(0).toUpperCase() + value.slice(1) : value\n}\n\n/*==========================================\n * SHIFTS AND THE CASH DRAWER\n *=========================================*/\n\n/**\n * Cash that moved through the drawer without a sale: money put in, money\n * paid out for a reason (a delivery, petty cash), a drop to the safe, and\n * a register return paid out in cash.\n */\nexport type PosCashEventType = 'paid_in' | 'paid_out' | 'drop' | 'refund'\n\nexport const POS_CASH_EVENT_LABELS: Readonly<Record<PosCashEventType, string>> = {\n paid_in: 'Paid in',\n paid_out: 'Paid out',\n drop: 'Safe drop',\n refund: 'Cash refund',\n}\n\nexport interface PosCashEvent {\n /** The register's attempt id, so a retried tap records one event. */\n id: string\n type: PosCashEventType\n amountCents: number\n reason: string\n /** The member who moved the cash. */\n by: string\n atMs: number\n /** The order a `refund` paid out against. */\n orderId?: string\n}\n\n/** Sales, refunds and cash movements over one shift. */\nexport interface PosShiftReport {\n orderCount: number\n /** What customers paid for the goods: order totals, tax in, tips out. */\n grossSalesCents: number\n discountsCents: number\n taxCents: number\n tipsCents: number\n salesByTender: Partial<Record<PosTenderMethod, number>>\n refundCount: number\n refundsCents: number\n refundsByTender: Partial<Record<PosTenderMethod, number>>\n /** Gross sales less refunds. */\n netSalesCents: number\n openingFloatCents: number\n cashSalesCents: number\n paidInCents: number\n paidOutCents: number\n dropsCents: number\n cashRefundsCents: number\n /** float + cash sales + paid in − paid out − drops − cash refunds. */\n expectedCashCents: number\n /** More sales were stamped with the shift than the report could read. */\n truncated?: boolean\n}\n\n/** `hosts/{hostId}/registers/{registerId}/shifts/{shiftId}`. */\nexport interface PosShift {\n hostId: string\n registerId: string\n status: 'open' | 'closed'\n openedBy: string\n /** The member's name when the shift opened, for the history and the export. */\n openedByName?: string\n openedAtMs: number\n openingFloatCents: number\n cashEvents: PosCashEvent[]\n closedBy?: string\n closedByName?: string\n /*\n * The history sorts by each of the five below (AGL-3680), and an `orderBy`\n * drops a shift that lacks the field, so the shift is opened with each one\n * `null` and the close fills them — see `POS_SHIFT_LIST_QUERY`.\n */\n closedAtMs?: number | null\n /** What the closing cashier counted in the drawer. */\n countedCashCents?: number | null\n expectedCashCents?: number | null\n /** counted − expected: positive is over, negative is short. */\n varianceCents?: number | null\n /** The Z report's `netSalesCents`, flattened so the history can order by it. */\n netSalesCents?: number | null\n closingNote?: string\n /** The Z report, frozen when the shift closed. */\n report?: PosShiftReport\n}\n\n/** The cash-event sums one formula reads. */\nexport function posCashEventTotals(events: readonly PosCashEvent[] | null | undefined): {\n paidInCents: number\n paidOutCents: number\n dropsCents: number\n cashRefundsCents: number\n} {\n const sum = (type: PosCashEventType) =>\n (events ?? [])\n .filter((event) => event?.type === type)\n .reduce((total, event) => total + wholeCents(event.amountCents), 0)\n return {\n paidInCents: sum('paid_in'),\n paidOutCents: sum('paid_out'),\n dropsCents: sum('drop'),\n cashRefundsCents: sum('refund'),\n }\n}\n\n/**\n * THE DRAWER'S EXPECTED CASH: what should be in it when it is counted.\n *\n * opening float + cash sales + paid in − paid out − safe drops − cash refunds\n *\n * Cash SALES are the cash tender's `amountCents`, which is what the sale took\n * and never what the customer handed over: the change went straight back out\n * of the same drawer. A tip left in cash is in the drawer too, so it counts.\n */\nexport function posExpectedCashCents(input: {\n openingFloatCents: number\n cashSalesCents: number\n paidInCents: number\n paidOutCents: number\n dropsCents: number\n cashRefundsCents: number\n}): number {\n return (\n wholeCents(input.openingFloatCents) +\n wholeCents(input.cashSalesCents) +\n wholeCents(input.paidInCents) -\n wholeCents(input.paidOutCents) -\n wholeCents(input.dropsCents) -\n wholeCents(input.cashRefundsCents)\n )\n}\n\n/** A sale as the shift report reads it. */\nexport interface PosShiftSale extends PosTenderSource {\n totals?: {\n totalCents?: number\n discountCents?: number\n taxCents?: number\n tipCents?: number\n } | null\n}\n\n/** A register return as the shift report reads it. */\nexport interface PosShiftRefund {\n refundedCents: number\n tenders: Array<{ method: PosTenderMethod; amountCents: number }>\n}\n\n/**\n * The X report (mid-shift) and the Z report (at close) are this one function\n * over the sales stamped with the shift, the returns rung at the register\n * during it and the shift's own cash events. A pending sale took no money and\n * a cancelled one gave it back, so neither counts; a sale refunded LATER\n * still counts as a sale, and its refund is counted where it was paid out.\n */\nexport function computePosShiftReport(input: {\n shift: Pick<PosShift, 'openingFloatCents' | 'cashEvents'>\n sales: readonly PosShiftSale[]\n refunds: readonly PosShiftRefund[]\n truncated?: boolean\n}): PosShiftReport {\n const salesByTender: Partial<Record<PosTenderMethod, number>> = {}\n const refundsByTender: Partial<Record<PosTenderMethod, number>> = {}\n let orderCount = 0\n let grossSalesCents = 0\n let discountsCents = 0\n let taxCents = 0\n let tipsCents = 0\n let cashSalesCents = 0\n for (const sale of input.sales) {\n if (sale.status === 'pending' || sale.status === 'cancelled') continue\n const tenders = posOrderTenders(sale)\n orderCount += 1\n grossSalesCents += wholeCents(sale.totals?.totalCents ?? sale.amountCents)\n discountsCents += wholeCents(sale.totals?.discountCents)\n taxCents += wholeCents(sale.totals?.taxCents)\n const tenderTips = tenders.reduce((sum, tender) => sum + tender.tipCents, 0)\n tipsCents += Math.max(wholeCents(sale.totals?.tipCents), tenderTips)\n for (const tender of tenders) {\n salesByTender[tender.method] = (salesByTender[tender.method] ?? 0) + tender.amountCents\n if (tender.method === 'cash') cashSalesCents += tender.amountCents + tender.tipCents\n }\n }\n let refundsCents = 0\n for (const refund of input.refunds) {\n refundsCents += wholeCents(refund.refundedCents)\n for (const tender of refund.tenders ?? []) {\n if (!isTenderMethod(tender.method)) continue\n refundsByTender[tender.method] =\n (refundsByTender[tender.method] ?? 0) + wholeCents(tender.amountCents)\n }\n }\n const cash = posCashEventTotals(input.shift.cashEvents)\n const openingFloatCents = wholeCents(input.shift.openingFloatCents)\n return {\n orderCount,\n grossSalesCents,\n discountsCents,\n taxCents,\n tipsCents,\n salesByTender,\n refundCount: input.refunds.length,\n refundsCents,\n refundsByTender,\n netSalesCents: grossSalesCents - refundsCents,\n openingFloatCents,\n cashSalesCents,\n ...cash,\n expectedCashCents: posExpectedCashCents({ openingFloatCents, cashSalesCents, ...cash }),\n ...(input.truncated ? { truncated: true } : {}),\n }\n}\n\n/** counted − expected; positive is over, negative is short. */\nexport function posCashVarianceCents(countedCashCents: number, expectedCashCents: number): number {\n return Math.round(Number(countedCashCents) || 0) - Math.round(Number(expectedCashCents) || 0)\n}\n\n/** `$12.34`, `-$0.50` — the register's money format, store currency USD. */\nexport function posMoney(cents: number): string {\n const value = Math.round(Number(cents) || 0)\n return `${value < 0 ? '-' : ''}$${(Math.abs(value) / 100).toFixed(2)}`\n}\n\n/** One CSV cell, quoted when it needs to be and never read as a formula. */\nfunction csvCell(value: unknown): string {\n let cell = value == null ? '' : String(value)\n if (/^[=+\\-@\\t\\r]/.test(cell) && !/^-?\\d+(\\.\\d+)?$/.test(cell)) cell = `'${cell}`\n return /[\",\\r\\n]/.test(cell) ? `\"${cell.replace(/\"/g, '\"\"')}\"` : cell\n}\n\nconst dollars = (cents: number | null | undefined): string =>\n cents == null ? '' : (Math.round(cents) / 100).toFixed(2)\n\n/** The shift history as a spreadsheet: one row per shift, money in dollars. */\nexport function posShiftsCsv(\n shifts: ReadonlyArray<PosShift & { id: string; registerName?: string }>,\n memberName: (uid: string) => string = (uid) => uid,\n): string {\n const header = [\n 'Shift',\n 'Register',\n 'Opened',\n 'Opened by',\n 'Closed',\n 'Closed by',\n 'Orders',\n 'Gross sales',\n 'Refunds',\n 'Net sales',\n 'Discounts',\n 'Tax',\n 'Tips',\n 'Cash sales',\n 'Card sales',\n 'Gift card sales',\n 'Room charges',\n 'Opening float',\n 'Paid in',\n 'Paid out',\n 'Safe drops',\n 'Cash refunds',\n 'Expected cash',\n 'Counted cash',\n 'Variance',\n ]\n const iso = (ms: number | null | undefined) => (ms ? new Date(ms).toISOString() : '')\n const rows = shifts.map((shift) => {\n const report = shift.report\n const card = report\n ? (report.salesByTender.card_present ?? 0) +\n (report.salesByTender.card_keyed ?? 0) +\n (report.salesByTender.card_link ?? 0)\n : undefined\n return [\n shift.id,\n shift.registerName ?? shift.registerId,\n iso(shift.openedAtMs),\n shift.openedByName ?? memberName(shift.openedBy),\n iso(shift.closedAtMs),\n shift.closedBy ? (shift.closedByName ?? memberName(shift.closedBy)) : '',\n report?.orderCount ?? '',\n dollars(report?.grossSalesCents),\n dollars(report?.refundsCents),\n dollars(report?.netSalesCents),\n dollars(report?.discountsCents),\n dollars(report?.taxCents),\n dollars(report?.tipsCents),\n dollars(report?.cashSalesCents),\n dollars(card),\n dollars(report ? (report.salesByTender.gift_card ?? 0) : undefined),\n dollars(report ? (report.salesByTender.folio ?? 0) : undefined),\n dollars(shift.openingFloatCents),\n dollars(report?.paidInCents),\n dollars(report?.paidOutCents),\n dollars(report?.dropsCents),\n dollars(report?.cashRefundsCents),\n dollars(shift.expectedCashCents ?? report?.expectedCashCents),\n dollars(shift.countedCashCents),\n dollars(shift.varianceCents),\n ]\n })\n return [header, ...rows].map((row) => row.map(csvCell).join(',')).join('\\r\\n')\n}\n\n/*==========================================\n * RETURNS AT THE REGISTER\n *=========================================*/\n\n/** One line picked for return: its index on the order and how many units. */\nexport interface PosReturnPick {\n index: number\n quantity: number\n}\n\n/** The order fields a return reads. */\nexport interface PosReturnSource extends PosTenderSource {\n lineItems?: Array<{ unitAmountCents?: number; quantity?: number; name?: string }> | null\n totals?: {\n totalCents?: number\n discountCents?: number\n taxCents?: number\n shippingCents?: number\n tipCents?: number\n } | null\n refundedCents?: number\n refundedLineItemIds?: number[]\n /** Units of each line already returned at a register, by line index. */\n returnedQuantities?: Record<string, number>\n /** Cents already refunded to each payment by a register return. */\n posPaymentRefunds?: Record<string, number>\n}\n\n/**\n * What each line is worth back, whole: its price less its share of the\n * order's discount, plus its share of the order's tax — the same\n * apportionment `orderLineRefundCents` uses for the discount, extended to the\n * tax a register sale charged on top. Shipping and tips are not a line's.\n */\nexport function posReturnLineValues(order: PosReturnSource): number[] {\n const lines = order.lineItems ?? []\n const gross = lines.map(\n (line) =>\n Math.max(0, Math.round(Number(line?.unitAmountCents ?? 0))) *\n Math.max(1, Math.round(Number(line?.quantity ?? 1))),\n )\n const discountShares = apportionCents(gross, wholeCents(order.totals?.discountCents))\n const net = gross.map((cents, index) => Math.max(0, cents - (discountShares[index] ?? 0)))\n const taxShares = apportionCents(net, wholeCents(order.totals?.taxCents))\n return net.map((cents, index) => cents + (taxShares[index] ?? 0))\n}\n\n/** Units of each line already returned, a line refunded whole counting as all of it. */\nexport function posReturnedQuantities(order: PosReturnSource): number[] {\n const lines = order.lineItems ?? []\n const wholeLines = new Set(order.refundedLineItemIds ?? [])\n return lines.map((line, index) => {\n const quantity = Math.max(1, Math.round(Number(line?.quantity ?? 1)))\n if (wholeLines.has(index)) return quantity\n const returned = Math.round(Number(order.returnedQuantities?.[String(index)] ?? 0))\n return Math.min(quantity, Math.max(0, Number.isFinite(returned) ? returned : 0))\n })\n}\n\nexport type PosReturnPlan =\n | {\n ok: true\n refundCents: number\n /** Cents per picked line, in the order picked. */\n lineCents: number[]\n /** The order's per-line returned quantities after this return. */\n returnedQuantities: Record<string, number>\n /** Lines this return finishes, for `refundedLineItemIds`. */\n completedLines: number[]\n }\n | { ok: false; error: string }\n\n/**\n * What a return of these picks refunds, to the cent.\n *\n * Each line's value is split across its units CUMULATIVELY: returning `k` of\n * `n` units already returned `r` refunds `round(V·(r+k)/n) − round(V·r/n)`,\n * so returning a line one unit at a time refunds exactly what returning it\n * whole would have, and the rounding never strands or invents a cent. The\n * sum is capped at what is left to refund on the order — a refund given\n * from the order dialog already took its share.\n *\n * `heldElsewhere` is, per line, the units an online return (the returns\n * flow) already holds or took back. They cap what the register may take, but\n * they are not written into `returnedQuantities`, which counts only what the\n * register itself took.\n */\nexport function planPosReturn(\n order: PosReturnSource,\n picks: readonly PosReturnPick[],\n heldElsewhere: readonly number[] = [],\n): PosReturnPlan {\n const lines = order.lineItems ?? []\n if (!picks.length) return { ok: false, error: 'Pick at least one item to return.' }\n const values = posReturnLineValues(order)\n const returned = posReturnedQuantities(order)\n const seen = new Set<number>()\n const lineCents: number[] = []\n const nextReturned: Record<string, number> = {}\n for (const [index, count] of returned.entries()) if (count > 0) nextReturned[String(index)] = count\n const completedLines: number[] = []\n for (const pick of picks) {\n const index = Number(pick.index)\n const quantity = Number(pick.quantity)\n if (!Number.isInteger(index) || index < 0 || index >= lines.length) {\n return { ok: false, error: `Line ${pick.index} is not on this order.` }\n }\n if (seen.has(index)) return { ok: false, error: 'Each line can be picked once.' }\n seen.add(index)\n if (!Number.isInteger(quantity) || quantity < 1) {\n return { ok: false, error: 'Return at least one of each line you pick.' }\n }\n const total = Math.max(1, Math.round(Number(lines[index]?.quantity ?? 1)))\n const already = returned[index] ?? 0\n const held = Math.max(0, Math.round(Number(heldElsewhere[index] ?? 0)))\n if (already + held + quantity > total) {\n const left = Math.max(0, total - already - held)\n return {\n ok: false,\n error:\n left > 0\n ? `Only ${left} of \"${lines[index]?.name ?? 'that item'}\" can still be returned.`\n : held > 0\n ? `\"${lines[index]?.name ?? 'That item'}\" is already in a return. Finish it under Returns.`\n : `\"${lines[index]?.name ?? 'That item'}\" has already been returned.`,\n }\n }\n const value = values[index] ?? 0\n lineCents.push(\n Math.round((value * (already + quantity)) / total) - Math.round((value * already) / total),\n )\n nextReturned[String(index)] = already + quantity\n if (already + quantity === total) completedLines.push(index)\n }\n const remaining = Math.max(\n 0,\n wholeCents(order.totals?.totalCents) - wholeCents(order.refundedCents),\n )\n const asked = lineCents.reduce((sum, cents) => sum + cents, 0)\n const refundCents = Math.min(asked, remaining)\n if (!(refundCents > 0)) return { ok: false, error: 'Nothing is left to refund on this order.' }\n return { ok: true, refundCents, lineCents, returnedQuantities: nextReturned, completedLines }\n}\n\n/** One slice of a refund, sent back to one of the sale's payments. */\nexport interface PosRefundAllocation {\n paymentId: string\n method: PosTenderMethod\n amountCents: number\n}\n\n/**\n * What each payment can still take back: what it put toward the sale, less\n * what register returns already sent to it. A refund given from the order\n * dialog refunds through Stripe without naming a payment, so whatever it\n * took that no payment accounts for comes off the Stripe payments first.\n */\nexport function posRefundableByTender(order: PosReturnSource): Array<PosOrderTender & { refundableCents: number }> {\n const tenders = posOrderTenders(order)\n const attributed = Object.values(order.posPaymentRefunds ?? {}).reduce(\n (sum, cents) => sum + wholeCents(cents),\n 0,\n )\n let unattributed = Math.max(0, wholeCents(order.refundedCents) - attributed)\n const ordered = [...tenders].sort(\n (a, b) => Number(POS_STRIPE_TENDERS.has(b.method)) - Number(POS_STRIPE_TENDERS.has(a.method)),\n )\n const refundable = new Map<string, number>()\n for (const tender of ordered) {\n let left = Math.max(0, tender.amountCents - wholeCents(order.posPaymentRefunds?.[tender.id]))\n if (POS_STRIPE_TENDERS.has(tender.method) && unattributed > 0) {\n const taken = Math.min(left, unattributed)\n left -= taken\n unattributed -= taken\n }\n refundable.set(tender.id, left)\n }\n return tenders.map((tender) => ({ ...tender, refundableCents: refundable.get(tender.id) ?? 0 }))\n}\n\n/**\n * The order a refund goes back in, when the cashier does not split it by\n * hand: the cards first, then store credit, then the room, and the drawer\n * LAST — so cash leaves the till only for what the customer paid in cash.\n */\nconst REFUND_ORDER: readonly PosTenderMethod[] = [\n 'card_present',\n 'card_keyed',\n 'card_link',\n 'gift_card',\n 'credit',\n 'folio',\n 'cash',\n]\n\nexport type PosRefundSplit =\n | { ok: true; allocations: PosRefundAllocation[] }\n | { ok: false; error: string }\n\n/**\n * Sends `amountCents` back to the sale's own payments.\n *\n * With no `requested` split, each payment takes what it can in\n * {@link REFUND_ORDER}. With one, every slice must name a payment of this\n * sale and fit what that payment can still take, and the slices must add up\n * to the refund exactly — a refund is never sent to a tender the sale was\n * not paid with.\n */\nexport function splitPosRefund(\n order: PosReturnSource,\n amountCents: number,\n requested?: ReadonlyArray<{ paymentId: string; amountCents: number }> | null,\n): PosRefundSplit {\n const amount = wholeCents(amountCents)\n const tenders = posRefundableByTender(order)\n if (!tenders.length) return { ok: false, error: 'This order has no payment to refund to.' }\n if (requested && requested.length) {\n const allocations: PosRefundAllocation[] = []\n const used = new Set<string>()\n for (const slice of requested) {\n const tender = tenders.find((candidate) => candidate.id === slice.paymentId)\n if (!tender) return { ok: false, error: 'That payment is not on this order.' }\n if (used.has(tender.id)) return { ok: false, error: 'Each payment can be named once.' }\n used.add(tender.id)\n const cents = Math.round(Number(slice.amountCents))\n if (!Number.isFinite(cents) || cents < 0) return { ok: false, error: 'Refund amounts must be positive.' }\n if (cents === 0) continue\n if (cents > tender.refundableCents) {\n return {\n ok: false,\n error: `${posTenderLabel(tender)} can take back at most ${posMoney(tender.refundableCents)}.`,\n }\n }\n allocations.push({ paymentId: tender.id, method: tender.method, amountCents: cents })\n }\n const total = allocations.reduce((sum, slice) => sum + slice.amountCents, 0)\n if (total !== amount) {\n return {\n ok: false,\n error: `The refund is ${posMoney(amount)}; the split adds up to ${posMoney(total)}.`,\n }\n }\n return { ok: true, allocations }\n }\n const ordered = [...tenders].sort(\n (a, b) => REFUND_ORDER.indexOf(a.method) - REFUND_ORDER.indexOf(b.method),\n )\n const allocations: PosRefundAllocation[] = []\n let left = amount\n for (const tender of ordered) {\n if (!(left > 0)) break\n const cents = Math.min(left, tender.refundableCents)\n if (!(cents > 0)) continue\n allocations.push({ paymentId: tender.id, method: tender.method, amountCents: cents })\n left -= cents\n }\n if (left > 0) {\n return {\n ok: false,\n error: `Only ${posMoney(amount - left)} can go back to this sale's payments.`,\n }\n }\n return { ok: true, allocations }\n}\n\n/** `hosts/{hostId}/registers/{registerId}/returns/{returnId}`. */\nexport interface PosRegisterReturn {\n hostId: string\n registerId: string\n orderId: string\n orderNumber?: number\n /** The shift the drawer was in when the return was rung, if one was open. */\n shiftId?: string\n lines: Array<{ index: number; quantity: number; name: string; refundCents: number }>\n refundedCents: number\n tenders: Array<PosRefundAllocation & { status: 'refunded' | 'failed'; refundId?: string; error?: string }>\n restocked: boolean\n locationId?: string\n /** The member at the register (by PIN or by sign-in). */\n cashierId: string\n /** The manager whose PIN allowed a refund above the cashier's limit. */\n approvedBy?: string\n reason?: string\n status: 'refunded' | 'partial'\n atMs: number\n}\n\n/*==========================================\n * STAFF PINS\n *=========================================*/\n\n/** How long a cashier PIN switch lasts before the register asks again. */\nexport const POS_CASHIER_ASSERTION_TTL_MS = 15 * 60 * 1000\n/** A manager's approval is for the one refund in front of them. */\nexport const POS_MANAGER_ASSERTION_TTL_MS = 2 * 60 * 1000\n/** Wrong PINs in a row before the member is locked out. */\nexport const POS_PIN_MAX_ATTEMPTS = 5\n/** How long a lockout lasts. */\nexport const POS_PIN_LOCKOUT_MS = 15 * 60 * 1000\n\n/**\n * Why a PIN cannot be used, or `null` when it can: 4 to 6 digits, and not\n * one a stranger tries first — every digit the same, or a straight run.\n */\nexport function posPinProblem(pin: unknown): string | null {\n const value = typeof pin === 'string' ? pin : ''\n if (!/^\\d{4,6}$/.test(value)) return 'A PIN is 4 to 6 digits.'\n if (/^(\\d)\\1+$/.test(value)) return 'Pick a PIN that is not one digit repeated.'\n const digits = [...value].map(Number)\n const step = (digits[1] ?? 0) - (digits[0] ?? 0)\n if (\n (step === 1 || step === -1) &&\n digits.every((digit, index) => index === 0 || digit - (digits[index - 1] ?? 0) === step)\n ) {\n return 'Pick a PIN that is not a straight run like 1234.'\n }\n return null\n}\n\n/*==========================================\n * THE THERMAL RECEIPT\n *=========================================*/\n\nexport interface PosReceiptLine {\n name: string\n variantLabel?: string\n quantity: number\n unitAmountCents: number\n lineCents: number\n}\n\nexport interface PosReceipt {\n storeName: string\n logoUrl?: string\n addressLines: string[]\n orderNumber: string\n /** The digits the barcode encodes and the register's return lookup reads. */\n barcodeValue: string\n dateLabel: string\n cashierName?: string\n registerName?: string\n customerName?: string\n lines: PosReceiptLine[]\n subtotalCents: number\n discountCents: number\n taxCents: number\n totalCents: number\n tipCents: number\n tenders: Array<{ label: string; amountCents: number }>\n changeCents: number\n refundedCents: number\n footer?: string\n returnPolicy?: string\n /** A gift receipt: the items and nothing about what they cost. */\n gift: boolean\n}\n\n/** The order fields a receipt prints. */\nexport interface PosReceiptOrder extends PosReturnSource {\n number?: number\n createdAtMs?: number\n customerName?: string | null\n lineItems?: Array<{\n name?: string\n variantLabel?: string\n unitAmountCents?: number\n quantity?: number\n }> | null\n}\n\n/**\n * The receipt for one sale, from the order alone plus what the store prints\n * on every receipt. The sale's lines, totals, tenders and change are the\n * receipt model's (`receiptDataFromOrder`, the one the cloud printers print\n * from), so a browser print and a cloud print never disagree; this adds the\n * logo, the address, the return policy and the gift receipt. A gift receipt\n * keeps the lines and the barcode — the recipient's way to return — and\n * drops every price, total and tender.\n */\nexport function buildPosReceipt(input: {\n orderId: string\n order: PosReceiptOrder\n store: {\n name: string\n logoUrl?: string\n address?: string\n footer?: string\n returnPolicy?: string\n }\n cashierName?: string\n registerName?: string\n gift?: boolean\n /** How the date prints; the register's own locale by default. */\n formatDate?: (ms: number) => string\n}): PosReceipt {\n const { order } = input\n const gift = input.gift === true\n const data = receiptDataFromOrder(input.orderId, order as unknown as HostOrder & Record<string, any>, {\n storeName: input.store.name,\n })\n const lines: PosReceiptLine[] = data.lines.map((line) => ({\n name: line.name,\n ...(line.detail ? { variantLabel: line.detail } : {}),\n quantity: line.quantity,\n unitAmountCents: gift ? 0 : line.unitCents,\n lineCents: gift ? 0 : line.totalCents,\n }))\n const number = Number(order.number)\n const digits = Number.isInteger(number) && number > 0 ? String(number) : ''\n const createdAtMs = Number(order.createdAtMs) || 0\n const formatDate =\n input.formatDate ?? ((ms: number) => (ms ? new Date(ms).toLocaleString() : ''))\n const money = (cents: number | undefined) => (gift ? 0 : wholeCents(cents))\n return {\n storeName: input.store.name,\n ...(input.store.logoUrl ? { logoUrl: input.store.logoUrl } : {}),\n addressLines: String(input.store.address ?? '')\n .split(/\\r?\\n/)\n .map((line) => line.trim())\n .filter(Boolean),\n orderNumber: digits ? `#${digits}` : `#${input.orderId.slice(0, 8)}`,\n barcodeValue: digits || input.orderId,\n dateLabel: formatDate(createdAtMs),\n ...(input.cashierName ? { cashierName: input.cashierName } : {}),\n ...(input.registerName ? { registerName: input.registerName } : {}),\n ...(order.customerName ? { customerName: String(order.customerName) } : {}),\n lines,\n subtotalCents: money(data.subtotalCents),\n discountCents: money(data.discountCents),\n taxCents: money(data.taxCents),\n totalCents: money(data.totalCents),\n tipCents: money(data.tipCents),\n tenders: gift ? [] : (data.tenders ?? []),\n changeCents: money(data.changeCents),\n refundedCents: money(data.refundedCents),\n ...(input.store.footer ? { footer: input.store.footer } : {}),\n ...(input.store.returnPolicy ? { returnPolicy: input.store.returnPolicy } : {}),\n gift,\n }\n}\n\n/** One titled block of an X or Z report: what the screen, the browser print and a cloud printer all show. */\nexport interface PosShiftReportSection {\n section: string\n rows: Array<{ label: string; value: string; strong?: boolean }>\n}\n\n/** The rows of an X or Z report, in the order a manager reads them. */\nexport function posShiftReportRows(\n report: PosShiftReport,\n shift?: Pick<PosShift, 'countedCashCents' | 'varianceCents'>,\n): PosShiftReportSection[] {\n const tenders = POS_TENDER_METHODS.filter(\n (method) => (report.salesByTender[method] ?? 0) > 0 || (report.refundsByTender[method] ?? 0) > 0,\n )\n return [\n {\n section: 'Sales',\n rows: [\n { label: 'Orders', value: String(report.orderCount) },\n { label: 'Gross sales', value: posMoney(report.grossSalesCents) },\n { label: 'Discounts', value: posMoney(report.discountsCents) },\n { label: 'Tax', value: posMoney(report.taxCents) },\n { label: 'Tips', value: posMoney(report.tipsCents) },\n { label: `Refunds (${report.refundCount})`, value: `-${posMoney(report.refundsCents)}` },\n { label: 'Net sales', value: posMoney(report.netSalesCents), strong: true },\n ],\n },\n {\n section: 'By tender',\n rows: tenders.length\n ? tenders.map((method) => ({\n label: POS_TENDER_LABELS[method],\n value:\n posMoney(report.salesByTender[method] ?? 0) +\n ((report.refundsByTender[method] ?? 0) > 0\n ? ` (−${posMoney(report.refundsByTender[method] ?? 0)})`\n : ''),\n }))\n : [{ label: 'No sales yet', value: '' }],\n },\n {\n section: 'Cash drawer',\n rows: [\n { label: 'Starting cash', value: posMoney(report.openingFloatCents) },\n { label: 'Cash sales', value: posMoney(report.cashSalesCents) },\n { label: 'Paid in', value: posMoney(report.paidInCents) },\n { label: 'Paid out', value: `-${posMoney(report.paidOutCents)}` },\n { label: 'Safe drops', value: `-${posMoney(report.dropsCents)}` },\n { label: 'Cash refunds', value: `-${posMoney(report.cashRefundsCents)}` },\n { label: 'Expected in drawer', value: posMoney(report.expectedCashCents), strong: true },\n ...(shift?.countedCashCents != null\n ? [\n { label: 'Counted', value: posMoney(shift.countedCashCents) },\n {\n label: (shift.varianceCents ?? 0) < 0 ? 'Short' : (shift.varianceCents ?? 0) > 0 ? 'Over' : 'Balanced',\n value: posMoney(shift.varianceCents ?? 0),\n strong: true,\n },\n ]\n : []),\n ],\n },\n ]\n}\n\n/** A shift's X or Z report as the receipt printer prints it. */\nexport function posShiftPrintReport(input: {\n title: 'X REPORT' | 'Z REPORT'\n storeName: string\n registerName?: string\n report: PosShiftReport\n shift?: Pick<PosShift, 'countedCashCents' | 'varianceCents'>\n subtitle?: string\n}): PrintReport {\n return {\n title: input.title,\n storeName: input.storeName,\n ...(input.subtitle || input.registerName\n ? { subtitle: [input.registerName, input.subtitle].filter(Boolean).join(' - ') }\n : {}),\n sections: posShiftReportRows(input.report, input.shift),\n }\n}\n"],"names":["apportionCents","orderPayments","receiptDataFromOrder","POS_TENDER_METHODS","POS_TENDER_LABELS","cash","card_present","card_keyed","card_link","gift_card","folio","credit","POS_STRIPE_TENDERS","Set","wholeCents","value","number","Math","round","Number","isFinite","isTenderMethod","includes","posOrderTenders","order","filter","payment","status","method","map","id","amountCents","tipCents","totals","paymentIntentId","giftCardId","reservationId","creditProviderId","creditReference","creditLabel","cardBrand","last4","cashTenderedCents","changeCents","posTenderLabel","tender","base","capitalize","charAt","toUpperCase","slice","POS_CASH_EVENT_LABELS","paid_in","paid_out","drop","refund","posCashEventTotals","events","sum","type","event","reduce","total","paidInCents","paidOutCents","dropsCents","cashRefundsCents","posExpectedCashCents","input","openingFloatCents","cashSalesCents","computePosShiftReport","salesByTender","refundsByTender","orderCount","grossSalesCents","discountsCents","taxCents","tipsCents","sale","sales","tenders","totalCents","discountCents","tenderTips","max","refundsCents","refunds","refundedCents","shift","cashEvents","refundCount","length","netSalesCents","expectedCashCents","truncated","posCashVarianceCents","countedCashCents","posMoney","cents","abs","toFixed","csvCell","cell","String","test","replace","dollars","posShiftsCsv","shifts","memberName","uid","header","iso","ms","Date","toISOString","rows","report","card","undefined","registerName","registerId","openedAtMs","openedByName","openedBy","closedAtMs","closedBy","closedByName","varianceCents","row","join","posReturnLineValues","lines","lineItems","gross","line","unitAmountCents","quantity","discountShares","net","index","taxShares","posReturnedQuantities","wholeLines","refundedLineItemIds","has","returned","returnedQuantities","min","planPosReturn","picks","heldElsewhere","ok","error","values","seen","lineCents","nextReturned","count","entries","completedLines","pick","isInteger","add","already","held","left","name","push","remaining","asked","refundCents","posRefundableByTender","attributed","Object","posPaymentRefunds","unattributed","ordered","sort","a","b","refundable","Map","taken","set","refundableCents","get","REFUND_ORDER","splitPosRefund","requested","amount","allocations","used","find","candidate","paymentId","indexOf","POS_CASHIER_ASSERTION_TTL_MS","POS_MANAGER_ASSERTION_TTL_MS","POS_PIN_MAX_ATTEMPTS","POS_PIN_LOCKOUT_MS","posPinProblem","pin","digits","step","every","digit","buildPosReceipt","data","gift","orderId","storeName","store","detail","variantLabel","unitCents","createdAtMs","formatDate","toLocaleString","money","logoUrl","addressLines","address","split","trim","Boolean","orderNumber","barcodeValue","dateLabel","cashierName","customerName","subtotalCents","footer","returnPolicy","posShiftReportRows","section","label","strong","posShiftPrintReport","title","subtitle","sections"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,cAAc,QAAwB,uBAAmB;AAClE,SAASC,aAAa,QAAQ,oBAAgB;AAE9C,SAASC,oBAAoB,QAAQ,wBAAoB;AA+BzD,OAAO,MAAMC,qBAAiD;IAC5D;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAED,oEAAoE,GACpE,OAAO,MAAMC,oBAA+D;IAC1EC,MAAM;IACNC,cAAc;IACdC,YAAY;IACZC,WAAW;IACXC,WAAW;IACXC,OAAO;IACPC,QAAQ;AACV,EAAC;AAED,2EAA2E,GAC3E,OAAO,MAAMC,qBAAmD,IAAIC,IAAI;IACtE;IACA;IACA;CACD,EAAC;AA0BF,MAAMC,aAAa,CAACC;IAClB,MAAMC,SAASC,KAAKC,KAAK,CAACC,OAAOJ;IACjC,OAAOI,OAAOC,QAAQ,CAACJ,WAAWA,SAAS,IAAIA,SAAS;AAC1D;AAGA,MAAMK,iBAAiB,CAACN,QACtB,OAAOA,UAAU,YAAY,AAACZ,mBAAyCmB,QAAQ,CAACP;AAclF;;;;;;;;;;;;CAYC,GACD,OAAO,SAASQ,gBAAgBC,KAAyC;IACvE,IAAI,CAACA,OAAO,OAAO,EAAE;IACrB,OAAOvB,cAAcuB,OAClBC,MAAM,CAAC,CAACC,UAAYA,QAAQC,MAAM,KAAK,eAAeN,eAAeK,QAAQE,MAAM,GACnFC,GAAG,CAAC,CAACH;YAIiBA;YAA+CF;eAJnD;YACjBM,IAAIJ,QAAQI,EAAE;YACdF,QAAQF,QAAQE,MAAM;YACtBG,aAAajB,WAAWY,QAAQK,WAAW;YAC3CC,UAAUlB,YAAWY,oBAAAA,QAAQM,QAAQ,YAAhBN,oBAAqBA,QAAQI,EAAE,KAAK,YAAWN,gBAAAA,MAAMS,MAAM,qBAAZT,cAAcQ,QAAQ,GAAG;WACzFN,QAAQQ,eAAe,GAAG;YAAEA,iBAAiBR,QAAQQ,eAAe;QAAC,IAAI,CAAC,GAC1ER,QAAQS,UAAU,GAAG;YAAEA,YAAYT,QAAQS,UAAU;QAAC,IAAI,CAAC,GAC3DT,QAAQU,aAAa,GAAG;YAAEA,eAAeV,QAAQU,aAAa;QAAC,IAAI,CAAC,GACpEV,QAAQW,gBAAgB,GAAG;YAAEA,kBAAkBX,QAAQW,gBAAgB;QAAC,IAAI,CAAC,GAC7EX,QAAQY,eAAe,GAAG;YAAEA,iBAAiBZ,QAAQY,eAAe;QAAC,IAAI,CAAC,GAC1EZ,QAAQa,WAAW,GAAG;YAAEA,aAAab,QAAQa,WAAW;QAAC,IAAI,CAAC,GAC9Db,QAAQc,SAAS,GAAG;YAAEA,WAAWd,QAAQc,SAAS;QAAC,IAAI,CAAC,GACxDd,QAAQe,KAAK,GAAG;YAAEA,OAAOf,QAAQe,KAAK;QAAC,IAAI,CAAC,GAC5Cf,QAAQgB,iBAAiB,IAAI,OAAO;YAAEA,mBAAmB5B,WAAWY,QAAQgB,iBAAiB;QAAE,IAAI,CAAC,GACpGhB,QAAQiB,WAAW,IAAI,OAAO;YAAEA,aAAa7B,WAAWY,QAAQiB,WAAW;QAAE,IAAI,CAAC;;AAE5F;AAEA,uDAAuD,GACvD,OAAO,SAASC,eACdC,MAAyF;IAEzF,MAAMC,OAAO,AAACD,OAAOjB,MAAM,KAAK,YAAYiB,OAAON,WAAW,IAAKnC,iBAAiB,CAACyC,OAAOjB,MAAM,CAAC,IAAIiB,OAAOjB,MAAM;IACpH,IAAIiB,OAAOJ,KAAK,EAAE;QAChB,OAAO,GAAGI,OAAOL,SAAS,GAAGO,WAAWF,OAAOL,SAAS,IAAIM,KAAK,IAAI,EAAED,OAAOJ,KAAK,EAAE;IACvF;IACA,OAAOK;AACT;AAEA,SAASC,WAAWhC,KAAa;IAC/B,OAAOA,QAAQA,MAAMiC,MAAM,CAAC,GAAGC,WAAW,KAAKlC,MAAMmC,KAAK,CAAC,KAAKnC;AAClE;AAaA,OAAO,MAAMoC,wBAAoE;IAC/EC,SAAS;IACTC,UAAU;IACVC,MAAM;IACNC,QAAQ;AACV,EAAC;AAwED,2CAA2C,GAC3C,OAAO,SAASC,mBAAmBC,MAAkD;IAMnF,MAAMC,MAAM,CAACC,OACX,CAACF,iBAAAA,SAAU,EAAE,EACVhC,MAAM,CAAC,CAACmC,QAAUA,CAAAA,yBAAAA,MAAOD,IAAI,MAAKA,MAClCE,MAAM,CAAC,CAACC,OAAOF,QAAUE,QAAQhD,WAAW8C,MAAM7B,WAAW,GAAG;IACrE,OAAO;QACLgC,aAAaL,IAAI;QACjBM,cAAcN,IAAI;QAClBO,YAAYP,IAAI;QAChBQ,kBAAkBR,IAAI;IACxB;AACF;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASS,qBAAqBC,KAOpC;IACC,OACEtD,WAAWsD,MAAMC,iBAAiB,IAClCvD,WAAWsD,MAAME,cAAc,IAC/BxD,WAAWsD,MAAML,WAAW,IAC5BjD,WAAWsD,MAAMJ,YAAY,IAC7BlD,WAAWsD,MAAMH,UAAU,IAC3BnD,WAAWsD,MAAMF,gBAAgB;AAErC;AAkBA;;;;;;CAMC,GACD,OAAO,SAASK,sBAAsBH,KAKrC;IACC,MAAMI,gBAA0D,CAAC;IACjE,MAAMC,kBAA4D,CAAC;IACnE,IAAIC,aAAa;IACjB,IAAIC,kBAAkB;IACtB,IAAIC,iBAAiB;IACrB,IAAIC,WAAW;IACf,IAAIC,YAAY;IAChB,IAAIR,iBAAiB;IACrB,KAAK,MAAMS,QAAQX,MAAMY,KAAK,CAAE;;YAIAD,cACDA,eACNA,eAEUA;QAPjC,IAAIA,KAAKpD,MAAM,KAAK,aAAaoD,KAAKpD,MAAM,KAAK,aAAa;QAC9D,MAAMsD,UAAU1D,gBAAgBwD;QAChCL,cAAc;QACdC,mBAAmB7D,oBAAWiE,eAAAA,KAAK9C,MAAM,qBAAX8C,aAAaG,UAAU,mBAAIH,KAAKhD,WAAW;QACzE6C,kBAAkB9D,YAAWiE,gBAAAA,KAAK9C,MAAM,qBAAX8C,cAAaI,aAAa;QACvDN,YAAY/D,YAAWiE,gBAAAA,KAAK9C,MAAM,qBAAX8C,cAAaF,QAAQ;QAC5C,MAAMO,aAAaH,QAAQpB,MAAM,CAAC,CAACH,KAAKb,SAAWa,MAAMb,OAAOb,QAAQ,EAAE;QAC1E8C,aAAa7D,KAAKoE,GAAG,CAACvE,YAAWiE,gBAAAA,KAAK9C,MAAM,qBAAX8C,cAAa/C,QAAQ,GAAGoD;QACzD,KAAK,MAAMvC,UAAUoC,QAAS;gBACIT;YAAhCA,aAAa,CAAC3B,OAAOjB,MAAM,CAAC,GAAG,EAAC4C,+BAAAA,aAAa,CAAC3B,OAAOjB,MAAM,CAAC,YAA5B4C,+BAAgC,KAAK3B,OAAOd,WAAW;YACvF,IAAIc,OAAOjB,MAAM,KAAK,QAAQ0C,kBAAkBzB,OAAOd,WAAW,GAAGc,OAAOb,QAAQ;QACtF;IACF;IACA,IAAIsD,eAAe;IACnB,KAAK,MAAM/B,UAAUa,MAAMmB,OAAO,CAAE;YAEbhC;QADrB+B,gBAAgBxE,WAAWyC,OAAOiC,aAAa;QAC/C,KAAK,MAAM3C,WAAUU,kBAAAA,OAAO0B,OAAO,YAAd1B,kBAAkB,EAAE,CAAE;gBAGtCkB;YAFH,IAAI,CAACpD,eAAewB,OAAOjB,MAAM,GAAG;YACpC6C,eAAe,CAAC5B,OAAOjB,MAAM,CAAC,GAC5B,EAAC6C,iCAAAA,eAAe,CAAC5B,OAAOjB,MAAM,CAAC,YAA9B6C,iCAAkC,KAAK3D,WAAW+B,OAAOd,WAAW;QACzE;IACF;IACA,MAAM1B,OAAOmD,mBAAmBY,MAAMqB,KAAK,CAACC,UAAU;IACtD,MAAMrB,oBAAoBvD,WAAWsD,MAAMqB,KAAK,CAACpB,iBAAiB;IAClE,OAAO;QACLK;QACAC;QACAC;QACAC;QACAC;QACAN;QACAmB,aAAavB,MAAMmB,OAAO,CAACK,MAAM;QACjCN;QACAb;QACAoB,eAAelB,kBAAkBW;QACjCjB;QACAC;OACGjE;QACHyF,mBAAmB3B,qBAAqB;YAAEE;YAAmBC;WAAmBjE;OAC5E+D,MAAM2B,SAAS,GAAG;QAAEA,WAAW;IAAK,IAAI,CAAC;AAEjD;AAEA,6DAA6D,GAC7D,OAAO,SAASC,qBAAqBC,gBAAwB,EAAEH,iBAAyB;IACtF,OAAO7E,KAAKC,KAAK,CAACC,OAAO8E,qBAAqB,KAAKhF,KAAKC,KAAK,CAACC,OAAO2E,sBAAsB;AAC7F;AAEA,0EAA0E,GAC1E,OAAO,SAASI,SAASC,KAAa;IACpC,MAAMpF,QAAQE,KAAKC,KAAK,CAACC,OAAOgF,UAAU;IAC1C,OAAO,GAAGpF,QAAQ,IAAI,MAAM,GAAG,CAAC,EAAE,AAACE,CAAAA,KAAKmF,GAAG,CAACrF,SAAS,GAAE,EAAGsF,OAAO,CAAC,IAAI;AACxE;AAEA,0EAA0E,GAC1E,SAASC,QAAQvF,KAAc;IAC7B,IAAIwF,OAAOxF,SAAS,OAAO,KAAKyF,OAAOzF;IACvC,IAAI,eAAe0F,IAAI,CAACF,SAAS,CAAC,kBAAkBE,IAAI,CAACF,OAAOA,OAAO,CAAC,CAAC,EAAEA,MAAM;IACjF,OAAO,WAAWE,IAAI,CAACF,QAAQ,CAAC,CAAC,EAAEA,KAAKG,OAAO,CAAC,MAAM,MAAM,CAAC,CAAC,GAAGH;AACnE;AAEA,MAAMI,UAAU,CAACR,QACfA,SAAS,OAAO,KAAK,AAAClF,CAAAA,KAAKC,KAAK,CAACiF,SAAS,GAAE,EAAGE,OAAO,CAAC;AAEzD,6EAA6E,GAC7E,OAAO,SAASO,aACdC,MAAuE,EACvEC,aAAsC,CAACC,MAAQA,GAAG;IAElD,MAAMC,SAAS;QACb;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;KACD;IACD,MAAMC,MAAM,CAACC,KAAmCA,KAAK,IAAIC,KAAKD,IAAIE,WAAW,KAAK;IAClF,MAAMC,OAAOR,OAAOhF,GAAG,CAAC,CAAC4D;YAGlB6B,oCACAA,kCACAA,iCAIH7B,qBAEAA,qBAEkBA,2BAUA6B,iCACAA,6BAMV7B;QA7BV,MAAM6B,SAAS7B,MAAM6B,MAAM;QAC3B,MAAMC,OAAOD,SACT,EAACA,qCAAAA,OAAO9C,aAAa,CAAClE,YAAY,YAAjCgH,qCAAqC,OACrCA,mCAAAA,OAAO9C,aAAa,CAACjE,UAAU,YAA/B+G,mCAAmC,OACnCA,kCAAAA,OAAO9C,aAAa,CAAChE,SAAS,YAA9B8G,kCAAkC,KACnCE;QACJ,OAAO;YACL/B,MAAM3D,EAAE;aACR2D,sBAAAA,MAAMgC,YAAY,YAAlBhC,sBAAsBA,MAAMiC,UAAU;YACtCT,IAAIxB,MAAMkC,UAAU;aACpBlC,sBAAAA,MAAMmC,YAAY,YAAlBnC,sBAAsBqB,WAAWrB,MAAMoC,QAAQ;YAC/CZ,IAAIxB,MAAMqC,UAAU;YACpBrC,MAAMsC,QAAQ,IAAItC,sBAAAA,MAAMuC,YAAY,YAAlBvC,sBAAsBqB,WAAWrB,MAAMsC,QAAQ,IAAK;oBACtET,0BAAAA,OAAQ5C,UAAU,mBAAI;YACtBiC,QAAQW,0BAAAA,OAAQ3C,eAAe;YAC/BgC,QAAQW,0BAAAA,OAAQhC,YAAY;YAC5BqB,QAAQW,0BAAAA,OAAQzB,aAAa;YAC7Bc,QAAQW,0BAAAA,OAAQ1C,cAAc;YAC9B+B,QAAQW,0BAAAA,OAAQzC,QAAQ;YACxB8B,QAAQW,0BAAAA,OAAQxC,SAAS;YACzB6B,QAAQW,0BAAAA,OAAQhD,cAAc;YAC9BqC,QAAQY;YACRZ,QAAQW,UAAUA,kCAAAA,OAAO9C,aAAa,CAAC/D,SAAS,YAA9B6G,kCAAkC,IAAKE;YACzDb,QAAQW,UAAUA,8BAAAA,OAAO9C,aAAa,CAAC9D,KAAK,YAA1B4G,8BAA8B,IAAKE;YACrDb,QAAQlB,MAAMpB,iBAAiB;YAC/BsC,QAAQW,0BAAAA,OAAQvD,WAAW;YAC3B4C,QAAQW,0BAAAA,OAAQtD,YAAY;YAC5B2C,QAAQW,0BAAAA,OAAQrD,UAAU;YAC1B0C,QAAQW,0BAAAA,OAAQpD,gBAAgB;YAChCyC,SAAQlB,2BAAAA,MAAMK,iBAAiB,YAAvBL,2BAA2B6B,0BAAAA,OAAQxB,iBAAiB;YAC5Da,QAAQlB,MAAMQ,gBAAgB;YAC9BU,QAAQlB,MAAMwC,aAAa;SAC5B;IACH;IACA,OAAO;QAACjB;WAAWK;KAAK,CAACxF,GAAG,CAAC,CAACqG,MAAQA,IAAIrG,GAAG,CAACyE,SAAS6B,IAAI,CAAC,MAAMA,IAAI,CAAC;AACzE;AA8BA;;;;;CAKC,GACD,OAAO,SAASC,oBAAoB5G,KAAsB;QAC1CA;QAM0CA,eAEPA;IARjD,MAAM6G,SAAQ7G,mBAAAA,MAAM8G,SAAS,YAAf9G,mBAAmB,EAAE;IACnC,MAAM+G,QAAQF,MAAMxG,GAAG,CACrB,CAAC2G;;eACCvH,KAAKoE,GAAG,CAAC,GAAGpE,KAAKC,KAAK,CAACC,eAAOqH,wBAAAA,KAAMC,eAAe,mBAAI,OACvDxH,KAAKoE,GAAG,CAAC,GAAGpE,KAAKC,KAAK,CAACC,gBAAOqH,wBAAAA,KAAME,QAAQ,oBAAI;;IAEpD,MAAMC,iBAAiB3I,eAAeuI,OAAOzH,YAAWU,gBAAAA,MAAMS,MAAM,qBAAZT,cAAc2D,aAAa;IACnF,MAAMyD,MAAML,MAAM1G,GAAG,CAAC,CAACsE,OAAO0C;YAA+BF;eAArB1H,KAAKoE,GAAG,CAAC,GAAGc,UAASwC,wBAAAA,cAAc,CAACE,MAAM,YAArBF,wBAAyB;;IACtF,MAAMG,YAAY9I,eAAe4I,KAAK9H,YAAWU,iBAAAA,MAAMS,MAAM,qBAAZT,eAAcqD,QAAQ;IACvE,OAAO+D,IAAI/G,GAAG,CAAC,CAACsE,OAAO0C;YAAmBC;eAAT3C,UAAS2C,mBAAAA,SAAS,CAACD,MAAM,YAAhBC,mBAAoB;;AAChE;AAEA,sFAAsF,GACtF,OAAO,SAASC,sBAAsBvH,KAAsB;QAC5CA,kBACaA;IAD3B,MAAM6G,SAAQ7G,mBAAAA,MAAM8G,SAAS,YAAf9G,mBAAmB,EAAE;IACnC,MAAMwH,aAAa,IAAInI,KAAIW,6BAAAA,MAAMyH,mBAAmB,YAAzBzH,6BAA6B,EAAE;IAC1D,OAAO6G,MAAMxG,GAAG,CAAC,CAAC2G,MAAMK;;YAGarH;QAFnC,MAAMkH,WAAWzH,KAAKoE,GAAG,CAAC,GAAGpE,KAAKC,KAAK,CAACC,eAAOqH,wBAAAA,KAAME,QAAQ,mBAAI;QACjE,IAAIM,WAAWE,GAAG,CAACL,QAAQ,OAAOH;QAClC,MAAMS,WAAWlI,KAAKC,KAAK,CAACC,iBAAOK,4BAAAA,MAAM4H,kBAAkB,qBAAxB5H,yBAA0B,CAACgF,OAAOqC,OAAO,oBAAI;QAChF,OAAO5H,KAAKoI,GAAG,CAACX,UAAUzH,KAAKoE,GAAG,CAAC,GAAGlE,OAAOC,QAAQ,CAAC+H,YAAYA,WAAW;IAC/E;AACF;AAeA;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASG,cACd9H,KAAsB,EACtB+H,KAA+B,EAC/BC,gBAAmC,EAAE;QAEvBhI;QA4CDA;IA5Cb,MAAM6G,SAAQ7G,mBAAAA,MAAM8G,SAAS,YAAf9G,mBAAmB,EAAE;IACnC,IAAI,CAAC+H,MAAM3D,MAAM,EAAE,OAAO;QAAE6D,IAAI;QAAOC,OAAO;IAAoC;IAClF,MAAMC,SAASvB,oBAAoB5G;IACnC,MAAM2H,WAAWJ,sBAAsBvH;IACvC,MAAMoI,OAAO,IAAI/I;IACjB,MAAMgJ,YAAsB,EAAE;IAC9B,MAAMC,eAAuC,CAAC;IAC9C,KAAK,MAAM,CAACjB,OAAOkB,MAAM,IAAIZ,SAASa,OAAO,GAAI,IAAID,QAAQ,GAAGD,YAAY,CAACtD,OAAOqC,OAAO,GAAGkB;IAC9F,MAAME,iBAA2B,EAAE;IACnC,KAAK,MAAMC,QAAQX,MAAO;kBAYRJ,iBAC2BK,sBAa7BG;YAf8BtB;QAV5C,MAAMQ,QAAQ1H,OAAO+I,KAAKrB,KAAK;QAC/B,MAAMH,WAAWvH,OAAO+I,KAAKxB,QAAQ;QACrC,IAAI,CAACvH,OAAOgJ,SAAS,CAACtB,UAAUA,QAAQ,KAAKA,SAASR,MAAMzC,MAAM,EAAE;YAClE,OAAO;gBAAE6D,IAAI;gBAAOC,OAAO,CAAC,KAAK,EAAEQ,KAAKrB,KAAK,CAAC,sBAAsB,CAAC;YAAC;QACxE;QACA,IAAIe,KAAKV,GAAG,CAACL,QAAQ,OAAO;YAAEY,IAAI;YAAOC,OAAO;QAAgC;QAChFE,KAAKQ,GAAG,CAACvB;QACT,IAAI,CAAC1H,OAAOgJ,SAAS,CAACzB,aAAaA,WAAW,GAAG;YAC/C,OAAO;gBAAEe,IAAI;gBAAOC,OAAO;YAA6C;QAC1E;QACA,MAAM5F,QAAQ7C,KAAKoE,GAAG,CAAC,GAAGpE,KAAKC,KAAK,CAACC,gBAAOkH,eAAAA,KAAK,CAACQ,MAAM,qBAAZR,aAAcK,QAAQ,mBAAI;QACtE,MAAM2B,WAAUlB,kBAAAA,QAAQ,CAACN,MAAM,YAAfM,kBAAmB;QACnC,MAAMmB,OAAOrJ,KAAKoE,GAAG,CAAC,GAAGpE,KAAKC,KAAK,CAACC,QAAOqI,uBAAAA,aAAa,CAACX,MAAM,YAApBW,uBAAwB;QACnE,IAAIa,UAAUC,OAAO5B,WAAW5E,OAAO;;gBAMTuE,eAEdA,eACAA;YARd,MAAMkC,OAAOtJ,KAAKoE,GAAG,CAAC,GAAGvB,QAAQuG,UAAUC;YAC3C,OAAO;gBACLb,IAAI;gBACJC,OACEa,OAAO,IACH,CAAC,KAAK,EAAEA,KAAK,KAAK,YAAElC,gBAAAA,KAAK,CAACQ,MAAM,qBAAZR,cAAcmC,IAAI,oBAAI,YAAY,wBAAwB,CAAC,GAC/EF,OAAO,IACL,CAAC,CAAC,YAAEjC,gBAAAA,KAAK,CAACQ,MAAM,qBAAZR,cAAcmC,IAAI,oBAAI,YAAY,kDAAkD,CAAC,GACzF,CAAC,CAAC,YAAEnC,gBAAAA,KAAK,CAACQ,MAAM,qBAAZR,cAAcmC,IAAI,oBAAI,YAAY,4BAA4B,CAAC;YAC7E;QACF;QACA,MAAMzJ,SAAQ4I,gBAAAA,MAAM,CAACd,MAAM,YAAbc,gBAAiB;QAC/BE,UAAUY,IAAI,CACZxJ,KAAKC,KAAK,CAAC,AAACH,QAASsJ,CAAAA,UAAU3B,QAAO,IAAM5E,SAAS7C,KAAKC,KAAK,CAAC,AAACH,QAAQsJ,UAAWvG;QAEtFgG,YAAY,CAACtD,OAAOqC,OAAO,GAAGwB,UAAU3B;QACxC,IAAI2B,UAAU3B,aAAa5E,OAAOmG,eAAeQ,IAAI,CAAC5B;IACxD;IACA,MAAM6B,YAAYzJ,KAAKoE,GAAG,CACxB,GACAvE,YAAWU,gBAAAA,MAAMS,MAAM,qBAAZT,cAAc0D,UAAU,IAAIpE,WAAWU,MAAMgE,aAAa;IAEvE,MAAMmF,QAAQd,UAAUhG,MAAM,CAAC,CAACH,KAAKyC,QAAUzC,MAAMyC,OAAO;IAC5D,MAAMyE,cAAc3J,KAAKoI,GAAG,CAACsB,OAAOD;IACpC,IAAI,CAAEE,CAAAA,cAAc,CAAA,GAAI,OAAO;QAAEnB,IAAI;QAAOC,OAAO;IAA2C;IAC9F,OAAO;QAAED,IAAI;QAAMmB;QAAaf;QAAWT,oBAAoBU;QAAcG;IAAe;AAC9F;AASA;;;;;CAKC,GACD,OAAO,SAASY,sBAAsBrJ,KAAsB;QAEzBA;IADjC,MAAMyD,UAAU1D,gBAAgBC;IAChC,MAAMsJ,aAAaC,OAAOpB,MAAM,EAACnI,2BAAAA,MAAMwJ,iBAAiB,YAAvBxJ,2BAA2B,CAAC,GAAGqC,MAAM,CACpE,CAACH,KAAKyC,QAAUzC,MAAM5C,WAAWqF,QACjC;IAEF,IAAI8E,eAAehK,KAAKoE,GAAG,CAAC,GAAGvE,WAAWU,MAAMgE,aAAa,IAAIsF;IACjE,MAAMI,UAAU;WAAIjG;KAAQ,CAACkG,IAAI,CAC/B,CAACC,GAAGC,IAAMlK,OAAOP,mBAAmBsI,GAAG,CAACmC,EAAEzJ,MAAM,KAAKT,OAAOP,mBAAmBsI,GAAG,CAACkC,EAAExJ,MAAM;IAE7F,MAAM0J,aAAa,IAAIC;IACvB,KAAK,MAAM1I,UAAUqI,QAAS;YAC2B1J;QAAvD,IAAI+I,OAAOtJ,KAAKoE,GAAG,CAAC,GAAGxC,OAAOd,WAAW,GAAGjB,YAAWU,4BAAAA,MAAMwJ,iBAAiB,qBAAvBxJ,yBAAyB,CAACqB,OAAOf,EAAE,CAAC;QAC3F,IAAIlB,mBAAmBsI,GAAG,CAACrG,OAAOjB,MAAM,KAAKqJ,eAAe,GAAG;YAC7D,MAAMO,QAAQvK,KAAKoI,GAAG,CAACkB,MAAMU;YAC7BV,QAAQiB;YACRP,gBAAgBO;QAClB;QACAF,WAAWG,GAAG,CAAC5I,OAAOf,EAAE,EAAEyI;IAC5B;IACA,OAAOtF,QAAQpD,GAAG,CAAC,CAACgB;YAA0CyI;eAA9B,aAAKzI;YAAQ6I,eAAe,GAAEJ,kBAAAA,WAAWK,GAAG,CAAC9I,OAAOf,EAAE,aAAxBwJ,kBAA6B;;;AAC7F;AAEA;;;;CAIC,GACD,MAAMM,eAA2C;IAC/C;IACA;IACA;IACA;IACA;IACA;IACA;CACD;AAMD;;;;;;;;CAQC,GACD,OAAO,SAASC,eACdrK,KAAsB,EACtBO,WAAmB,EACnB+J,SAA4E;IAE5E,MAAMC,SAASjL,WAAWiB;IAC1B,MAAMkD,UAAU4F,sBAAsBrJ;IACtC,IAAI,CAACyD,QAAQW,MAAM,EAAE,OAAO;QAAE6D,IAAI;QAAOC,OAAO;IAA0C;IAC1F,IAAIoC,aAAaA,UAAUlG,MAAM,EAAE;QACjC,MAAMoG,cAAqC,EAAE;QAC7C,MAAMC,OAAO,IAAIpL;QACjB,KAAK,MAAMqC,SAAS4I,UAAW;YAC7B,MAAMjJ,SAASoC,QAAQiH,IAAI,CAAC,CAACC,YAAcA,UAAUrK,EAAE,KAAKoB,MAAMkJ,SAAS;YAC3E,IAAI,CAACvJ,QAAQ,OAAO;gBAAE4G,IAAI;gBAAOC,OAAO;YAAqC;YAC7E,IAAIuC,KAAK/C,GAAG,CAACrG,OAAOf,EAAE,GAAG,OAAO;gBAAE2H,IAAI;gBAAOC,OAAO;YAAkC;YACtFuC,KAAK7B,GAAG,CAACvH,OAAOf,EAAE;YAClB,MAAMqE,QAAQlF,KAAKC,KAAK,CAACC,OAAO+B,MAAMnB,WAAW;YACjD,IAAI,CAACZ,OAAOC,QAAQ,CAAC+E,UAAUA,QAAQ,GAAG,OAAO;gBAAEsD,IAAI;gBAAOC,OAAO;YAAmC;YACxG,IAAIvD,UAAU,GAAG;YACjB,IAAIA,QAAQtD,OAAO6I,eAAe,EAAE;gBAClC,OAAO;oBACLjC,IAAI;oBACJC,OAAO,GAAG9G,eAAeC,QAAQ,uBAAuB,EAAEqD,SAASrD,OAAO6I,eAAe,EAAE,CAAC,CAAC;gBAC/F;YACF;YACAM,YAAYvB,IAAI,CAAC;gBAAE2B,WAAWvJ,OAAOf,EAAE;gBAAEF,QAAQiB,OAAOjB,MAAM;gBAAEG,aAAaoE;YAAM;QACrF;QACA,MAAMrC,QAAQkI,YAAYnI,MAAM,CAAC,CAACH,KAAKR,QAAUQ,MAAMR,MAAMnB,WAAW,EAAE;QAC1E,IAAI+B,UAAUiI,QAAQ;YACpB,OAAO;gBACLtC,IAAI;gBACJC,OAAO,CAAC,cAAc,EAAExD,SAAS6F,QAAQ,uBAAuB,EAAE7F,SAASpC,OAAO,CAAC,CAAC;YACtF;QACF;QACA,OAAO;YAAE2F,IAAI;YAAMuC;QAAY;IACjC;IACA,MAAMd,UAAU;WAAIjG;KAAQ,CAACkG,IAAI,CAC/B,CAACC,GAAGC,IAAMO,aAAaS,OAAO,CAACjB,EAAExJ,MAAM,IAAIgK,aAAaS,OAAO,CAAChB,EAAEzJ,MAAM;IAE1E,MAAMoK,cAAqC,EAAE;IAC7C,IAAIzB,OAAOwB;IACX,KAAK,MAAMlJ,UAAUqI,QAAS;QAC5B,IAAI,CAAEX,CAAAA,OAAO,CAAA,GAAI;QACjB,MAAMpE,QAAQlF,KAAKoI,GAAG,CAACkB,MAAM1H,OAAO6I,eAAe;QACnD,IAAI,CAAEvF,CAAAA,QAAQ,CAAA,GAAI;QAClB6F,YAAYvB,IAAI,CAAC;YAAE2B,WAAWvJ,OAAOf,EAAE;YAAEF,QAAQiB,OAAOjB,MAAM;YAAEG,aAAaoE;QAAM;QACnFoE,QAAQpE;IACV;IACA,IAAIoE,OAAO,GAAG;QACZ,OAAO;YACLd,IAAI;YACJC,OAAO,CAAC,KAAK,EAAExD,SAAS6F,SAASxB,MAAM,qCAAqC,CAAC;QAC/E;IACF;IACA,OAAO;QAAEd,IAAI;QAAMuC;IAAY;AACjC;AAwBA;;2CAE2C,GAE3C,wEAAwE,GACxE,OAAO,MAAMM,+BAA+B,KAAK,KAAK,KAAI;AAC1D,iEAAiE,GACjE,OAAO,MAAMC,+BAA+B,IAAI,KAAK,KAAI;AACzD,yDAAyD,GACzD,OAAO,MAAMC,uBAAuB,EAAC;AACrC,8BAA8B,GAC9B,OAAO,MAAMC,qBAAqB,KAAK,KAAK,KAAI;AAEhD;;;CAGC,GACD,OAAO,SAASC,cAAcC,GAAY;QAK1BC,UAAmBA;IAJjC,MAAM7L,QAAQ,OAAO4L,QAAQ,WAAWA,MAAM;IAC9C,IAAI,CAAC,YAAYlG,IAAI,CAAC1F,QAAQ,OAAO;IACrC,IAAI,YAAY0F,IAAI,CAAC1F,QAAQ,OAAO;IACpC,MAAM6L,SAAS;WAAI7L;KAAM,CAACc,GAAG,CAACV;IAC9B,MAAM0L,OAAO,EAACD,WAAAA,MAAM,CAAC,EAAE,YAATA,WAAa,OAAMA,YAAAA,MAAM,CAAC,EAAE,YAATA,YAAa;IAC9C,IACE,AAACC,CAAAA,SAAS,KAAKA,SAAS,CAAC,CAAA,KACzBD,OAAOE,KAAK,CAAC,CAACC,OAAOlE;YAAkC+D;eAAxB/D,UAAU,KAAKkE,UAASH,WAAAA,MAAM,CAAC/D,QAAQ,EAAE,YAAjB+D,WAAqB,OAAOC;QACnF;QACA,OAAO;IACT;IACA,OAAO;AACT;AAqDA;;;;;;;;CAQC,GACD,OAAO,SAASG,gBAAgB5I,KAe/B;QAiBGA,mBAKqBA,sBAgBC6I;IArCxB,MAAM,EAAEzL,KAAK,EAAE,GAAG4C;IAClB,MAAM8I,OAAO9I,MAAM8I,IAAI,KAAK;IAC5B,MAAMD,OAAO/M,qBAAqBkE,MAAM+I,OAAO,EAAE3L,OAAqD;QACpG4L,WAAWhJ,MAAMiJ,KAAK,CAAC7C,IAAI;IAC7B;IACA,MAAMnC,QAA0B4E,KAAK5E,KAAK,CAACxG,GAAG,CAAC,CAAC2G,OAAU;YACxDgC,MAAMhC,KAAKgC,IAAI;WACXhC,KAAK8E,MAAM,GAAG;YAAEC,cAAc/E,KAAK8E,MAAM;QAAC,IAAI,CAAC;YACnD5E,UAAUF,KAAKE,QAAQ;YACvBD,iBAAiByE,OAAO,IAAI1E,KAAKgF,SAAS;YAC1C3D,WAAWqD,OAAO,IAAI1E,KAAKtD,UAAU;;IAEvC,MAAMlE,SAASG,OAAOK,MAAMR,MAAM;IAClC,MAAM4L,SAASzL,OAAOgJ,SAAS,CAACnJ,WAAWA,SAAS,IAAIwF,OAAOxF,UAAU;IACzE,MAAMyM,cAActM,OAAOK,MAAMiM,WAAW,KAAK;IACjD,MAAMC,cACJtJ,oBAAAA,MAAMsJ,UAAU,YAAhBtJ,oBAAqB,CAAC8C,KAAgBA,KAAK,IAAIC,KAAKD,IAAIyG,cAAc,KAAK;IAC7E,MAAMC,QAAQ,CAACzH,QAA+B+G,OAAO,IAAIpM,WAAWqF;IACpE,OAAO;QACLiH,WAAWhJ,MAAMiJ,KAAK,CAAC7C,IAAI;OACvBpG,MAAMiJ,KAAK,CAACQ,OAAO,GAAG;QAAEA,SAASzJ,MAAMiJ,KAAK,CAACQ,OAAO;IAAC,IAAI,CAAC;QAC9DC,cAActH,QAAOpC,uBAAAA,MAAMiJ,KAAK,CAACU,OAAO,YAAnB3J,uBAAuB,IACzC4J,KAAK,CAAC,SACNnM,GAAG,CAAC,CAAC2G,OAASA,KAAKyF,IAAI,IACvBxM,MAAM,CAACyM;QACVC,aAAavB,SAAS,CAAC,CAAC,EAAEA,QAAQ,GAAG,CAAC,CAAC,EAAExI,MAAM+I,OAAO,CAACjK,KAAK,CAAC,GAAG,IAAI;QACpEkL,cAAcxB,UAAUxI,MAAM+I,OAAO;QACrCkB,WAAWX,WAAWD;OAClBrJ,MAAMkK,WAAW,GAAG;QAAEA,aAAalK,MAAMkK,WAAW;IAAC,IAAI,CAAC,GAC1DlK,MAAMqD,YAAY,GAAG;QAAEA,cAAcrD,MAAMqD,YAAY;IAAC,IAAI,CAAC,GAC7DjG,MAAM+M,YAAY,GAAG;QAAEA,cAAc/H,OAAOhF,MAAM+M,YAAY;IAAE,IAAI,CAAC;QACzElG;QACAmG,eAAeZ,MAAMX,KAAKuB,aAAa;QACvCrJ,eAAeyI,MAAMX,KAAK9H,aAAa;QACvCN,UAAU+I,MAAMX,KAAKpI,QAAQ;QAC7BK,YAAY0I,MAAMX,KAAK/H,UAAU;QACjClD,UAAU4L,MAAMX,KAAKjL,QAAQ;QAC7BiD,SAASiI,OAAO,EAAE,IAAID,gBAAAA,KAAKhI,OAAO,YAAZgI,gBAAgB,EAAE;QACxCtK,aAAaiL,MAAMX,KAAKtK,WAAW;QACnC6C,eAAeoI,MAAMX,KAAKzH,aAAa;OACnCpB,MAAMiJ,KAAK,CAACoB,MAAM,GAAG;QAAEA,QAAQrK,MAAMiJ,KAAK,CAACoB,MAAM;IAAC,IAAI,CAAC,GACvDrK,MAAMiJ,KAAK,CAACqB,YAAY,GAAG;QAAEA,cAActK,MAAMiJ,KAAK,CAACqB,YAAY;IAAC,IAAI,CAAC;QAC7ExB;;AAEJ;AAQA,qEAAqE,GACrE,OAAO,SAASyB,mBACdrH,MAAsB,EACtB7B,KAA4D;QA6CtCA,sBAA2CA,uBACnCA;IA5C9B,MAAMR,UAAU9E,mBAAmBsB,MAAM,CACvC,CAACG;YAAY0F,8BAA2CA;eAA5C,EAACA,+BAAAA,OAAO9C,aAAa,CAAC5C,OAAO,YAA5B0F,+BAAgC,KAAK,KAAK,EAACA,iCAAAA,OAAO7C,eAAe,CAAC7C,OAAO,YAA9B0F,iCAAkC,KAAK;;IAEjG,OAAO;QACL;YACEsH,SAAS;YACTvH,MAAM;gBACJ;oBAAEwH,OAAO;oBAAU9N,OAAOyF,OAAOc,OAAO5C,UAAU;gBAAE;gBACpD;oBAAEmK,OAAO;oBAAe9N,OAAOmF,SAASoB,OAAO3C,eAAe;gBAAE;gBAChE;oBAAEkK,OAAO;oBAAa9N,OAAOmF,SAASoB,OAAO1C,cAAc;gBAAE;gBAC7D;oBAAEiK,OAAO;oBAAO9N,OAAOmF,SAASoB,OAAOzC,QAAQ;gBAAE;gBACjD;oBAAEgK,OAAO;oBAAQ9N,OAAOmF,SAASoB,OAAOxC,SAAS;gBAAE;gBACnD;oBAAE+J,OAAO,CAAC,SAAS,EAAEvH,OAAO3B,WAAW,CAAC,CAAC,CAAC;oBAAE5E,OAAO,CAAC,CAAC,EAAEmF,SAASoB,OAAOhC,YAAY,GAAG;gBAAC;gBACvF;oBAAEuJ,OAAO;oBAAa9N,OAAOmF,SAASoB,OAAOzB,aAAa;oBAAGiJ,QAAQ;gBAAK;aAC3E;QACH;QACA;YACEF,SAAS;YACTvH,MAAMpC,QAAQW,MAAM,GAChBX,QAAQpD,GAAG,CAAC,CAACD;oBAGA0F,8BACPA,gCACiBA;uBALE;oBACvBuH,OAAOzO,iBAAiB,CAACwB,OAAO;oBAChCb,OACEmF,UAASoB,+BAAAA,OAAO9C,aAAa,CAAC5C,OAAO,YAA5B0F,+BAAgC,KACxC,CAAA,EAACA,iCAAAA,OAAO7C,eAAe,CAAC7C,OAAO,YAA9B0F,iCAAkC,KAAK,IACrC,CAAC,GAAG,EAAEpB,UAASoB,kCAAAA,OAAO7C,eAAe,CAAC7C,OAAO,YAA9B0F,kCAAkC,GAAG,CAAC,CAAC,GACtD,EAAC;gBACT;iBACA;gBAAC;oBAAEuH,OAAO;oBAAgB9N,OAAO;gBAAG;aAAE;QAC5C;QACA;YACE6N,SAAS;YACTvH,MAAM;gBACJ;oBAAEwH,OAAO;oBAAiB9N,OAAOmF,SAASoB,OAAOjD,iBAAiB;gBAAE;gBACpE;oBAAEwK,OAAO;oBAAc9N,OAAOmF,SAASoB,OAAOhD,cAAc;gBAAE;gBAC9D;oBAAEuK,OAAO;oBAAW9N,OAAOmF,SAASoB,OAAOvD,WAAW;gBAAE;gBACxD;oBAAE8K,OAAO;oBAAY9N,OAAO,CAAC,CAAC,EAAEmF,SAASoB,OAAOtD,YAAY,GAAG;gBAAC;gBAChE;oBAAE6K,OAAO;oBAAc9N,OAAO,CAAC,CAAC,EAAEmF,SAASoB,OAAOrD,UAAU,GAAG;gBAAC;gBAChE;oBAAE4K,OAAO;oBAAgB9N,OAAO,CAAC,CAAC,EAAEmF,SAASoB,OAAOpD,gBAAgB,GAAG;gBAAC;gBACxE;oBAAE2K,OAAO;oBAAsB9N,OAAOmF,SAASoB,OAAOxB,iBAAiB;oBAAGgJ,QAAQ;gBAAK;mBACnFrJ,CAAAA,yBAAAA,MAAOQ,gBAAgB,KAAI,OAC3B;oBACE;wBAAE4I,OAAO;wBAAW9N,OAAOmF,SAAST,MAAMQ,gBAAgB;oBAAE;oBAC5D;wBACE4I,OAAO,EAACpJ,uBAAAA,MAAMwC,aAAa,YAAnBxC,uBAAuB,KAAK,IAAI,UAAU,EAACA,wBAAAA,MAAMwC,aAAa,YAAnBxC,wBAAuB,KAAK,IAAI,SAAS;wBAC5F1E,OAAOmF,UAAST,wBAAAA,MAAMwC,aAAa,YAAnBxC,wBAAuB;wBACvCqJ,QAAQ;oBACV;iBACD,GACD,EAAE;aACP;QACH;KACD;AACH;AAEA,8DAA8D,GAC9D,OAAO,SAASC,oBAAoB3K,KAOnC;IACC,OAAO;QACL4K,OAAO5K,MAAM4K,KAAK;QAClB5B,WAAWhJ,MAAMgJ,SAAS;OACtBhJ,MAAM6K,QAAQ,IAAI7K,MAAMqD,YAAY,GACpC;QAAEwH,UAAU;YAAC7K,MAAMqD,YAAY;YAAErD,MAAM6K,QAAQ;SAAC,CAACxN,MAAM,CAACyM,SAAS/F,IAAI,CAAC;IAAO,IAC7E,CAAC;QACL+G,UAAUP,mBAAmBvK,MAAMkD,MAAM,EAAElD,MAAMqB,KAAK;;AAE1D"}
@@ -23,7 +23,13 @@ export type OrderPaymentMethod = 'cash'
23
23
  /** The Stripe payment page the customer opens from a QR code. */
24
24
  | 'card_link' | 'gift_card'
25
25
  /** Charged to a checked-in reservation's folio. */
26
- | 'folio';
26
+ | 'folio'
27
+ /**
28
+ * Store credit another plugin keeps — rewards, referral credit — taken
29
+ * through core's checkout-credits seam (AGL-3640). Which plugin and which
30
+ * account ride on the payment as `creditProviderId` and `creditReference`.
31
+ */
32
+ | 'credit';
27
33
  export type OrderPaymentStatus =
28
34
  /** Started and not finished: reserves its amount against the balance. */
29
35
  'pending' | 'succeeded'
@@ -56,6 +62,12 @@ export interface OrderPayment {
56
62
  readerId?: string;
57
63
  /** The gift card's code (its document id) for `gift_card`. */
58
64
  giftCardId?: string;
65
+ /** For `credit`: the provider that paid it (`{pluginId}.{key}`, AGL-3640). */
66
+ creditProviderId?: string;
67
+ /** For `credit`: the provider's own handle on the account it came from. */
68
+ creditReference?: string;
69
+ /** For `credit`: what the provider calls it, for the register and receipt. */
70
+ creditLabel?: string;
59
71
  cardBrand?: string;
60
72
  last4?: string;
61
73
  /** Cash handed over for a `cash` payment, and the change given back. */
@@ -37,7 +37,8 @@ export const ORDER_PAYMENT_METHODS = [
37
37
  'card_keyed',
38
38
  'card_link',
39
39
  'gift_card',
40
- 'folio'
40
+ 'folio',
41
+ 'credit'
41
42
  ];
42
43
  /** The tenders whose money moves through Stripe. */ export const POS_CARD_METHODS = new Set([
43
44
  'card_present',
@@ -137,9 +138,8 @@ export function isCardPaymentMethod(method) {
137
138
  return card ? 'payout' : 'invoice';
138
139
  }
139
140
  /** A one-line description of a payment for the register, receipt and timeline. */ export function describeOrderPayment(payment) {
140
- var _ORDER_PAYMENT_METHOD_LABELS_payment_method;
141
141
  const dollars = (cents)=>`$${(cents / 100).toFixed(2)}`;
142
- const label = (_ORDER_PAYMENT_METHOD_LABELS_payment_method = ORDER_PAYMENT_METHOD_LABELS[payment.method]) != null ? _ORDER_PAYMENT_METHOD_LABELS_payment_method : 'Payment';
142
+ const label = payment.method === 'credit' && payment.creditLabel || ORDER_PAYMENT_METHOD_LABELS[payment.method] || 'Payment';
143
143
  const card = payment.cardBrand || payment.last4 ? ` (${[
144
144
  payment.cardBrand,
145
145
  payment.last4 ? `•••• ${payment.last4}` : ''
@@ -153,7 +153,8 @@ export const ORDER_PAYMENT_METHOD_LABELS = {
153
153
  card_keyed: 'Typed card',
154
154
  card_link: 'Card (QR)',
155
155
  gift_card: 'Gift card',
156
- folio: 'Charged to room'
156
+ folio: 'Charged to room',
157
+ credit: 'Store credit'
157
158
  };
158
159
  /*==========================================
159
160
  * TIPS (AGL-3607).
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-pos.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * THE POS TENDER LEDGER (AGL-3607).\n *\n * A register sale used to be settled by exactly one tender and recorded none:\n * whether a POS order was paid in cash, by the QR link or to a room was\n * inferred afterwards from which other fields happened to be set. A sale now\n * carries `payments[]`, one entry per tender, and takes payments until the\n * balance due is zero. Only then is it `paid`.\n *\n * Every figure here is integer cents. `amountCents` is what a payment put\n * toward the SALE; a tip rides beside it in `tipCents` and never counts toward\n * the balance, the platform's take or the store's revenue. Tips belong to the\n * merchant and their staff.\n *\n * Pure and framework-free, so the register, the customer display and the\n * server read one answer.\n *=========================================*/\n\n/** How one payment toward a register sale was taken. */\nexport type OrderPaymentMethod =\n | 'cash'\n /** A Stripe Terminal smart reader (tap, insert or swipe). */\n | 'card_present'\n /** A card typed into the console by staff (card not present). */\n | 'card_keyed'\n /** The Stripe payment page the customer opens from a QR code. */\n | 'card_link'\n | 'gift_card'\n /** Charged to a checked-in reservation's folio. */\n | 'folio'\n\nexport type OrderPaymentStatus =\n /** Started and not finished: reserves its amount against the balance. */\n | 'pending'\n | 'succeeded'\n /** Declined or errored; may be retried, reserves nothing. */\n | 'failed'\n | 'canceled'\n /** Taken and then handed back when the open sale was voided. */\n | 'reversed'\n\nexport const ORDER_PAYMENT_METHODS: readonly OrderPaymentMethod[] = [\n 'cash',\n 'card_present',\n 'card_keyed',\n 'card_link',\n 'gift_card',\n 'folio',\n]\n\n/** One payment toward a register sale, on `HostOrder.payments`. */\nexport interface OrderPayment {\n /** Stable id, derived from the register's attempt key so a retry finds it. */\n id: string\n method: OrderPaymentMethod\n /** Cents put toward the sale. Never includes the tip. */\n amountCents: number\n /** Gratuity on top of `amountCents`, the merchant's and not revenue. */\n tipCents?: number\n status: OrderPaymentStatus\n /** When the payment was started. */\n atMs: number\n /** When it reached `succeeded`. */\n settledAtMs?: number\n /** Stripe PaymentIntent for `card_present` / `card_keyed` / a paid link. */\n paymentIntentId?: string\n /** Stripe Checkout Session for `card_link`. */\n checkoutSessionId?: string\n /** The payment page a `card_link` payment shows as a QR. */\n checkoutUrl?: string\n /** The Terminal reader a `card_present` payment runs on. */\n readerId?: string\n /** The gift card's code (its document id) for `gift_card`. */\n giftCardId?: string\n cardBrand?: string\n last4?: string\n /** Cash handed over for a `cash` payment, and the change given back. */\n cashTenderedCents?: number\n changeCents?: number\n /** The stay a `folio` payment was charged to. */\n reservationId?: string\n /** The folio entry's `atMs`, so a void can remove exactly that entry. */\n folioAtMs?: number\n /**\n * The platform's take attributed to this payment (excludes the tip). For a\n * card payment it is netted from the payout with Stripe's processing cost\n * added, and recorded in `feeCents`; otherwise it accrues to the invoice.\n */\n takeFeeCents?: number\n /** The `application_fee_amount` a card payment carried, or 0. */\n feeCents?: number\n /** Why a `failed` payment failed, in words the cashier can read out. */\n failureMessage?: string\n /** Whether Stripe moved real money; absent for tenders Stripe never saw. */\n livemode?: boolean\n /** The console user who took the payment. */\n cashierId?: string\n}\n\n/** The tenders whose money moves through Stripe. */\nexport const POS_CARD_METHODS: ReadonlySet<OrderPaymentMethod> = new Set([\n 'card_present',\n 'card_keyed',\n 'card_link',\n])\n\nexport function isCardPaymentMethod(method: OrderPaymentMethod): boolean {\n return POS_CARD_METHODS.has(method)\n}\n\n/** Whole non-negative cents from an untrusted value. */\nfunction wholeCents(value: unknown): number {\n const number = Math.round(Number(value))\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n/**\n * The order's payments, read through one door.\n *\n * Orders written before the ledger existed carry no `payments`, so one is\n * INFERRED from the fields the single-tender register wrote: a folio sale has\n * `reservationId`, a QR sale has `checkoutSessionId`, and anything else on the\n * POS channel was cash. A pending legacy sale is the QR link still waiting.\n * Online orders paid through Stripe Checkout read as one `card_link` payment.\n * Nothing is written back: this is a reader, never a migration.\n */\nexport function orderPayments(order: {\n payments?: OrderPayment[] | null\n channel?: string\n status?: string\n totals?: { totalCents?: number; tipCents?: number } | null\n amountCents?: number\n reservationId?: string\n checkoutSessionId?: string\n paymentIntentId?: string\n createdAtMs?: number\n}): OrderPayment[] {\n if (Array.isArray(order?.payments)) {\n return order.payments.filter(\n (payment): payment is OrderPayment =>\n Boolean(payment) && typeof payment === 'object' && Boolean(payment.id),\n )\n }\n const totalCents = wholeCents(order?.totals?.totalCents ?? order?.amountCents)\n if (!(totalCents > 0)) return []\n const settled = order?.status !== 'pending' && order?.status !== 'cancelled'\n const atMs = Number(order?.createdAtMs) || 0\n const method: OrderPaymentMethod = order?.reservationId\n ? 'folio'\n : order?.checkoutSessionId || order?.paymentIntentId || order?.channel !== 'pos'\n ? 'card_link'\n : 'cash'\n return [\n {\n id: 'legacy',\n method,\n amountCents: totalCents,\n status: settled ? 'succeeded' : order?.status === 'cancelled' ? 'canceled' : 'pending',\n atMs,\n ...(order?.paymentIntentId ? { paymentIntentId: order.paymentIntentId } : {}),\n ...(order?.checkoutSessionId ? { checkoutSessionId: order.checkoutSessionId } : {}),\n ...(order?.reservationId ? { reservationId: order.reservationId } : {}),\n },\n ]\n}\n\n/** Cents put toward the sale by payments that succeeded. */\nexport function posSettledCents(payments: readonly OrderPayment[]): number {\n return payments\n .filter((payment) => payment.status === 'succeeded')\n .reduce((sum, payment) => sum + wholeCents(payment.amountCents), 0)\n}\n\n/**\n * Cents spoken for: succeeded payments plus the ones still in flight. A\n * reader waiting for a tap has claimed its share of the balance, so a second\n * tender cannot be started for the same money.\n */\nexport function posReservedCents(payments: readonly OrderPayment[]): number {\n return payments\n .filter((payment) => payment.status === 'succeeded' || payment.status === 'pending')\n .reduce((sum, payment) => sum + wholeCents(payment.amountCents), 0)\n}\n\n/** What the customer still owes, by settled payments. */\nexport function posBalanceDueCents(\n totalCents: number,\n payments: readonly OrderPayment[],\n): number {\n return Math.max(0, wholeCents(totalCents) - posSettledCents(payments))\n}\n\n/** What a NEW payment may still take: the balance less what is in flight. */\nexport function posTenderableCents(\n totalCents: number,\n payments: readonly OrderPayment[],\n): number {\n return Math.max(0, wholeCents(totalCents) - posReservedCents(payments))\n}\n\n/** Tips taken across the sale's settled payments. */\nexport function posTipCents(payments: readonly OrderPayment[]): number {\n return payments\n .filter((payment) => payment.status === 'succeeded')\n .reduce((sum, payment) => sum + wholeCents(payment.tipCents), 0)\n}\n\n/**\n * The share of the sale's take a payment of `amountCents` carries.\n *\n * Proportional to the part of the SALE it pays, never to the tip. Card\n * payments carry their share in `application_fee_amount`; whatever the card\n * shares leave is accrued to the invoice at completion\n * ({@link posInvoiceTakeCents}), so the take is exact however the sale was\n * split and however each share rounded.\n */\nexport function posTakeShareCents(input: {\n takeFeeCents: number\n totalCents: number\n amountCents: number\n}): number {\n const take = wholeCents(input.takeFeeCents)\n const total = wholeCents(input.totalCents)\n const amount = Math.min(wholeCents(input.amountCents), total)\n if (!(take > 0) || !(total > 0) || !(amount > 0)) return 0\n return Math.min(take, Math.round((take * amount) / total))\n}\n\n/**\n * What of the sale's take was NOT netted from a card payout, and therefore\n * goes on the merchant's invoice: the whole take less every succeeded card\n * payment's share. Never negative.\n */\nexport function posInvoiceTakeCents(input: {\n takeFeeCents: number\n payments: readonly OrderPayment[]\n}): number {\n const netted = input.payments\n .filter(\n (payment) =>\n payment.status === 'succeeded' && isCardPaymentMethod(payment.method),\n )\n .reduce((sum, payment) => sum + wholeCents(payment.takeFeeCents), 0)\n return Math.max(0, wholeCents(input.takeFeeCents) - netted)\n}\n\n/** How the sale's fee reaches the platform, from what actually paid it. */\nexport function posFeeCollection(\n payments: readonly OrderPayment[],\n): 'payout' | 'invoice' | 'split' {\n const settled = payments.filter((payment) => payment.status === 'succeeded')\n const card = settled.some((payment) => isCardPaymentMethod(payment.method))\n const other = settled.some((payment) => !isCardPaymentMethod(payment.method))\n if (card && other) return 'split'\n return card ? 'payout' : 'invoice'\n}\n\n/** A one-line description of a payment for the register, receipt and timeline. */\nexport function describeOrderPayment(payment: OrderPayment): string {\n const dollars = (cents: number) => `$${(cents / 100).toFixed(2)}`\n const label = ORDER_PAYMENT_METHOD_LABELS[payment.method] ?? 'Payment'\n const card =\n payment.cardBrand || payment.last4\n ? ` (${[payment.cardBrand, payment.last4 ? `•••• ${payment.last4}` : '']\n .filter(Boolean)\n .join(' ')})`\n : ''\n const tip = wholeCents(payment.tipCents)\n ? ` + ${dollars(wholeCents(payment.tipCents))} tip`\n : ''\n return `${label}${card} ${dollars(wholeCents(payment.amountCents))}${tip}`\n}\n\nexport const ORDER_PAYMENT_METHOD_LABELS: Record<OrderPaymentMethod, string> = {\n cash: 'Cash',\n card_present: 'Card reader',\n card_keyed: 'Typed card',\n card_link: 'Card (QR)',\n gift_card: 'Gift card',\n folio: 'Charged to room',\n}\n\n/*==========================================\n * TIPS (AGL-3607).\n *=========================================*/\n\n/** The presets a merchant starts with. */\nexport const POS_TIP_PERCENTAGES_DEFAULT: readonly number[] = [15, 18, 20, 25]\n\n/**\n * The merchant's tip presets from the plugin setting, a comma list such as\n * `15, 18, 20, 25`. Whole percentages from 1 to 100, at most four, in the order\n * given; anything unreadable falls back to the defaults rather than showing\n * the customer an empty choice.\n */\nexport function posTipPercentages(raw: unknown): number[] {\n const parsed = String(raw ?? '')\n .split(/[,\\s]+/)\n .map((part) => Math.round(Number(part)))\n .filter((value) => Number.isFinite(value) && value >= 1 && value <= 100)\n const unique = [...new Set(parsed)].slice(0, 4)\n return unique.length ? unique : [...POS_TIP_PERCENTAGES_DEFAULT]\n}\n\n/** A percentage tip on `baseCents`, rounded to the cent. */\nexport function posTipFromPercent(baseCents: number, percent: number): number {\n const base = wholeCents(baseCents)\n const pct = Number(percent)\n if (!(base > 0) || !Number.isFinite(pct) || pct <= 0) return 0\n return Math.round((base * pct) / 100)\n}\n\n/** The largest tip the register accepts on one payment: the payment itself. */\nexport function posTipProblem(tipCents: number, amountCents: number): string | null {\n if (!Number.isInteger(tipCents) || tipCents < 0) return 'Enter a tip of $0 or more.'\n if (tipCents > wholeCents(amountCents)) {\n return 'A tip can be at most the amount being paid. Check the amount and try again.'\n }\n return null\n}\n\n/*==========================================\n * THE CUSTOMER DISPLAY (AGL-3608).\n *\n * A paired screen facing the customer reads one document per register,\n * `hosts/{hostId}/registers/{registerId}/display/state`, through the\n * `commerce/pos-display` route and its display token. The register writes\n * the prompt; the customer's answer comes back in `response`.\n *=========================================*/\n\nexport type PosDisplayMode =\n | 'idle'\n /** The live basket, mirrored as the cashier builds it. */\n | 'cart'\n /** The customer picks a tip. */\n | 'tip'\n /** The customer picks how they want their receipt. */\n | 'receipt'\n /** A card payment is waiting on the reader. */\n | 'processing'\n | 'thanks'\n\nexport type PosReceiptChannel = 'email' | 'sms' | 'print' | 'none'\n\nexport interface PosDisplayLine {\n name: string\n variantLabel?: string\n quantity: number\n amountCents: number\n}\n\nexport interface PosDisplayCart {\n lines: PosDisplayLine[]\n itemsCents: number\n discountCents: number\n taxCents: number\n totalCents: number\n /** Taken so far, and what is still due, once payments start. */\n paidCents?: number\n dueCents?: number\n tipCents?: number\n}\n\n/** The customer's answer to the current prompt. */\nexport interface PosDisplayResponse {\n /** The prompt this answers; a stale answer is refused. */\n promptId: string\n tipCents?: number\n tipPercent?: number\n /** `none` when the customer chose no tip. */\n tipChoice?: 'percent' | 'custom' | 'none'\n receiptChannel?: PosReceiptChannel\n email?: string\n phone?: string\n marketingOptIn?: boolean\n atMs: number\n}\n\nexport interface PosDisplayState {\n mode: PosDisplayMode\n updatedAtMs: number\n /**\n * The ISO currency every amount on the screen is in (`usd`). Stamped by\n * the server from the store's own currency, never taken from the register.\n */\n currency?: string\n /** Changes every time the register asks something new. */\n promptId?: string\n cart?: PosDisplayCart\n tip?: {\n /** What a percentage is taken of: the amount being paid now. */\n baseCents: number\n percentages: number[]\n allowCustom: boolean\n }\n receipt?: {\n channels: PosReceiptChannel[]\n /** Whether to offer the store's marketing opt-in under the email field. */\n offerMarketing: boolean\n }\n processing?: { message: string }\n response?: PosDisplayResponse\n}\n\n/** Most lines a display state carries; a longer basket shows its newest. */\nexport const POS_DISPLAY_MAX_LINES = 60\n\n/** A pairing code lives this long before the register must show a new one. */\nexport const POS_DISPLAY_PAIRING_TTL_MS = 10 * 60 * 1000\n\n/** A thank-you screen returns to idle on the display after this long. */\nexport const POS_DISPLAY_THANKS_MS = 8_000\n\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\n/** An email address as the display may store it, or '' when it is not one. */\nexport function posDisplayEmail(value: unknown): string {\n const email = String(value ?? '').trim().toLowerCase().slice(0, 254)\n return EMAIL_PATTERN.test(email) ? email : ''\n}\n\n/** A phone number as digits with an optional leading +, or '' when too short. */\nexport function posDisplayPhone(value: unknown): string {\n const raw = String(value ?? '').trim()\n const digits = raw.replace(/[^0-9]/g, '')\n if (digits.length < 7 || digits.length > 15) return ''\n return `${raw.startsWith('+') ? '+' : ''}${digits}`\n}\n\n/**\n * The display state as the REGISTER may send it: clamped, typed and stripped\n * of anything the customer screen has no business holding. The register is\n * staff and trusted for intent, not for shape.\n */\nexport function sanitizePosDisplayState(\n raw: unknown,\n nowMs: number,\n): PosDisplayState {\n const input = (raw && typeof raw === 'object' ? raw : {}) as Record<string, any>\n const modes: PosDisplayMode[] = ['idle', 'cart', 'tip', 'receipt', 'processing', 'thanks']\n const mode = modes.includes(input['mode']) ? (input['mode'] as PosDisplayMode) : 'idle'\n const state: PosDisplayState = { mode, updatedAtMs: nowMs }\n const promptId = String(input['promptId'] ?? '').slice(0, 64)\n if (promptId) state.promptId = promptId\n const cart = input['cart']\n if (cart && typeof cart === 'object' && mode !== 'idle') {\n const lines = (Array.isArray(cart.lines) ? cart.lines : [])\n .slice(-POS_DISPLAY_MAX_LINES)\n .map((line: any) => ({\n name: String(line?.name ?? '').slice(0, 120),\n ...(line?.variantLabel\n ? { variantLabel: String(line.variantLabel).slice(0, 80) }\n : {}),\n quantity: Math.max(1, Math.min(999, Math.round(Number(line?.quantity) || 1))),\n amountCents: wholeCents(line?.amountCents),\n }))\n state.cart = {\n lines,\n itemsCents: wholeCents(cart.itemsCents),\n discountCents: wholeCents(cart.discountCents),\n taxCents: wholeCents(cart.taxCents),\n totalCents: wholeCents(cart.totalCents),\n ...(cart.paidCents != null ? { paidCents: wholeCents(cart.paidCents) } : {}),\n ...(cart.dueCents != null ? { dueCents: wholeCents(cart.dueCents) } : {}),\n ...(cart.tipCents != null ? { tipCents: wholeCents(cart.tipCents) } : {}),\n }\n }\n if (mode === 'tip') {\n const tip = input['tip'] ?? {}\n state.tip = {\n baseCents: wholeCents(tip.baseCents),\n percentages: posTipPercentages(\n Array.isArray(tip.percentages) ? tip.percentages.join(',') : tip.percentages,\n ),\n allowCustom: tip.allowCustom !== false,\n }\n }\n if (mode === 'receipt') {\n const receipt = input['receipt'] ?? {}\n const allowed: PosReceiptChannel[] = ['email', 'sms', 'print', 'none']\n const channels = (Array.isArray(receipt.channels) ? receipt.channels : [])\n .filter((channel: unknown): channel is PosReceiptChannel =>\n allowed.includes(channel as PosReceiptChannel),\n )\n state.receipt = {\n channels: channels.length ? [...new Set<PosReceiptChannel>(channels)] : ['print', 'none'],\n offerMarketing: receipt.offerMarketing === true,\n }\n }\n if (mode === 'processing') {\n state.processing = {\n message:\n String(input['processing']?.message ?? '').slice(0, 160) ||\n 'Tap, insert or swipe your card on the reader.',\n }\n }\n return state\n}\n\n/**\n * The customer's answer as the DISPLAY may send it, checked against the\n * prompt it claims to answer. Returns null when it answers nothing the\n * register asked: a stale screen, a replay, or a mode that takes no answer.\n */\nexport function sanitizePosDisplayResponse(\n state: PosDisplayState | null | undefined,\n raw: unknown,\n nowMs: number,\n): PosDisplayResponse | null {\n if (!state || !state.promptId) return null\n const input = (raw && typeof raw === 'object' ? raw : {}) as Record<string, any>\n if (String(input['promptId'] ?? '') !== state.promptId) return null\n const response: PosDisplayResponse = { promptId: state.promptId, atMs: nowMs }\n if (state.mode === 'tip' && state.tip) {\n const choice = input['tipChoice']\n if (choice === 'none') {\n response.tipChoice = 'none'\n response.tipCents = 0\n } else if (choice === 'percent') {\n const percent = Math.round(Number(input['tipPercent']))\n if (!state.tip.percentages.includes(percent)) return null\n response.tipChoice = 'percent'\n response.tipPercent = percent\n response.tipCents = posTipFromPercent(state.tip.baseCents, percent)\n } else if (choice === 'custom' && state.tip.allowCustom) {\n const tipCents = Math.round(Number(input['tipCents']))\n if (posTipProblem(tipCents, state.tip.baseCents)) return null\n response.tipChoice = 'custom'\n response.tipCents = tipCents\n } else {\n return null\n }\n return response\n }\n if (state.mode === 'receipt' && state.receipt) {\n const channel = input['receiptChannel'] as PosReceiptChannel\n if (!state.receipt.channels.includes(channel)) return null\n response.receiptChannel = channel\n if (channel === 'email') {\n const email = posDisplayEmail(input['email'])\n if (!email) return null\n response.email = email\n if (state.receipt.offerMarketing && input['marketingOptIn'] === true) {\n response.marketingOptIn = true\n }\n }\n if (channel === 'sms') {\n const phone = posDisplayPhone(input['phone'])\n if (!phone) return null\n response.phone = phone\n }\n return response\n }\n return null\n}\n\n/**\n * The state as the DISPLAY reads it. The customer's own typed address is\n * never echoed back to the screen: once answered, the prompt is done and the\n * screen shows what comes next, so nothing a later customer could read off\n * an unattended tablet is ever sent to it.\n */\nexport function posDisplayPublicState(\n state: PosDisplayState | null | undefined,\n nowMs: number,\n): Omit<PosDisplayState, 'response'> & { answered: boolean } {\n if (!state) return { mode: 'idle', updatedAtMs: nowMs, answered: false }\n const { response, ...rest } = state\n if (rest.mode === 'thanks' && nowMs - rest.updatedAtMs > POS_DISPLAY_THANKS_MS) {\n return { mode: 'idle', updatedAtMs: rest.updatedAtMs, answered: false }\n }\n return {\n ...rest,\n answered: Boolean(response && response.promptId === rest.promptId),\n }\n}\n"],"names":["ORDER_PAYMENT_METHODS","POS_CARD_METHODS","Set","isCardPaymentMethod","method","has","wholeCents","value","number","Math","round","Number","isFinite","orderPayments","order","Array","isArray","payments","filter","payment","Boolean","id","totalCents","totals","amountCents","settled","status","atMs","createdAtMs","reservationId","checkoutSessionId","paymentIntentId","channel","posSettledCents","reduce","sum","posReservedCents","posBalanceDueCents","max","posTenderableCents","posTipCents","tipCents","posTakeShareCents","input","take","takeFeeCents","total","amount","min","posInvoiceTakeCents","netted","posFeeCollection","card","some","other","describeOrderPayment","ORDER_PAYMENT_METHOD_LABELS","dollars","cents","toFixed","label","cardBrand","last4","join","tip","cash","card_present","card_keyed","card_link","gift_card","folio","POS_TIP_PERCENTAGES_DEFAULT","posTipPercentages","raw","parsed","String","split","map","part","unique","slice","length","posTipFromPercent","baseCents","percent","base","pct","posTipProblem","isInteger","POS_DISPLAY_MAX_LINES","POS_DISPLAY_PAIRING_TTL_MS","POS_DISPLAY_THANKS_MS","EMAIL_PATTERN","posDisplayEmail","email","trim","toLowerCase","test","posDisplayPhone","digits","replace","startsWith","sanitizePosDisplayState","nowMs","modes","mode","includes","state","updatedAtMs","promptId","cart","lines","line","name","variantLabel","quantity","itemsCents","discountCents","taxCents","paidCents","dueCents","percentages","allowCustom","receipt","allowed","channels","offerMarketing","processing","message","sanitizePosDisplayResponse","response","choice","tipChoice","tipPercent","receiptChannel","marketingOptIn","phone","posDisplayPublicState","answered","rest"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-pos.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/*==========================================\n * THE POS TENDER LEDGER (AGL-3607).\n *\n * A register sale used to be settled by exactly one tender and recorded none:\n * whether a POS order was paid in cash, by the QR link or to a room was\n * inferred afterwards from which other fields happened to be set. A sale now\n * carries `payments[]`, one entry per tender, and takes payments until the\n * balance due is zero. Only then is it `paid`.\n *\n * Every figure here is integer cents. `amountCents` is what a payment put\n * toward the SALE; a tip rides beside it in `tipCents` and never counts toward\n * the balance, the platform's take or the store's revenue. Tips belong to the\n * merchant and their staff.\n *\n * Pure and framework-free, so the register, the customer display and the\n * server read one answer.\n *=========================================*/\n\n/** How one payment toward a register sale was taken. */\nexport type OrderPaymentMethod =\n | 'cash'\n /** A Stripe Terminal smart reader (tap, insert or swipe). */\n | 'card_present'\n /** A card typed into the console by staff (card not present). */\n | 'card_keyed'\n /** The Stripe payment page the customer opens from a QR code. */\n | 'card_link'\n | 'gift_card'\n /** Charged to a checked-in reservation's folio. */\n | 'folio'\n /**\n * Store credit another plugin keeps — rewards, referral credit — taken\n * through core's checkout-credits seam (AGL-3640). Which plugin and which\n * account ride on the payment as `creditProviderId` and `creditReference`.\n */\n | 'credit'\n\nexport type OrderPaymentStatus =\n /** Started and not finished: reserves its amount against the balance. */\n | 'pending'\n | 'succeeded'\n /** Declined or errored; may be retried, reserves nothing. */\n | 'failed'\n | 'canceled'\n /** Taken and then handed back when the open sale was voided. */\n | 'reversed'\n\nexport const ORDER_PAYMENT_METHODS: readonly OrderPaymentMethod[] = [\n 'cash',\n 'card_present',\n 'card_keyed',\n 'card_link',\n 'gift_card',\n 'folio',\n 'credit',\n]\n\n/** One payment toward a register sale, on `HostOrder.payments`. */\nexport interface OrderPayment {\n /** Stable id, derived from the register's attempt key so a retry finds it. */\n id: string\n method: OrderPaymentMethod\n /** Cents put toward the sale. Never includes the tip. */\n amountCents: number\n /** Gratuity on top of `amountCents`, the merchant's and not revenue. */\n tipCents?: number\n status: OrderPaymentStatus\n /** When the payment was started. */\n atMs: number\n /** When it reached `succeeded`. */\n settledAtMs?: number\n /** Stripe PaymentIntent for `card_present` / `card_keyed` / a paid link. */\n paymentIntentId?: string\n /** Stripe Checkout Session for `card_link`. */\n checkoutSessionId?: string\n /** The payment page a `card_link` payment shows as a QR. */\n checkoutUrl?: string\n /** The Terminal reader a `card_present` payment runs on. */\n readerId?: string\n /** The gift card's code (its document id) for `gift_card`. */\n giftCardId?: string\n /** For `credit`: the provider that paid it (`{pluginId}.{key}`, AGL-3640). */\n creditProviderId?: string\n /** For `credit`: the provider's own handle on the account it came from. */\n creditReference?: string\n /** For `credit`: what the provider calls it, for the register and receipt. */\n creditLabel?: string\n cardBrand?: string\n last4?: string\n /** Cash handed over for a `cash` payment, and the change given back. */\n cashTenderedCents?: number\n changeCents?: number\n /** The stay a `folio` payment was charged to. */\n reservationId?: string\n /** The folio entry's `atMs`, so a void can remove exactly that entry. */\n folioAtMs?: number\n /**\n * The platform's take attributed to this payment (excludes the tip). For a\n * card payment it is netted from the payout with Stripe's processing cost\n * added, and recorded in `feeCents`; otherwise it accrues to the invoice.\n */\n takeFeeCents?: number\n /** The `application_fee_amount` a card payment carried, or 0. */\n feeCents?: number\n /** Why a `failed` payment failed, in words the cashier can read out. */\n failureMessage?: string\n /** Whether Stripe moved real money; absent for tenders Stripe never saw. */\n livemode?: boolean\n /** The console user who took the payment. */\n cashierId?: string\n}\n\n/** The tenders whose money moves through Stripe. */\nexport const POS_CARD_METHODS: ReadonlySet<OrderPaymentMethod> = new Set([\n 'card_present',\n 'card_keyed',\n 'card_link',\n])\n\nexport function isCardPaymentMethod(method: OrderPaymentMethod): boolean {\n return POS_CARD_METHODS.has(method)\n}\n\n/** Whole non-negative cents from an untrusted value. */\nfunction wholeCents(value: unknown): number {\n const number = Math.round(Number(value))\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n/**\n * The order's payments, read through one door.\n *\n * Orders written before the ledger existed carry no `payments`, so one is\n * INFERRED from the fields the single-tender register wrote: a folio sale has\n * `reservationId`, a QR sale has `checkoutSessionId`, and anything else on the\n * POS channel was cash. A pending legacy sale is the QR link still waiting.\n * Online orders paid through Stripe Checkout read as one `card_link` payment.\n * Nothing is written back: this is a reader, never a migration.\n */\nexport function orderPayments(order: {\n payments?: OrderPayment[] | null\n channel?: string\n status?: string\n totals?: { totalCents?: number; tipCents?: number } | null\n amountCents?: number\n reservationId?: string\n checkoutSessionId?: string\n paymentIntentId?: string\n createdAtMs?: number\n}): OrderPayment[] {\n if (Array.isArray(order?.payments)) {\n return order.payments.filter(\n (payment): payment is OrderPayment =>\n Boolean(payment) && typeof payment === 'object' && Boolean(payment.id),\n )\n }\n const totalCents = wholeCents(order?.totals?.totalCents ?? order?.amountCents)\n if (!(totalCents > 0)) return []\n const settled = order?.status !== 'pending' && order?.status !== 'cancelled'\n const atMs = Number(order?.createdAtMs) || 0\n const method: OrderPaymentMethod = order?.reservationId\n ? 'folio'\n : order?.checkoutSessionId || order?.paymentIntentId || order?.channel !== 'pos'\n ? 'card_link'\n : 'cash'\n return [\n {\n id: 'legacy',\n method,\n amountCents: totalCents,\n status: settled ? 'succeeded' : order?.status === 'cancelled' ? 'canceled' : 'pending',\n atMs,\n ...(order?.paymentIntentId ? { paymentIntentId: order.paymentIntentId } : {}),\n ...(order?.checkoutSessionId ? { checkoutSessionId: order.checkoutSessionId } : {}),\n ...(order?.reservationId ? { reservationId: order.reservationId } : {}),\n },\n ]\n}\n\n/** Cents put toward the sale by payments that succeeded. */\nexport function posSettledCents(payments: readonly OrderPayment[]): number {\n return payments\n .filter((payment) => payment.status === 'succeeded')\n .reduce((sum, payment) => sum + wholeCents(payment.amountCents), 0)\n}\n\n/**\n * Cents spoken for: succeeded payments plus the ones still in flight. A\n * reader waiting for a tap has claimed its share of the balance, so a second\n * tender cannot be started for the same money.\n */\nexport function posReservedCents(payments: readonly OrderPayment[]): number {\n return payments\n .filter((payment) => payment.status === 'succeeded' || payment.status === 'pending')\n .reduce((sum, payment) => sum + wholeCents(payment.amountCents), 0)\n}\n\n/** What the customer still owes, by settled payments. */\nexport function posBalanceDueCents(\n totalCents: number,\n payments: readonly OrderPayment[],\n): number {\n return Math.max(0, wholeCents(totalCents) - posSettledCents(payments))\n}\n\n/** What a NEW payment may still take: the balance less what is in flight. */\nexport function posTenderableCents(\n totalCents: number,\n payments: readonly OrderPayment[],\n): number {\n return Math.max(0, wholeCents(totalCents) - posReservedCents(payments))\n}\n\n/** Tips taken across the sale's settled payments. */\nexport function posTipCents(payments: readonly OrderPayment[]): number {\n return payments\n .filter((payment) => payment.status === 'succeeded')\n .reduce((sum, payment) => sum + wholeCents(payment.tipCents), 0)\n}\n\n/**\n * The share of the sale's take a payment of `amountCents` carries.\n *\n * Proportional to the part of the SALE it pays, never to the tip. Card\n * payments carry their share in `application_fee_amount`; whatever the card\n * shares leave is accrued to the invoice at completion\n * ({@link posInvoiceTakeCents}), so the take is exact however the sale was\n * split and however each share rounded.\n */\nexport function posTakeShareCents(input: {\n takeFeeCents: number\n totalCents: number\n amountCents: number\n}): number {\n const take = wholeCents(input.takeFeeCents)\n const total = wholeCents(input.totalCents)\n const amount = Math.min(wholeCents(input.amountCents), total)\n if (!(take > 0) || !(total > 0) || !(amount > 0)) return 0\n return Math.min(take, Math.round((take * amount) / total))\n}\n\n/**\n * What of the sale's take was NOT netted from a card payout, and therefore\n * goes on the merchant's invoice: the whole take less every succeeded card\n * payment's share. Never negative.\n */\nexport function posInvoiceTakeCents(input: {\n takeFeeCents: number\n payments: readonly OrderPayment[]\n}): number {\n const netted = input.payments\n .filter(\n (payment) =>\n payment.status === 'succeeded' && isCardPaymentMethod(payment.method),\n )\n .reduce((sum, payment) => sum + wholeCents(payment.takeFeeCents), 0)\n return Math.max(0, wholeCents(input.takeFeeCents) - netted)\n}\n\n/** How the sale's fee reaches the platform, from what actually paid it. */\nexport function posFeeCollection(\n payments: readonly OrderPayment[],\n): 'payout' | 'invoice' | 'split' {\n const settled = payments.filter((payment) => payment.status === 'succeeded')\n const card = settled.some((payment) => isCardPaymentMethod(payment.method))\n const other = settled.some((payment) => !isCardPaymentMethod(payment.method))\n if (card && other) return 'split'\n return card ? 'payout' : 'invoice'\n}\n\n/** A one-line description of a payment for the register, receipt and timeline. */\nexport function describeOrderPayment(payment: OrderPayment): string {\n const dollars = (cents: number) => `$${(cents / 100).toFixed(2)}`\n const label =\n (payment.method === 'credit' && payment.creditLabel) || ORDER_PAYMENT_METHOD_LABELS[payment.method] || 'Payment'\n const card =\n payment.cardBrand || payment.last4\n ? ` (${[payment.cardBrand, payment.last4 ? `•••• ${payment.last4}` : '']\n .filter(Boolean)\n .join(' ')})`\n : ''\n const tip = wholeCents(payment.tipCents)\n ? ` + ${dollars(wholeCents(payment.tipCents))} tip`\n : ''\n return `${label}${card} ${dollars(wholeCents(payment.amountCents))}${tip}`\n}\n\nexport const ORDER_PAYMENT_METHOD_LABELS: Record<OrderPaymentMethod, string> = {\n cash: 'Cash',\n card_present: 'Card reader',\n card_keyed: 'Typed card',\n card_link: 'Card (QR)',\n gift_card: 'Gift card',\n folio: 'Charged to room',\n credit: 'Store credit',\n}\n\n/*==========================================\n * TIPS (AGL-3607).\n *=========================================*/\n\n/** The presets a merchant starts with. */\nexport const POS_TIP_PERCENTAGES_DEFAULT: readonly number[] = [15, 18, 20, 25]\n\n/**\n * The merchant's tip presets from the plugin setting, a comma list such as\n * `15, 18, 20, 25`. Whole percentages from 1 to 100, at most four, in the order\n * given; anything unreadable falls back to the defaults rather than showing\n * the customer an empty choice.\n */\nexport function posTipPercentages(raw: unknown): number[] {\n const parsed = String(raw ?? '')\n .split(/[,\\s]+/)\n .map((part) => Math.round(Number(part)))\n .filter((value) => Number.isFinite(value) && value >= 1 && value <= 100)\n const unique = [...new Set(parsed)].slice(0, 4)\n return unique.length ? unique : [...POS_TIP_PERCENTAGES_DEFAULT]\n}\n\n/** A percentage tip on `baseCents`, rounded to the cent. */\nexport function posTipFromPercent(baseCents: number, percent: number): number {\n const base = wholeCents(baseCents)\n const pct = Number(percent)\n if (!(base > 0) || !Number.isFinite(pct) || pct <= 0) return 0\n return Math.round((base * pct) / 100)\n}\n\n/** The largest tip the register accepts on one payment: the payment itself. */\nexport function posTipProblem(tipCents: number, amountCents: number): string | null {\n if (!Number.isInteger(tipCents) || tipCents < 0) return 'Enter a tip of $0 or more.'\n if (tipCents > wholeCents(amountCents)) {\n return 'A tip can be at most the amount being paid. Check the amount and try again.'\n }\n return null\n}\n\n/*==========================================\n * THE CUSTOMER DISPLAY (AGL-3608).\n *\n * A paired screen facing the customer reads one document per register,\n * `hosts/{hostId}/registers/{registerId}/display/state`, through the\n * `commerce/pos-display` route and its display token. The register writes\n * the prompt; the customer's answer comes back in `response`.\n *=========================================*/\n\nexport type PosDisplayMode =\n | 'idle'\n /** The live basket, mirrored as the cashier builds it. */\n | 'cart'\n /** The customer picks a tip. */\n | 'tip'\n /** The customer picks how they want their receipt. */\n | 'receipt'\n /** A card payment is waiting on the reader. */\n | 'processing'\n | 'thanks'\n\nexport type PosReceiptChannel = 'email' | 'sms' | 'print' | 'none'\n\nexport interface PosDisplayLine {\n name: string\n variantLabel?: string\n quantity: number\n amountCents: number\n}\n\nexport interface PosDisplayCart {\n lines: PosDisplayLine[]\n itemsCents: number\n discountCents: number\n taxCents: number\n totalCents: number\n /** Taken so far, and what is still due, once payments start. */\n paidCents?: number\n dueCents?: number\n tipCents?: number\n}\n\n/** The customer's answer to the current prompt. */\nexport interface PosDisplayResponse {\n /** The prompt this answers; a stale answer is refused. */\n promptId: string\n tipCents?: number\n tipPercent?: number\n /** `none` when the customer chose no tip. */\n tipChoice?: 'percent' | 'custom' | 'none'\n receiptChannel?: PosReceiptChannel\n email?: string\n phone?: string\n marketingOptIn?: boolean\n atMs: number\n}\n\nexport interface PosDisplayState {\n mode: PosDisplayMode\n updatedAtMs: number\n /**\n * The ISO currency every amount on the screen is in (`usd`). Stamped by\n * the server from the store's own currency, never taken from the register.\n */\n currency?: string\n /** Changes every time the register asks something new. */\n promptId?: string\n cart?: PosDisplayCart\n tip?: {\n /** What a percentage is taken of: the amount being paid now. */\n baseCents: number\n percentages: number[]\n allowCustom: boolean\n }\n receipt?: {\n channels: PosReceiptChannel[]\n /** Whether to offer the store's marketing opt-in under the email field. */\n offerMarketing: boolean\n }\n processing?: { message: string }\n response?: PosDisplayResponse\n}\n\n/** Most lines a display state carries; a longer basket shows its newest. */\nexport const POS_DISPLAY_MAX_LINES = 60\n\n/** A pairing code lives this long before the register must show a new one. */\nexport const POS_DISPLAY_PAIRING_TTL_MS = 10 * 60 * 1000\n\n/** A thank-you screen returns to idle on the display after this long. */\nexport const POS_DISPLAY_THANKS_MS = 8_000\n\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n\n/** An email address as the display may store it, or '' when it is not one. */\nexport function posDisplayEmail(value: unknown): string {\n const email = String(value ?? '').trim().toLowerCase().slice(0, 254)\n return EMAIL_PATTERN.test(email) ? email : ''\n}\n\n/** A phone number as digits with an optional leading +, or '' when too short. */\nexport function posDisplayPhone(value: unknown): string {\n const raw = String(value ?? '').trim()\n const digits = raw.replace(/[^0-9]/g, '')\n if (digits.length < 7 || digits.length > 15) return ''\n return `${raw.startsWith('+') ? '+' : ''}${digits}`\n}\n\n/**\n * The display state as the REGISTER may send it: clamped, typed and stripped\n * of anything the customer screen has no business holding. The register is\n * staff and trusted for intent, not for shape.\n */\nexport function sanitizePosDisplayState(\n raw: unknown,\n nowMs: number,\n): PosDisplayState {\n const input = (raw && typeof raw === 'object' ? raw : {}) as Record<string, any>\n const modes: PosDisplayMode[] = ['idle', 'cart', 'tip', 'receipt', 'processing', 'thanks']\n const mode = modes.includes(input['mode']) ? (input['mode'] as PosDisplayMode) : 'idle'\n const state: PosDisplayState = { mode, updatedAtMs: nowMs }\n const promptId = String(input['promptId'] ?? '').slice(0, 64)\n if (promptId) state.promptId = promptId\n const cart = input['cart']\n if (cart && typeof cart === 'object' && mode !== 'idle') {\n const lines = (Array.isArray(cart.lines) ? cart.lines : [])\n .slice(-POS_DISPLAY_MAX_LINES)\n .map((line: any) => ({\n name: String(line?.name ?? '').slice(0, 120),\n ...(line?.variantLabel\n ? { variantLabel: String(line.variantLabel).slice(0, 80) }\n : {}),\n quantity: Math.max(1, Math.min(999, Math.round(Number(line?.quantity) || 1))),\n amountCents: wholeCents(line?.amountCents),\n }))\n state.cart = {\n lines,\n itemsCents: wholeCents(cart.itemsCents),\n discountCents: wholeCents(cart.discountCents),\n taxCents: wholeCents(cart.taxCents),\n totalCents: wholeCents(cart.totalCents),\n ...(cart.paidCents != null ? { paidCents: wholeCents(cart.paidCents) } : {}),\n ...(cart.dueCents != null ? { dueCents: wholeCents(cart.dueCents) } : {}),\n ...(cart.tipCents != null ? { tipCents: wholeCents(cart.tipCents) } : {}),\n }\n }\n if (mode === 'tip') {\n const tip = input['tip'] ?? {}\n state.tip = {\n baseCents: wholeCents(tip.baseCents),\n percentages: posTipPercentages(\n Array.isArray(tip.percentages) ? tip.percentages.join(',') : tip.percentages,\n ),\n allowCustom: tip.allowCustom !== false,\n }\n }\n if (mode === 'receipt') {\n const receipt = input['receipt'] ?? {}\n const allowed: PosReceiptChannel[] = ['email', 'sms', 'print', 'none']\n const channels = (Array.isArray(receipt.channels) ? receipt.channels : [])\n .filter((channel: unknown): channel is PosReceiptChannel =>\n allowed.includes(channel as PosReceiptChannel),\n )\n state.receipt = {\n channels: channels.length ? [...new Set<PosReceiptChannel>(channels)] : ['print', 'none'],\n offerMarketing: receipt.offerMarketing === true,\n }\n }\n if (mode === 'processing') {\n state.processing = {\n message:\n String(input['processing']?.message ?? '').slice(0, 160) ||\n 'Tap, insert or swipe your card on the reader.',\n }\n }\n return state\n}\n\n/**\n * The customer's answer as the DISPLAY may send it, checked against the\n * prompt it claims to answer. Returns null when it answers nothing the\n * register asked: a stale screen, a replay, or a mode that takes no answer.\n */\nexport function sanitizePosDisplayResponse(\n state: PosDisplayState | null | undefined,\n raw: unknown,\n nowMs: number,\n): PosDisplayResponse | null {\n if (!state || !state.promptId) return null\n const input = (raw && typeof raw === 'object' ? raw : {}) as Record<string, any>\n if (String(input['promptId'] ?? '') !== state.promptId) return null\n const response: PosDisplayResponse = { promptId: state.promptId, atMs: nowMs }\n if (state.mode === 'tip' && state.tip) {\n const choice = input['tipChoice']\n if (choice === 'none') {\n response.tipChoice = 'none'\n response.tipCents = 0\n } else if (choice === 'percent') {\n const percent = Math.round(Number(input['tipPercent']))\n if (!state.tip.percentages.includes(percent)) return null\n response.tipChoice = 'percent'\n response.tipPercent = percent\n response.tipCents = posTipFromPercent(state.tip.baseCents, percent)\n } else if (choice === 'custom' && state.tip.allowCustom) {\n const tipCents = Math.round(Number(input['tipCents']))\n if (posTipProblem(tipCents, state.tip.baseCents)) return null\n response.tipChoice = 'custom'\n response.tipCents = tipCents\n } else {\n return null\n }\n return response\n }\n if (state.mode === 'receipt' && state.receipt) {\n const channel = input['receiptChannel'] as PosReceiptChannel\n if (!state.receipt.channels.includes(channel)) return null\n response.receiptChannel = channel\n if (channel === 'email') {\n const email = posDisplayEmail(input['email'])\n if (!email) return null\n response.email = email\n if (state.receipt.offerMarketing && input['marketingOptIn'] === true) {\n response.marketingOptIn = true\n }\n }\n if (channel === 'sms') {\n const phone = posDisplayPhone(input['phone'])\n if (!phone) return null\n response.phone = phone\n }\n return response\n }\n return null\n}\n\n/**\n * The state as the DISPLAY reads it. The customer's own typed address is\n * never echoed back to the screen: once answered, the prompt is done and the\n * screen shows what comes next, so nothing a later customer could read off\n * an unattended tablet is ever sent to it.\n */\nexport function posDisplayPublicState(\n state: PosDisplayState | null | undefined,\n nowMs: number,\n): Omit<PosDisplayState, 'response'> & { answered: boolean } {\n if (!state) return { mode: 'idle', updatedAtMs: nowMs, answered: false }\n const { response, ...rest } = state\n if (rest.mode === 'thanks' && nowMs - rest.updatedAtMs > POS_DISPLAY_THANKS_MS) {\n return { mode: 'idle', updatedAtMs: rest.updatedAtMs, answered: false }\n }\n return {\n ...rest,\n answered: Boolean(response && response.promptId === rest.promptId),\n }\n}\n"],"names":["ORDER_PAYMENT_METHODS","POS_CARD_METHODS","Set","isCardPaymentMethod","method","has","wholeCents","value","number","Math","round","Number","isFinite","orderPayments","order","Array","isArray","payments","filter","payment","Boolean","id","totalCents","totals","amountCents","settled","status","atMs","createdAtMs","reservationId","checkoutSessionId","paymentIntentId","channel","posSettledCents","reduce","sum","posReservedCents","posBalanceDueCents","max","posTenderableCents","posTipCents","tipCents","posTakeShareCents","input","take","takeFeeCents","total","amount","min","posInvoiceTakeCents","netted","posFeeCollection","card","some","other","describeOrderPayment","dollars","cents","toFixed","label","creditLabel","ORDER_PAYMENT_METHOD_LABELS","cardBrand","last4","join","tip","cash","card_present","card_keyed","card_link","gift_card","folio","credit","POS_TIP_PERCENTAGES_DEFAULT","posTipPercentages","raw","parsed","String","split","map","part","unique","slice","length","posTipFromPercent","baseCents","percent","base","pct","posTipProblem","isInteger","POS_DISPLAY_MAX_LINES","POS_DISPLAY_PAIRING_TTL_MS","POS_DISPLAY_THANKS_MS","EMAIL_PATTERN","posDisplayEmail","email","trim","toLowerCase","test","posDisplayPhone","digits","replace","startsWith","sanitizePosDisplayState","nowMs","modes","mode","includes","state","updatedAtMs","promptId","cart","lines","line","name","variantLabel","quantity","itemsCents","discountCents","taxCents","paidCents","dueCents","percentages","allowCustom","receipt","allowed","channels","offerMarketing","processing","message","sanitizePosDisplayResponse","response","choice","tipChoice","tipPercent","receiptChannel","marketingOptIn","phone","posDisplayPublicState","answered","rest"],"mappings":"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"}
@@ -73,6 +73,13 @@ export interface StockHold {
73
73
  * cart reserve the wrong shelf.
74
74
  */
75
75
  units?: Record<string, number>;
76
+ /**
77
+ * The location the units are reserved AT (AGL-3624): a pickup or local
78
+ * delivery cart takes its stock off one location's bucket, so its hold
79
+ * spends that bucket. Absent for a shipped cart, whose hold spends only the
80
+ * variant's total.
81
+ */
82
+ locationId?: string;
76
83
  }
77
84
  /**
78
85
  * The product fields this file reads. Structural on purpose: `HostProduct`
@@ -143,7 +150,9 @@ export declare function pruneStockHolds(holds: Record<string, StockHold> | undef
143
150
  * same hold key, and without this exclusion it would be refused the unit it is
144
151
  * already holding.
145
152
  */
146
- export declare function heldVariantUnits(product: HeldStockProduct | undefined, variantId: string | undefined, nowMs: number, exceptHoldKey?: string): number;
153
+ export declare function heldVariantUnits(product: HeldStockProduct | undefined, variantId: string | undefined, nowMs: number, exceptHoldKey?: string,
154
+ /** Count only the holds taken at this location (AGL-3624). */
155
+ atLocationId?: string): number;
147
156
  /** Units held across every variant of a product — the console's figure. */
148
157
  export declare function heldProductUnits(product: HeldStockProduct | undefined, nowMs: number): number;
149
158
  /**
@@ -160,7 +169,17 @@ export declare function heldProductUnits(product: HeldStockProduct | undefined,
160
169
  * distinguish `null` from `0` here — and `0` is a legitimate, load-bearing
161
170
  * answer meaning "tracked, and none left". Every caller must test `== null`.
162
171
  */
163
- export declare function availableVariantUnits(product: HeldStockProduct | undefined, variantId: string | undefined, nowMs: number, exceptHoldKey?: string): number | null;
172
+ export declare function availableVariantUnits(product: HeldStockProduct | undefined, variantId: string | undefined, nowMs: number, exceptHoldKey?: string,
173
+ /**
174
+ * The location the buyer is collecting from or being delivered from
175
+ * (AGL-3624). With one, and a variant whose stock is split by location,
176
+ * the answer is the SMALLER of what that location's bucket has free and
177
+ * what the whole variant has free: a shipped cart's hold names no bucket,
178
+ * so it can only be charged against the total, and a location with ten on
179
+ * its shelf cannot sell ten when shipped checkouts already hold the other
180
+ * locations' every unit plus some of these.
181
+ */
182
+ locationId?: string): number | null;
164
183
  /**
165
184
  * Is there a hold to take at all?
166
185
  *
@@ -183,7 +202,9 @@ export declare function stockIsReservable(product: HeldStockProduct | undefined,
183
202
  * `canPurchase` is still the first test rather than re-derived arithmetic, so
184
203
  * the two cannot drift on the cases they share.
185
204
  */
186
- export declare function canReserveStock(product: HeldStockProduct | undefined, variantId: string | undefined, quantity?: number, nowMs?: number, exceptHoldKey?: string): boolean;
205
+ export declare function canReserveStock(product: HeldStockProduct | undefined, variantId: string | undefined, quantity?: number, nowMs?: number, exceptHoldKey?: string,
206
+ /** Reserve at this location's bucket (AGL-3624); see `availableVariantUnits`. */
207
+ locationId?: string): boolean;
187
208
  /**
188
209
  * What the console prints beside a stock count (AGL-2356).
189
210
  *