@aglyn/plugins-commerce 1.0.0-beta.285 → 1.0.0-beta.288

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 (430) hide show
  1. package/README.md +1 -1
  2. package/package.json +13 -13
  3. package/src/lib/components/cart.js +25 -12
  4. package/src/lib/components/cart.js.map +1 -1
  5. package/src/lib/components/commerce-console-page.js +70 -13
  6. package/src/lib/components/commerce-console-page.js.map +1 -1
  7. package/src/lib/components/console/order-detail-dialog.component.js +2 -1
  8. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  9. package/src/lib/components/console/order-webhooks-card.component.js +1 -1
  10. package/src/lib/components/console/order-webhooks-card.component.js.map +1 -1
  11. package/src/lib/components/console/payment-methods-card.component.js +1 -1
  12. package/src/lib/components/console/payment-methods-card.component.js.map +1 -1
  13. package/src/lib/components/console/pos/pos-cart-panel.component.d.ts +5 -0
  14. package/src/lib/components/console/pos/pos-cart-panel.component.js +18 -12
  15. package/src/lib/components/console/pos/pos-cart-panel.component.js.map +1 -1
  16. package/src/lib/components/console/pos/pos-hardware-api.d.ts +76 -5
  17. package/src/lib/components/console/pos/pos-hardware-api.js +53 -11
  18. package/src/lib/components/console/pos/pos-hardware-api.js.map +1 -1
  19. package/src/lib/components/console/pos/pos-hardware-buyer.component.d.ts +18 -0
  20. package/src/lib/components/console/pos/pos-hardware-buyer.component.js +629 -0
  21. package/src/lib/components/console/pos/pos-hardware-buyer.component.js.map +1 -0
  22. package/src/lib/components/console/pos/pos-hardware-sale-api.d.ts +162 -0
  23. package/src/lib/components/console/pos/pos-hardware-sale-api.js +122 -0
  24. package/src/lib/components/console/pos/pos-hardware-sale-api.js.map +1 -0
  25. package/src/lib/components/console/pos/pos-hardware-sale-terms.component.d.ts +39 -0
  26. package/src/lib/components/console/pos/pos-hardware-sale-terms.component.js +251 -0
  27. package/src/lib/components/console/pos/pos-hardware-sale-terms.component.js.map +1 -0
  28. package/src/lib/components/console/pos/use-pos-display.js +50 -0
  29. package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
  30. package/src/lib/components/console/pos-cellular-control.component.d.ts +41 -0
  31. package/src/lib/components/console/pos-cellular-control.component.js +211 -0
  32. package/src/lib/components/console/pos-cellular-control.component.js.map +1 -0
  33. package/src/lib/components/console/pos-devices-card.component.d.ts +9 -1
  34. package/src/lib/components/console/pos-devices-card.component.js +47 -15
  35. package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
  36. package/src/lib/components/console/pos-display/pos-display-api.d.ts +6 -0
  37. package/src/lib/components/console/pos-display/pos-display-api.js +8 -1
  38. package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -1
  39. package/src/lib/components/console/pos-display-page.component.d.ts +13 -0
  40. package/src/lib/components/console/pos-display-page.component.js +44 -2
  41. package/src/lib/components/console/pos-display-page.component.js.map +1 -1
  42. package/src/lib/components/console/pos-hardware-card.component.d.ts +12 -4
  43. package/src/lib/components/console/pos-hardware-card.component.js +157 -36
  44. package/src/lib/components/console/pos-hardware-card.component.js.map +1 -1
  45. package/src/lib/components/console/pos-hardware-orders-staff-page.component.js +360 -80
  46. package/src/lib/components/console/pos-hardware-orders-staff-page.component.js.map +1 -1
  47. package/src/lib/components/console/pos-hardware-terms-page.component.d.ts +34 -0
  48. package/src/lib/components/console/pos-hardware-terms-page.component.js +257 -0
  49. package/src/lib/components/console/pos-hardware-terms-page.component.js.map +1 -0
  50. package/src/lib/components/console/pos-hardware-terms-sign-page.component.d.ts +27 -0
  51. package/src/lib/components/console/pos-hardware-terms-sign-page.component.js +104 -0
  52. package/src/lib/components/console/pos-hardware-terms-sign-page.component.js.map +1 -0
  53. package/src/lib/components/console/pos-hardware-waiting-card.component.d.ts +24 -0
  54. package/src/lib/components/console/pos-hardware-waiting-card.component.js +205 -0
  55. package/src/lib/components/console/pos-hardware-waiting-card.component.js.map +1 -0
  56. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js +297 -201
  57. package/src/lib/components/console/pos-ops/pos-check-in-dialog.component.js.map +1 -1
  58. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.d.ts +3 -0
  59. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +260 -92
  60. package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -1
  61. package/src/lib/components/console/pos-ops/pos-operations-bar.component.d.ts +9 -0
  62. package/src/lib/components/console/pos-ops/pos-operations-bar.component.js +15 -6
  63. package/src/lib/components/console/pos-ops/pos-operations-bar.component.js.map +1 -1
  64. package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.d.ts +25 -0
  65. package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.js +136 -0
  66. package/src/lib/components/console/pos-ops/pos-rain-check-dialog.component.js.map +1 -0
  67. package/src/lib/components/console/pos-ops/pos-shift-panel.component.d.ts +6 -0
  68. package/src/lib/components/console/pos-ops/pos-shift-panel.component.js +48 -31
  69. package/src/lib/components/console/pos-ops/pos-shift-panel.component.js.map +1 -1
  70. package/src/lib/components/console/pos-page.component.d.ts +1 -1
  71. package/src/lib/components/console/pos-page.component.js +63 -12
  72. package/src/lib/components/console/pos-page.component.js.map +1 -1
  73. package/src/lib/components/console/pos-reader-splash-setting.component.js +1 -1
  74. package/src/lib/components/console/pos-reader-splash-setting.component.js.map +1 -1
  75. package/src/lib/components/console/product-check-in-fields.component.d.ts +4 -2
  76. package/src/lib/components/console/product-check-in-fields.component.js +60 -14
  77. package/src/lib/components/console/product-check-in-fields.component.js.map +1 -1
  78. package/src/lib/components/console/product-editor-dialog.component.js +44 -1
  79. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  80. package/src/lib/components/console/product-hardware-sku-field.component.d.ts +19 -0
  81. package/src/lib/components/console/product-hardware-sku-field.component.js +92 -0
  82. package/src/lib/components/console/product-hardware-sku-field.component.js.map +1 -0
  83. package/src/lib/components/console/product-kiosk-offer-field.component.d.ts +22 -0
  84. package/src/lib/components/console/product-kiosk-offer-field.component.js +94 -0
  85. package/src/lib/components/console/product-kiosk-offer-field.component.js.map +1 -0
  86. package/src/lib/components/console/product-register-fields.component.d.ts +4 -0
  87. package/src/lib/components/console/product-register-fields.component.js +31 -2
  88. package/src/lib/components/console/product-register-fields.component.js.map +1 -1
  89. package/src/lib/components/console/products-hub-card.component.js +11 -2
  90. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  91. package/src/lib/components/console/register-hardware-card.component.d.ts +11 -0
  92. package/src/lib/components/console/register-hardware-card.component.js +56 -27
  93. package/src/lib/components/console/register-hardware-card.component.js.map +1 -1
  94. package/src/lib/components/console/reservation-guests-dialog.component.d.ts +28 -0
  95. package/src/lib/components/console/reservation-guests-dialog.component.js +189 -0
  96. package/src/lib/components/console/reservation-guests-dialog.component.js.map +1 -0
  97. package/src/lib/components/console/reservations-card.component.d.ts +9 -0
  98. package/src/lib/components/console/reservations-card.component.js +158 -23
  99. package/src/lib/components/console/reservations-card.component.js.map +1 -1
  100. package/src/lib/components/console/shippingeasy-card.component.js +1 -1
  101. package/src/lib/components/console/shippingeasy-card.component.js.map +1 -1
  102. package/src/lib/components/console/shipstation-card.component.d.ts +1 -1
  103. package/src/lib/components/console/shipstation-card.component.js +3 -3
  104. package/src/lib/components/console/shipstation-card.component.js.map +1 -1
  105. package/src/lib/components/product-detail.d.ts +4 -0
  106. package/src/lib/components/product-detail.js +40 -5
  107. package/src/lib/components/product-detail.js.map +1 -1
  108. package/src/lib/components/product-reviews.js +28 -11
  109. package/src/lib/components/product-reviews.js.map +1 -1
  110. package/src/lib/components/reservation-widget.js +28 -5
  111. package/src/lib/components/reservation-widget.js.map +1 -1
  112. package/src/lib/components/use-day-picker.d.ts +25 -0
  113. package/src/lib/components/use-day-picker.js +57 -0
  114. package/src/lib/components/use-day-picker.js.map +1 -0
  115. package/src/lib/constants/operator-alerts.d.ts +55 -0
  116. package/src/lib/constants/operator-alerts.js +156 -1
  117. package/src/lib/constants/operator-alerts.js.map +1 -1
  118. package/src/lib/declarations.console-server.js +54 -1
  119. package/src/lib/declarations.console-server.js.map +1 -1
  120. package/src/lib/declarations.d.ts +4 -2
  121. package/src/lib/declarations.js +15 -4
  122. package/src/lib/declarations.js.map +1 -1
  123. package/src/lib/declarations.server.js +9 -0
  124. package/src/lib/declarations.server.js.map +1 -1
  125. package/src/lib/model/commerce-cart.d.ts +9 -1
  126. package/src/lib/model/commerce-cart.js +4 -3
  127. package/src/lib/model/commerce-cart.js.map +1 -1
  128. package/src/lib/model/commerce-check-in-product.d.ts +26 -0
  129. package/src/lib/model/commerce-check-in-product.js +64 -2
  130. package/src/lib/model/commerce-check-in-product.js.map +1 -1
  131. package/src/lib/model/commerce-check-in-record.d.ts +127 -0
  132. package/src/lib/model/commerce-check-in-record.js +36 -0
  133. package/src/lib/model/commerce-check-in-record.js.map +1 -0
  134. package/src/lib/model/commerce-check-in.d.ts +91 -87
  135. package/src/lib/model/commerce-check-in.js +186 -62
  136. package/src/lib/model/commerce-check-in.js.map +1 -1
  137. package/src/lib/model/commerce-kiosk-offer.d.ts +31 -0
  138. package/src/lib/model/commerce-kiosk-offer.js +35 -0
  139. package/src/lib/model/commerce-kiosk-offer.js.map +1 -0
  140. package/src/lib/model/commerce-orders.d.ts +76 -0
  141. package/src/lib/model/commerce-orders.js +6 -0
  142. package/src/lib/model/commerce-orders.js.map +1 -1
  143. package/src/lib/model/commerce-pos-day-report.d.ts +54 -1
  144. package/src/lib/model/commerce-pos-day-report.js +39 -2
  145. package/src/lib/model/commerce-pos-day-report.js.map +1 -1
  146. package/src/lib/model/commerce-pos-offline.d.ts +1 -1
  147. package/src/lib/model/commerce-pos-offline.js +6 -1
  148. package/src/lib/model/commerce-pos-offline.js.map +1 -1
  149. package/src/lib/model/commerce-pos-ops.js +3 -2
  150. package/src/lib/model/commerce-pos-ops.js.map +1 -1
  151. package/src/lib/model/commerce-pos.d.ts +36 -3
  152. package/src/lib/model/commerce-pos.js +14 -6
  153. package/src/lib/model/commerce-pos.js.map +1 -1
  154. package/src/lib/model/commerce-receipt.js +3 -2
  155. package/src/lib/model/commerce-receipt.js.map +1 -1
  156. package/src/lib/model/commerce-reservations.d.ts +69 -4
  157. package/src/lib/model/commerce-reservations.js +57 -5
  158. package/src/lib/model/commerce-reservations.js.map +1 -1
  159. package/src/lib/model/commerce.d.ts +66 -0
  160. package/src/lib/model/commerce.js +15 -0
  161. package/src/lib/model/commerce.js.map +1 -1
  162. package/src/lib/model/order-list-fields.d.ts +11 -0
  163. package/src/lib/model/order-list-fields.js +17 -1
  164. package/src/lib/model/order-list-fields.js.map +1 -1
  165. package/src/lib/model/own-store-tax-summary.d.ts +152 -0
  166. package/src/lib/model/own-store-tax-summary.js +288 -0
  167. package/src/lib/model/own-store-tax-summary.js.map +1 -0
  168. package/src/lib/model/plugin-permissions.d.ts +74 -0
  169. package/src/lib/model/plugin-permissions.js +224 -7
  170. package/src/lib/model/plugin-permissions.js.map +1 -1
  171. package/src/lib/model/pos-cellular.d.ts +223 -0
  172. package/src/lib/model/pos-cellular.js +293 -0
  173. package/src/lib/model/pos-cellular.js.map +1 -0
  174. package/src/lib/model/pos-hardware-in-person.d.ts +98 -0
  175. package/src/lib/model/pos-hardware-in-person.js +224 -0
  176. package/src/lib/model/pos-hardware-in-person.js.map +1 -0
  177. package/src/lib/model/pos-hardware-tax-summary.d.ts +166 -0
  178. package/src/lib/model/pos-hardware-tax-summary.js +225 -0
  179. package/src/lib/model/pos-hardware-tax-summary.js.map +1 -0
  180. package/src/lib/model/pos-hardware-terms.d.ts +74 -0
  181. package/src/lib/model/pos-hardware-terms.js +115 -0
  182. package/src/lib/model/pos-hardware-terms.js.map +1 -0
  183. package/src/lib/model/pos-hardware.d.ts +349 -7
  184. package/src/lib/model/pos-hardware.js +291 -20
  185. package/src/lib/model/pos-hardware.js.map +1 -1
  186. package/src/lib/model/product-write.d.ts +10 -0
  187. package/src/lib/model/product-write.js +4 -2
  188. package/src/lib/model/product-write.js.map +1 -1
  189. package/src/lib/model/workspace-site-permissions.d.ts +26 -0
  190. package/src/lib/model/workspace-site-permissions.js +81 -0
  191. package/src/lib/model/workspace-site-permissions.js.map +1 -0
  192. package/src/lib/plugin.js +16 -0
  193. package/src/lib/plugin.js.map +1 -1
  194. package/src/lib/server/billing-webhook.js +25 -1
  195. package/src/lib/server/billing-webhook.js.map +1 -1
  196. package/src/lib/server/cancel-order.js +7 -5
  197. package/src/lib/server/cancel-order.js.map +1 -1
  198. package/src/lib/server/cart-checkout.js +20 -8
  199. package/src/lib/server/cart-checkout.js.map +1 -1
  200. package/src/lib/server/cart.js +29 -0
  201. package/src/lib/server/cart.js.map +1 -1
  202. package/src/lib/server/checkout.js +42 -13
  203. package/src/lib/server/checkout.js.map +1 -1
  204. package/src/lib/server/collection-membership.d.ts +4 -0
  205. package/src/lib/server/collection-membership.js +5 -10
  206. package/src/lib/server/collection-membership.js.map +1 -1
  207. package/src/lib/server/commerce-site-keys.d.ts +48 -0
  208. package/src/lib/server/commerce-site-keys.js +61 -0
  209. package/src/lib/server/commerce-site-keys.js.map +1 -0
  210. package/src/lib/server/draft-order.js +8 -5
  211. package/src/lib/server/draft-order.js.map +1 -1
  212. package/src/lib/server/fulfill-order.js +7 -5
  213. package/src/lib/server/fulfill-order.js.map +1 -1
  214. package/src/lib/server/gift-cards.js +8 -5
  215. package/src/lib/server/gift-cards.js.map +1 -1
  216. package/src/lib/server/local-fulfillment-status.js +9 -6
  217. package/src/lib/server/local-fulfillment-status.js.map +1 -1
  218. package/src/lib/server/member-post.js +6 -5
  219. package/src/lib/server/member-post.js.map +1 -1
  220. package/src/lib/server/membership-admin-password.d.ts +4 -2
  221. package/src/lib/server/membership-admin-password.js +10 -8
  222. package/src/lib/server/membership-admin-password.js.map +1 -1
  223. package/src/lib/server/membership-admin-remove.d.ts +7 -5
  224. package/src/lib/server/membership-admin-remove.js +9 -9
  225. package/src/lib/server/membership-admin-remove.js.map +1 -1
  226. package/src/lib/server/order-notifications.js +5 -2
  227. package/src/lib/server/order-notifications.js.map +1 -1
  228. package/src/lib/server/order-print.js +7 -5
  229. package/src/lib/server/order-print.js.map +1 -1
  230. package/src/lib/server/order-receipt-send.js +8 -5
  231. package/src/lib/server/order-receipt-send.js.map +1 -1
  232. package/src/lib/server/order-webhooks.js +8 -8
  233. package/src/lib/server/order-webhooks.js.map +1 -1
  234. package/src/lib/server/orders-shipping-prepare.js +9 -6
  235. package/src/lib/server/orders-shipping-prepare.js.map +1 -1
  236. package/src/lib/server/own-store-tax-return-source.d.ts +47 -0
  237. package/src/lib/server/own-store-tax-return-source.js +312 -0
  238. package/src/lib/server/own-store-tax-return-source.js.map +1 -0
  239. package/src/lib/server/payment-methods.js +13 -9
  240. package/src/lib/server/payment-methods.js.map +1 -1
  241. package/src/lib/server/pos-auth.d.ts +37 -6
  242. package/src/lib/server/pos-auth.js +40 -15
  243. package/src/lib/server/pos-auth.js.map +1 -1
  244. package/src/lib/server/pos-cellular-billing.d.ts +81 -0
  245. package/src/lib/server/pos-cellular-billing.js +711 -0
  246. package/src/lib/server/pos-cellular-billing.js.map +1 -0
  247. package/src/lib/server/pos-cellular-notify.d.ts +47 -0
  248. package/src/lib/server/pos-cellular-notify.js +150 -0
  249. package/src/lib/server/pos-cellular-notify.js.map +1 -0
  250. package/src/lib/server/pos-cellular.d.ts +88 -0
  251. package/src/lib/server/pos-cellular.js +339 -0
  252. package/src/lib/server/pos-cellular.js.map +1 -0
  253. package/src/lib/server/pos-check-in.d.ts +5 -1
  254. package/src/lib/server/pos-check-in.js +296 -28
  255. package/src/lib/server/pos-check-in.js.map +1 -1
  256. package/src/lib/server/pos-day-report.js +29 -3
  257. package/src/lib/server/pos-day-report.js.map +1 -1
  258. package/src/lib/server/pos-display.d.ts +16 -0
  259. package/src/lib/server/pos-display.js +129 -2
  260. package/src/lib/server/pos-display.js.map +1 -1
  261. package/src/lib/server/pos-hardware-admin.d.ts +10 -1
  262. package/src/lib/server/pos-hardware-admin.js +250 -84
  263. package/src/lib/server/pos-hardware-admin.js.map +1 -1
  264. package/src/lib/server/pos-hardware-attach.d.ts +105 -0
  265. package/src/lib/server/pos-hardware-attach.js +443 -0
  266. package/src/lib/server/pos-hardware-attach.js.map +1 -0
  267. package/src/lib/server/pos-hardware-buyer-lookup.d.ts +58 -0
  268. package/src/lib/server/pos-hardware-buyer-lookup.js +180 -0
  269. package/src/lib/server/pos-hardware-buyer-lookup.js.map +1 -0
  270. package/src/lib/server/pos-hardware-buyer-record.d.ts +29 -0
  271. package/src/lib/server/pos-hardware-buyer-record.js +86 -0
  272. package/src/lib/server/pos-hardware-buyer-record.js.map +1 -0
  273. package/src/lib/server/pos-hardware-cancel.d.ts +58 -0
  274. package/src/lib/server/pos-hardware-cancel.js +147 -0
  275. package/src/lib/server/pos-hardware-cancel.js.map +1 -0
  276. package/src/lib/server/pos-hardware-closure-notices.d.ts +27 -0
  277. package/src/lib/server/pos-hardware-closure-notices.js +85 -0
  278. package/src/lib/server/pos-hardware-closure-notices.js.map +1 -0
  279. package/src/lib/server/pos-hardware-closure.d.ts +60 -0
  280. package/src/lib/server/pos-hardware-closure.js +153 -0
  281. package/src/lib/server/pos-hardware-closure.js.map +1 -0
  282. package/src/lib/server/pos-hardware-in-person.d.ts +61 -0
  283. package/src/lib/server/pos-hardware-in-person.js +236 -0
  284. package/src/lib/server/pos-hardware-in-person.js.map +1 -0
  285. package/src/lib/server/pos-hardware-notify.d.ts +40 -2
  286. package/src/lib/server/pos-hardware-notify.js +195 -14
  287. package/src/lib/server/pos-hardware-notify.js.map +1 -1
  288. package/src/lib/server/pos-hardware-readers.d.ts +115 -0
  289. package/src/lib/server/pos-hardware-readers.js +430 -0
  290. package/src/lib/server/pos-hardware-readers.js.map +1 -0
  291. package/src/lib/server/pos-hardware-returns.d.ts +116 -0
  292. package/src/lib/server/pos-hardware-returns.js +502 -0
  293. package/src/lib/server/pos-hardware-returns.js.map +1 -0
  294. package/src/lib/server/pos-hardware-sale-fields.d.ts +28 -0
  295. package/src/lib/server/pos-hardware-sale-fields.js +44 -0
  296. package/src/lib/server/pos-hardware-sale-fields.js.map +1 -0
  297. package/src/lib/server/pos-hardware-sale.d.ts +54 -0
  298. package/src/lib/server/pos-hardware-sale.js +369 -0
  299. package/src/lib/server/pos-hardware-sale.js.map +1 -0
  300. package/src/lib/server/pos-hardware-tax-return-source.d.ts +99 -0
  301. package/src/lib/server/pos-hardware-tax-return-source.js +462 -0
  302. package/src/lib/server/pos-hardware-tax-return-source.js.map +1 -0
  303. package/src/lib/server/pos-hardware-terms.d.ts +64 -0
  304. package/src/lib/server/pos-hardware-terms.js +154 -0
  305. package/src/lib/server/pos-hardware-terms.js.map +1 -0
  306. package/src/lib/server/pos-hardware-user-eraser.d.ts +3 -0
  307. package/src/lib/server/pos-hardware-user-eraser.js +31 -7
  308. package/src/lib/server/pos-hardware-user-eraser.js.map +1 -1
  309. package/src/lib/server/pos-hardware-webhook.d.ts +5 -0
  310. package/src/lib/server/pos-hardware-webhook.js +51 -6
  311. package/src/lib/server/pos-hardware-webhook.js.map +1 -1
  312. package/src/lib/server/pos-hardware.d.ts +74 -1
  313. package/src/lib/server/pos-hardware.js +175 -10
  314. package/src/lib/server/pos-hardware.js.map +1 -1
  315. package/src/lib/server/pos-kiosk.d.ts +2 -0
  316. package/src/lib/server/pos-kiosk.js +17 -12
  317. package/src/lib/server/pos-kiosk.js.map +1 -1
  318. package/src/lib/server/pos-offline-card-order.d.ts +64 -0
  319. package/src/lib/server/pos-offline-card-order.js +102 -0
  320. package/src/lib/server/pos-offline-card-order.js.map +1 -0
  321. package/src/lib/server/pos-offline-card-reconcile.d.ts +67 -1
  322. package/src/lib/server/pos-offline-card-reconcile.js +215 -18
  323. package/src/lib/server/pos-offline-card-reconcile.js.map +1 -1
  324. package/src/lib/server/pos-offline-sync.d.ts +39 -0
  325. package/src/lib/server/pos-offline-sync.js +190 -55
  326. package/src/lib/server/pos-offline-sync.js.map +1 -1
  327. package/src/lib/server/pos-ops-gate.d.ts +28 -9
  328. package/src/lib/server/pos-ops-gate.js +50 -19
  329. package/src/lib/server/pos-ops-gate.js.map +1 -1
  330. package/src/lib/server/pos-ops-routes.js +24 -0
  331. package/src/lib/server/pos-ops-routes.js.map +1 -1
  332. package/src/lib/server/pos-order-refund-ids.d.ts +28 -0
  333. package/src/lib/server/pos-order-refund-ids.js +31 -0
  334. package/src/lib/server/pos-order-refund-ids.js.map +1 -0
  335. package/src/lib/server/pos-order.js +52 -49
  336. package/src/lib/server/pos-order.js.map +1 -1
  337. package/src/lib/server/pos-payment.js +1 -1
  338. package/src/lib/server/pos-payment.js.map +1 -1
  339. package/src/lib/server/pos-reader-splash-admin.js +30 -32
  340. package/src/lib/server/pos-reader-splash-admin.js.map +1 -1
  341. package/src/lib/server/pos-reader-splash-images-route.d.ts +17 -0
  342. package/src/lib/server/pos-reader-splash-images-route.js +113 -0
  343. package/src/lib/server/pos-reader-splash-images-route.js.map +1 -0
  344. package/src/lib/server/pos-reader-splash-images.d.ts +51 -0
  345. package/src/lib/server/pos-reader-splash-images.js +123 -0
  346. package/src/lib/server/pos-reader-splash-images.js.map +1 -0
  347. package/src/lib/server/pos-reader-splash.d.ts +68 -1
  348. package/src/lib/server/pos-reader-splash.js +182 -11
  349. package/src/lib/server/pos-reader-splash.js.map +1 -1
  350. package/src/lib/server/pos-readers.d.ts +29 -0
  351. package/src/lib/server/pos-readers.js +134 -35
  352. package/src/lib/server/pos-readers.js.map +1 -1
  353. package/src/lib/server/pos-refund-authority.d.ts +3 -1
  354. package/src/lib/server/pos-refund-authority.js +3 -2
  355. package/src/lib/server/pos-refund-authority.js.map +1 -1
  356. package/src/lib/server/pos-return.d.ts +10 -0
  357. package/src/lib/server/pos-return.js +29 -7
  358. package/src/lib/server/pos-return.js.map +1 -1
  359. package/src/lib/server/pos-sale.js +6 -1
  360. package/src/lib/server/pos-sale.js.map +1 -1
  361. package/src/lib/server/pos-shift.js +11 -0
  362. package/src/lib/server/pos-shift.js.map +1 -1
  363. package/src/lib/server/pos-staff-pin.js +29 -24
  364. package/src/lib/server/pos-staff-pin.js.map +1 -1
  365. package/src/lib/server/pos-terminal.d.ts +14 -4
  366. package/src/lib/server/pos-terminal.js +34 -38
  367. package/src/lib/server/pos-terminal.js.map +1 -1
  368. package/src/lib/server/printers.d.ts +7 -3
  369. package/src/lib/server/printers.js +18 -12
  370. package/src/lib/server/printers.js.map +1 -1
  371. package/src/lib/server/product-drafts.d.ts +11 -1
  372. package/src/lib/server/product-drafts.js +12 -8
  373. package/src/lib/server/product-drafts.js.map +1 -1
  374. package/src/lib/server/product.d.ts +15 -1
  375. package/src/lib/server/product.js +16 -3
  376. package/src/lib/server/product.js.map +1 -1
  377. package/src/lib/server/products-write.d.ts +26 -2
  378. package/src/lib/server/products-write.js +19 -7
  379. package/src/lib/server/products-write.js.map +1 -1
  380. package/src/lib/server/refund.js +55 -42
  381. package/src/lib/server/refund.js.map +1 -1
  382. package/src/lib/server/reservation-availability-query.d.ts +8 -0
  383. package/src/lib/server/reservation-availability-query.js +11 -1
  384. package/src/lib/server/reservation-availability-query.js.map +1 -1
  385. package/src/lib/server/reservation-availability.d.ts +1 -1
  386. package/src/lib/server/reservation-availability.js +7 -2
  387. package/src/lib/server/reservation-availability.js.map +1 -1
  388. package/src/lib/server/reservation-guests.d.ts +32 -0
  389. package/src/lib/server/reservation-guests.js +285 -0
  390. package/src/lib/server/reservation-guests.js.map +1 -0
  391. package/src/lib/server/reservation-walk-in.d.ts +11 -1
  392. package/src/lib/server/reservation-walk-in.js +56 -12
  393. package/src/lib/server/reservation-walk-in.js.map +1 -1
  394. package/src/lib/server/reserve.js +18 -5
  395. package/src/lib/server/reserve.js.map +1 -1
  396. package/src/lib/server/returns.js +9 -6
  397. package/src/lib/server/returns.js.map +1 -1
  398. package/src/lib/server/reviews.d.ts +22 -2
  399. package/src/lib/server/reviews.js +88 -20
  400. package/src/lib/server/reviews.js.map +1 -1
  401. package/src/lib/server/shipping-connectors.js +10 -11
  402. package/src/lib/server/shipping-connectors.js.map +1 -1
  403. package/src/lib/server/site-page-resolver.js +20 -3
  404. package/src/lib/server/site-page-resolver.js.map +1 -1
  405. package/src/lib/server/store-day.d.ts +33 -0
  406. package/src/lib/server/store-day.js +46 -0
  407. package/src/lib/server/store-day.js.map +1 -0
  408. package/src/lib/server/store-setup-ai-writer.d.ts +2 -1
  409. package/src/lib/server/store-setup-ai-writer.js +5 -6
  410. package/src/lib/server/store-setup-ai-writer.js.map +1 -1
  411. package/src/lib/server/storefront-tax.d.ts +13 -0
  412. package/src/lib/server/storefront-tax.js +8 -1
  413. package/src/lib/server/storefront-tax.js.map +1 -1
  414. package/src/lib/server/tax-return-source.d.ts +32 -3
  415. package/src/lib/server/tax-return-source.js +99 -20
  416. package/src/lib/server/tax-return-source.js.map +1 -1
  417. package/src/lib/server.js +100 -2
  418. package/src/lib/server.js.map +1 -1
  419. package/src/lib/testing/fake-firestore.js +58 -16
  420. package/src/lib/testing/fake-firestore.js.map +1 -1
  421. package/src/lib/testing/pos-ops-memory-firestore.js +18 -4
  422. package/src/lib/testing/pos-ops-memory-firestore.js.map +1 -1
  423. package/src/lib/testing/site-register-permissions.d.ts +29 -0
  424. package/src/lib/testing/site-register-permissions.js +38 -0
  425. package/src/lib/testing/site-register-permissions.js.map +1 -0
  426. package/src/lib/transfer/tracking.server.js +8 -5
  427. package/src/lib/transfer/tracking.server.js.map +1 -1
  428. package/src/lib/utils/add-to-cart.d.ts +2 -0
  429. package/src/lib/utils/add-to-cart.js +6 -3
  430. package/src/lib/utils/add-to-cart.js.map +1 -1
@@ -15,7 +15,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
17
  */ import { zonedDayKey, zonedWallTimeToInstant } from "@aglyn/shared-util-timestamp/zoned-time";
18
- import { computePosShiftReport, posCsvCell, POS_TENDER_LABELS, POS_TENDER_METHODS } from "./commerce-pos-ops.js";
18
+ import { computePosShiftReport, posCsvCell, posOrderTenders, POS_STRIPE_TENDERS, POS_TENDER_LABELS, POS_TENDER_METHODS } from "./commerce-pos-ops.js";
19
19
  import { currencyMinorDigits } from "./order-fulfillment.js";
20
20
  /*==========================================
21
21
  * DAY AND EVENT TOTALS (AGL-4037): every register, every shift and every
@@ -32,7 +32,11 @@ import { currencyMinorDigits } from "./order-fulfillment.js";
32
32
  * - SALES: register orders (`channel: 'pos'`) rung in the range, by their
33
33
  * `createdAtMs`; online orders never count. A pending sale took nothing
34
34
  * and a canceled one gave it back, so neither counts, as on a Z report.
35
- * - REFUNDS: returns rung at a register in the range, whenever the sale was.
35
+ * - REFUNDS: returns rung at a register in the range, whenever the sale was,
36
+ * and every other refund of a register sale given in the range: from the
37
+ * console's order dialog (`refund.ts`) or in the Stripe Dashboard. Those
38
+ * are stored as {@link PosOrderRefund} records beside the register's
39
+ * returns, on the day they were given, and counted as stored.
36
40
  * - SHIFTS: every shift that opened or closed in the range, that a sale in
37
41
  * the range was rung in, or that is still open.
38
42
  * - THE DRAWER: the floats of the shifts that opened in the range, and the
@@ -102,6 +106,39 @@ function dayParts(key) {
102
106
  days
103
107
  };
104
108
  }
109
+ /** The collection under a register that holds its {@link PosOrderRefund}s. */ export const POS_ORDER_REFUNDS_COLLECTION = 'orderRefunds';
110
+ const REGISTER_ID = /^[A-Za-z0-9_-]{1,128}$/;
111
+ /**
112
+ * The record a refund of a register sale leaves for the day report, or null
113
+ * when the order is not a register sale (or names no register) or nothing was
114
+ * refunded. The tender is the Stripe payment the money went back to, as the
115
+ * order's tender ledger names it; a card when the ledger cannot say which.
116
+ */ export function posOrderRefundRecord(input) {
117
+ var _stripeTenders_find, _ref;
118
+ const { order } = input;
119
+ if (!order || order.channel !== 'pos') return null;
120
+ const registerId = typeof order.registerId === 'string' ? order.registerId : '';
121
+ if (!REGISTER_ID.test(registerId)) return null;
122
+ const refundedCents = whole(input.refundedCents);
123
+ if (!(refundedCents > 0)) return null;
124
+ const stripeTenders = posOrderTenders(order).filter((tender)=>POS_STRIPE_TENDERS.has(tender.method));
125
+ const tender = (_stripeTenders_find = stripeTenders.find((one)=>input.paymentIntentId && one.paymentIntentId === input.paymentIntentId)) != null ? _stripeTenders_find : stripeTenders[0];
126
+ return {
127
+ orderId: input.orderId,
128
+ registerId,
129
+ atMs: whole(input.atMs),
130
+ refundedCents,
131
+ tenders: [
132
+ {
133
+ method: (_ref = tender == null ? void 0 : tender.method) != null ? _ref : 'card_present',
134
+ amountCents: refundedCents
135
+ }
136
+ ],
137
+ cashierId: input.cashierId || null,
138
+ source: input.source,
139
+ stripeRefundId: input.stripeRefundId || null
140
+ };
141
+ }
105
142
  const whole = (value)=>{
106
143
  const number = Math.round(Number(value));
107
144
  return Number.isFinite(number) ? number : 0;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-pos-day-report.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 {\n zonedDayKey,\n zonedWallTimeToInstant,\n} from '@aglyn/shared-util-timestamp/zoned-time'\nimport {\n computePosShiftReport,\n posCsvCell,\n POS_TENDER_LABELS,\n POS_TENDER_METHODS,\n type PosCashEvent,\n type PosShift,\n type PosShiftRefund,\n type PosShiftReport,\n type PosShiftSale,\n type PosTenderMethod,\n} from './commerce-pos-ops'\nimport { currencyMinorDigits } from './order-fulfillment'\n\n/*==========================================\n * DAY AND EVENT TOTALS (AGL-4037): every register, every shift and every\n * cashier on one site, over one day or a run of days in the store's time zone.\n *\n * GET /api/commerce/pos-day-report?hostId=…&from=YYYY-MM-DD&to=YYYY-MM-DD[&format=csv]\n *\n * The figures are the shift report's own (`computePosShiftReport`), run over\n * the range instead of over one shift, and again per register and per\n * cashier, so a day's total and the X/Z reports of its shifts can never\n * disagree on what a gross sale, a tip or a cash sale is.\n *\n * What the range holds:\n * - SALES: register orders (`channel: 'pos'`) rung in the range, by their\n * `createdAtMs`; online orders never count. A pending sale took nothing\n * and a canceled one gave it back, so neither counts, as on a Z report.\n * - REFUNDS: returns rung at a register in the range, whenever the sale was.\n * - SHIFTS: every shift that opened or closed in the range, that a sale in\n * the range was rung in, or that is still open.\n * - THE DRAWER: the floats of the shifts that opened in the range, and the\n * paid in, paid out, safe drops and cash refunds recorded in it.\n *\n * Pure and client-safe: the route computes with it, the console renders and\n * exports with it, and the native register mirrors these types.\n *=========================================*/\n\n/** The route, as the console and the native register call it. */\nexport const POS_DAY_REPORT_ROUTE = '/api/commerce/pos-day-report'\n/** The longest range one report covers: a month of trading, or a long event. */\nexport const POS_DAY_REPORT_MAX_DAYS = 31\n/** The most sales one report reads; past it the report says it is partial. */\nexport const POS_DAY_REPORT_SALES_CEILING = 5000\n/** The most returns one report reads per register. */\nexport const POS_DAY_REPORT_RETURNS_CEILING = 2000\n/** The most shifts one report lists. */\nexport const POS_DAY_REPORT_SHIFTS_CEILING = 500\n\n/** The sales figures every total, register row and cashier row carries. */\nexport interface PosDayTotals {\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 refundCount: number\n refundsCents: number\n /** Gross sales less refunds. */\n netSalesCents: number\n /** Sales by tender, the shift report's keys; a tender with nothing is absent. */\n salesByTender: Partial<Record<PosTenderMethod, number>>\n}\n\n/** One tender's line: what it took, what went back to it, and the difference. */\nexport interface PosDayTenderRow {\n method: PosTenderMethod\n label: string\n salesCents: number\n refundsCents: number\n netCents: number\n}\n\nexport interface PosDayRegisterRow extends PosDayTotals {\n registerId: string\n /** The register's name, or its id when the register has since been removed. */\n name: string\n}\n\nexport interface PosDayCashierRow extends PosDayTotals {\n /** The member who rang the sales, or `''` for sales that name nobody. */\n memberId: string\n name: string\n}\n\n/** A shift in the range, as the shifts table lists it. */\nexport interface PosDayShiftRow {\n shiftId: string\n registerId: string\n registerName: string\n status: 'open' | 'closed'\n openedAtMs: number\n closedAtMs: number | null\n openedByName: string\n closedByName: string | null\n openingFloatCents: number\n /** The Z report's net sales, or the X report's so far while it is open. */\n netSalesCents: number\n /** The Z report's expected cash, or what the drawer should hold now while it is open. */\n expectedCashCents: number\n /** What the closing cashier counted; `null` while the shift is open. */\n countedCashCents: number | null\n /** counted − expected: positive is over, negative is short; `null` while open. */\n varianceCents: number | null\n}\n\n/** The cash drawers, summed over every register. */\nexport interface PosDayCash {\n /** The floats of the shifts that opened in the range. */\n openingFloatCents: number\n /** Cash taken by sales in the range (change handed back is not counted), plus cash tips. */\n cashSalesCents: number\n /** Returns paid out of a drawer in the range. */\n cashRefundsCents: number\n paidInCents: number\n paidOutCents: number\n dropsCents: number\n closedShiftCount: number\n openShiftCount: number\n /** What the closed shifts' drawers should have held when they were counted. */\n expectedCashCents: number\n /** What was counted in them. */\n countedCashCents: number\n /** counted − expected over the closed shifts: positive is over, negative is short. */\n varianceCents: number\n /** What the drawers still open should hold right now; not counted yet. */\n openExpectedCashCents: number\n}\n\n/** `GET /api/commerce/pos-day-report`'s answer. Every amount is integer minor units. */\nexport interface PosDayReport {\n hostId: string\n /** The site's name, as the printed report heads it. */\n storeName: string\n /** The store's currency, lower case ISO 4217 (`usd`). */\n currency: string\n /** The zone the days are counted in (the site's, else the workspace's, else UTC). */\n timeZone: string\n /** The first day, `YYYY-MM-DD`, inclusive. */\n from: string\n /** The last day, `YYYY-MM-DD`, inclusive. */\n to: string\n /** The instant the first day starts in `timeZone`. */\n startMs: number\n /** The instant after the last day ends in `timeZone` (exclusive). */\n endMs: number\n days: number\n generatedAtMs: number\n totals: PosDayTotals\n byTender: PosDayTenderRow[]\n byRegister: PosDayRegisterRow[]\n byCashier: PosDayCashierRow[]\n shifts: PosDayShiftRow[]\n cash: PosDayCash\n /** More sales, returns or shifts were in the range than one report reads. */\n truncated?: boolean\n}\n\n/*==========================================\n * THE RANGE\n *=========================================*/\n\nconst DAY_KEY = /^(\\d{4})-(\\d{2})-(\\d{2})$/\n\nfunction dayParts(key: string): { year: number; month: number; day: number } | null {\n const match = DAY_KEY.exec(key)\n if (!match) return null\n const year = Number(match[1])\n const month = Number(match[2])\n const day = Number(match[3])\n const check = new Date(Date.UTC(year, month - 1, day))\n if (\n check.getUTCFullYear() !== year ||\n check.getUTCMonth() !== month - 1 ||\n check.getUTCDate() !== day\n ) {\n return null\n }\n return { year, month, day }\n}\n\nexport type PosDayRange =\n | { ok: true; from: string; to: string; startMs: number; endMs: number; days: number }\n | { ok: false; error: string }\n\n/**\n * The days a report covers, as instants in the store's zone. No `from` is\n * today there; no `to` is the same day as `from`. Midnight is found on the\n * zone's own calendar, so a day a clock change falls in is 23 or 25 hours.\n */\nexport function posDayReportRange(input: {\n from?: unknown\n to?: unknown\n nowMs: number\n timeZone: string\n}): PosDayRange {\n const rawFrom = String(input.from ?? '').trim()\n const rawTo = String(input.to ?? '').trim()\n const from = rawFrom || zonedDayKey(input.nowMs, input.timeZone)\n const to = rawTo || from\n const start = dayParts(from)\n const end = dayParts(to)\n if (!start || !end) return { ok: false, error: 'Choose the days as YYYY-MM-DD.' }\n const days =\n Math.round(\n (Date.UTC(end.year, end.month - 1, end.day) - Date.UTC(start.year, start.month - 1, start.day)) /\n 86_400_000,\n ) + 1\n if (days < 1) return { ok: false, error: 'The last day comes before the first.' }\n if (days > POS_DAY_REPORT_MAX_DAYS) {\n return { ok: false, error: `Choose ${POS_DAY_REPORT_MAX_DAYS} days or fewer.` }\n }\n return {\n ok: true,\n from,\n to,\n startMs: zonedWallTimeToInstant(start, input.timeZone),\n endMs: zonedWallTimeToInstant({ ...end, day: end.day + 1 }, input.timeZone),\n days,\n }\n}\n\n/*==========================================\n * THE FIGURES\n *=========================================*/\n\n/** A register sale as the day report reads it. */\nexport interface PosDaySale extends PosShiftSale {\n registerId?: string\n cashierId?: string\n shiftId?: string\n}\n\n/** A register return as the day report reads it: its register and who rang it. */\nexport interface PosDayReturn extends PosShiftRefund {\n registerId: string\n cashierId?: string\n}\n\n/** A shift in the range, with the X report of an open one already read. */\nexport type PosDayShift = Pick<\n PosShift,\n | 'registerId'\n | 'status'\n | 'openedAtMs'\n | 'openedBy'\n | 'openedByName'\n | 'openingFloatCents'\n | 'cashEvents'\n | 'closedAtMs'\n | 'closedBy'\n | 'closedByName'\n | 'countedCashCents'\n | 'expectedCashCents'\n | 'varianceCents'\n | 'netSalesCents'\n | 'report'\n> & { id: string; liveReport?: PosShiftReport }\n\nexport interface PosDayReportInput {\n hostId: string\n storeName: string\n currency: string\n timeZone: string\n range: Extract<PosDayRange, { ok: true }>\n generatedAtMs: number\n sales: readonly PosDaySale[]\n returns: readonly PosDayReturn[]\n shifts: readonly PosDayShift[]\n /** Register id → name. */\n registerNames: Readonly<Record<string, string>>\n /** Member id → display name. */\n memberNames: Readonly<Record<string, string>>\n truncated?: boolean\n}\n\nconst whole = (value: unknown): number => {\n const number = Math.round(Number(value))\n return Number.isFinite(number) ? number : 0\n}\n\nfunction totalsOf(report: PosShiftReport): PosDayTotals {\n return {\n orderCount: report.orderCount,\n grossSalesCents: report.grossSalesCents,\n discountsCents: report.discountsCents,\n taxCents: report.taxCents,\n tipsCents: report.tipsCents,\n refundCount: report.refundCount,\n refundsCents: report.refundsCents,\n netSalesCents: report.netSalesCents,\n salesByTender: report.salesByTender,\n }\n}\n\nconst NO_DRAWER = { openingFloatCents: 0, cashEvents: [] as PosCashEvent[] }\n\n/** Sales figures alone, through the shift report's calculator. */\nconst salesFigures = (sales: readonly PosShiftSale[], refunds: readonly PosShiftRefund[]) =>\n totalsOf(computePosShiftReport({ shift: NO_DRAWER, sales, refunds }))\n\nfunction groupBy<T>(items: readonly T[], key: (item: T) => string): Map<string, T[]> {\n const groups = new Map<string, T[]>()\n for (const item of items) {\n const id = key(item)\n const group = groups.get(id)\n if (group) group.push(item)\n else groups.set(id, [item])\n }\n return groups\n}\n\n/** The day report from what the route read. */\nexport function computePosDayReport(input: PosDayReportInput): PosDayReport {\n const { startMs, endMs } = input.range\n const inRange = (atMs: unknown) => {\n const at = Number(atMs)\n return Number.isFinite(at) && at >= startMs && at < endMs\n }\n const shifts = [...input.shifts].sort((a, b) => a.openedAtMs - b.openedAtMs || a.id.localeCompare(b.id))\n // The drawer over the range: floats counted the day their shift opened,\n // and the cash moved while the range ran.\n const openingFloatCents = shifts\n .filter((shift) => inRange(shift.openedAtMs))\n .reduce((sum, shift) => sum + whole(shift.openingFloatCents), 0)\n const cashEvents = shifts.flatMap((shift) =>\n (shift.cashEvents ?? []).filter((event) => inRange(event?.atMs)),\n )\n const overall = computePosShiftReport({\n shift: { openingFloatCents, cashEvents },\n sales: input.sales,\n refunds: input.returns,\n })\n\n const byTender: PosDayTenderRow[] = POS_TENDER_METHODS.filter(\n (method) => (overall.salesByTender[method] ?? 0) > 0 || (overall.refundsByTender[method] ?? 0) > 0,\n ).map((method) => {\n const salesCents = overall.salesByTender[method] ?? 0\n const refundsCents = overall.refundsByTender[method] ?? 0\n return {\n method,\n label: POS_TENDER_LABELS[method],\n salesCents,\n refundsCents,\n netCents: salesCents - refundsCents,\n }\n })\n\n const registerName = (id: string) => input.registerNames[id] || id || 'Unknown register'\n const salesByRegister = groupBy(input.sales, (sale) => String(sale.registerId ?? ''))\n const returnsByRegister = groupBy(input.returns, (refund) => refund.registerId)\n const registerIds = new Set([...salesByRegister.keys(), ...returnsByRegister.keys()])\n const byRegister: PosDayRegisterRow[] = [...registerIds]\n .map((registerId) => ({\n registerId,\n name: registerName(registerId),\n ...salesFigures(salesByRegister.get(registerId) ?? [], returnsByRegister.get(registerId) ?? []),\n }))\n .filter((row) => row.orderCount > 0 || row.refundCount > 0)\n .sort((a, b) => a.name.localeCompare(b.name) || a.registerId.localeCompare(b.registerId))\n\n const memberName = (id: string) => (id ? input.memberNames[id] || 'Staff member' : 'No cashier recorded')\n const salesByCashier = groupBy(input.sales, (sale) => String(sale.cashierId ?? ''))\n const returnsByCashier = groupBy(input.returns, (refund) => String(refund.cashierId ?? ''))\n const cashierIds = new Set([...salesByCashier.keys(), ...returnsByCashier.keys()])\n const byCashier: PosDayCashierRow[] = [...cashierIds]\n .map((memberId) => ({\n memberId,\n name: memberName(memberId),\n ...salesFigures(salesByCashier.get(memberId) ?? [], returnsByCashier.get(memberId) ?? []),\n }))\n .filter((row) => row.orderCount > 0 || row.refundCount > 0)\n .sort((a, b) => b.netSalesCents - a.netSalesCents || a.name.localeCompare(b.name))\n\n const shiftRows: PosDayShiftRow[] = shifts.map((shift) => {\n const closed = shift.status === 'closed'\n const report = closed ? shift.report : shift.liveReport\n return {\n shiftId: shift.id,\n registerId: shift.registerId,\n registerName: registerName(shift.registerId),\n status: closed ? 'closed' : 'open',\n openedAtMs: whole(shift.openedAtMs),\n closedAtMs: closed && shift.closedAtMs != null ? whole(shift.closedAtMs) : null,\n openedByName: shift.openedByName || memberName(shift.openedBy),\n closedByName: closed ? shift.closedByName || (shift.closedBy ? memberName(shift.closedBy) : null) : null,\n openingFloatCents: whole(shift.openingFloatCents),\n netSalesCents: whole((closed ? shift.netSalesCents : null) ?? report?.netSalesCents),\n expectedCashCents: whole((closed ? shift.expectedCashCents : null) ?? report?.expectedCashCents),\n countedCashCents: closed && shift.countedCashCents != null ? whole(shift.countedCashCents) : null,\n varianceCents: closed && shift.varianceCents != null ? whole(shift.varianceCents) : null,\n }\n })\n const closedRows = shiftRows.filter((row) => row.status === 'closed')\n const openRows = shiftRows.filter((row) => row.status === 'open')\n const sum = (rows: PosDayShiftRow[], pick: (row: PosDayShiftRow) => number | null) =>\n rows.reduce((total, row) => total + (pick(row) ?? 0), 0)\n\n return {\n hostId: input.hostId,\n storeName: input.storeName,\n currency: input.currency,\n timeZone: input.timeZone,\n from: input.range.from,\n to: input.range.to,\n startMs,\n endMs,\n days: input.range.days,\n generatedAtMs: input.generatedAtMs,\n totals: totalsOf(overall),\n byTender,\n byRegister,\n byCashier,\n shifts: shiftRows,\n cash: {\n openingFloatCents: overall.openingFloatCents,\n cashSalesCents: overall.cashSalesCents,\n cashRefundsCents: overall.cashRefundsCents,\n paidInCents: overall.paidInCents,\n paidOutCents: overall.paidOutCents,\n dropsCents: overall.dropsCents,\n closedShiftCount: closedRows.length,\n openShiftCount: openRows.length,\n expectedCashCents: sum(closedRows, (row) => row.expectedCashCents),\n countedCashCents: sum(closedRows, (row) => row.countedCashCents),\n varianceCents: sum(closedRows, (row) => row.varianceCents),\n openExpectedCashCents: sum(openRows, (row) => row.expectedCashCents),\n },\n ...(input.truncated ? { truncated: true } : {}),\n }\n}\n\n/** Whether the range had nothing to report: no sale, no return, no shift. */\nexport function posDayReportIsEmpty(report: PosDayReport): boolean {\n return !report.totals.orderCount && !report.totals.refundCount && !report.shifts.length\n}\n\n/*==========================================\n * EXPORT\n *=========================================*/\n\n/** Minor units as a spreadsheet number in the store's currency: `12.34`, `1200` for yen. */\nexport function posDayAmount(cents: number | null | undefined, currency: string): string {\n if (cents == null) return ''\n const digits = currencyMinorDigits(currency)\n return (Math.round(cents) / 10 ** digits).toFixed(digits)\n}\n\n/** The range as a reader says it: `2026-10-24` or `2026-10-24 to 2026-10-26`. */\nexport function posDayRangeLabel(report: Pick<PosDayReport, 'from' | 'to'>): string {\n return report.from === report.to ? report.from : `${report.from} to ${report.to}`\n}\n\n/**\n * The report as one spreadsheet: a heading block, the sales by register and\n * cashier under the total, the tenders, the shifts and the drawer — each a\n * block of its own with its own header row, amounts in the store's currency.\n */\nexport function posDayReportCsv(report: PosDayReport): string {\n const money = (cents: number | null | undefined) => posDayAmount(cents, report.currency)\n const iso = (ms: number | null | undefined) => (ms ? new Date(ms).toISOString() : '')\n const salesRow = (section: string, name: string, row: PosDayTotals) => [\n section,\n name,\n row.orderCount,\n money(row.grossSalesCents),\n money(row.discountsCents),\n money(row.taxCents),\n money(row.tipsCents),\n row.refundCount,\n money(row.refundsCents),\n money(row.netSalesCents),\n ]\n const blocks: unknown[][][] = [\n [\n ['Report', 'Day totals'],\n ['Days', posDayRangeLabel(report)],\n ['Time zone', report.timeZone],\n ['Currency', report.currency.toUpperCase()],\n ...(report.truncated ? [['Note', 'More activity than one report reads; the figures are partial.']] : []),\n ],\n [\n ['Section', 'Name', 'Orders', 'Gross sales', 'Discounts', 'Tax', 'Tips', 'Refunds', 'Refunded', 'Net sales'],\n salesRow('Total', 'Every register', report.totals),\n ...report.byRegister.map((row) => salesRow('Register', row.name, row)),\n ...report.byCashier.map((row) => salesRow('Cashier', row.name, row)),\n ],\n [\n ['Tender', 'Sales', 'Refunds', 'Net'],\n ...report.byTender.map((row) => [row.label, money(row.salesCents), money(row.refundsCents), money(row.netCents)]),\n ],\n [\n [\n 'Shift',\n 'Register',\n 'Status',\n 'Opened',\n 'Opened by',\n 'Closed',\n 'Closed by',\n 'Starting cash',\n 'Net sales',\n 'Expected cash',\n 'Counted cash',\n 'Variance',\n ],\n ...report.shifts.map((row) => [\n row.shiftId,\n row.registerName,\n row.status === 'open' ? 'Open' : 'Closed',\n iso(row.openedAtMs),\n row.openedByName,\n iso(row.closedAtMs),\n row.closedByName ?? '',\n money(row.openingFloatCents),\n money(row.netSalesCents),\n money(row.expectedCashCents),\n money(row.countedCashCents),\n money(row.varianceCents),\n ]),\n ],\n [\n ['Cash drawers', 'Amount'],\n ['Starting cash', money(report.cash.openingFloatCents)],\n ['Cash sales', money(report.cash.cashSalesCents)],\n ['Paid in', money(report.cash.paidInCents)],\n ['Paid out', money(report.cash.paidOutCents)],\n ['Safe drops', money(report.cash.dropsCents)],\n ['Cash refunds', money(report.cash.cashRefundsCents)],\n ['Expected in closed drawers', money(report.cash.expectedCashCents)],\n ['Counted in closed drawers', money(report.cash.countedCashCents)],\n ['Variance', money(report.cash.varianceCents)],\n ['Expected in open drawers', money(report.cash.openExpectedCashCents)],\n ],\n ]\n return blocks\n .map((rows) => rows.map((row) => row.map(posCsvCell).join(',')).join('\\r\\n'))\n .join('\\r\\n\\r\\n')\n}\n\n/** The report's rows as the screen, the browser print and an 80 mm printer all read them. */\nexport function posDayReportSections(\n report: PosDayReport,\n money: (cents: number) => string,\n): Array<{ section: string; rows: Array<{ label: string; value: string; strong?: boolean }> }> {\n const t = report.totals\n const c = report.cash\n return [\n {\n section: 'Sales',\n rows: [\n { label: 'Orders', value: String(t.orderCount) },\n { label: 'Gross sales', value: money(t.grossSalesCents) },\n { label: 'Discounts', value: money(t.discountsCents) },\n { label: 'Tax', value: money(t.taxCents) },\n { label: 'Tips', value: money(t.tipsCents) },\n { label: `Refunds (${t.refundCount})`, value: money(-t.refundsCents) },\n { label: 'Net sales', value: money(t.netSalesCents), strong: true },\n ],\n },\n {\n section: 'By tender',\n rows: report.byTender.length\n ? report.byTender.map((row) => ({ label: row.label, value: money(row.netCents) }))\n : [{ label: 'No sales', value: '' }],\n },\n ...(report.byRegister.length\n ? [\n {\n section: 'By register',\n rows: report.byRegister.map((row) => ({ label: row.name, value: money(row.netSalesCents) })),\n },\n ]\n : []),\n ...(report.byCashier.length\n ? [\n {\n section: 'By cashier',\n rows: report.byCashier.map((row) => ({ label: row.name, value: money(row.netSalesCents) })),\n },\n ]\n : []),\n {\n section: 'Cash drawers',\n rows: [\n { label: 'Starting cash', value: money(c.openingFloatCents) },\n { label: 'Cash sales', value: money(c.cashSalesCents) },\n { label: 'Paid in', value: money(c.paidInCents) },\n { label: 'Paid out', value: money(-c.paidOutCents) },\n { label: 'Safe drops', value: money(-c.dropsCents) },\n { label: 'Cash refunds', value: money(-c.cashRefundsCents) },\n { label: `Expected (${c.closedShiftCount} closed)`, value: money(c.expectedCashCents) },\n { label: 'Counted', value: money(c.countedCashCents) },\n {\n label: c.varianceCents < 0 ? 'Short' : c.varianceCents > 0 ? 'Over' : 'Balanced',\n value: money(c.varianceCents),\n strong: true,\n },\n ...(c.openShiftCount\n ? [{ label: `Still open (${c.openShiftCount})`, value: money(c.openExpectedCashCents) }]\n : []),\n ],\n },\n ]\n}\n"],"names":["zonedDayKey","zonedWallTimeToInstant","computePosShiftReport","posCsvCell","POS_TENDER_LABELS","POS_TENDER_METHODS","currencyMinorDigits","POS_DAY_REPORT_ROUTE","POS_DAY_REPORT_MAX_DAYS","POS_DAY_REPORT_SALES_CEILING","POS_DAY_REPORT_RETURNS_CEILING","POS_DAY_REPORT_SHIFTS_CEILING","DAY_KEY","dayParts","key","match","exec","year","Number","month","day","check","Date","UTC","getUTCFullYear","getUTCMonth","getUTCDate","posDayReportRange","input","rawFrom","String","from","trim","rawTo","to","nowMs","timeZone","start","end","ok","error","days","Math","round","startMs","endMs","whole","value","number","isFinite","totalsOf","report","orderCount","grossSalesCents","discountsCents","taxCents","tipsCents","refundCount","refundsCents","netSalesCents","salesByTender","NO_DRAWER","openingFloatCents","cashEvents","salesFigures","sales","refunds","shift","groupBy","items","groups","Map","item","id","group","get","push","set","computePosDayReport","range","inRange","atMs","at","shifts","sort","a","b","openedAtMs","localeCompare","filter","reduce","sum","flatMap","event","overall","returns","byTender","method","refundsByTender","map","salesCents","label","netCents","registerName","registerNames","salesByRegister","sale","registerId","returnsByRegister","refund","registerIds","Set","keys","byRegister","name","row","memberName","memberNames","salesByCashier","cashierId","returnsByCashier","cashierIds","byCashier","memberId","shiftRows","closed","status","liveReport","shiftId","closedAtMs","openedByName","openedBy","closedByName","closedBy","expectedCashCents","countedCashCents","varianceCents","closedRows","openRows","rows","pick","total","hostId","storeName","currency","generatedAtMs","totals","cash","cashSalesCents","cashRefundsCents","paidInCents","paidOutCents","dropsCents","closedShiftCount","length","openShiftCount","openExpectedCashCents","truncated","posDayReportIsEmpty","posDayAmount","cents","digits","toFixed","posDayRangeLabel","posDayReportCsv","money","iso","ms","toISOString","salesRow","section","blocks","toUpperCase","join","posDayReportSections","t","c","strong"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-pos-day-report.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 {\n zonedDayKey,\n zonedWallTimeToInstant,\n} from '@aglyn/shared-util-timestamp/zoned-time'\nimport {\n computePosShiftReport,\n posCsvCell,\n posOrderTenders,\n POS_STRIPE_TENDERS,\n POS_TENDER_LABELS,\n POS_TENDER_METHODS,\n type PosCashEvent,\n type PosShift,\n type PosShiftRefund,\n type PosShiftReport,\n type PosShiftSale,\n type PosTenderMethod,\n type PosTenderSource,\n} from './commerce-pos-ops'\nimport { currencyMinorDigits } from './order-fulfillment'\n\n/*==========================================\n * DAY AND EVENT TOTALS (AGL-4037): every register, every shift and every\n * cashier on one site, over one day or a run of days in the store's time zone.\n *\n * GET /api/commerce/pos-day-report?hostId=…&from=YYYY-MM-DD&to=YYYY-MM-DD[&format=csv]\n *\n * The figures are the shift report's own (`computePosShiftReport`), run over\n * the range instead of over one shift, and again per register and per\n * cashier, so a day's total and the X/Z reports of its shifts can never\n * disagree on what a gross sale, a tip or a cash sale is.\n *\n * What the range holds:\n * - SALES: register orders (`channel: 'pos'`) rung in the range, by their\n * `createdAtMs`; online orders never count. A pending sale took nothing\n * and a canceled one gave it back, so neither counts, as on a Z report.\n * - REFUNDS: returns rung at a register in the range, whenever the sale was,\n * and every other refund of a register sale given in the range: from the\n * console's order dialog (`refund.ts`) or in the Stripe Dashboard. Those\n * are stored as {@link PosOrderRefund} records beside the register's\n * returns, on the day they were given, and counted as stored.\n * - SHIFTS: every shift that opened or closed in the range, that a sale in\n * the range was rung in, or that is still open.\n * - THE DRAWER: the floats of the shifts that opened in the range, and the\n * paid in, paid out, safe drops and cash refunds recorded in it.\n *\n * Pure and client-safe: the route computes with it, the console renders and\n * exports with it, and the native register mirrors these types.\n *=========================================*/\n\n/** The route, as the console and the native register call it. */\nexport const POS_DAY_REPORT_ROUTE = '/api/commerce/pos-day-report'\n/** The longest range one report covers: a month of trading, or a long event. */\nexport const POS_DAY_REPORT_MAX_DAYS = 31\n/** The most sales one report reads; past it the report says it is partial. */\nexport const POS_DAY_REPORT_SALES_CEILING = 5000\n/** The most returns one report reads per register. */\nexport const POS_DAY_REPORT_RETURNS_CEILING = 2000\n/** The most shifts one report lists. */\nexport const POS_DAY_REPORT_SHIFTS_CEILING = 500\n\n/** The sales figures every total, register row and cashier row carries. */\nexport interface PosDayTotals {\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 refundCount: number\n refundsCents: number\n /** Gross sales less refunds. */\n netSalesCents: number\n /** Sales by tender, the shift report's keys; a tender with nothing is absent. */\n salesByTender: Partial<Record<PosTenderMethod, number>>\n}\n\n/** One tender's line: what it took, what went back to it, and the difference. */\nexport interface PosDayTenderRow {\n method: PosTenderMethod\n label: string\n salesCents: number\n refundsCents: number\n netCents: number\n}\n\nexport interface PosDayRegisterRow extends PosDayTotals {\n registerId: string\n /** The register's name, or its id when the register has since been removed. */\n name: string\n}\n\nexport interface PosDayCashierRow extends PosDayTotals {\n /** The member who rang the sales, or `''` for sales that name nobody. */\n memberId: string\n name: string\n}\n\n/** A shift in the range, as the shifts table lists it. */\nexport interface PosDayShiftRow {\n shiftId: string\n registerId: string\n registerName: string\n status: 'open' | 'closed'\n openedAtMs: number\n closedAtMs: number | null\n openedByName: string\n closedByName: string | null\n openingFloatCents: number\n /** The Z report's net sales, or the X report's so far while it is open. */\n netSalesCents: number\n /** The Z report's expected cash, or what the drawer should hold now while it is open. */\n expectedCashCents: number\n /** What the closing cashier counted; `null` while the shift is open. */\n countedCashCents: number | null\n /** counted − expected: positive is over, negative is short; `null` while open. */\n varianceCents: number | null\n}\n\n/** The cash drawers, summed over every register. */\nexport interface PosDayCash {\n /** The floats of the shifts that opened in the range. */\n openingFloatCents: number\n /** Cash taken by sales in the range (change handed back is not counted), plus cash tips. */\n cashSalesCents: number\n /** Returns paid out of a drawer in the range. */\n cashRefundsCents: number\n paidInCents: number\n paidOutCents: number\n dropsCents: number\n closedShiftCount: number\n openShiftCount: number\n /** What the closed shifts' drawers should have held when they were counted. */\n expectedCashCents: number\n /** What was counted in them. */\n countedCashCents: number\n /** counted − expected over the closed shifts: positive is over, negative is short. */\n varianceCents: number\n /** What the drawers still open should hold right now; not counted yet. */\n openExpectedCashCents: number\n}\n\n/** `GET /api/commerce/pos-day-report`'s answer. Every amount is integer minor units. */\nexport interface PosDayReport {\n hostId: string\n /** The site's name, as the printed report heads it. */\n storeName: string\n /** The store's currency, lower case ISO 4217 (`usd`). */\n currency: string\n /** The zone the days are counted in (the site's, else the workspace's, else UTC). */\n timeZone: string\n /** The first day, `YYYY-MM-DD`, inclusive. */\n from: string\n /** The last day, `YYYY-MM-DD`, inclusive. */\n to: string\n /** The instant the first day starts in `timeZone`. */\n startMs: number\n /** The instant after the last day ends in `timeZone` (exclusive). */\n endMs: number\n days: number\n generatedAtMs: number\n totals: PosDayTotals\n byTender: PosDayTenderRow[]\n byRegister: PosDayRegisterRow[]\n byCashier: PosDayCashierRow[]\n shifts: PosDayShiftRow[]\n cash: PosDayCash\n /** More sales, returns or shifts were in the range than one report reads. */\n truncated?: boolean\n}\n\n/*==========================================\n * THE RANGE\n *=========================================*/\n\nconst DAY_KEY = /^(\\d{4})-(\\d{2})-(\\d{2})$/\n\nfunction dayParts(key: string): { year: number; month: number; day: number } | null {\n const match = DAY_KEY.exec(key)\n if (!match) return null\n const year = Number(match[1])\n const month = Number(match[2])\n const day = Number(match[3])\n const check = new Date(Date.UTC(year, month - 1, day))\n if (\n check.getUTCFullYear() !== year ||\n check.getUTCMonth() !== month - 1 ||\n check.getUTCDate() !== day\n ) {\n return null\n }\n return { year, month, day }\n}\n\nexport type PosDayRange =\n | { ok: true; from: string; to: string; startMs: number; endMs: number; days: number }\n | { ok: false; error: string }\n\n/**\n * The days a report covers, as instants in the store's zone. No `from` is\n * today there; no `to` is the same day as `from`. Midnight is found on the\n * zone's own calendar, so a day a clock change falls in is 23 or 25 hours.\n */\nexport function posDayReportRange(input: {\n from?: unknown\n to?: unknown\n nowMs: number\n timeZone: string\n}): PosDayRange {\n const rawFrom = String(input.from ?? '').trim()\n const rawTo = String(input.to ?? '').trim()\n const from = rawFrom || zonedDayKey(input.nowMs, input.timeZone)\n const to = rawTo || from\n const start = dayParts(from)\n const end = dayParts(to)\n if (!start || !end) return { ok: false, error: 'Choose the days as YYYY-MM-DD.' }\n const days =\n Math.round(\n (Date.UTC(end.year, end.month - 1, end.day) - Date.UTC(start.year, start.month - 1, start.day)) /\n 86_400_000,\n ) + 1\n if (days < 1) return { ok: false, error: 'The last day comes before the first.' }\n if (days > POS_DAY_REPORT_MAX_DAYS) {\n return { ok: false, error: `Choose ${POS_DAY_REPORT_MAX_DAYS} days or fewer.` }\n }\n return {\n ok: true,\n from,\n to,\n startMs: zonedWallTimeToInstant(start, input.timeZone),\n endMs: zonedWallTimeToInstant({ ...end, day: end.day + 1 }, input.timeZone),\n days,\n }\n}\n\n/*==========================================\n * THE FIGURES\n *=========================================*/\n\n/** A register sale as the day report reads it. */\nexport interface PosDaySale extends PosShiftSale {\n registerId?: string\n cashierId?: string\n shiftId?: string\n}\n\n/** A register return as the day report reads it: its register and who rang it. */\nexport interface PosDayReturn extends PosShiftRefund {\n registerId: string\n cashierId?: string\n}\n\n/**\n * A refund of a register sale given AWAY from the register (AGL-4037): from\n * the console's order dialog (`refund.ts`) or in the Stripe Dashboard. Stored\n * at `hosts/{hostId}/registers/{registerId}/orderRefunds/{id}` (the register\n * the sale was rung at), written by the server in the same transaction that\n * records the refund on the order, and counted by the day report on the day\n * it was given, in the store's time zone.\n *\n * Not a return: nothing came back over the counter and no drawer moved, so no\n * shift's X or Z report reads it. Only Stripe tenders are refunded this way\n * (a register's cash and gift cards go back at the register), so its tender\n * is always a card.\n */\nexport interface PosOrderRefund {\n orderId: string\n registerId: string\n /** When it was given. */\n atMs: number\n refundedCents: number\n tenders: Array<{ method: PosTenderMethod; amountCents: number }>\n /** The member who gave it; null for a refund made in Stripe. */\n cashierId: string | null\n /**\n * Where it was given: the order dialog, Stripe, or the platform's staff\n * refunding a card reader sold in person at its own store (AGL-4038).\n */\n source: 'order_dialog' | 'stripe' | 'staff'\n /** Stripe's refund id when it named one. */\n stripeRefundId: string | null\n}\n\n/** The collection under a register that holds its {@link PosOrderRefund}s. */\nexport const POS_ORDER_REFUNDS_COLLECTION = 'orderRefunds'\n\nconst REGISTER_ID = /^[A-Za-z0-9_-]{1,128}$/\n\n/**\n * The record a refund of a register sale leaves for the day report, or null\n * when the order is not a register sale (or names no register) or nothing was\n * refunded. The tender is the Stripe payment the money went back to, as the\n * order's tender ledger names it; a card when the ledger cannot say which.\n */\nexport function posOrderRefundRecord(input: {\n orderId: string\n order: (PosTenderSource & { registerId?: unknown }) | null | undefined\n refundedCents: number\n paymentIntentId: string | null | undefined\n cashierId: string | null\n source: PosOrderRefund['source']\n stripeRefundId?: string | null\n atMs: number\n}): PosOrderRefund | null {\n const { order } = input\n if (!order || order.channel !== 'pos') return null\n const registerId = typeof order.registerId === 'string' ? order.registerId : ''\n if (!REGISTER_ID.test(registerId)) return null\n const refundedCents = whole(input.refundedCents)\n if (!(refundedCents > 0)) return null\n const stripeTenders = posOrderTenders(order).filter((tender) => POS_STRIPE_TENDERS.has(tender.method))\n const tender =\n stripeTenders.find((one) => input.paymentIntentId && one.paymentIntentId === input.paymentIntentId) ??\n stripeTenders[0]\n return {\n orderId: input.orderId,\n registerId,\n atMs: whole(input.atMs),\n refundedCents,\n tenders: [{ method: tender?.method ?? 'card_present', amountCents: refundedCents }],\n cashierId: input.cashierId || null,\n source: input.source,\n stripeRefundId: input.stripeRefundId || null,\n }\n}\n\n/** A shift in the range, with the X report of an open one already read. */\nexport type PosDayShift = Pick<\n PosShift,\n | 'registerId'\n | 'status'\n | 'openedAtMs'\n | 'openedBy'\n | 'openedByName'\n | 'openingFloatCents'\n | 'cashEvents'\n | 'closedAtMs'\n | 'closedBy'\n | 'closedByName'\n | 'countedCashCents'\n | 'expectedCashCents'\n | 'varianceCents'\n | 'netSalesCents'\n | 'report'\n> & { id: string; liveReport?: PosShiftReport }\n\nexport interface PosDayReportInput {\n hostId: string\n storeName: string\n currency: string\n timeZone: string\n range: Extract<PosDayRange, { ok: true }>\n generatedAtMs: number\n sales: readonly PosDaySale[]\n returns: readonly PosDayReturn[]\n shifts: readonly PosDayShift[]\n /** Register id → name. */\n registerNames: Readonly<Record<string, string>>\n /** Member id → display name. */\n memberNames: Readonly<Record<string, string>>\n truncated?: boolean\n}\n\nconst whole = (value: unknown): number => {\n const number = Math.round(Number(value))\n return Number.isFinite(number) ? number : 0\n}\n\nfunction totalsOf(report: PosShiftReport): PosDayTotals {\n return {\n orderCount: report.orderCount,\n grossSalesCents: report.grossSalesCents,\n discountsCents: report.discountsCents,\n taxCents: report.taxCents,\n tipsCents: report.tipsCents,\n refundCount: report.refundCount,\n refundsCents: report.refundsCents,\n netSalesCents: report.netSalesCents,\n salesByTender: report.salesByTender,\n }\n}\n\nconst NO_DRAWER = { openingFloatCents: 0, cashEvents: [] as PosCashEvent[] }\n\n/** Sales figures alone, through the shift report's calculator. */\nconst salesFigures = (sales: readonly PosShiftSale[], refunds: readonly PosShiftRefund[]) =>\n totalsOf(computePosShiftReport({ shift: NO_DRAWER, sales, refunds }))\n\nfunction groupBy<T>(items: readonly T[], key: (item: T) => string): Map<string, T[]> {\n const groups = new Map<string, T[]>()\n for (const item of items) {\n const id = key(item)\n const group = groups.get(id)\n if (group) group.push(item)\n else groups.set(id, [item])\n }\n return groups\n}\n\n/** The day report from what the route read. */\nexport function computePosDayReport(input: PosDayReportInput): PosDayReport {\n const { startMs, endMs } = input.range\n const inRange = (atMs: unknown) => {\n const at = Number(atMs)\n return Number.isFinite(at) && at >= startMs && at < endMs\n }\n const shifts = [...input.shifts].sort((a, b) => a.openedAtMs - b.openedAtMs || a.id.localeCompare(b.id))\n // The drawer over the range: floats counted the day their shift opened,\n // and the cash moved while the range ran.\n const openingFloatCents = shifts\n .filter((shift) => inRange(shift.openedAtMs))\n .reduce((sum, shift) => sum + whole(shift.openingFloatCents), 0)\n const cashEvents = shifts.flatMap((shift) =>\n (shift.cashEvents ?? []).filter((event) => inRange(event?.atMs)),\n )\n const overall = computePosShiftReport({\n shift: { openingFloatCents, cashEvents },\n sales: input.sales,\n refunds: input.returns,\n })\n\n const byTender: PosDayTenderRow[] = POS_TENDER_METHODS.filter(\n (method) => (overall.salesByTender[method] ?? 0) > 0 || (overall.refundsByTender[method] ?? 0) > 0,\n ).map((method) => {\n const salesCents = overall.salesByTender[method] ?? 0\n const refundsCents = overall.refundsByTender[method] ?? 0\n return {\n method,\n label: POS_TENDER_LABELS[method],\n salesCents,\n refundsCents,\n netCents: salesCents - refundsCents,\n }\n })\n\n const registerName = (id: string) => input.registerNames[id] || id || 'Unknown register'\n const salesByRegister = groupBy(input.sales, (sale) => String(sale.registerId ?? ''))\n const returnsByRegister = groupBy(input.returns, (refund) => refund.registerId)\n const registerIds = new Set([...salesByRegister.keys(), ...returnsByRegister.keys()])\n const byRegister: PosDayRegisterRow[] = [...registerIds]\n .map((registerId) => ({\n registerId,\n name: registerName(registerId),\n ...salesFigures(salesByRegister.get(registerId) ?? [], returnsByRegister.get(registerId) ?? []),\n }))\n .filter((row) => row.orderCount > 0 || row.refundCount > 0)\n .sort((a, b) => a.name.localeCompare(b.name) || a.registerId.localeCompare(b.registerId))\n\n const memberName = (id: string) => (id ? input.memberNames[id] || 'Staff member' : 'No cashier recorded')\n const salesByCashier = groupBy(input.sales, (sale) => String(sale.cashierId ?? ''))\n const returnsByCashier = groupBy(input.returns, (refund) => String(refund.cashierId ?? ''))\n const cashierIds = new Set([...salesByCashier.keys(), ...returnsByCashier.keys()])\n const byCashier: PosDayCashierRow[] = [...cashierIds]\n .map((memberId) => ({\n memberId,\n name: memberName(memberId),\n ...salesFigures(salesByCashier.get(memberId) ?? [], returnsByCashier.get(memberId) ?? []),\n }))\n .filter((row) => row.orderCount > 0 || row.refundCount > 0)\n .sort((a, b) => b.netSalesCents - a.netSalesCents || a.name.localeCompare(b.name))\n\n const shiftRows: PosDayShiftRow[] = shifts.map((shift) => {\n const closed = shift.status === 'closed'\n const report = closed ? shift.report : shift.liveReport\n return {\n shiftId: shift.id,\n registerId: shift.registerId,\n registerName: registerName(shift.registerId),\n status: closed ? 'closed' : 'open',\n openedAtMs: whole(shift.openedAtMs),\n closedAtMs: closed && shift.closedAtMs != null ? whole(shift.closedAtMs) : null,\n openedByName: shift.openedByName || memberName(shift.openedBy),\n closedByName: closed ? shift.closedByName || (shift.closedBy ? memberName(shift.closedBy) : null) : null,\n openingFloatCents: whole(shift.openingFloatCents),\n netSalesCents: whole((closed ? shift.netSalesCents : null) ?? report?.netSalesCents),\n expectedCashCents: whole((closed ? shift.expectedCashCents : null) ?? report?.expectedCashCents),\n countedCashCents: closed && shift.countedCashCents != null ? whole(shift.countedCashCents) : null,\n varianceCents: closed && shift.varianceCents != null ? whole(shift.varianceCents) : null,\n }\n })\n const closedRows = shiftRows.filter((row) => row.status === 'closed')\n const openRows = shiftRows.filter((row) => row.status === 'open')\n const sum = (rows: PosDayShiftRow[], pick: (row: PosDayShiftRow) => number | null) =>\n rows.reduce((total, row) => total + (pick(row) ?? 0), 0)\n\n return {\n hostId: input.hostId,\n storeName: input.storeName,\n currency: input.currency,\n timeZone: input.timeZone,\n from: input.range.from,\n to: input.range.to,\n startMs,\n endMs,\n days: input.range.days,\n generatedAtMs: input.generatedAtMs,\n totals: totalsOf(overall),\n byTender,\n byRegister,\n byCashier,\n shifts: shiftRows,\n cash: {\n openingFloatCents: overall.openingFloatCents,\n cashSalesCents: overall.cashSalesCents,\n cashRefundsCents: overall.cashRefundsCents,\n paidInCents: overall.paidInCents,\n paidOutCents: overall.paidOutCents,\n dropsCents: overall.dropsCents,\n closedShiftCount: closedRows.length,\n openShiftCount: openRows.length,\n expectedCashCents: sum(closedRows, (row) => row.expectedCashCents),\n countedCashCents: sum(closedRows, (row) => row.countedCashCents),\n varianceCents: sum(closedRows, (row) => row.varianceCents),\n openExpectedCashCents: sum(openRows, (row) => row.expectedCashCents),\n },\n ...(input.truncated ? { truncated: true } : {}),\n }\n}\n\n/** Whether the range had nothing to report: no sale, no return, no shift. */\nexport function posDayReportIsEmpty(report: PosDayReport): boolean {\n return !report.totals.orderCount && !report.totals.refundCount && !report.shifts.length\n}\n\n/*==========================================\n * EXPORT\n *=========================================*/\n\n/** Minor units as a spreadsheet number in the store's currency: `12.34`, `1200` for yen. */\nexport function posDayAmount(cents: number | null | undefined, currency: string): string {\n if (cents == null) return ''\n const digits = currencyMinorDigits(currency)\n return (Math.round(cents) / 10 ** digits).toFixed(digits)\n}\n\n/** The range as a reader says it: `2026-10-24` or `2026-10-24 to 2026-10-26`. */\nexport function posDayRangeLabel(report: Pick<PosDayReport, 'from' | 'to'>): string {\n return report.from === report.to ? report.from : `${report.from} to ${report.to}`\n}\n\n/**\n * The report as one spreadsheet: a heading block, the sales by register and\n * cashier under the total, the tenders, the shifts and the drawer — each a\n * block of its own with its own header row, amounts in the store's currency.\n */\nexport function posDayReportCsv(report: PosDayReport): string {\n const money = (cents: number | null | undefined) => posDayAmount(cents, report.currency)\n const iso = (ms: number | null | undefined) => (ms ? new Date(ms).toISOString() : '')\n const salesRow = (section: string, name: string, row: PosDayTotals) => [\n section,\n name,\n row.orderCount,\n money(row.grossSalesCents),\n money(row.discountsCents),\n money(row.taxCents),\n money(row.tipsCents),\n row.refundCount,\n money(row.refundsCents),\n money(row.netSalesCents),\n ]\n const blocks: unknown[][][] = [\n [\n ['Report', 'Day totals'],\n ['Days', posDayRangeLabel(report)],\n ['Time zone', report.timeZone],\n ['Currency', report.currency.toUpperCase()],\n ...(report.truncated ? [['Note', 'More activity than one report reads; the figures are partial.']] : []),\n ],\n [\n ['Section', 'Name', 'Orders', 'Gross sales', 'Discounts', 'Tax', 'Tips', 'Refunds', 'Refunded', 'Net sales'],\n salesRow('Total', 'Every register', report.totals),\n ...report.byRegister.map((row) => salesRow('Register', row.name, row)),\n ...report.byCashier.map((row) => salesRow('Cashier', row.name, row)),\n ],\n [\n ['Tender', 'Sales', 'Refunds', 'Net'],\n ...report.byTender.map((row) => [row.label, money(row.salesCents), money(row.refundsCents), money(row.netCents)]),\n ],\n [\n [\n 'Shift',\n 'Register',\n 'Status',\n 'Opened',\n 'Opened by',\n 'Closed',\n 'Closed by',\n 'Starting cash',\n 'Net sales',\n 'Expected cash',\n 'Counted cash',\n 'Variance',\n ],\n ...report.shifts.map((row) => [\n row.shiftId,\n row.registerName,\n row.status === 'open' ? 'Open' : 'Closed',\n iso(row.openedAtMs),\n row.openedByName,\n iso(row.closedAtMs),\n row.closedByName ?? '',\n money(row.openingFloatCents),\n money(row.netSalesCents),\n money(row.expectedCashCents),\n money(row.countedCashCents),\n money(row.varianceCents),\n ]),\n ],\n [\n ['Cash drawers', 'Amount'],\n ['Starting cash', money(report.cash.openingFloatCents)],\n ['Cash sales', money(report.cash.cashSalesCents)],\n ['Paid in', money(report.cash.paidInCents)],\n ['Paid out', money(report.cash.paidOutCents)],\n ['Safe drops', money(report.cash.dropsCents)],\n ['Cash refunds', money(report.cash.cashRefundsCents)],\n ['Expected in closed drawers', money(report.cash.expectedCashCents)],\n ['Counted in closed drawers', money(report.cash.countedCashCents)],\n ['Variance', money(report.cash.varianceCents)],\n ['Expected in open drawers', money(report.cash.openExpectedCashCents)],\n ],\n ]\n return blocks\n .map((rows) => rows.map((row) => row.map(posCsvCell).join(',')).join('\\r\\n'))\n .join('\\r\\n\\r\\n')\n}\n\n/** The report's rows as the screen, the browser print and an 80 mm printer all read them. */\nexport function posDayReportSections(\n report: PosDayReport,\n money: (cents: number) => string,\n): Array<{ section: string; rows: Array<{ label: string; value: string; strong?: boolean }> }> {\n const t = report.totals\n const c = report.cash\n return [\n {\n section: 'Sales',\n rows: [\n { label: 'Orders', value: String(t.orderCount) },\n { label: 'Gross sales', value: money(t.grossSalesCents) },\n { label: 'Discounts', value: money(t.discountsCents) },\n { label: 'Tax', value: money(t.taxCents) },\n { label: 'Tips', value: money(t.tipsCents) },\n { label: `Refunds (${t.refundCount})`, value: money(-t.refundsCents) },\n { label: 'Net sales', value: money(t.netSalesCents), strong: true },\n ],\n },\n {\n section: 'By tender',\n rows: report.byTender.length\n ? report.byTender.map((row) => ({ label: row.label, value: money(row.netCents) }))\n : [{ label: 'No sales', value: '' }],\n },\n ...(report.byRegister.length\n ? [\n {\n section: 'By register',\n rows: report.byRegister.map((row) => ({ label: row.name, value: money(row.netSalesCents) })),\n },\n ]\n : []),\n ...(report.byCashier.length\n ? [\n {\n section: 'By cashier',\n rows: report.byCashier.map((row) => ({ label: row.name, value: money(row.netSalesCents) })),\n },\n ]\n : []),\n {\n section: 'Cash drawers',\n rows: [\n { label: 'Starting cash', value: money(c.openingFloatCents) },\n { label: 'Cash sales', value: money(c.cashSalesCents) },\n { label: 'Paid in', value: money(c.paidInCents) },\n { label: 'Paid out', value: money(-c.paidOutCents) },\n { label: 'Safe drops', value: money(-c.dropsCents) },\n { label: 'Cash refunds', value: money(-c.cashRefundsCents) },\n { label: `Expected (${c.closedShiftCount} closed)`, value: money(c.expectedCashCents) },\n { label: 'Counted', value: money(c.countedCashCents) },\n {\n label: c.varianceCents < 0 ? 'Short' : c.varianceCents > 0 ? 'Over' : 'Balanced',\n value: money(c.varianceCents),\n strong: true,\n },\n ...(c.openShiftCount\n ? [{ label: `Still open (${c.openShiftCount})`, value: money(c.openExpectedCashCents) }]\n : []),\n ],\n },\n ]\n}\n"],"names":["zonedDayKey","zonedWallTimeToInstant","computePosShiftReport","posCsvCell","posOrderTenders","POS_STRIPE_TENDERS","POS_TENDER_LABELS","POS_TENDER_METHODS","currencyMinorDigits","POS_DAY_REPORT_ROUTE","POS_DAY_REPORT_MAX_DAYS","POS_DAY_REPORT_SALES_CEILING","POS_DAY_REPORT_RETURNS_CEILING","POS_DAY_REPORT_SHIFTS_CEILING","DAY_KEY","dayParts","key","match","exec","year","Number","month","day","check","Date","UTC","getUTCFullYear","getUTCMonth","getUTCDate","posDayReportRange","input","rawFrom","String","from","trim","rawTo","to","nowMs","timeZone","start","end","ok","error","days","Math","round","startMs","endMs","POS_ORDER_REFUNDS_COLLECTION","REGISTER_ID","posOrderRefundRecord","stripeTenders","order","channel","registerId","test","refundedCents","whole","filter","tender","has","method","find","one","paymentIntentId","orderId","atMs","tenders","amountCents","cashierId","source","stripeRefundId","value","number","isFinite","totalsOf","report","orderCount","grossSalesCents","discountsCents","taxCents","tipsCents","refundCount","refundsCents","netSalesCents","salesByTender","NO_DRAWER","openingFloatCents","cashEvents","salesFigures","sales","refunds","shift","groupBy","items","groups","Map","item","id","group","get","push","set","computePosDayReport","range","inRange","at","shifts","sort","a","b","openedAtMs","localeCompare","reduce","sum","flatMap","event","overall","returns","byTender","refundsByTender","map","salesCents","label","netCents","registerName","registerNames","salesByRegister","sale","returnsByRegister","refund","registerIds","Set","keys","byRegister","name","row","memberName","memberNames","salesByCashier","returnsByCashier","cashierIds","byCashier","memberId","shiftRows","closed","status","liveReport","shiftId","closedAtMs","openedByName","openedBy","closedByName","closedBy","expectedCashCents","countedCashCents","varianceCents","closedRows","openRows","rows","pick","total","hostId","storeName","currency","generatedAtMs","totals","cash","cashSalesCents","cashRefundsCents","paidInCents","paidOutCents","dropsCents","closedShiftCount","length","openShiftCount","openExpectedCashCents","truncated","posDayReportIsEmpty","posDayAmount","cents","digits","toFixed","posDayRangeLabel","posDayReportCsv","money","iso","ms","toISOString","salesRow","section","blocks","toUpperCase","join","posDayReportSections","t","c","strong"],"mappings":"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"}
@@ -110,7 +110,7 @@ export interface PosOfflineSale {
110
110
  card?: PosOfflineSaleCard;
111
111
  }
112
112
  /** What does not match, said on the order and back to the register. */
113
- export type PosOfflineFlag = 'price-changed' | 'unknown-product' | 'tax-differs' | 'totals-restated' | 'discount-over-limit' | 'cash-short' | 'stock-short' | 'shift-closed' | 'no-shift' | 'unknown-register' | 'register-over-cap' | 'late-sync' | 'clock-adjusted' | 'cashier-unverified' | 'card-declined' | 'card-not-charged';
113
+ export type PosOfflineFlag = 'price-changed' | 'unknown-product' | 'tax-differs' | 'totals-restated' | 'discount-over-limit' | 'discount-not-permitted' | 'cash-short' | 'stock-short' | 'shift-closed' | 'no-shift' | 'unknown-register' | 'register-over-cap' | 'late-sync' | 'clock-adjusted' | 'cashier-unverified' | 'card-declined' | 'card-not-charged' | 'card-amount-differs' | 'card-fee-short';
114
114
  /** Each flag in the words the order and the register show. */
115
115
  export declare const POS_OFFLINE_FLAG_LABELS: Record<PosOfflineFlag, string>;
116
116
  /** A line whose stock went short when the sale synced. */
@@ -86,6 +86,9 @@ export { POS_OFFLINE_CARD_MAX_SALE_CENTS_LIMIT, POS_OFFLINE_CARD_RECONNECT_WITHI
86
86
  'tax-differs': 'The tax rung differs from the store’s rate now; the sale kept the tax it collected',
87
87
  'totals-restated': 'The register’s totals did not add up; the server restated them from the lines',
88
88
  'discount-over-limit': 'The discount is above this register’s limit',
89
+ // Names the permission by its label in `plugin-permissions.ts` (AGL-4047);
90
+ // `pos-offline-sync.spec.ts` holds the two to the same words.
91
+ 'discount-not-permitted': 'The discount was given by a member whose role does not include “Give discounts at the register”',
89
92
  'cash-short': 'The cash recorded is less than the total',
90
93
  'stock-short': 'Stock went short: more was sold than the shelf held',
91
94
  'shift-closed': 'Rung in a shift that closed before the sale synced; its Z report does not include it',
@@ -96,7 +99,9 @@ export { POS_OFFLINE_CARD_MAX_SALE_CENTS_LIMIT, POS_OFFLINE_CARD_RECONNECT_WITHI
96
99
  'clock-adjusted': 'The device clock was off; the sale is dated when it synced',
97
100
  'cashier-unverified': 'The PIN-switched cashier could not be confirmed; the sale is under the signed-in member',
98
101
  'card-declined': 'The card taken offline was declined when it was charged; the order is unpaid',
99
- 'card-not-charged': 'The card taken offline could not be charged; the order is unpaid'
102
+ 'card-not-charged': 'The card taken offline could not be charged; the order is unpaid',
103
+ 'card-amount-differs': 'The card was charged a different amount than the sale’s total; the payment records what Stripe took',
104
+ 'card-fee-short': 'Stripe took less than the platform fee from this card’s payout; the rest goes on the workspace’s monthly invoice'
100
105
  };
101
106
  /**
102
107
  * The totals of an offline sale: the cashier's discount on the goods, then
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-pos-offline.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 { computeOrderTotals, type OrderLineItem, type OrderTotals } from './commerce-orders'\nimport { computeTaxCents } from './commerce-tax'\nimport type { ModifierSelection } from './product-modifiers'\nimport { POS_OFFLINE_CARD_MAX_SALE_CENTS_LIMIT } from './pos-offline-card-limits'\n\n/*==========================================\n * THE OFFLINE REGISTER (AGL-3625): the contract every register client — the\n * web register and the native Aglyn POS app — shares with\n * `POST /api/commerce/pos-offline-sync`.\n *\n * WHAT SELLS OFFLINE IS CASH, AND A CARD ON THE NATIVE APP'S OWN READER.\n *\n * - Cash, on every register client.\n * - A card, only on the native Aglyn POS app's own Stripe Terminal reader\n * (AGL-4039): Stripe's SDK stores the card on the device (\"store and\n * forward\") and forwards it once a connection returns. The app creates\n * the PaymentIntent itself, a destination charge with MANUAL capture and\n * NO application fee; the server captures it when the sale syncs and\n * sizes the platform fee then, exactly as for an online card sale. A bank\n * can still decline it at that point, after the customer left: the order\n * is then recorded unpaid and flagged, and the register says so.\n * Off until the store turns it on, then within the store's two limits:\n * per sale, and per device per day (as Shopify POS words it).\n * - Every other card tender cannot: Tap to Pay on Android, the server's\n * smart readers, the typed card and the QR link are server-driven, and\n * Stripe does not take a server-driven card payment without the server.\n * - Gift cards DO NOT, deliberately. A balance cannot be checked without the\n * server, and a capped \"allow it anyway\" (Square's offer for cards) turns a\n * short balance into a sale whose money nobody collected — discovered at\n * sync, after the customer left. Square itself does not redeem gift cards\n * offline. A gift card is never over-redeemed silently because it is never\n * redeemed offline at all.\n * - Room charges do not: the stay is validated on the server.\n * - Store credit and rewards do not, for the gift card's reason: a balance\n * is the server's to check.\n * - Codes and store promotions do not: a redemption cap is counted on the\n * server. The cashier's own discount, bounded by the site's ceiling, does.\n *\n * WHAT THE SALE IS: the register prices the basket from its cached catalog and\n * the store's own tax rate, takes the cash and queues the sale with a key it\n * minted. That key is the order's id on the server, so the receipt printed\n * offline carries the barcode the real order answers to, and a sale sent twice\n * — a flapping connection, a retry after a lost answer — lands once.\n *\n * WHAT THE SERVER DOES WITH IT: records it as it happened, never re-prices the\n * money the customer handed over, and FLAGS what does not match — a price\n * that changed, stock that went short, a shift that closed first — on the\n * order and back to the register. A sale is never dropped for a flag.\n *=========================================*/\n\n/** The route both register clients sync through. */\nexport const POS_OFFLINE_SYNC_ROUTE = '/api/commerce/pos-offline-sync'\n\n/** The most sales one sync request carries; a longer queue sends in batches. */\nexport const POS_OFFLINE_SYNC_BATCH_MAX = 25\n\n/** Lines one offline sale may carry, the same bound the register's basket has. */\nexport const POS_OFFLINE_SALE_MAX_LINES = 100\n\n/** A sale synced later than this after it was rung is flagged `late-sync`. */\nexport const POS_OFFLINE_LATE_SYNC_MS = 24 * 60 * 60 * 1000\n\n/**\n * How far back a sale's own clock is believed. Older, and the server stamps\n * the sale at this bound and flags it, rather than letting a device clock\n * place a sale anywhere in history.\n */\nexport const POS_OFFLINE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000\n\n/** How far ahead of the server a device clock may run before it is clamped. */\nexport const POS_OFFLINE_CLOCK_SKEW_MS = 10 * 60 * 1000\n\n/** What an offline sale was paid with. A sale with no tender is cash (the shape before AGL-4039). */\nexport type PosOfflineTender = 'cash' | 'card'\n\nexport {\n POS_OFFLINE_CARD_MAX_SALE_CENTS_LIMIT,\n POS_OFFLINE_CARD_RECONNECT_WITHIN_MS,\n} from './pos-offline-card-limits'\n\n/** The `metadata[kind]` of a PaymentIntent the app created for a card rung offline. */\nexport const POS_OFFLINE_CARD_INTENT_KIND = 'pos-offline'\n\n/**\n * Where a card rung offline stands. `stored`: on the device, not yet sent to\n * Stripe. `forwarded`: Stripe has it and it is authorized, waiting for the\n * sync to capture it. `declined`: the bank refused it when it was forwarded.\n * `failed`: Stripe could not take it at all (no PaymentIntent was made).\n */\nexport type PosOfflineCardForward = 'stored' | 'forwarded' | 'declined' | 'failed'\n\n/** The card side of a sale rung offline on the app's own reader. */\nexport interface PosOfflineSaleCard {\n forward: PosOfflineCardForward\n /** Stripe's PaymentIntent id, once the SDK forwarded it. */\n paymentIntentId?: string | null\n brand?: string\n last4?: string\n /** The bank's or Stripe's words for a decline or failure. */\n declineMessage?: string\n}\n\n/** Every tender the register turns off while offline, and why, in its words. */\nexport const POS_OFFLINE_DISABLED_TENDERS: Readonly<Record<string, string>> = {\n card_present: 'Card readers need the connection.',\n card_keyed: 'Typed cards need the connection.',\n card_link: 'The QR card link needs the connection.',\n gift_card: 'A gift card balance can only be checked online.',\n folio: 'Room charges are checked against the stay online.',\n credit: 'Store credit and rewards balances can only be checked online.',\n}\n\n/** One basket line as the register rang it. */\nexport interface PosOfflineSaleLine {\n productId: string\n variantId?: string\n name: string\n /** Variant options and modifier choices, as the receipt printed them. */\n variantLabel?: string\n sku?: string\n productType?: 'physical' | 'digital' | 'service'\n quantity: number\n /** Per unit, modifiers included, from the cached catalog. */\n unitAmountCents: number\n modifiers?: ModifierSelection[]\n}\n\n/** The customer the register attached: only what the sale and its receipt need. */\nexport interface PosOfflineSaleCustomer {\n email?: string\n name?: string\n kind?: string\n id?: string\n}\n\n/** One sale rung while offline, exactly as the register queued it. */\nexport interface PosOfflineSale {\n v: 1\n /** The register's key for this sale, minted once; the order's id on the server. */\n saleKey: string\n hostId: string\n /** The workspace the register was signed in to. */\n orgId: string\n /** Who was signed in on the device: the only member who may sync it. */\n signedInUid: string\n registerId: string\n /** The shift open on the register when the sale was rung. */\n shiftId?: string | null\n locationId?: string\n /** A PIN-switched cashier's assertion, valid when the sale was rung. */\n cashierAssertion?: string\n /** The device's clock when the cash was taken. */\n soldAtMs: number\n lines: PosOfflineSaleLine[]\n /** The cashier's discount, a percentage of the goods. */\n discountPct: number\n /** The totals the receipt printed and the customer paid. */\n totals: OrderTotals\n /** Cash handed over; 0 for a card sale. */\n cashTenderedCents: number\n changeCents: number\n customer?: PosOfflineSaleCustomer\n /** `card` only on the native app's own reader (AGL-4039); absent means cash. */\n tender?: PosOfflineTender\n /** The card, when `tender` is `card`. */\n card?: PosOfflineSaleCard\n}\n\n/** What does not match, said on the order and back to the register. */\nexport type PosOfflineFlag =\n | 'price-changed'\n | 'unknown-product'\n | 'tax-differs'\n | 'totals-restated'\n | 'discount-over-limit'\n | 'cash-short'\n | 'stock-short'\n | 'shift-closed'\n | 'no-shift'\n | 'unknown-register'\n | 'register-over-cap'\n | 'late-sync'\n | 'clock-adjusted'\n | 'cashier-unverified'\n | 'card-declined'\n | 'card-not-charged'\n\n/** Each flag in the words the order and the register show. */\nexport const POS_OFFLINE_FLAG_LABELS: Record<PosOfflineFlag, string> = {\n 'price-changed': 'A price changed after the register cached it; the sale kept the price it was rung at',\n 'unknown-product': 'A product was deleted before the sale synced; the line kept what the receipt printed',\n 'tax-differs': 'The tax rung differs from the store’s rate now; the sale kept the tax it collected',\n 'totals-restated': 'The register’s totals did not add up; the server restated them from the lines',\n 'discount-over-limit': 'The discount is above this register’s limit',\n 'cash-short': 'The cash recorded is less than the total',\n 'stock-short': 'Stock went short: more was sold than the shelf held',\n 'shift-closed': 'Rung in a shift that closed before the sale synced; its Z report does not include it',\n 'no-shift': 'Rung with no shift open, on a site that requires one',\n 'unknown-register': 'The register it was rung on no longer exists',\n 'register-over-cap': 'The register it was rung on is over the plan’s register limit',\n 'late-sync': 'Synced more than a day after it was rung',\n 'clock-adjusted': 'The device clock was off; the sale is dated when it synced',\n 'cashier-unverified': 'The PIN-switched cashier could not be confirmed; the sale is under the signed-in member',\n 'card-declined': 'The card taken offline was declined when it was charged; the order is unpaid',\n 'card-not-charged': 'The card taken offline could not be charged; the order is unpaid',\n}\n\n/** A line whose stock went short when the sale synced. */\nexport interface PosOfflineStockConflict {\n productId: string\n variantId?: string\n name: string\n /** Units sold. */\n requested: number\n /** Units the shelf actually gave up. */\n applied: number\n /** Units sold that the shelf did not hold. */\n shortUnits: number\n}\n\n/** What the order records about having been rung offline. */\nexport interface PosOfflineOrderStamp {\n saleKey: string\n /** The device's clock when the cash was taken. */\n soldAtMs: number\n syncedAtMs: number\n /** Who synced it: always the member who was signed in when it was rung. */\n syncedBy: string\n flags: PosOfflineFlag[]\n stockConflicts?: PosOfflineStockConflict[]\n /** The server's own price per line where it differed, for the merchant to compare. */\n priceDrift?: Array<{ index: number; rungCents: number; currentCents: number }>\n}\n\n/** Why a sale was refused, and whether the register should keep trying. */\nexport type PosOfflineRefusal =\n | 'wrong-staff'\n | 'wrong-site'\n | 'wrong-workspace'\n | 'invalid'\n | 'conflict'\n | 'failed'\n\n/** What became of one sale the register sent. */\nexport type PosOfflineOutcomeStatus = 'recorded' | 'replayed' | 'refused'\n\n/**\n * One sale's answer, in the order the request sent them. `recorded` and\n * `replayed` carry the order; `refused` carries the reason, the sentence and\n * whether to keep trying. One flat shape, so every register client — the web\n * register and the native apps — decodes it the same way.\n */\nexport interface PosOfflineSaleOutcome {\n saleKey: string\n status: PosOfflineOutcomeStatus\n orderId?: string\n number?: number | null\n flags?: PosOfflineFlag[]\n stockConflicts?: PosOfflineStockConflict[]\n reason?: PosOfflineRefusal\n error?: string\n /** Keep it queued and try again later (a transient failure). */\n retry?: boolean\n}\n\nexport interface PosOfflineSyncRequest {\n hostId: string\n sales: PosOfflineSale[]\n}\n\nexport interface PosOfflineSyncResponse {\n results: PosOfflineSaleOutcome[]\n}\n\n/** One register as the offline kit lists it. */\nexport interface PosOfflineKitRegister {\n id: string\n name: string\n locationId?: string\n openShiftId: string | null\n}\n\n/** What the native app needs to take a card offline on its own reader (AGL-4039). */\nexport interface PosOfflineKitCard {\n available: boolean\n /** Why not, in the cashier's words, when it is not. */\n unavailableReason?: string\n /** The connected account the charge transfers to (a destination charge, as online). */\n destination: string\n currency: string\n /** The limit of one card sale rung offline, in cents: the store's, or Stripe's own ceiling when it set none. */\n maxSaleCents: number\n /** The limit of every card one device takes offline in a day, in cents; 0 is no daily limit. */\n maxDeviceDailyCents: number\n}\n\n/**\n * Everything a register needs to sell offline besides the catalog, from\n * `GET /api/commerce/pos-offline-sync?hostId=`: the server's own answers, so\n * the register and the sync agree about the rate and the rules.\n */\nexport interface PosOfflineKit {\n v: 1\n hostId: string\n orgId: string\n issuedAtMs: number\n /** Offline selling is possible for this site right now. */\n available: boolean\n /** Why not, in the cashier's words, when it is not. */\n unavailableReason?: string\n /** The store's own in-person rate; `null` pct is no tax. */\n tax: { pct: number | null; pricesIncludeTax: boolean }\n maxDiscountPct: number\n requireOpenShift: boolean\n registers: PosOfflineKitRegister[]\n /** Card sales on the app's own reader while offline; absent from a server that does not take them. */\n card?: PosOfflineKitCard\n /** What the receipt prints at its head and foot. `logo` is the stored value. */\n receipt: {\n name: string\n logo?: string\n address?: string\n footer?: string\n returnPolicy?: string\n }\n}\n\n/**\n * The totals of an offline sale: the cashier's discount on the goods, then\n * the store's in-person tax on what is left — exactly the arithmetic the\n * online register's sale route uses for the same basket with no promotion.\n * `pricesIncludeTax` adds nothing, as at the online till.\n */\nexport function posOfflineSaleTotals(input: {\n lines: ReadonlyArray<Pick<PosOfflineSaleLine, 'unitAmountCents' | 'quantity'>>\n discountPct: number\n tax: { pct: number | null; pricesIncludeTax: boolean }\n}): OrderTotals {\n const lines = input.lines.map((line) => ({\n unitAmountCents: wholeCents(line.unitAmountCents),\n quantity: Math.max(1, Math.round(Number(line.quantity) || 1)),\n }))\n const itemsCents = lines.reduce((sum, line) => sum + line.unitAmountCents * line.quantity, 0)\n const pct = Number.isFinite(input.discountPct) ? Math.min(100, Math.max(0, input.discountPct)) : 0\n const discountCents = Math.min(itemsCents, Math.round((itemsCents * pct) / 100))\n const taxPct = Number(input.tax.pct)\n const taxCents =\n taxPct > 0 && !input.tax.pricesIncludeTax ? computeTaxCents(itemsCents - discountCents, taxPct) : 0\n return computeOrderTotals(lines as unknown as OrderLineItem[], { discountCents, taxCents, feeCents: 0 })\n}\n\n/** Change for cash handed over, never negative. */\nexport function posOfflineChangeCents(totalCents: number, tenderedCents: number): number {\n return Math.max(0, wholeCents(tenderedCents) - wholeCents(totalCents))\n}\n\n/** A key the register minted: an id the order can take, and nothing else. */\nexport function posOfflineSaleKeyIsValid(key: unknown): key is string {\n return typeof key === 'string' && /^[A-Za-z0-9_-]{16,64}$/.test(key) && !/^__.*__$/.test(key)\n}\n\n/**\n * The sale's time as the server records it: the device's clock when it is\n * believable, clamped to now when it runs ahead, and to the oldest believable\n * moment when it claims to be older.\n */\nexport function posOfflineSoldAt(\n soldAtMs: number,\n nowMs: number,\n): { atMs: number; adjusted: boolean } {\n const at = Number(soldAtMs)\n if (!Number.isFinite(at) || at <= 0) return { atMs: nowMs, adjusted: true }\n if (at > nowMs + POS_OFFLINE_CLOCK_SKEW_MS) return { atMs: nowMs, adjusted: true }\n if (at < nowMs - POS_OFFLINE_MAX_AGE_MS) return { atMs: nowMs, adjusted: true }\n return { atMs: Math.min(at, nowMs), adjusted: false }\n}\n\nconst bounded = (value: unknown, max: number): string =>\n typeof value === 'string' ? value.trim().slice(0, max) : ''\n\nconst cleanId = (value: unknown): string => {\n const id = String(value ?? '').trim()\n return /^[A-Za-z0-9_-]{1,128}$/.test(id) && !/^__.*__$/.test(id) ? id : ''\n}\n\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 * A queued sale read off the wire, every field typed and bounded — or the\n * sentence that says why it cannot be read at all. Pure, so the server and\n * any client that wants to check its own queue answer the same.\n */\nexport function sanitizePosOfflineSale(\n raw: unknown,\n): { ok: true; sale: PosOfflineSale } | { ok: false; error: string } {\n if (!raw || typeof raw !== 'object') return { ok: false, error: 'Not a sale' }\n const input = raw as Record<string, any>\n const saleKey = input['saleKey']\n if (!posOfflineSaleKeyIsValid(saleKey)) return { ok: false, error: 'The sale has no valid key' }\n const hostId = cleanId(input['hostId'])\n const registerId = cleanId(input['registerId'])\n const signedInUid = bounded(input['signedInUid'], 128)\n if (!hostId || !registerId || !signedInUid) {\n return { ok: false, error: 'The sale is missing its site, register or member' }\n }\n const rawLines = Array.isArray(input['lines']) ? input['lines'] : []\n if (rawLines.length === 0 || rawLines.length > POS_OFFLINE_SALE_MAX_LINES) {\n return { ok: false, error: 'The sale has no lines' }\n }\n const lines: PosOfflineSaleLine[] = []\n for (const line of rawLines) {\n const productId = cleanId(line?.productId)\n const name = bounded(line?.name, 200)\n const unit = Math.round(Number(line?.unitAmountCents))\n const quantity = Math.round(Number(line?.quantity))\n if (!productId || !name || !Number.isFinite(unit) || unit < 0 || !(quantity >= 1 && quantity <= 99)) {\n return { ok: false, error: 'A line of the sale cannot be read' }\n }\n const variantId = cleanId(line?.variantId)\n const variantLabel = bounded(line?.variantLabel, 200)\n const sku = bounded(line?.sku, 100)\n const productType = ['physical', 'digital', 'service'].includes(line?.productType)\n ? (line.productType as PosOfflineSaleLine['productType'])\n : undefined\n const modifiers = Array.isArray(line?.modifiers)\n ? (line.modifiers as unknown[])\n .slice(0, 50)\n .map((pick: any) => ({ groupId: cleanId(pick?.groupId), optionId: cleanId(pick?.optionId) }))\n .filter((pick) => pick.groupId && pick.optionId)\n : []\n lines.push({\n productId,\n ...(variantId ? { variantId } : {}),\n name,\n ...(variantLabel ? { variantLabel } : {}),\n ...(sku ? { sku } : {}),\n ...(productType ? { productType } : {}),\n quantity,\n unitAmountCents: unit,\n ...(modifiers.length ? { modifiers } : {}),\n })\n }\n const discountPct = Number(input['discountPct'] ?? 0)\n if (!Number.isFinite(discountPct) || discountPct < 0 || discountPct > 100) {\n return { ok: false, error: 'The sale’s discount cannot be read' }\n }\n const totals = (input['totals'] ?? {}) as Record<string, unknown>\n const customerRaw = input['customer']\n const customer: PosOfflineSaleCustomer = {}\n if (customerRaw && typeof customerRaw === 'object') {\n const email = bounded((customerRaw as any).email, 254).toLowerCase()\n if (email && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)) customer.email = email\n const name = bounded((customerRaw as any).name, 200)\n if (name) customer.name = name\n const kind = bounded((customerRaw as any).kind, 40)\n const id = cleanId((customerRaw as any).id)\n if (kind && kind !== 'none' && id) {\n customer.kind = kind\n customer.id = id\n }\n }\n const shiftId = cleanId(input['shiftId'])\n const locationId = cleanId(input['locationId'])\n const assertion = bounded(input['cashierAssertion'], 2000)\n const orgId = bounded(input['orgId'], 128)\n let card: PosOfflineSaleCard | undefined\n if (input['tender'] === 'card') {\n const read = sanitizePosOfflineSaleCard(input['card'])\n if (!read) return { ok: false, error: 'The card of the sale cannot be read' }\n card = read\n }\n return {\n ok: true,\n sale: {\n v: 1,\n saleKey,\n hostId,\n orgId,\n signedInUid,\n registerId,\n shiftId: shiftId || null,\n ...(locationId ? { locationId } : {}),\n ...(assertion ? { cashierAssertion: assertion } : {}),\n soldAtMs: Number(input['soldAtMs']) || 0,\n lines,\n discountPct,\n totals: {\n itemsCents: wholeCents(totals['itemsCents']),\n shippingCents: 0,\n taxCents: wholeCents(totals['taxCents']),\n discountCents: wholeCents(totals['discountCents']),\n totalCents: wholeCents(totals['totalCents']),\n feeCents: 0,\n },\n cashTenderedCents: card ? 0 : wholeCents(input['cashTenderedCents']),\n changeCents: card ? 0 : wholeCents(input['changeCents']),\n ...(Object.keys(customer).length ? { customer } : {}),\n ...(card ? { tender: 'card' as const, card } : {}),\n },\n }\n}\n\n/**\n * Whether the register's totals are the arithmetic of its own lines: the\n * goods are the lines' sum, the discount is the percentage of them, and the\n * total is goods less discount plus the tax it rang. The tax itself is the\n * register's, and checked against the store's rate separately.\n */\nexport function posOfflineTotalsAddUp(sale: Pick<PosOfflineSale, 'lines' | 'discountPct' | 'totals'>): boolean {\n const expected = posOfflineSaleTotals({\n lines: sale.lines,\n discountPct: sale.discountPct,\n tax: { pct: null, pricesIncludeTax: false },\n })\n return (\n expected.itemsCents === sale.totals.itemsCents &&\n expected.discountCents === sale.totals.discountCents &&\n sale.totals.taxCents <= sale.totals.itemsCents &&\n sale.totals.totalCents === expected.itemsCents - expected.discountCents + sale.totals.taxCents\n )\n}\n\n/** The flags in the order and words the register lists them. */\nexport function posOfflineFlagLabels(flags: readonly PosOfflineFlag[]): string[] {\n return flags.map((flag) => POS_OFFLINE_FLAG_LABELS[flag] ?? flag)\n}\n\nconst CARD_FORWARDS: readonly PosOfflineCardForward[] = ['stored', 'forwarded', 'declined', 'failed']\n\n/** The card side of a queued sale, typed and bounded, or `null` when it cannot be read. */\nexport function sanitizePosOfflineSaleCard(raw: unknown): PosOfflineSaleCard | null {\n if (!raw || typeof raw !== 'object') return null\n const input = raw as Record<string, unknown>\n const forward = input['forward'] as PosOfflineCardForward\n if (!CARD_FORWARDS.includes(forward)) return null\n const intent = typeof input['paymentIntentId'] === 'string' ? input['paymentIntentId'].trim() : ''\n if (intent && !/^pi_[A-Za-z0-9]{8,64}$/.test(intent)) return null\n if (forward === 'forwarded' && !intent) return null\n const brand = bounded(input['brand'], 24)\n const last4 = typeof input['last4'] === 'string' && /^\\d{4}$/.test(input['last4']) ? input['last4'] : ''\n const declineMessage = bounded(input['declineMessage'], 300)\n return {\n forward,\n ...(intent ? { paymentIntentId: intent } : {}),\n ...(brand ? { brand } : {}),\n ...(last4 ? { last4 } : {}),\n ...(declineMessage ? { declineMessage } : {}),\n }\n}\n\n/**\n * Why a card cannot be taken offline for `totalCents`, in the cashier's\n * words, or `null` when it can: the store has not turned it on, the sale is\n * over the store's (or Stripe's) limit per sale, or the cards this device\n * has taken offline today would pass the store's daily limit per device (a\n * daily limit of 0 is none: the store did not set one).\n */\nexport function posOfflineCardRefusal(input: {\n card: Pick<PosOfflineKitCard, 'available' | 'unavailableReason' | 'maxSaleCents' | 'maxDeviceDailyCents'> | null | undefined\n totalCents: number\n /** Every card this device took offline today, in cents. */\n takenTodayCents: number\n}): string | null {\n const card = input.card\n if (!card) return 'Card sales offline need the register to connect once first.'\n if (!card.available) return card.unavailableReason || 'Card sales offline are not turned on for this store.'\n const total = wholeCents(input.totalCents)\n if (total <= 0) return 'There is nothing to charge.'\n const perSale = Math.min(wholeCents(card.maxSaleCents), POS_OFFLINE_CARD_MAX_SALE_CENTS_LIMIT)\n if (total > perSale) return `Offline, a card sale can be up to ${dollars(perSale)}. Take cash, or charge it when you are back online.`\n const today = wholeCents(input.takenTodayCents)\n const daily = wholeCents(card.maxDeviceDailyCents)\n if (daily > 0 && today + total > daily) {\n return `This device has taken ${dollars(today)} in cards offline today, and its daily limit is ${dollars(daily)}. Take cash, or wait for the connection.`\n }\n return null\n}\n\n/**\n * What the app writes on the PaymentIntent it creates for a card rung\n * offline: the sale key (the order's id once it syncs) and where it was rung,\n * so the server can match the charge to the sale even if the sale's own sync\n * never arrives.\n */\nexport function posOfflineCardIntentMetadata(\n sale: Pick<PosOfflineSale, 'saleKey' | 'hostId' | 'orgId' | 'registerId'>,\n): Record<string, string> {\n return {\n kind: POS_OFFLINE_CARD_INTENT_KIND,\n saleKey: sale.saleKey,\n hostId: sale.hostId,\n orgId: sale.orgId,\n registerId: sale.registerId,\n }\n}\n\nfunction dollars(cents: number): string {\n const whole = Math.floor(cents / 100)\n const rest = cents % 100\n return `$${whole.toLocaleString('en-US')}.${String(rest).padStart(2, '0')}`\n}\n"],"names":["computeOrderTotals","computeTaxCents","POS_OFFLINE_CARD_MAX_SALE_CENTS_LIMIT","POS_OFFLINE_SYNC_ROUTE","POS_OFFLINE_SYNC_BATCH_MAX","POS_OFFLINE_SALE_MAX_LINES","POS_OFFLINE_LATE_SYNC_MS","POS_OFFLINE_MAX_AGE_MS","POS_OFFLINE_CLOCK_SKEW_MS","POS_OFFLINE_CARD_RECONNECT_WITHIN_MS","POS_OFFLINE_CARD_INTENT_KIND","POS_OFFLINE_DISABLED_TENDERS","card_present","card_keyed","card_link","gift_card","folio","credit","POS_OFFLINE_FLAG_LABELS","posOfflineSaleTotals","input","lines","map","line","unitAmountCents","wholeCents","quantity","Math","max","round","Number","itemsCents","reduce","sum","pct","isFinite","discountPct","min","discountCents","taxPct","tax","taxCents","pricesIncludeTax","feeCents","posOfflineChangeCents","totalCents","tenderedCents","posOfflineSaleKeyIsValid","key","test","posOfflineSoldAt","soldAtMs","nowMs","at","atMs","adjusted","bounded","value","trim","slice","cleanId","id","String","number","sanitizePosOfflineSale","raw","ok","error","saleKey","hostId","registerId","signedInUid","rawLines","Array","isArray","length","productId","name","unit","variantId","variantLabel","sku","productType","includes","undefined","modifiers","pick","groupId","optionId","filter","push","totals","customerRaw","customer","email","toLowerCase","kind","shiftId","locationId","assertion","orgId","card","read","sanitizePosOfflineSaleCard","sale","v","cashierAssertion","shippingCents","cashTenderedCents","changeCents","Object","keys","tender","posOfflineTotalsAddUp","expected","posOfflineFlagLabels","flags","flag","CARD_FORWARDS","forward","intent","brand","last4","declineMessage","paymentIntentId","posOfflineCardRefusal","available","unavailableReason","total","perSale","maxSaleCents","dollars","today","takenTodayCents","daily","maxDeviceDailyCents","posOfflineCardIntentMetadata","cents","whole","floor","rest","toLocaleString","padStart"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-pos-offline.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 { computeOrderTotals, type OrderLineItem, type OrderTotals } from './commerce-orders'\nimport { computeTaxCents } from './commerce-tax'\nimport type { ModifierSelection } from './product-modifiers'\nimport { POS_OFFLINE_CARD_MAX_SALE_CENTS_LIMIT } from './pos-offline-card-limits'\n\n/*==========================================\n * THE OFFLINE REGISTER (AGL-3625): the contract every register client — the\n * web register and the native Aglyn POS app — shares with\n * `POST /api/commerce/pos-offline-sync`.\n *\n * WHAT SELLS OFFLINE IS CASH, AND A CARD ON THE NATIVE APP'S OWN READER.\n *\n * - Cash, on every register client.\n * - A card, only on the native Aglyn POS app's own Stripe Terminal reader\n * (AGL-4039): Stripe's SDK stores the card on the device (\"store and\n * forward\") and forwards it once a connection returns. The app creates\n * the PaymentIntent itself, a destination charge with MANUAL capture and\n * NO application fee; the server captures it when the sale syncs and\n * sizes the platform fee then, exactly as for an online card sale. A bank\n * can still decline it at that point, after the customer left: the order\n * is then recorded unpaid and flagged, and the register says so.\n * Off until the store turns it on, then within the store's two limits:\n * per sale, and per device per day (as Shopify POS words it).\n * - Every other card tender cannot: Tap to Pay on Android, the server's\n * smart readers, the typed card and the QR link are server-driven, and\n * Stripe does not take a server-driven card payment without the server.\n * - Gift cards DO NOT, deliberately. A balance cannot be checked without the\n * server, and a capped \"allow it anyway\" (Square's offer for cards) turns a\n * short balance into a sale whose money nobody collected — discovered at\n * sync, after the customer left. Square itself does not redeem gift cards\n * offline. A gift card is never over-redeemed silently because it is never\n * redeemed offline at all.\n * - Room charges do not: the stay is validated on the server.\n * - Store credit and rewards do not, for the gift card's reason: a balance\n * is the server's to check.\n * - Codes and store promotions do not: a redemption cap is counted on the\n * server. The cashier's own discount, bounded by the site's ceiling, does.\n *\n * WHAT THE SALE IS: the register prices the basket from its cached catalog and\n * the store's own tax rate, takes the cash and queues the sale with a key it\n * minted. That key is the order's id on the server, so the receipt printed\n * offline carries the barcode the real order answers to, and a sale sent twice\n * — a flapping connection, a retry after a lost answer — lands once.\n *\n * WHAT THE SERVER DOES WITH IT: records it as it happened, never re-prices the\n * money the customer handed over, and FLAGS what does not match — a price\n * that changed, stock that went short, a shift that closed first — on the\n * order and back to the register. A sale is never dropped for a flag.\n *=========================================*/\n\n/** The route both register clients sync through. */\nexport const POS_OFFLINE_SYNC_ROUTE = '/api/commerce/pos-offline-sync'\n\n/** The most sales one sync request carries; a longer queue sends in batches. */\nexport const POS_OFFLINE_SYNC_BATCH_MAX = 25\n\n/** Lines one offline sale may carry, the same bound the register's basket has. */\nexport const POS_OFFLINE_SALE_MAX_LINES = 100\n\n/** A sale synced later than this after it was rung is flagged `late-sync`. */\nexport const POS_OFFLINE_LATE_SYNC_MS = 24 * 60 * 60 * 1000\n\n/**\n * How far back a sale's own clock is believed. Older, and the server stamps\n * the sale at this bound and flags it, rather than letting a device clock\n * place a sale anywhere in history.\n */\nexport const POS_OFFLINE_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000\n\n/** How far ahead of the server a device clock may run before it is clamped. */\nexport const POS_OFFLINE_CLOCK_SKEW_MS = 10 * 60 * 1000\n\n/** What an offline sale was paid with. A sale with no tender is cash (the shape before AGL-4039). */\nexport type PosOfflineTender = 'cash' | 'card'\n\nexport {\n POS_OFFLINE_CARD_MAX_SALE_CENTS_LIMIT,\n POS_OFFLINE_CARD_RECONNECT_WITHIN_MS,\n} from './pos-offline-card-limits'\n\n/** The `metadata[kind]` of a PaymentIntent the app created for a card rung offline. */\nexport const POS_OFFLINE_CARD_INTENT_KIND = 'pos-offline'\n\n/**\n * Where a card rung offline stands. `stored`: on the device, not yet sent to\n * Stripe. `forwarded`: Stripe has it and it is authorized, waiting for the\n * sync to capture it. `declined`: the bank refused it when it was forwarded.\n * `failed`: Stripe could not take it at all (no PaymentIntent was made).\n */\nexport type PosOfflineCardForward = 'stored' | 'forwarded' | 'declined' | 'failed'\n\n/** The card side of a sale rung offline on the app's own reader. */\nexport interface PosOfflineSaleCard {\n forward: PosOfflineCardForward\n /** Stripe's PaymentIntent id, once the SDK forwarded it. */\n paymentIntentId?: string | null\n brand?: string\n last4?: string\n /** The bank's or Stripe's words for a decline or failure. */\n declineMessage?: string\n}\n\n/** Every tender the register turns off while offline, and why, in its words. */\nexport const POS_OFFLINE_DISABLED_TENDERS: Readonly<Record<string, string>> = {\n card_present: 'Card readers need the connection.',\n card_keyed: 'Typed cards need the connection.',\n card_link: 'The QR card link needs the connection.',\n gift_card: 'A gift card balance can only be checked online.',\n folio: 'Room charges are checked against the stay online.',\n credit: 'Store credit and rewards balances can only be checked online.',\n}\n\n/** One basket line as the register rang it. */\nexport interface PosOfflineSaleLine {\n productId: string\n variantId?: string\n name: string\n /** Variant options and modifier choices, as the receipt printed them. */\n variantLabel?: string\n sku?: string\n productType?: 'physical' | 'digital' | 'service'\n quantity: number\n /** Per unit, modifiers included, from the cached catalog. */\n unitAmountCents: number\n modifiers?: ModifierSelection[]\n}\n\n/** The customer the register attached: only what the sale and its receipt need. */\nexport interface PosOfflineSaleCustomer {\n email?: string\n name?: string\n kind?: string\n id?: string\n}\n\n/** One sale rung while offline, exactly as the register queued it. */\nexport interface PosOfflineSale {\n v: 1\n /** The register's key for this sale, minted once; the order's id on the server. */\n saleKey: string\n hostId: string\n /** The workspace the register was signed in to. */\n orgId: string\n /** Who was signed in on the device: the only member who may sync it. */\n signedInUid: string\n registerId: string\n /** The shift open on the register when the sale was rung. */\n shiftId?: string | null\n locationId?: string\n /** A PIN-switched cashier's assertion, valid when the sale was rung. */\n cashierAssertion?: string\n /** The device's clock when the cash was taken. */\n soldAtMs: number\n lines: PosOfflineSaleLine[]\n /** The cashier's discount, a percentage of the goods. */\n discountPct: number\n /** The totals the receipt printed and the customer paid. */\n totals: OrderTotals\n /** Cash handed over; 0 for a card sale. */\n cashTenderedCents: number\n changeCents: number\n customer?: PosOfflineSaleCustomer\n /** `card` only on the native app's own reader (AGL-4039); absent means cash. */\n tender?: PosOfflineTender\n /** The card, when `tender` is `card`. */\n card?: PosOfflineSaleCard\n}\n\n/** What does not match, said on the order and back to the register. */\nexport type PosOfflineFlag =\n | 'price-changed'\n | 'unknown-product'\n | 'tax-differs'\n | 'totals-restated'\n | 'discount-over-limit'\n | 'discount-not-permitted'\n | 'cash-short'\n | 'stock-short'\n | 'shift-closed'\n | 'no-shift'\n | 'unknown-register'\n | 'register-over-cap'\n | 'late-sync'\n | 'clock-adjusted'\n | 'cashier-unverified'\n | 'card-declined'\n | 'card-not-charged'\n | 'card-amount-differs'\n | 'card-fee-short'\n\n/** Each flag in the words the order and the register show. */\nexport const POS_OFFLINE_FLAG_LABELS: Record<PosOfflineFlag, string> = {\n 'price-changed': 'A price changed after the register cached it; the sale kept the price it was rung at',\n 'unknown-product': 'A product was deleted before the sale synced; the line kept what the receipt printed',\n 'tax-differs': 'The tax rung differs from the store’s rate now; the sale kept the tax it collected',\n 'totals-restated': 'The register’s totals did not add up; the server restated them from the lines',\n 'discount-over-limit': 'The discount is above this register’s limit',\n // Names the permission by its label in `plugin-permissions.ts` (AGL-4047);\n // `pos-offline-sync.spec.ts` holds the two to the same words.\n 'discount-not-permitted': 'The discount was given by a member whose role does not include “Give discounts at the register”',\n 'cash-short': 'The cash recorded is less than the total',\n 'stock-short': 'Stock went short: more was sold than the shelf held',\n 'shift-closed': 'Rung in a shift that closed before the sale synced; its Z report does not include it',\n 'no-shift': 'Rung with no shift open, on a site that requires one',\n 'unknown-register': 'The register it was rung on no longer exists',\n 'register-over-cap': 'The register it was rung on is over the plan’s register limit',\n 'late-sync': 'Synced more than a day after it was rung',\n 'clock-adjusted': 'The device clock was off; the sale is dated when it synced',\n 'cashier-unverified': 'The PIN-switched cashier could not be confirmed; the sale is under the signed-in member',\n 'card-declined': 'The card taken offline was declined when it was charged; the order is unpaid',\n 'card-not-charged': 'The card taken offline could not be charged; the order is unpaid',\n 'card-amount-differs': 'The card was charged a different amount than the sale’s total; the payment records what Stripe took',\n 'card-fee-short': 'Stripe took less than the platform fee from this card’s payout; the rest goes on the workspace’s monthly invoice',\n}\n\n/** A line whose stock went short when the sale synced. */\nexport interface PosOfflineStockConflict {\n productId: string\n variantId?: string\n name: string\n /** Units sold. */\n requested: number\n /** Units the shelf actually gave up. */\n applied: number\n /** Units sold that the shelf did not hold. */\n shortUnits: number\n}\n\n/** What the order records about having been rung offline. */\nexport interface PosOfflineOrderStamp {\n saleKey: string\n /** The device's clock when the cash was taken. */\n soldAtMs: number\n syncedAtMs: number\n /** Who synced it: always the member who was signed in when it was rung. */\n syncedBy: string\n flags: PosOfflineFlag[]\n stockConflicts?: PosOfflineStockConflict[]\n /** The server's own price per line where it differed, for the merchant to compare. */\n priceDrift?: Array<{ index: number; rungCents: number; currentCents: number }>\n}\n\n/** Why a sale was refused, and whether the register should keep trying. */\nexport type PosOfflineRefusal =\n | 'wrong-staff'\n | 'wrong-site'\n | 'wrong-workspace'\n | 'invalid'\n | 'conflict'\n | 'failed'\n\n/** What became of one sale the register sent. */\nexport type PosOfflineOutcomeStatus = 'recorded' | 'replayed' | 'refused'\n\n/**\n * One sale's answer, in the order the request sent them. `recorded` and\n * `replayed` carry the order; `refused` carries the reason, the sentence and\n * whether to keep trying. One flat shape, so every register client — the web\n * register and the native apps — decodes it the same way.\n */\nexport interface PosOfflineSaleOutcome {\n saleKey: string\n status: PosOfflineOutcomeStatus\n orderId?: string\n number?: number | null\n flags?: PosOfflineFlag[]\n stockConflicts?: PosOfflineStockConflict[]\n reason?: PosOfflineRefusal\n error?: string\n /** Keep it queued and try again later (a transient failure). */\n retry?: boolean\n}\n\nexport interface PosOfflineSyncRequest {\n hostId: string\n sales: PosOfflineSale[]\n}\n\nexport interface PosOfflineSyncResponse {\n results: PosOfflineSaleOutcome[]\n}\n\n/** One register as the offline kit lists it. */\nexport interface PosOfflineKitRegister {\n id: string\n name: string\n locationId?: string\n openShiftId: string | null\n}\n\n/** What the native app needs to take a card offline on its own reader (AGL-4039). */\nexport interface PosOfflineKitCard {\n available: boolean\n /** Why not, in the cashier's words, when it is not. */\n unavailableReason?: string\n /** The connected account the charge transfers to (a destination charge, as online). */\n destination: string\n currency: string\n /** The limit of one card sale rung offline, in cents: the store's, or Stripe's own ceiling when it set none. */\n maxSaleCents: number\n /** The limit of every card one device takes offline in a day, in cents; 0 is no daily limit. */\n maxDeviceDailyCents: number\n}\n\n/**\n * Everything a register needs to sell offline besides the catalog, from\n * `GET /api/commerce/pos-offline-sync?hostId=`: the server's own answers, so\n * the register and the sync agree about the rate and the rules.\n */\nexport interface PosOfflineKit {\n v: 1\n hostId: string\n orgId: string\n issuedAtMs: number\n /** Offline selling is possible for this site right now. */\n available: boolean\n /** Why not, in the cashier's words, when it is not. */\n unavailableReason?: string\n /** The store's own in-person rate; `null` pct is no tax. */\n tax: { pct: number | null; pricesIncludeTax: boolean }\n maxDiscountPct: number\n requireOpenShift: boolean\n registers: PosOfflineKitRegister[]\n /** Card sales on the app's own reader while offline; absent from a server that does not take them. */\n card?: PosOfflineKitCard\n /** What the receipt prints at its head and foot. `logo` is the stored value. */\n receipt: {\n name: string\n logo?: string\n address?: string\n footer?: string\n returnPolicy?: string\n }\n}\n\n/**\n * The totals of an offline sale: the cashier's discount on the goods, then\n * the store's in-person tax on what is left — exactly the arithmetic the\n * online register's sale route uses for the same basket with no promotion.\n * `pricesIncludeTax` adds nothing, as at the online till.\n */\nexport function posOfflineSaleTotals(input: {\n lines: ReadonlyArray<Pick<PosOfflineSaleLine, 'unitAmountCents' | 'quantity'>>\n discountPct: number\n tax: { pct: number | null; pricesIncludeTax: boolean }\n}): OrderTotals {\n const lines = input.lines.map((line) => ({\n unitAmountCents: wholeCents(line.unitAmountCents),\n quantity: Math.max(1, Math.round(Number(line.quantity) || 1)),\n }))\n const itemsCents = lines.reduce((sum, line) => sum + line.unitAmountCents * line.quantity, 0)\n const pct = Number.isFinite(input.discountPct) ? Math.min(100, Math.max(0, input.discountPct)) : 0\n const discountCents = Math.min(itemsCents, Math.round((itemsCents * pct) / 100))\n const taxPct = Number(input.tax.pct)\n const taxCents =\n taxPct > 0 && !input.tax.pricesIncludeTax ? computeTaxCents(itemsCents - discountCents, taxPct) : 0\n return computeOrderTotals(lines as unknown as OrderLineItem[], { discountCents, taxCents, feeCents: 0 })\n}\n\n/** Change for cash handed over, never negative. */\nexport function posOfflineChangeCents(totalCents: number, tenderedCents: number): number {\n return Math.max(0, wholeCents(tenderedCents) - wholeCents(totalCents))\n}\n\n/** A key the register minted: an id the order can take, and nothing else. */\nexport function posOfflineSaleKeyIsValid(key: unknown): key is string {\n return typeof key === 'string' && /^[A-Za-z0-9_-]{16,64}$/.test(key) && !/^__.*__$/.test(key)\n}\n\n/**\n * The sale's time as the server records it: the device's clock when it is\n * believable, clamped to now when it runs ahead, and to the oldest believable\n * moment when it claims to be older.\n */\nexport function posOfflineSoldAt(\n soldAtMs: number,\n nowMs: number,\n): { atMs: number; adjusted: boolean } {\n const at = Number(soldAtMs)\n if (!Number.isFinite(at) || at <= 0) return { atMs: nowMs, adjusted: true }\n if (at > nowMs + POS_OFFLINE_CLOCK_SKEW_MS) return { atMs: nowMs, adjusted: true }\n if (at < nowMs - POS_OFFLINE_MAX_AGE_MS) return { atMs: nowMs, adjusted: true }\n return { atMs: Math.min(at, nowMs), adjusted: false }\n}\n\nconst bounded = (value: unknown, max: number): string =>\n typeof value === 'string' ? value.trim().slice(0, max) : ''\n\nconst cleanId = (value: unknown): string => {\n const id = String(value ?? '').trim()\n return /^[A-Za-z0-9_-]{1,128}$/.test(id) && !/^__.*__$/.test(id) ? id : ''\n}\n\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 * A queued sale read off the wire, every field typed and bounded — or the\n * sentence that says why it cannot be read at all. Pure, so the server and\n * any client that wants to check its own queue answer the same.\n */\nexport function sanitizePosOfflineSale(\n raw: unknown,\n): { ok: true; sale: PosOfflineSale } | { ok: false; error: string } {\n if (!raw || typeof raw !== 'object') return { ok: false, error: 'Not a sale' }\n const input = raw as Record<string, any>\n const saleKey = input['saleKey']\n if (!posOfflineSaleKeyIsValid(saleKey)) return { ok: false, error: 'The sale has no valid key' }\n const hostId = cleanId(input['hostId'])\n const registerId = cleanId(input['registerId'])\n const signedInUid = bounded(input['signedInUid'], 128)\n if (!hostId || !registerId || !signedInUid) {\n return { ok: false, error: 'The sale is missing its site, register or member' }\n }\n const rawLines = Array.isArray(input['lines']) ? input['lines'] : []\n if (rawLines.length === 0 || rawLines.length > POS_OFFLINE_SALE_MAX_LINES) {\n return { ok: false, error: 'The sale has no lines' }\n }\n const lines: PosOfflineSaleLine[] = []\n for (const line of rawLines) {\n const productId = cleanId(line?.productId)\n const name = bounded(line?.name, 200)\n const unit = Math.round(Number(line?.unitAmountCents))\n const quantity = Math.round(Number(line?.quantity))\n if (!productId || !name || !Number.isFinite(unit) || unit < 0 || !(quantity >= 1 && quantity <= 99)) {\n return { ok: false, error: 'A line of the sale cannot be read' }\n }\n const variantId = cleanId(line?.variantId)\n const variantLabel = bounded(line?.variantLabel, 200)\n const sku = bounded(line?.sku, 100)\n const productType = ['physical', 'digital', 'service'].includes(line?.productType)\n ? (line.productType as PosOfflineSaleLine['productType'])\n : undefined\n const modifiers = Array.isArray(line?.modifiers)\n ? (line.modifiers as unknown[])\n .slice(0, 50)\n .map((pick: any) => ({ groupId: cleanId(pick?.groupId), optionId: cleanId(pick?.optionId) }))\n .filter((pick) => pick.groupId && pick.optionId)\n : []\n lines.push({\n productId,\n ...(variantId ? { variantId } : {}),\n name,\n ...(variantLabel ? { variantLabel } : {}),\n ...(sku ? { sku } : {}),\n ...(productType ? { productType } : {}),\n quantity,\n unitAmountCents: unit,\n ...(modifiers.length ? { modifiers } : {}),\n })\n }\n const discountPct = Number(input['discountPct'] ?? 0)\n if (!Number.isFinite(discountPct) || discountPct < 0 || discountPct > 100) {\n return { ok: false, error: 'The sale’s discount cannot be read' }\n }\n const totals = (input['totals'] ?? {}) as Record<string, unknown>\n const customerRaw = input['customer']\n const customer: PosOfflineSaleCustomer = {}\n if (customerRaw && typeof customerRaw === 'object') {\n const email = bounded((customerRaw as any).email, 254).toLowerCase()\n if (email && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)) customer.email = email\n const name = bounded((customerRaw as any).name, 200)\n if (name) customer.name = name\n const kind = bounded((customerRaw as any).kind, 40)\n const id = cleanId((customerRaw as any).id)\n if (kind && kind !== 'none' && id) {\n customer.kind = kind\n customer.id = id\n }\n }\n const shiftId = cleanId(input['shiftId'])\n const locationId = cleanId(input['locationId'])\n const assertion = bounded(input['cashierAssertion'], 2000)\n const orgId = bounded(input['orgId'], 128)\n let card: PosOfflineSaleCard | undefined\n if (input['tender'] === 'card') {\n const read = sanitizePosOfflineSaleCard(input['card'])\n if (!read) return { ok: false, error: 'The card of the sale cannot be read' }\n card = read\n }\n return {\n ok: true,\n sale: {\n v: 1,\n saleKey,\n hostId,\n orgId,\n signedInUid,\n registerId,\n shiftId: shiftId || null,\n ...(locationId ? { locationId } : {}),\n ...(assertion ? { cashierAssertion: assertion } : {}),\n soldAtMs: Number(input['soldAtMs']) || 0,\n lines,\n discountPct,\n totals: {\n itemsCents: wholeCents(totals['itemsCents']),\n shippingCents: 0,\n taxCents: wholeCents(totals['taxCents']),\n discountCents: wholeCents(totals['discountCents']),\n totalCents: wholeCents(totals['totalCents']),\n feeCents: 0,\n },\n cashTenderedCents: card ? 0 : wholeCents(input['cashTenderedCents']),\n changeCents: card ? 0 : wholeCents(input['changeCents']),\n ...(Object.keys(customer).length ? { customer } : {}),\n ...(card ? { tender: 'card' as const, card } : {}),\n },\n }\n}\n\n/**\n * Whether the register's totals are the arithmetic of its own lines: the\n * goods are the lines' sum, the discount is the percentage of them, and the\n * total is goods less discount plus the tax it rang. The tax itself is the\n * register's, and checked against the store's rate separately.\n */\nexport function posOfflineTotalsAddUp(sale: Pick<PosOfflineSale, 'lines' | 'discountPct' | 'totals'>): boolean {\n const expected = posOfflineSaleTotals({\n lines: sale.lines,\n discountPct: sale.discountPct,\n tax: { pct: null, pricesIncludeTax: false },\n })\n return (\n expected.itemsCents === sale.totals.itemsCents &&\n expected.discountCents === sale.totals.discountCents &&\n sale.totals.taxCents <= sale.totals.itemsCents &&\n sale.totals.totalCents === expected.itemsCents - expected.discountCents + sale.totals.taxCents\n )\n}\n\n/** The flags in the order and words the register lists them. */\nexport function posOfflineFlagLabels(flags: readonly PosOfflineFlag[]): string[] {\n return flags.map((flag) => POS_OFFLINE_FLAG_LABELS[flag] ?? flag)\n}\n\nconst CARD_FORWARDS: readonly PosOfflineCardForward[] = ['stored', 'forwarded', 'declined', 'failed']\n\n/** The card side of a queued sale, typed and bounded, or `null` when it cannot be read. */\nexport function sanitizePosOfflineSaleCard(raw: unknown): PosOfflineSaleCard | null {\n if (!raw || typeof raw !== 'object') return null\n const input = raw as Record<string, unknown>\n const forward = input['forward'] as PosOfflineCardForward\n if (!CARD_FORWARDS.includes(forward)) return null\n const intent = typeof input['paymentIntentId'] === 'string' ? input['paymentIntentId'].trim() : ''\n if (intent && !/^pi_[A-Za-z0-9]{8,64}$/.test(intent)) return null\n if (forward === 'forwarded' && !intent) return null\n const brand = bounded(input['brand'], 24)\n const last4 = typeof input['last4'] === 'string' && /^\\d{4}$/.test(input['last4']) ? input['last4'] : ''\n const declineMessage = bounded(input['declineMessage'], 300)\n return {\n forward,\n ...(intent ? { paymentIntentId: intent } : {}),\n ...(brand ? { brand } : {}),\n ...(last4 ? { last4 } : {}),\n ...(declineMessage ? { declineMessage } : {}),\n }\n}\n\n/**\n * Why a card cannot be taken offline for `totalCents`, in the cashier's\n * words, or `null` when it can: the store has not turned it on, the sale is\n * over the store's (or Stripe's) limit per sale, or the cards this device\n * has taken offline today would pass the store's daily limit per device (a\n * daily limit of 0 is none: the store did not set one).\n */\nexport function posOfflineCardRefusal(input: {\n card: Pick<PosOfflineKitCard, 'available' | 'unavailableReason' | 'maxSaleCents' | 'maxDeviceDailyCents'> | null | undefined\n totalCents: number\n /** Every card this device took offline today, in cents. */\n takenTodayCents: number\n}): string | null {\n const card = input.card\n if (!card) return 'Card sales offline need the register to connect once first.'\n if (!card.available) return card.unavailableReason || 'Card sales offline are not turned on for this store.'\n const total = wholeCents(input.totalCents)\n if (total <= 0) return 'There is nothing to charge.'\n const perSale = Math.min(wholeCents(card.maxSaleCents), POS_OFFLINE_CARD_MAX_SALE_CENTS_LIMIT)\n if (total > perSale) return `Offline, a card sale can be up to ${dollars(perSale)}. Take cash, or charge it when you are back online.`\n const today = wholeCents(input.takenTodayCents)\n const daily = wholeCents(card.maxDeviceDailyCents)\n if (daily > 0 && today + total > daily) {\n return `This device has taken ${dollars(today)} in cards offline today, and its daily limit is ${dollars(daily)}. Take cash, or wait for the connection.`\n }\n return null\n}\n\n/**\n * What the app writes on the PaymentIntent it creates for a card rung\n * offline: the sale key (the order's id once it syncs) and where it was rung,\n * so the server can match the charge to the sale even if the sale's own sync\n * never arrives.\n */\nexport function posOfflineCardIntentMetadata(\n sale: Pick<PosOfflineSale, 'saleKey' | 'hostId' | 'orgId' | 'registerId'>,\n): Record<string, string> {\n return {\n kind: POS_OFFLINE_CARD_INTENT_KIND,\n saleKey: sale.saleKey,\n hostId: sale.hostId,\n orgId: sale.orgId,\n registerId: sale.registerId,\n }\n}\n\nfunction dollars(cents: number): string {\n const whole = Math.floor(cents / 100)\n const rest = cents % 100\n return `$${whole.toLocaleString('en-US')}.${String(rest).padStart(2, '0')}`\n}\n"],"names":["computeOrderTotals","computeTaxCents","POS_OFFLINE_CARD_MAX_SALE_CENTS_LIMIT","POS_OFFLINE_SYNC_ROUTE","POS_OFFLINE_SYNC_BATCH_MAX","POS_OFFLINE_SALE_MAX_LINES","POS_OFFLINE_LATE_SYNC_MS","POS_OFFLINE_MAX_AGE_MS","POS_OFFLINE_CLOCK_SKEW_MS","POS_OFFLINE_CARD_RECONNECT_WITHIN_MS","POS_OFFLINE_CARD_INTENT_KIND","POS_OFFLINE_DISABLED_TENDERS","card_present","card_keyed","card_link","gift_card","folio","credit","POS_OFFLINE_FLAG_LABELS","posOfflineSaleTotals","input","lines","map","line","unitAmountCents","wholeCents","quantity","Math","max","round","Number","itemsCents","reduce","sum","pct","isFinite","discountPct","min","discountCents","taxPct","tax","taxCents","pricesIncludeTax","feeCents","posOfflineChangeCents","totalCents","tenderedCents","posOfflineSaleKeyIsValid","key","test","posOfflineSoldAt","soldAtMs","nowMs","at","atMs","adjusted","bounded","value","trim","slice","cleanId","id","String","number","sanitizePosOfflineSale","raw","ok","error","saleKey","hostId","registerId","signedInUid","rawLines","Array","isArray","length","productId","name","unit","variantId","variantLabel","sku","productType","includes","undefined","modifiers","pick","groupId","optionId","filter","push","totals","customerRaw","customer","email","toLowerCase","kind","shiftId","locationId","assertion","orgId","card","read","sanitizePosOfflineSaleCard","sale","v","cashierAssertion","shippingCents","cashTenderedCents","changeCents","Object","keys","tender","posOfflineTotalsAddUp","expected","posOfflineFlagLabels","flags","flag","CARD_FORWARDS","forward","intent","brand","last4","declineMessage","paymentIntentId","posOfflineCardRefusal","available","unavailableReason","total","perSale","maxSaleCents","dollars","today","takenTodayCents","daily","maxDeviceDailyCents","posOfflineCardIntentMetadata","cents","whole","floor","rest","toLocaleString","padStart"],"mappings":"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"}
@@ -15,6 +15,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
15
15
  * See the License for the specific language governing permissions and
16
16
  * limitations under the License.
17
17
  */ import { apportionCents } from "./commerce-orders.js";
18
+ import { withPosHardwareReceiptNotice } from "./pos-hardware-terms.js";
18
19
  import { orderPayments } from "./commerce-pos.js";
19
20
  import { receiptDataFromOrder } from "./commerce-receipt.js";
20
21
  export const POS_TENDER_METHODS = [
@@ -602,8 +603,8 @@ const dollars = (cents)=>cents == null ? '' : (Math.round(cents) / 100).toFixed(
602
603
  tenders: gift ? [] : (_data_tenders = data.tenders) != null ? _data_tenders : [],
603
604
  changeCents: money(data.changeCents),
604
605
  refundedCents: money(data.refundedCents)
605
- }, input.store.footer ? {
606
- footer: input.store.footer
606
+ }, withPosHardwareReceiptNotice(input.store.footer, order) ? {
607
+ footer: withPosHardwareReceiptNotice(input.store.footer, order)
607
608
  } : {}, input.store.returnPolicy ? {
608
609
  returnPolicy: input.store.returnPolicy
609
610
  } : {}, {