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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/cart-extras.d.ts +6 -0
  3. package/src/lib/components/cart-extras.js +4 -1
  4. package/src/lib/components/cart-extras.js.map +1 -1
  5. package/src/lib/components/cart-fulfillment.d.ts +117 -0
  6. package/src/lib/components/cart-fulfillment.js +367 -0
  7. package/src/lib/components/cart-fulfillment.js.map +1 -0
  8. package/src/lib/components/cart.js +52 -10
  9. package/src/lib/components/cart.js.map +1 -1
  10. package/src/lib/components/commerce-console-page.js +42 -1
  11. package/src/lib/components/commerce-console-page.js.map +1 -1
  12. package/src/lib/components/console/host-orders-card.component.js +38 -10
  13. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  14. package/src/lib/components/console/local-delivery-settings-card.component.d.ts +37 -0
  15. package/src/lib/components/console/local-delivery-settings-card.component.js +542 -0
  16. package/src/lib/components/console/local-delivery-settings-card.component.js.map +1 -0
  17. package/src/lib/components/console/locations-card.component.js +177 -3
  18. package/src/lib/components/console/locations-card.component.js.map +1 -1
  19. package/src/lib/components/console/order-detail-dialog.component.js +10 -0
  20. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  21. package/src/lib/components/console/order-local-fulfillment-panel.component.d.ts +44 -0
  22. package/src/lib/components/console/order-local-fulfillment-panel.component.js +265 -0
  23. package/src/lib/components/console/order-local-fulfillment-panel.component.js.map +1 -0
  24. package/src/lib/components/console/order-offline-notice.component.d.ts +12 -0
  25. package/src/lib/components/console/order-offline-notice.component.js +53 -0
  26. package/src/lib/components/console/order-offline-notice.component.js.map +1 -0
  27. package/src/lib/components/console/pickup-delivery-queue-card.component.d.ts +35 -0
  28. package/src/lib/components/console/pickup-delivery-queue-card.component.js +222 -0
  29. package/src/lib/components/console/pickup-delivery-queue-card.component.js.map +1 -0
  30. package/src/lib/components/console/pos/pos-api.d.ts +24 -1
  31. package/src/lib/components/console/pos/pos-api.js +13 -0
  32. package/src/lib/components/console/pos/pos-api.js.map +1 -1
  33. package/src/lib/components/console/pos/pos-item-dialog.component.d.ts +12 -0
  34. package/src/lib/components/console/pos/pos-item-dialog.component.js +10 -8
  35. package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -1
  36. package/src/lib/components/console/pos/pos-kiosk-queue.component.d.ts +22 -0
  37. package/src/lib/components/console/pos/pos-kiosk-queue.component.js +168 -0
  38. package/src/lib/components/console/pos/pos-kiosk-queue.component.js.map +1 -0
  39. package/src/lib/components/console/pos/pos-tender-dialogs.component.d.ts +18 -0
  40. package/src/lib/components/console/pos/pos-tender-dialogs.component.js +116 -1
  41. package/src/lib/components/console/pos/pos-tender-dialogs.component.js.map +1 -1
  42. package/src/lib/components/console/pos/pos-tender-panel.component.d.ts +8 -0
  43. package/src/lib/components/console/pos/pos-tender-panel.component.js +77 -18
  44. package/src/lib/components/console/pos/pos-tender-panel.component.js.map +1 -1
  45. package/src/lib/components/console/pos/use-pos-display.d.ts +5 -2
  46. package/src/lib/components/console/pos/use-pos-display.js +3 -2
  47. package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
  48. package/src/lib/components/console/pos-devices-card.component.js +2 -2
  49. package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
  50. package/src/lib/components/console/pos-display/pairing-screen.d.ts +5 -2
  51. package/src/lib/components/console/pos-display/pairing-screen.js +7 -5
  52. package/src/lib/components/console/pos-display/pairing-screen.js.map +1 -1
  53. package/src/lib/components/console/pos-display/pos-display-api.d.ts +7 -1
  54. package/src/lib/components/console/pos-display/pos-display-api.js +7 -2
  55. package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -1
  56. package/src/lib/components/console/pos-kiosk/kiosk-checkout.d.ts +41 -0
  57. package/src/lib/components/console/pos-kiosk/kiosk-checkout.js +275 -0
  58. package/src/lib/components/console/pos-kiosk/kiosk-checkout.js.map +1 -0
  59. package/src/lib/components/console/pos-kiosk/kiosk-menu.d.ts +32 -0
  60. package/src/lib/components/console/pos-kiosk/kiosk-menu.js +344 -0
  61. package/src/lib/components/console/pos-kiosk/kiosk-menu.js.map +1 -0
  62. package/src/lib/components/console/pos-kiosk/kiosk-staff.d.ts +12 -0
  63. package/src/lib/components/console/pos-kiosk/kiosk-staff.js +257 -0
  64. package/src/lib/components/console/pos-kiosk/kiosk-staff.js.map +1 -0
  65. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.d.ts +114 -0
  66. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js +189 -0
  67. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js.map +1 -0
  68. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.d.ts +16 -0
  69. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js +80 -0
  70. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js.map +1 -0
  71. package/src/lib/components/console/pos-kiosk-page.component.d.ts +28 -0
  72. package/src/lib/components/console/pos-kiosk-page.component.js +791 -0
  73. package/src/lib/components/console/pos-kiosk-page.component.js.map +1 -0
  74. package/src/lib/components/console/pos-offline/pos-offline-api.d.ts +44 -0
  75. package/src/lib/components/console/pos-offline/pos-offline-api.js +69 -0
  76. package/src/lib/components/console/pos-offline/pos-offline-api.js.map +1 -0
  77. package/src/lib/components/console/pos-offline/pos-offline-banner.component.d.ts +7 -0
  78. package/src/lib/components/console/pos-offline/pos-offline-banner.component.js +135 -0
  79. package/src/lib/components/console/pos-offline/pos-offline-banner.component.js.map +1 -0
  80. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.d.ts +32 -0
  81. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js +287 -0
  82. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js.map +1 -0
  83. package/src/lib/components/console/pos-offline/pos-offline-store.d.ts +63 -0
  84. package/src/lib/components/console/pos-offline/pos-offline-store.js +145 -0
  85. package/src/lib/components/console/pos-offline/pos-offline-store.js.map +1 -0
  86. package/src/lib/components/console/pos-offline/register-offline.d.ts +24 -0
  87. package/src/lib/components/console/pos-offline/register-offline.js +24 -0
  88. package/src/lib/components/console/pos-offline/register-offline.js.map +1 -0
  89. package/src/lib/components/console/pos-offline/use-pos-offline.d.ts +85 -0
  90. package/src/lib/components/console/pos-offline/use-pos-offline.js +542 -0
  91. package/src/lib/components/console/pos-offline/use-pos-offline.js.map +1 -0
  92. package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js +72 -17
  93. package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js.map +1 -1
  94. package/src/lib/components/console/pos-page.component.js +174 -27
  95. package/src/lib/components/console/pos-page.component.js.map +1 -1
  96. package/src/lib/components/console/products-hub-card.component.js +35 -10
  97. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  98. package/src/lib/components/console/shipping-settings-card.component.js +1 -1
  99. package/src/lib/components/console/shipping-settings-card.component.js.map +1 -1
  100. package/src/lib/components/console/stock-movements-card.component.js +48 -16
  101. package/src/lib/components/console/stock-movements-card.component.js.map +1 -1
  102. package/src/lib/components/console/store-zones.d.ts +8 -1
  103. package/src/lib/components/console/store-zones.js +11 -1
  104. package/src/lib/components/console/store-zones.js.map +1 -1
  105. package/src/lib/components/order-status.js +66 -0
  106. package/src/lib/components/order-status.js.map +1 -1
  107. package/src/lib/components/product-detail.js +42 -15
  108. package/src/lib/components/product-detail.js.map +1 -1
  109. package/src/lib/constants/local-fulfillment-list-query.d.ts +48 -0
  110. package/src/lib/constants/local-fulfillment-list-query.js +84 -0
  111. package/src/lib/constants/local-fulfillment-list-query.js.map +1 -0
  112. package/src/lib/constants/orders-list-query.d.ts +7 -2
  113. package/src/lib/constants/orders-list-query.js +89 -8
  114. package/src/lib/constants/orders-list-query.js.map +1 -1
  115. package/src/lib/constants/pos-shift-list-query.d.ts +22 -0
  116. package/src/lib/constants/pos-shift-list-query.js +115 -0
  117. package/src/lib/constants/pos-shift-list-query.js.map +1 -0
  118. package/src/lib/constants/product-list-query.d.ts +6 -1
  119. package/src/lib/constants/product-list-query.js +77 -8
  120. package/src/lib/constants/product-list-query.js.map +1 -1
  121. package/src/lib/constants/stock-movement-query.d.ts +3 -2
  122. package/src/lib/constants/stock-movement-query.js +52 -8
  123. package/src/lib/constants/stock-movement-query.js.map +1 -1
  124. package/src/lib/model/buyer-notifications.d.ts +5 -3
  125. package/src/lib/model/buyer-notifications.js +25 -4
  126. package/src/lib/model/buyer-notifications.js.map +1 -1
  127. package/src/lib/model/commerce-orders.d.ts +59 -0
  128. package/src/lib/model/commerce-orders.js.map +1 -1
  129. package/src/lib/model/commerce-pos-kiosk.d.ts +175 -0
  130. package/src/lib/model/commerce-pos-kiosk.js +147 -0
  131. package/src/lib/model/commerce-pos-kiosk.js.map +1 -0
  132. package/src/lib/model/commerce-pos-offline.d.ts +234 -0
  133. package/src/lib/model/commerce-pos-offline.js +301 -0
  134. package/src/lib/model/commerce-pos-offline.js.map +1 -0
  135. package/src/lib/model/commerce-pos-ops.d.ts +16 -6
  136. package/src/lib/model/commerce-pos-ops.js +12 -4
  137. package/src/lib/model/commerce-pos-ops.js.map +1 -1
  138. package/src/lib/model/commerce-pos.d.ts +13 -1
  139. package/src/lib/model/commerce-pos.js +5 -4
  140. package/src/lib/model/commerce-pos.js.map +1 -1
  141. package/src/lib/model/commerce-stock-holds.d.ts +24 -3
  142. package/src/lib/model/commerce-stock-holds.js +26 -8
  143. package/src/lib/model/commerce-stock-holds.js.map +1 -1
  144. package/src/lib/model/commerce.d.ts +3 -0
  145. package/src/lib/model/commerce.js.map +1 -1
  146. package/src/lib/model/index.d.ts +4 -0
  147. package/src/lib/model/index.js +4 -0
  148. package/src/lib/model/index.js.map +1 -1
  149. package/src/lib/model/local-fulfillment-settings.d.ts +178 -0
  150. package/src/lib/model/local-fulfillment-settings.js +361 -0
  151. package/src/lib/model/local-fulfillment-settings.js.map +1 -0
  152. package/src/lib/model/order-list-fields.d.ts +10 -1
  153. package/src/lib/model/order-list-fields.js +1 -0
  154. package/src/lib/model/order-list-fields.js.map +1 -1
  155. package/src/lib/model/order-local-fulfillment.d.ts +131 -0
  156. package/src/lib/model/order-local-fulfillment.js +125 -0
  157. package/src/lib/model/order-local-fulfillment.js.map +1 -0
  158. package/src/lib/model/order-shipping-export.js +4 -0
  159. package/src/lib/model/order-shipping-export.js.map +1 -1
  160. package/src/lib/model/order-status-view.d.ts +20 -1
  161. package/src/lib/model/order-status-view.js +60 -6
  162. package/src/lib/model/order-status-view.js.map +1 -1
  163. package/src/lib/plugin-config.js +6 -2
  164. package/src/lib/plugin-config.js.map +1 -1
  165. package/src/lib/plugin.js +8 -0
  166. package/src/lib/plugin.js.map +1 -1
  167. package/src/lib/pos-kiosk-config.d.ts +26 -0
  168. package/src/lib/pos-kiosk-config.js +59 -0
  169. package/src/lib/pos-kiosk-config.js.map +1 -0
  170. package/src/lib/server/api-v1/openapi.js +8 -0
  171. package/src/lib/server/api-v1/openapi.js.map +1 -1
  172. package/src/lib/server/api-v1/order-view.d.ts +19 -0
  173. package/src/lib/server/api-v1/order-view.js +35 -0
  174. package/src/lib/server/api-v1/order-view.js.map +1 -1
  175. package/src/lib/server/billing-webhook.js +238 -87
  176. package/src/lib/server/billing-webhook.js.map +1 -1
  177. package/src/lib/server/cart-checkout.d.ts +2 -0
  178. package/src/lib/server/cart-checkout.js +173 -13
  179. package/src/lib/server/cart-checkout.js.map +1 -1
  180. package/src/lib/server/checkout-extras.js +18 -4
  181. package/src/lib/server/checkout-extras.js.map +1 -1
  182. package/src/lib/server/checkout.js +71 -14
  183. package/src/lib/server/checkout.js.map +1 -1
  184. package/src/lib/server/local-fulfillment-status.d.ts +86 -0
  185. package/src/lib/server/local-fulfillment-status.js +379 -0
  186. package/src/lib/server/local-fulfillment-status.js.map +1 -0
  187. package/src/lib/server/local-fulfillment.d.ts +190 -0
  188. package/src/lib/server/local-fulfillment.js +580 -0
  189. package/src/lib/server/local-fulfillment.js.map +1 -0
  190. package/src/lib/server/membership-register.js +3 -1
  191. package/src/lib/server/membership-register.js.map +1 -1
  192. package/src/lib/server/order-notifications.js +61 -0
  193. package/src/lib/server/order-notifications.js.map +1 -1
  194. package/src/lib/server/pos-display.d.ts +27 -1
  195. package/src/lib/server/pos-display.js +50 -9
  196. package/src/lib/server/pos-display.js.map +1 -1
  197. package/src/lib/server/pos-kiosk-principal.d.ts +29 -0
  198. package/src/lib/server/pos-kiosk-principal.js +42 -0
  199. package/src/lib/server/pos-kiosk-principal.js.map +1 -0
  200. package/src/lib/server/pos-kiosk.d.ts +54 -0
  201. package/src/lib/server/pos-kiosk.js +972 -0
  202. package/src/lib/server/pos-kiosk.js.map +1 -0
  203. package/src/lib/server/pos-offline-sync.d.ts +85 -0
  204. package/src/lib/server/pos-offline-sync.js +765 -0
  205. package/src/lib/server/pos-offline-sync.js.map +1 -0
  206. package/src/lib/server/pos-order.js +22 -4
  207. package/src/lib/server/pos-order.js.map +1 -1
  208. package/src/lib/server/pos-payment.d.ts +6 -0
  209. package/src/lib/server/pos-payment.js +225 -6
  210. package/src/lib/server/pos-payment.js.map +1 -1
  211. package/src/lib/server/pos-print.js +5 -0
  212. package/src/lib/server/pos-print.js.map +1 -1
  213. package/src/lib/server/pos-return.js +27 -0
  214. package/src/lib/server/pos-return.js.map +1 -1
  215. package/src/lib/server/pos-sale.d.ts +6 -0
  216. package/src/lib/server/pos-sale.js +2 -0
  217. package/src/lib/server/pos-sale.js.map +1 -1
  218. package/src/lib/server/pos-shift.js +8 -1
  219. package/src/lib/server/pos-shift.js.map +1 -1
  220. package/src/lib/server/refund.js +3 -0
  221. package/src/lib/server/refund.js.map +1 -1
  222. package/src/lib/server/stock-hold.d.ts +9 -1
  223. package/src/lib/server/stock-hold.js +15 -7
  224. package/src/lib/server/stock-hold.js.map +1 -1
  225. package/src/lib/server.js +16 -0
  226. package/src/lib/server.js.map +1 -1
  227. package/src/lib/tenant-emails.js +161 -0
  228. package/src/lib/tenant-emails.js.map +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/plugins/commerce/src/lib/components/console/pickup-delivery-queue-card.component.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport * as CommerceModel from '../../model'\nimport {\n LOCAL_FULFILLMENT_QUEUE_TABS,\n localFulfillmentQueueQuery,\n} from '../../constants/local-fulfillment-list-query'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport {\n Button,\n MenuItem,\n Stack,\n Tab,\n Tabs,\n TextField,\n Typography,\n} from '@mui/material'\nimport { collection, doc, limit, orderBy, query, where } from 'firebase/firestore'\nimport { useMemo, useState } from 'react'\nimport {\n useFirestore,\n useFirestoreCollection,\n useFirestoreDoc,\n usePagedCollection,\n} from '@aglyn/tenant-feature-instance'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport OrderDetailDialog from './order-detail-dialog.component'\nimport { nextLocalFulfillmentSteps, useLocalFulfillmentStep } from './order-local-fulfillment-panel.component'\n\nexport interface PickupDeliveryQueueCardProps {\n hostId: string\n}\n\n/**\n * The pickup and local delivery queue (AGL-3624): what to prepare, what is\n * waiting at the counter, and what the store's driver takes out, per\n * location, soonest first — each tab ONE Firestore query on the fields every\n * pickup and delivery writer stamps (`constants/local-fulfillment-list-query`),\n * which the native Aglyn app runs too. A row's next step is one click; the\n * order opens for anything more.\n *\n * Draws nothing for a store with no pickup location and no local delivery\n * switched on, so a store that only ships never sees it.\n */\nexport function PickupDeliveryQueueCard(props: PickupDeliveryQueueCardProps) {\n const { hostId } = props\n const firestore = useFirestore()\n const [tab, setTab] = useState<CommerceModel.OrderFulfillmentKey>('pickup_preparing')\n const [locationId, setLocationId] = useState('')\n const [open, setOpen] = useState<(CommerceModel.HostOrder & { $id: string }) | null>(null)\n const { run, busy } = useLocalFulfillmentStep(hostId)\n\n const { data: locationDocs } = useFirestoreCollection<any>(\n () => query(collection(firestore, 'hosts', hostId, 'locations'), limit(25)),\n [firestore, hostId],\n { idField: '$id' },\n )\n const { data: settings } = useFirestoreDoc<any>(\n () => doc(firestore, 'hosts', hostId, 'settings', 'store'),\n [firestore, hostId],\n )\n const pickupOn = (locationDocs ?? []).some(\n (location: any) => CommerceModel.normalizePickupSettings(location?.pickup).enabled,\n )\n const deliveryOn = Boolean(\n CommerceModel.normalizeLocalDeliverySettings(settings?.localDelivery).enabled,\n )\n const tabs = LOCAL_FULFILLMENT_QUEUE_TABS.filter((entry) =>\n entry.method === 'pickup' ? pickupOn : deliveryOn,\n )\n const active = tabs.some((entry) => entry.key === tab) ? tab : (tabs[0]?.key ?? tab)\n const plan = useMemo(() => localFulfillmentQueueQuery(active, locationId || null), [active, locationId])\n const anyOn = tabs.length > 0\n // Paged like every list of something that grows: one tab's query, a page\n // at a time (the native app pages it by `plan.limit`).\n const {\n rows,\n status,\n page,\n setPage,\n pageSize,\n setPageSize,\n hasMore,\n } = usePagedCollection<any>(\n (pageLimit) =>\n anyOn\n ? query(\n collection(firestore, 'hosts', hostId, 'orders'),\n ...plan.where.map((clause) => where(clause.field, clause.op, clause.value as never)),\n orderBy(plan.orderBy.field, plan.orderBy.direction),\n limit(pageLimit),\n )\n : null,\n [firestore, hostId, active, locationId, anyOn],\n { idField: '$id' },\n )\n\n if (!tabs.length) return null\n const orders = (rows ?? []).map((row: any) => ({\n ...CommerceModel.liftLegacyOrder(row),\n $id: String(row.$id),\n })) as Array<CommerceModel.HostOrder & { $id: string }>\n const locations = [...(locationDocs ?? [])].sort((a: any, b: any) =>\n String(a.name ?? '').localeCompare(String(b.name ?? '')),\n )\n\n return (\n <CardDisplay\n header={'Pickup & delivery'}\n help={pluginDocsHelp('commerce', { anchor: '#pickup-and-local-delivery' })}\n HeaderProps={{\n action:\n locations.length > 1 ? (\n <TextField\n select\n label=\"Location\"\n value={locationId}\n onChange={(event) => setLocationId(event.target.value)}\n size=\"small\"\n sx={{ minWidth: 160 }}\n >\n <MenuItem value=\"\">{'All locations'}</MenuItem>\n {locations.map((location: any) => (\n <MenuItem key={location.$id} value={location.$id}>\n {location.name}\n </MenuItem>\n ))}\n </TextField>\n ) : null,\n }}\n contentGutterX\n contentGutterY\n >\n <Tabs\n value={active}\n onChange={(_event, value) => setTab(value)}\n variant=\"scrollable\"\n allowScrollButtonsMobile\n >\n {tabs.map((entry) => (\n <Tab key={entry.key} value={entry.key} label={entry.label} />\n ))}\n </Tabs>\n <Stack spacing={1} sx={{ pt: 1.5 }}>\n {status === 'loading' ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : orders.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Nothing here right now.'}\n </Typography>\n ) : (\n orders.map((order) => {\n const units = (order.lineItems ?? []).reduce((sum, line) => sum + (Number(line.quantity) || 0), 0)\n const when =\n order.fulfillmentMethod === 'local_delivery'\n ? (order.localDelivery?.windowLabel ?? '')\n : order.createdAtMs\n ? `Ordered ${new Date(order.createdAtMs).toLocaleString()}`\n : ''\n const where =\n order.fulfillmentMethod === 'pickup'\n ? (order.pickup?.locationName ?? '')\n : [order.shippingAddress?.line1, order.localDelivery?.postalCode].filter(Boolean).join(', ')\n return (\n <Stack\n key={order.$id}\n direction={{ xs: 'column', sm: 'row' }}\n spacing={1}\n sx={{ alignItems: { sm: 'center' } }}\n >\n <Stack sx={{ flex: 1, minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {`${CommerceModel.formatOrderNumber(order, order.$id)} · ${\n order.customerName || order.customerEmail || 'Guest buyer'\n } · ${units} item${units === 1 ? '' : 's'}`}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {[when, where].filter(Boolean).join(' · ')}\n </Typography>\n </Stack>\n {nextLocalFulfillmentSteps(order)\n .filter((entry) => entry.primary)\n .map((entry) => (\n <Button\n key={entry.step}\n size=\"small\"\n variant=\"outlined\"\n disabled={busy}\n onClick={() => void run(order.$id, entry.step)}\n >\n {entry.label}\n </Button>\n ))}\n <Button size=\"small\" onClick={() => setOpen(order)}>\n {'Open'}\n </Button>\n </Stack>\n )\n })\n )}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={orders.length}\n hasMore={hasMore}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </Stack>\n {open ? <OrderDetailDialog hostId={hostId} order={open} onClose={() => setOpen(null)} /> : null}\n </CardDisplay>\n )\n}\nPickupDeliveryQueueCard.displayName = 'PickupDeliveryQueueCard'\n\nexport default PickupDeliveryQueueCard\n"],"names":["CommerceModel","LOCAL_FULFILLMENT_QUEUE_TABS","localFulfillmentQueueQuery","CardDisplay","pluginDocsHelp","Button","MenuItem","Stack","Tab","Tabs","TextField","Typography","collection","doc","limit","orderBy","query","where","useMemo","useState","useFirestore","useFirestoreCollection","useFirestoreDoc","usePagedCollection","ListPagination","OrderDetailDialog","nextLocalFulfillmentSteps","useLocalFulfillmentStep","PickupDeliveryQueueCard","props","tabs","hostId","firestore","tab","setTab","locationId","setLocationId","open","setOpen","run","busy","data","locationDocs","idField","settings","pickupOn","some","location","normalizePickupSettings","pickup","enabled","deliveryOn","Boolean","normalizeLocalDeliverySettings","localDelivery","filter","entry","method","active","key","plan","anyOn","length","rows","status","page","setPage","pageSize","setPageSize","hasMore","pageLimit","map","clause","field","op","value","direction","orders","row","liftLegacyOrder","$id","String","locations","sort","a","b","name","localeCompare","header","help","anchor","HeaderProps","action","select","label","onChange","event","target","size","sx","minWidth","contentGutterX","contentGutterY","_event","variant","allowScrollButtonsMobile","spacing","pt","color","order","units","lineItems","reduce","sum","line","Number","quantity","when","fulfillmentMethod","windowLabel","createdAtMs","Date","toLocaleString","locationName","shippingAddress","line1","postalCode","join","xs","sm","alignItems","flex","noWrap","formatOrderNumber","customerName","customerEmail","primary","disabled","onClick","step","rowCount","onPageChange","onPageSizeChange","onClose","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;AAEA,YAAYA,mBAAmB,uBAAa;AAC5C,SACEC,4BAA4B,EAC5BC,0BAA0B,QACrB,kDAA8C;AACrD,SAASC,WAAW,QAAQ,uBAAsB;AAClD,SAASC,cAAc,QAAQ,eAAc;AAC7C,SACEC,MAAM,EACNC,QAAQ,EACRC,KAAK,EACLC,GAAG,EACHC,IAAI,EACJC,SAAS,EACTC,UAAU,QACL,gBAAe;AACtB,SAASC,UAAU,EAAEC,GAAG,EAAEC,KAAK,EAAEC,OAAO,EAAEC,KAAK,EAAEC,KAAK,QAAQ,qBAAoB;AAClF,SAASC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AACzC,SACEC,YAAY,EACZC,sBAAsB,EACtBC,eAAe,EACfC,kBAAkB,QACb,iCAAgC;AACvC,SAASC,cAAc,QAAQ,4DAA2D;AAC1F,OAAOC,uBAAuB,qCAAiC;AAC/D,SAASC,yBAAyB,EAAEC,uBAAuB,QAAQ,+CAA2C;AAM9G;;;;;;;;;;CAUC,GACD,OAAO,SAASC,wBAAwBC,KAAmC;;QA0BTC;IAzBhE,MAAM,EAAEC,MAAM,EAAE,GAAGF;IACnB,MAAMG,YAAYZ;IAClB,MAAM,CAACa,KAAKC,OAAO,GAAGf,SAA4C;IAClE,MAAM,CAACgB,YAAYC,cAAc,GAAGjB,SAAS;IAC7C,MAAM,CAACkB,MAAMC,QAAQ,GAAGnB,SAA6D;IACrF,MAAM,EAAEoB,GAAG,EAAEC,IAAI,EAAE,GAAGb,wBAAwBI;IAE9C,MAAM,EAAEU,MAAMC,YAAY,EAAE,GAAGrB,uBAC7B,IAAML,MAAMJ,WAAWoB,WAAW,SAASD,QAAQ,cAAcjB,MAAM,MACvE;QAACkB;QAAWD;KAAO,EACnB;QAAEY,SAAS;IAAM;IAEnB,MAAM,EAAEF,MAAMG,QAAQ,EAAE,GAAGtB,gBACzB,IAAMT,IAAImB,WAAW,SAASD,QAAQ,YAAY,UAClD;QAACC;QAAWD;KAAO;IAErB,MAAMc,WAAW,CAACH,uBAAAA,eAAgB,EAAE,EAAEI,IAAI,CACxC,CAACC,WAAkB/C,cAAcgD,uBAAuB,CAACD,4BAAAA,SAAUE,MAAM,EAAEC,OAAO;IAEpF,MAAMC,aAAaC,QACjBpD,cAAcqD,8BAA8B,CAACT,4BAAAA,SAAUU,aAAa,EAAEJ,OAAO;IAE/E,MAAMpB,OAAO7B,6BAA6BsD,MAAM,CAAC,CAACC,QAChDA,MAAMC,MAAM,KAAK,WAAWZ,WAAWM;IAEzC,MAAMO,SAAS5B,KAAKgB,IAAI,CAAC,CAACU,QAAUA,MAAMG,GAAG,KAAK1B,OAAOA,eAAOH,SAAAA,IAAI,CAAC,EAAE,qBAAPA,OAAS6B,GAAG,mBAAI1B;IAChF,MAAM2B,OAAO1C,QAAQ,IAAMhB,2BAA2BwD,QAAQvB,cAAc,OAAO;QAACuB;QAAQvB;KAAW;IACvG,MAAM0B,QAAQ/B,KAAKgC,MAAM,GAAG;IAC5B,yEAAyE;IACzE,uDAAuD;IACvD,MAAM,EACJC,IAAI,EACJC,MAAM,EACNC,IAAI,EACJC,OAAO,EACPC,QAAQ,EACRC,WAAW,EACXC,OAAO,EACR,GAAG9C,mBACF,CAAC+C,YACCT,QACI7C,MACEJ,WAAWoB,WAAW,SAASD,QAAQ,cACpC6B,KAAK3C,KAAK,CAACsD,GAAG,CAAC,CAACC,SAAWvD,MAAMuD,OAAOC,KAAK,EAAED,OAAOE,EAAE,EAAEF,OAAOG,KAAK,IACzE5D,QAAQ6C,KAAK7C,OAAO,CAAC0D,KAAK,EAAEb,KAAK7C,OAAO,CAAC6D,SAAS,GAClD9D,MAAMwD,cAER,MACN;QAACtC;QAAWD;QAAQ2B;QAAQvB;QAAY0B;KAAM,EAC9C;QAAElB,SAAS;IAAM;IAGnB,IAAI,CAACb,KAAKgC,MAAM,EAAE,OAAO;IACzB,MAAMe,SAAS,CAACd,eAAAA,OAAQ,EAAE,EAAEQ,GAAG,CAAC,CAACO,MAAc,aAC1C9E,cAAc+E,eAAe,CAACD;YACjCE,KAAKC,OAAOH,IAAIE,GAAG;;IAErB,MAAME,YAAY;WAAKxC,uBAAAA,eAAgB,EAAE;KAAE,CAACyC,IAAI,CAAC,CAACC,GAAQC;YACjDD,SAAmCC;eAA1CJ,QAAOG,UAAAA,EAAEE,IAAI,YAANF,UAAU,IAAIG,aAAa,CAACN,QAAOI,UAAAA,EAAEC,IAAI,YAAND,UAAU;;IAGtD,qBACE,MAAClF;QACCqF,QAAQ;QACRC,MAAMrF,eAAe,YAAY;YAAEsF,QAAQ;QAA6B;QACxEC,aAAa;YACXC,QACEV,UAAUpB,MAAM,GAAG,kBACjB,MAACpD;gBACCmF,MAAM;gBACNC,OAAM;gBACNnB,OAAOxC;gBACP4D,UAAU,CAACC,QAAU5D,cAAc4D,MAAMC,MAAM,CAACtB,KAAK;gBACrDuB,MAAK;gBACLC,IAAI;oBAAEC,UAAU;gBAAI;;kCAEpB,KAAC9F;wBAASqE,OAAM;kCAAI;;oBACnBO,UAAUX,GAAG,CAAC,CAACxB,yBACd,KAACzC;4BAA4BqE,OAAO5B,SAASiC,GAAG;sCAC7CjC,SAASuC,IAAI;2BADDvC,SAASiC,GAAG;;iBAK7B;QACR;QACAqB,cAAc;QACdC,cAAc;;0BAEd,KAAC7F;gBACCkE,OAAOjB;gBACPqC,UAAU,CAACQ,QAAQ5B,QAAUzC,OAAOyC;gBACpC6B,SAAQ;gBACRC,wBAAwB;0BAEvB3E,KAAKyC,GAAG,CAAC,CAACf,sBACT,KAAChD;wBAAoBmE,OAAOnB,MAAMG,GAAG;wBAAEmC,OAAOtC,MAAMsC,KAAK;uBAA/CtC,MAAMG,GAAG;;0BAGvB,MAACpD;gBAAMmG,SAAS;gBAAGP,IAAI;oBAAEQ,IAAI;gBAAI;;oBAC9B3C,WAAW,0BACV,KAACrD;wBAAW6F,SAAQ;wBAAQI,OAAM;kCAC/B;yBAED/B,OAAOf,MAAM,KAAK,kBACpB,KAACnD;wBAAW6F,SAAQ;wBAAQI,OAAM;kCAC/B;yBAGH/B,OAAON,GAAG,CAAC,CAACsC;4BACKA;4BAGRA,sBAMAA,eACAA,wBAA8BA;wBAVrC,MAAMC,QAAQ,EAACD,mBAAAA,MAAME,SAAS,YAAfF,mBAAmB,EAAE,EAAEG,MAAM,CAAC,CAACC,KAAKC,OAASD,MAAOE,CAAAA,OAAOD,KAAKE,QAAQ,KAAK,CAAA,GAAI;wBAChG,MAAMC,OACJR,MAAMS,iBAAiB,KAAK,4BACvBT,uBAAAA,MAAMvD,aAAa,qBAAnBuD,qBAAqBU,WAAW,mBAAI,KACrCV,MAAMW,WAAW,GACf,CAAC,QAAQ,EAAE,IAAIC,KAAKZ,MAAMW,WAAW,EAAEE,cAAc,IAAI,GACzD;wBACR,MAAMzG,QACJ4F,MAAMS,iBAAiB,KAAK,qBACvBT,gBAAAA,MAAM5D,MAAM,qBAAZ4D,cAAcc,YAAY,oBAAI,KAC/B;6BAACd,yBAAAA,MAAMe,eAAe,qBAArBf,uBAAuBgB,KAAK;6BAAEhB,wBAAAA,MAAMvD,aAAa,qBAAnBuD,sBAAqBiB,UAAU;yBAAC,CAACvE,MAAM,CAACH,SAAS2E,IAAI,CAAC;wBAC3F,qBACE,MAACxH;4BAECqE,WAAW;gCAAEoD,IAAI;gCAAUC,IAAI;4BAAM;4BACrCvB,SAAS;4BACTP,IAAI;gCAAE+B,YAAY;oCAAED,IAAI;gCAAS;4BAAE;;8CAEnC,MAAC1H;oCAAM4F,IAAI;wCAAEgC,MAAM;wCAAG/B,UAAU;oCAAE;;sDAChC,KAACzF;4CAAW6F,SAAQ;4CAAQ4B,MAAM;sDAC/B,GAAGpI,cAAcqI,iBAAiB,CAACxB,OAAOA,MAAM7B,GAAG,EAAE,GAAG,EACvD6B,MAAMyB,YAAY,IAAIzB,MAAM0B,aAAa,IAAI,cAC9C,GAAG,EAAEzB,MAAM,KAAK,EAAEA,UAAU,IAAI,KAAK,KAAK;;sDAE7C,KAACnG;4CAAW6F,SAAQ;4CAAUI,OAAM;4CAAiBwB,MAAM;sDACxD;gDAACf;gDAAMpG;6CAAM,CAACsC,MAAM,CAACH,SAAS2E,IAAI,CAAC;;;;gCAGvCrG,0BAA0BmF,OACxBtD,MAAM,CAAC,CAACC,QAAUA,MAAMgF,OAAO,EAC/BjE,GAAG,CAAC,CAACf,sBACJ,KAACnD;wCAEC6F,MAAK;wCACLM,SAAQ;wCACRiC,UAAUjG;wCACVkG,SAAS,IAAM,KAAKnG,IAAIsE,MAAM7B,GAAG,EAAExB,MAAMmF,IAAI;kDAE5CnF,MAAMsC,KAAK;uCANPtC,MAAMmF,IAAI;8CASrB,KAACtI;oCAAO6F,MAAK;oCAAQwC,SAAS,IAAMpG,QAAQuE;8CACzC;;;2BA7BEA,MAAM7B,GAAG;oBAiCpB;kCAEF,KAACxD;wBACCyC,MAAMA;wBACNE,UAAUA;wBACVyE,UAAU/D,OAAOf,MAAM;wBACvBO,SAASA;wBACTwE,cAAc3E;wBACd4E,kBAAkB1E;;;;YAGrB/B,qBAAO,KAACZ;gBAAkBM,QAAQA;gBAAQ8E,OAAOxE;gBAAM0G,SAAS,IAAMzG,QAAQ;iBAAY;;;AAGjG;AACAV,wBAAwBoH,WAAW,GAAG;AAEtC,eAAepH,wBAAuB"}
@@ -43,6 +43,8 @@ export interface PosSaleSummary {
43
43
  changeCents?: number;
44
44
  checkoutUrl?: string;
45
45
  readerId?: string;
46
+ /** What a store-credit payment's provider calls it (AGL-3640). */
47
+ creditLabel?: string;
46
48
  failureMessage?: string;
47
49
  livemode?: boolean;
48
50
  }>;
@@ -69,6 +71,20 @@ export interface PosRegisterContext {
69
71
  }>;
70
72
  publishableKey: string;
71
73
  smsReceipts: boolean;
74
+ /** Store credit other plugins keep, taken as a tender (AGL-3640). */
75
+ credits?: Array<{
76
+ providerId: string;
77
+ label: string;
78
+ lookup: boolean;
79
+ }>;
80
+ }
81
+ /** A store-credit account a staff lookup found (AGL-3640). */
82
+ export interface PosCreditAccount {
83
+ reference: string;
84
+ label: string;
85
+ last4: string;
86
+ availableCents: number;
87
+ detail?: string;
72
88
  }
73
89
  type User = Parameters<typeof authorizedFetch>[0];
74
90
  export declare class PosRequestError extends Error {
@@ -96,7 +112,7 @@ export declare function openPosSale(user: User, attemptKey: string, body: {
96
112
  dueCents: number;
97
113
  stockWarnings?: unknown[];
98
114
  }>;
99
- export type PosTenderAction = 'cash' | 'gift-card' | 'folio' | 'card-present' | 'card-present-sdk' | 'card-keyed' | 'card-link' | 'status' | 'cancel' | 'retry' | 'simulate' | 'void' | 'receipt';
115
+ export type PosTenderAction = 'cash' | 'gift-card' | 'credit' | 'folio' | 'card-present' | 'card-present-sdk' | 'card-keyed' | 'card-link' | 'status' | 'cancel' | 'retry' | 'simulate' | 'void' | 'receipt';
100
116
  export declare function posTender(user: User, hostId: string, orderId: string, action: PosTenderAction, body?: Record<string, unknown>, attemptKey?: string): Promise<PosTenderResult>;
101
117
  export declare function posGiftCardBalance(user: User, hostId: string, code: string): Promise<{
102
118
  availableCents: number;
@@ -104,7 +120,14 @@ export declare function posGiftCardBalance(user: User, hostId: string, code: str
104
120
  voided: boolean;
105
121
  last4: string;
106
122
  }>;
123
+ /** Finds store-credit accounts by a customer's email or code (AGL-3640). */
124
+ export declare function posCreditLookup(user: User, hostId: string, providerId: string, query: string): Promise<{
125
+ providerId: string;
126
+ accounts: PosCreditAccount[];
127
+ }>;
107
128
  export declare function posDisplayCall<T = any>(user: User, body: Record<string, unknown>, method?: 'GET' | 'POST'): Promise<T>;
129
+ /** The register's side of the self-service kiosk (AGL-3623): its queue of "pay at counter" orders. */
130
+ export declare function posKioskCall<T = any>(user: User, body: Record<string, unknown>, method?: 'GET' | 'POST'): Promise<T>;
108
131
  export declare function posReadersCall<T = any>(user: User, body: Record<string, unknown>): Promise<T>;
109
132
  /** Cents as the register shows them. */
110
133
  export declare const usd: (cents: number) => string;
@@ -80,11 +80,24 @@ export function posGiftCardBalance(user, hostId, code) {
80
80
  code
81
81
  });
82
82
  }
83
+ /** Finds store-credit accounts by a customer's email or code (AGL-3640). */ export function posCreditLookup(user, hostId, providerId, query) {
84
+ return call(user, '/api/commerce/pos-payment', {
85
+ action: 'credit-lookup',
86
+ hostId,
87
+ providerId,
88
+ query
89
+ });
90
+ }
83
91
  export function posDisplayCall(user, body, method = 'POST') {
84
92
  return call(user, '/api/commerce/pos-display', body, {
85
93
  method
86
94
  });
87
95
  }
96
+ /** The register's side of the self-service kiosk (AGL-3623): its queue of "pay at counter" orders. */ export function posKioskCall(user, body, method = 'POST') {
97
+ return call(user, '/api/commerce/pos-kiosk', body, {
98
+ method
99
+ });
100
+ }
88
101
  export function posReadersCall(user, body) {
89
102
  return call(user, '/api/commerce/pos-readers', body);
90
103
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos/pos-api.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 { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport type * as CommerceModel from '../../../model'\nimport type { PosRegisterSettings } from '../../../plugin-config'\n\n/**\n * The register's client half (AGL-3607): one place that knows the routes, so\n * the components ask \"take cash\" rather than building requests. Every call\n * that starts a payment carries the attempt key it is given; the server\n * derives the payment from it, so a retried press finds the same payment.\n */\n\n/** The open sale as the server reports it after every change. */\nexport interface PosSaleSummary {\n orderId: string\n status: string\n totalCents: number\n paidCents: number\n dueCents: number\n tenderableCents: number\n tipCents: number\n payments: Array<{\n id: string\n method: CommerceModel.OrderPaymentMethod\n amountCents: number\n tipCents?: number\n status: CommerceModel.OrderPaymentStatus\n cardBrand?: string\n last4?: string\n changeCents?: number\n checkoutUrl?: string\n readerId?: string\n failureMessage?: string\n livemode?: boolean\n }>\n}\n\nexport interface PosTenderResult {\n sale: PosSaleSummary\n paymentId?: string\n completed: boolean\n clientSecret?: string\n paymentIntentId?: string\n}\n\nexport interface PosRegisterContext {\n settings: PosRegisterSettings\n terminal: { available: boolean; testMode: boolean }\n readers: Array<{\n id: string\n label: string\n registerId: string | null\n status: string\n livemode: boolean\n }>\n publishableKey: string\n smsReceipts: boolean\n}\n\ntype User = Parameters<typeof authorizedFetch>[0]\n\nexport class PosRequestError extends Error {\n constructor(\n message: string,\n readonly status: number,\n ) {\n super(message)\n }\n}\n\nasync function call<T>(\n user: User,\n route: string,\n body: Record<string, unknown>,\n options: { attemptKey?: string; method?: 'GET' | 'POST' } = {},\n): Promise<T> {\n const method = options.method ?? 'POST'\n const url =\n method === 'GET'\n ? `${route}?${new URLSearchParams(\n Object.entries(body).map(([key, value]) => [key, String(value)]),\n ).toString()}`\n : route\n const response = await authorizedFetch(user, url, {\n method,\n headers: {\n ...(method === 'POST' ? { 'Content-Type': 'application/json' } : {}),\n ...(options.attemptKey ? { 'Idempotency-Key': options.attemptKey } : {}),\n },\n ...(method === 'POST' ? { body: JSON.stringify(body) } : {}),\n })\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n throw new PosRequestError(String(payload?.error ?? 'Something went wrong'), response.status)\n }\n return payload as T\n}\n\n/** A fresh attempt key, for one press of one tender. */\nexport function newAttemptKey(): string {\n return (\n globalThis.crypto?.randomUUID?.() ??\n `${Date.now()}-${Math.random().toString(36).slice(2)}`\n )\n}\n\nexport function posRegisterContext(user: User, hostId: string) {\n return call<PosRegisterContext>(user, '/api/commerce/pos-payment', { action: 'context', hostId }, { method: 'GET' })\n}\n\n/** Prices the basket on the server and opens a sale with an empty ledger. */\nexport function openPosSale(\n user: User,\n attemptKey: string,\n body: {\n hostId: string\n registerId: string\n lines: unknown[]\n discountPct: number\n customerEmail?: string\n /** The person the register's customer lookup attached (AGL-3609). */\n customer?: unknown\n /** A PIN-switched cashier's signed assertion (AGL-3609). */\n cashierAssertion?: string\n locationId?: string\n },\n) {\n return call<{ orderId: string; totals: CommerceModel.OrderTotals; dueCents: number; stockWarnings?: unknown[] }>(\n user,\n '/api/commerce/pos-order',\n { ...body, payment: 'open' },\n { attemptKey },\n )\n}\n\nexport type PosTenderAction =\n | 'cash'\n | 'gift-card'\n | 'folio'\n | 'card-present'\n | 'card-present-sdk'\n | 'card-keyed'\n | 'card-link'\n | 'status'\n | 'cancel'\n | 'retry'\n | 'simulate'\n | 'void'\n | 'receipt'\n\nexport function posTender(\n user: User,\n hostId: string,\n orderId: string,\n action: PosTenderAction,\n body: Record<string, unknown> = {},\n attemptKey?: string,\n) {\n return call<PosTenderResult>(\n user,\n '/api/commerce/pos-payment',\n { ...body, action, hostId, orderId },\n attemptKey ? { attemptKey } : {},\n )\n}\n\nexport function posGiftCardBalance(user: User, hostId: string, code: string) {\n return call<{ availableCents: number; frozen: boolean; voided: boolean; last4: string }>(\n user,\n '/api/commerce/pos-payment',\n { action: 'gift-card-balance', hostId, code },\n )\n}\n\nexport function posDisplayCall<T = any>(\n user: User,\n body: Record<string, unknown>,\n method: 'GET' | 'POST' = 'POST',\n) {\n return call<T>(user, '/api/commerce/pos-display', body, { method })\n}\n\nexport function posReadersCall<T = any>(user: User, body: Record<string, unknown>) {\n return call<T>(user, '/api/commerce/pos-readers', body)\n}\n\n/** Cents as the register shows them. */\nexport const usd = (cents: number) => `$${(Math.max(0, cents) / 100).toFixed(2)}`\n\n/** A dollar string the cashier typed, as whole cents (NaN-safe, never negative). */\nexport function centsFromInput(value: string): number {\n const number = Math.round(Number(String(value).replace(/[^0-9.]/g, '')) * 100)\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n/*==========================================\n * THE NATIVE BRIDGE (AGL-3618).\n *\n * The POS companion app loads this register in a WebView and injects\n * `window.AglynPosBridge`. When it is there, the register offers a third\n * card option — Tap to Pay or a Bluetooth reader — and hands the app a\n * PaymentIntent the SERVER created (`card-present-sdk`); the app collects\n * the card with the Stripe Terminal SDK and the register settles it exactly\n * like a smart-reader payment, by asking the server.\n *=========================================*/\n\nexport interface AglynPosBridge {\n collectCardPayment(input: {\n paymentIntentId: string\n clientSecret: string\n amountCents: number\n }): Promise<{ status: 'collected' | 'canceled' | 'failed'; message?: string }>\n}\n\nexport function posNativeBridge(): AglynPosBridge | null {\n const bridge = (globalThis as { AglynPosBridge?: AglynPosBridge }).AglynPosBridge\n return bridge && typeof bridge.collectCardPayment === 'function' ? bridge : null\n}\n"],"names":["authorizedFetch","PosRequestError","Error","message","status","call","user","route","body","options","method","url","URLSearchParams","Object","entries","map","key","value","String","toString","response","headers","attemptKey","JSON","stringify","payload","json","catch","ok","error","newAttemptKey","globalThis","crypto","randomUUID","Date","now","Math","random","slice","posRegisterContext","hostId","action","openPosSale","payment","posTender","orderId","posGiftCardBalance","code","posDisplayCall","posReadersCall","usd","cents","max","toFixed","centsFromInput","number","round","Number","replace","isFinite","posNativeBridge","bridge","AglynPosBridge","collectCardPayment"],"mappings":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,eAAe,QAAQ,2CAA0C;AA4D1E,OAAO,MAAMC,wBAAwBC;IACnC,YACEC,OAAe,EACf,AAASC,MAAc,CACvB;QACA,KAAK,CAACD,eAFGC,SAAAA;IAGX;AACF;AAEA,eAAeC,KACbC,IAAU,EACVC,KAAa,EACbC,IAA6B,EAC7BC,UAA4D,CAAC,CAAC;QAE/CA;IAAf,MAAMC,UAASD,kBAAAA,QAAQC,MAAM,YAAdD,kBAAkB;IACjC,MAAME,MACJD,WAAW,QACP,GAAGH,MAAM,CAAC,EAAE,IAAIK,gBACdC,OAAOC,OAAO,CAACN,MAAMO,GAAG,CAAC,CAAC,CAACC,KAAKC,MAAM,GAAK;YAACD;YAAKE,OAAOD;SAAO,GAC/DE,QAAQ,IAAI,GACdZ;IACN,MAAMa,WAAW,MAAMpB,gBAAgBM,MAAMK,KAAK;QAChDD;QACAW,SAAS,aACHX,WAAW,SAAS;YAAE,gBAAgB;QAAmB,IAAI,CAAC,GAC9DD,QAAQa,UAAU,GAAG;YAAE,mBAAmBb,QAAQa,UAAU;QAAC,IAAI,CAAC;OAEpEZ,WAAW,SAAS;QAAEF,MAAMe,KAAKC,SAAS,CAAChB;IAAM,IAAI,CAAC;IAE5D,MAAMiB,UAAU,MAAML,SAASM,IAAI,GAAGC,KAAK,CAAC,IAAO,CAAA,CAAC,CAAA;IACpD,IAAI,CAACP,SAASQ,EAAE,EAAE;;QAChB,MAAM,IAAI3B,gBAAgBiB,eAAOO,2BAAAA,QAASI,KAAK,mBAAI,yBAAyBT,SAAShB,MAAM;IAC7F;IACA,OAAOqB;AACT;AAEA,sDAAsD,GACtD,OAAO,SAASK;;QAEZC,+BAAAA;IADF,gBACEA,qBAAAA,WAAWC,MAAM,sBAAjBD,gCAAAA,mBAAmBE,UAAU,qBAA7BF,mCAAAA,sCACA,GAAGG,KAAKC,GAAG,GAAG,CAAC,EAAEC,KAAKC,MAAM,GAAGlB,QAAQ,CAAC,IAAImB,KAAK,CAAC,IAAI;AAE1D;AAEA,OAAO,SAASC,mBAAmBjC,IAAU,EAAEkC,MAAc;IAC3D,OAAOnC,KAAyBC,MAAM,6BAA6B;QAAEmC,QAAQ;QAAWD;IAAO,GAAG;QAAE9B,QAAQ;IAAM;AACpH;AAEA,2EAA2E,GAC3E,OAAO,SAASgC,YACdpC,IAAU,EACVgB,UAAkB,EAClBd,IAWC;IAED,OAAOH,KACLC,MACA,2BACA,aAAKE;QAAMmC,SAAS;QACpB;QAAErB;IAAW;AAEjB;AAiBA,OAAO,SAASsB,UACdtC,IAAU,EACVkC,MAAc,EACdK,OAAe,EACfJ,MAAuB,EACvBjC,OAAgC,CAAC,CAAC,EAClCc,UAAmB;IAEnB,OAAOjB,KACLC,MACA,6BACA,aAAKE;QAAMiC;QAAQD;QAAQK;QAC3BvB,aAAa;QAAEA;IAAW,IAAI,CAAC;AAEnC;AAEA,OAAO,SAASwB,mBAAmBxC,IAAU,EAAEkC,MAAc,EAAEO,IAAY;IACzE,OAAO1C,KACLC,MACA,6BACA;QAAEmC,QAAQ;QAAqBD;QAAQO;IAAK;AAEhD;AAEA,OAAO,SAASC,eACd1C,IAAU,EACVE,IAA6B,EAC7BE,SAAyB,MAAM;IAE/B,OAAOL,KAAQC,MAAM,6BAA6BE,MAAM;QAAEE;IAAO;AACnE;AAEA,OAAO,SAASuC,eAAwB3C,IAAU,EAAEE,IAA6B;IAC/E,OAAOH,KAAQC,MAAM,6BAA6BE;AACpD;AAEA,sCAAsC,GACtC,OAAO,MAAM0C,MAAM,CAACC,QAAkB,CAAC,CAAC,EAAE,AAACf,CAAAA,KAAKgB,GAAG,CAAC,GAAGD,SAAS,GAAE,EAAGE,OAAO,CAAC,IAAI,CAAA;AAEjF,kFAAkF,GAClF,OAAO,SAASC,eAAerC,KAAa;IAC1C,MAAMsC,SAASnB,KAAKoB,KAAK,CAACC,OAAOvC,OAAOD,OAAOyC,OAAO,CAAC,YAAY,OAAO;IAC1E,OAAOD,OAAOE,QAAQ,CAACJ,WAAWA,SAAS,IAAIA,SAAS;AAC1D;AAqBA,OAAO,SAASK;IACd,MAAMC,SAAS,AAAC9B,WAAmD+B,cAAc;IACjF,OAAOD,UAAU,OAAOA,OAAOE,kBAAkB,KAAK,aAAaF,SAAS;AAC9E"}
1
+ {"version":3,"sources":["../../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos/pos-api.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 { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport type * as CommerceModel from '../../../model'\nimport type { PosRegisterSettings } from '../../../plugin-config'\n\n/**\n * The register's client half (AGL-3607): one place that knows the routes, so\n * the components ask \"take cash\" rather than building requests. Every call\n * that starts a payment carries the attempt key it is given; the server\n * derives the payment from it, so a retried press finds the same payment.\n */\n\n/** The open sale as the server reports it after every change. */\nexport interface PosSaleSummary {\n orderId: string\n status: string\n totalCents: number\n paidCents: number\n dueCents: number\n tenderableCents: number\n tipCents: number\n payments: Array<{\n id: string\n method: CommerceModel.OrderPaymentMethod\n amountCents: number\n tipCents?: number\n status: CommerceModel.OrderPaymentStatus\n cardBrand?: string\n last4?: string\n changeCents?: number\n checkoutUrl?: string\n readerId?: string\n /** What a store-credit payment's provider calls it (AGL-3640). */\n creditLabel?: string\n failureMessage?: string\n livemode?: boolean\n }>\n}\n\nexport interface PosTenderResult {\n sale: PosSaleSummary\n paymentId?: string\n completed: boolean\n clientSecret?: string\n paymentIntentId?: string\n}\n\nexport interface PosRegisterContext {\n settings: PosRegisterSettings\n terminal: { available: boolean; testMode: boolean }\n readers: Array<{\n id: string\n label: string\n registerId: string | null\n status: string\n livemode: boolean\n }>\n publishableKey: string\n smsReceipts: boolean\n /** Store credit other plugins keep, taken as a tender (AGL-3640). */\n credits?: Array<{ providerId: string; label: string; lookup: boolean }>\n}\n\n/** A store-credit account a staff lookup found (AGL-3640). */\nexport interface PosCreditAccount {\n reference: string\n label: string\n last4: string\n availableCents: number\n detail?: string\n}\n\ntype User = Parameters<typeof authorizedFetch>[0]\n\nexport class PosRequestError extends Error {\n constructor(\n message: string,\n readonly status: number,\n ) {\n super(message)\n }\n}\n\nasync function call<T>(\n user: User,\n route: string,\n body: Record<string, unknown>,\n options: { attemptKey?: string; method?: 'GET' | 'POST' } = {},\n): Promise<T> {\n const method = options.method ?? 'POST'\n const url =\n method === 'GET'\n ? `${route}?${new URLSearchParams(\n Object.entries(body).map(([key, value]) => [key, String(value)]),\n ).toString()}`\n : route\n const response = await authorizedFetch(user, url, {\n method,\n headers: {\n ...(method === 'POST' ? { 'Content-Type': 'application/json' } : {}),\n ...(options.attemptKey ? { 'Idempotency-Key': options.attemptKey } : {}),\n },\n ...(method === 'POST' ? { body: JSON.stringify(body) } : {}),\n })\n const payload = await response.json().catch(() => ({}))\n if (!response.ok) {\n throw new PosRequestError(String(payload?.error ?? 'Something went wrong'), response.status)\n }\n return payload as T\n}\n\n/** A fresh attempt key, for one press of one tender. */\nexport function newAttemptKey(): string {\n return (\n globalThis.crypto?.randomUUID?.() ??\n `${Date.now()}-${Math.random().toString(36).slice(2)}`\n )\n}\n\nexport function posRegisterContext(user: User, hostId: string) {\n return call<PosRegisterContext>(user, '/api/commerce/pos-payment', { action: 'context', hostId }, { method: 'GET' })\n}\n\n/** Prices the basket on the server and opens a sale with an empty ledger. */\nexport function openPosSale(\n user: User,\n attemptKey: string,\n body: {\n hostId: string\n registerId: string\n lines: unknown[]\n discountPct: number\n customerEmail?: string\n /** The person the register's customer lookup attached (AGL-3609). */\n customer?: unknown\n /** A PIN-switched cashier's signed assertion (AGL-3609). */\n cashierAssertion?: string\n locationId?: string\n },\n) {\n return call<{ orderId: string; totals: CommerceModel.OrderTotals; dueCents: number; stockWarnings?: unknown[] }>(\n user,\n '/api/commerce/pos-order',\n { ...body, payment: 'open' },\n { attemptKey },\n )\n}\n\nexport type PosTenderAction =\n | 'cash'\n | 'gift-card'\n | 'credit'\n | 'folio'\n | 'card-present'\n | 'card-present-sdk'\n | 'card-keyed'\n | 'card-link'\n | 'status'\n | 'cancel'\n | 'retry'\n | 'simulate'\n | 'void'\n | 'receipt'\n\nexport function posTender(\n user: User,\n hostId: string,\n orderId: string,\n action: PosTenderAction,\n body: Record<string, unknown> = {},\n attemptKey?: string,\n) {\n return call<PosTenderResult>(\n user,\n '/api/commerce/pos-payment',\n { ...body, action, hostId, orderId },\n attemptKey ? { attemptKey } : {},\n )\n}\n\nexport function posGiftCardBalance(user: User, hostId: string, code: string) {\n return call<{ availableCents: number; frozen: boolean; voided: boolean; last4: string }>(\n user,\n '/api/commerce/pos-payment',\n { action: 'gift-card-balance', hostId, code },\n )\n}\n\n/** Finds store-credit accounts by a customer's email or code (AGL-3640). */\nexport function posCreditLookup(user: User, hostId: string, providerId: string, query: string) {\n return call<{ providerId: string; accounts: PosCreditAccount[] }>(user, '/api/commerce/pos-payment', {\n action: 'credit-lookup',\n hostId,\n providerId,\n query,\n })\n}\n\nexport function posDisplayCall<T = any>(\n user: User,\n body: Record<string, unknown>,\n method: 'GET' | 'POST' = 'POST',\n) {\n return call<T>(user, '/api/commerce/pos-display', body, { method })\n}\n\n/** The register's side of the self-service kiosk (AGL-3623): its queue of \"pay at counter\" orders. */\nexport function posKioskCall<T = any>(\n user: User,\n body: Record<string, unknown>,\n method: 'GET' | 'POST' = 'POST',\n) {\n return call<T>(user, '/api/commerce/pos-kiosk', body, { method })\n}\n\nexport function posReadersCall<T = any>(user: User, body: Record<string, unknown>) {\n return call<T>(user, '/api/commerce/pos-readers', body)\n}\n\n/** Cents as the register shows them. */\nexport const usd = (cents: number) => `$${(Math.max(0, cents) / 100).toFixed(2)}`\n\n/** A dollar string the cashier typed, as whole cents (NaN-safe, never negative). */\nexport function centsFromInput(value: string): number {\n const number = Math.round(Number(String(value).replace(/[^0-9.]/g, '')) * 100)\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n/*==========================================\n * THE NATIVE BRIDGE (AGL-3618).\n *\n * The POS companion app loads this register in a WebView and injects\n * `window.AglynPosBridge`. When it is there, the register offers a third\n * card option — Tap to Pay or a Bluetooth reader — and hands the app a\n * PaymentIntent the SERVER created (`card-present-sdk`); the app collects\n * the card with the Stripe Terminal SDK and the register settles it exactly\n * like a smart-reader payment, by asking the server.\n *=========================================*/\n\nexport interface AglynPosBridge {\n collectCardPayment(input: {\n paymentIntentId: string\n clientSecret: string\n amountCents: number\n }): Promise<{ status: 'collected' | 'canceled' | 'failed'; message?: string }>\n}\n\nexport function posNativeBridge(): AglynPosBridge | null {\n const bridge = (globalThis as { AglynPosBridge?: AglynPosBridge }).AglynPosBridge\n return bridge && typeof bridge.collectCardPayment === 'function' ? bridge : null\n}\n"],"names":["authorizedFetch","PosRequestError","Error","message","status","call","user","route","body","options","method","url","URLSearchParams","Object","entries","map","key","value","String","toString","response","headers","attemptKey","JSON","stringify","payload","json","catch","ok","error","newAttemptKey","globalThis","crypto","randomUUID","Date","now","Math","random","slice","posRegisterContext","hostId","action","openPosSale","payment","posTender","orderId","posGiftCardBalance","code","posCreditLookup","providerId","query","posDisplayCall","posKioskCall","posReadersCall","usd","cents","max","toFixed","centsFromInput","number","round","Number","replace","isFinite","posNativeBridge","bridge","AglynPosBridge","collectCardPayment"],"mappings":"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"}
@@ -20,6 +20,18 @@ export interface PosItemDialogProps {
20
20
  onConfirm: (choice: PosItemChoice) => void;
21
21
  /** Editing only: takes the line out of the basket. */
22
22
  onRemove?: () => void;
23
+ /**
24
+ * How a price reads; the register's dollars by default. The self-service
25
+ * kiosk (AGL-3623) passes the store's currency.
26
+ */
27
+ formatMoney?: (cents: number) => string;
28
+ /**
29
+ * A sold-out variant cannot be added, rather than warned about: a kiosk's
30
+ * customer is not holding the goods the way a cashier is (AGL-3623).
31
+ */
32
+ blockSoldOut?: boolean;
33
+ /** The most of one line that can be added; 99 at the register. */
34
+ maxQuantity?: number;
23
35
  }
24
36
  /** Whether the register must ask before adding: a choice of variant or any modifier. */
25
37
  export declare function posItemNeedsChoice(product: CommerceModel.HostProduct): boolean;
@@ -52,8 +52,10 @@ function soldOut(variant) {
52
52
  * The price shown is the register's estimate. The server prices the line
53
53
  * again from the product when the sale is charged.
54
54
  */ export function PosItemDialog(props) {
55
- var _ref;
55
+ var _props_formatMoney, _props_maxQuantity, _ref;
56
56
  const { product, initial } = props;
57
+ const money = (_props_formatMoney = props.formatMoney) != null ? _props_formatMoney : usd;
58
+ const maxQuantity = (_props_maxQuantity = props.maxQuantity) != null ? _props_maxQuantity : 99;
57
59
  const theme = useTheme();
58
60
  const phone = useMediaQuery(theme.breakpoints.down('sm'));
59
61
  const groups = useMemo(()=>product ? CommerceModel.productModifierGroups(product) : [], [
@@ -81,7 +83,7 @@ function soldOut(variant) {
81
83
  const chosen = CommerceModel.resolveLineModifiers(product, modifiers);
82
84
  const unitCents = variant ? Math.round(Number(variant.priceUsd) * 100) + (chosen.ok ? chosen.extraCents : 0) : 0;
83
85
  const missing = groups.find((group)=>modifiers.filter((pick)=>pick.groupId === group.id).length < group.min);
84
- const canAdd = Boolean(variant) && chosen.ok && quantity >= 1;
86
+ const canAdd = Boolean(variant) && chosen.ok && quantity >= 1 && !(props.blockSoldOut && soldOut(variant));
85
87
  const toggle = (group, optionId)=>{
86
88
  setModifiers((current)=>{
87
89
  const inGroup = current.filter((pick)=>pick.groupId === group.id);
@@ -176,7 +178,7 @@ function soldOut(variant) {
176
178
  minHeight: POS_TOUCH_PX,
177
179
  minWidth: 88
178
180
  },
179
- children: `${variantLabel(item)} · ${usd(Math.round(Number(item.priceUsd) * 100))}`
181
+ children: `${variantLabel(item)} · ${money(Math.round(Number(item.priceUsd) * 100))}`
180
182
  }, item.id))
181
183
  })
182
184
  }) : null,
@@ -188,7 +190,7 @@ function soldOut(variant) {
188
190
  variant && soldOut(variant) ? /*#__PURE__*/ _jsx(Typography, {
189
191
  variant: "body2",
190
192
  color: "warning.main",
191
- children: 'Out of stock: selling it takes the count below zero.'
193
+ children: props.blockSoldOut ? 'Sold out' : 'Out of stock: selling it takes the count below zero.'
192
194
  }) : null,
193
195
  groups.map((group)=>{
194
196
  const count = modifiers.filter((pick)=>pick.groupId === group.id).length;
@@ -226,7 +228,7 @@ function soldOut(variant) {
226
228
  children: group.options.map((option)=>{
227
229
  const selected = modifiers.some((pick)=>pick.groupId === group.id && pick.optionId === option.id);
228
230
  return /*#__PURE__*/ _jsx(Chip, {
229
- label: option.priceCents ? `${option.name} +${usd(option.priceCents)}` : option.name,
231
+ label: option.priceCents ? `${option.name} +${money(option.priceCents)}` : option.name,
230
232
  color: selected ? 'primary' : 'default',
231
233
  variant: selected ? 'filled' : 'outlined',
232
234
  "aria-pressed": selected,
@@ -272,7 +274,7 @@ function soldOut(variant) {
272
274
  value: quantity,
273
275
  onChange: (event)=>{
274
276
  const next = Math.round(Number(event.target.value.replace(/\D/g, '')));
275
- setQuantity(Math.max(1, Math.min(99, next || 1)));
277
+ setQuantity(Math.max(1, Math.min(maxQuantity, next || 1)));
276
278
  },
277
279
  sx: {
278
280
  width: 72
@@ -289,7 +291,7 @@ function soldOut(variant) {
289
291
  }),
290
292
  /*#__PURE__*/ _jsx(IconButton, {
291
293
  "aria-label": "One more",
292
- onClick: ()=>setQuantity((value)=>Math.min(99, value + 1)),
294
+ onClick: ()=>setQuantity((value)=>Math.min(maxQuantity, value + 1)),
293
295
  sx: {
294
296
  width: POS_TOUCH_PX,
295
297
  height: POS_TOUCH_PX,
@@ -340,7 +342,7 @@ function soldOut(variant) {
340
342
  minHeight: POS_TOUCH_PX,
341
343
  minWidth: 160
342
344
  },
343
- children: missing ? `Choose ${missing.name.toLowerCase()}` : `${props.onRemove ? 'Update' : 'Add'} · ${usd(unitCents * quantity)}`
345
+ children: missing ? `Choose ${missing.name.toLowerCase()}` : `${props.onRemove ? 'Update' : 'Add'} · ${money(unitCents * quantity)}`
344
346
  })
345
347
  ]
346
348
  })
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos/pos-item-dialog.component.tsx"],"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'use client'\n\nimport {\n Box,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n IconButton,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { useEffect, useMemo, useState } from 'react'\nimport * as CommerceModel from '../../../model'\nimport { usd } from './pos-api'\nimport { POS_TOUCH_PX } from './pos-product-grid.component'\n\n/** What the dialog hands back: one basket line's worth of choices. */\nexport interface PosItemChoice {\n variant: CommerceModel.ProductVariant\n modifiers: CommerceModel.ModifierSelection[]\n quantity: number\n}\n\nexport interface PosItemDialogProps {\n /** The product being added or edited; `null` closes the dialog. */\n product: (CommerceModel.HostProduct & { $id: string }) | null\n /** An existing line's choices, when the cashier tapped a basket line. */\n initial?: { variantId?: string; modifiers?: CommerceModel.ModifierSelection[]; quantity: number }\n onClose: () => void\n onConfirm: (choice: PosItemChoice) => void\n /** Editing only: takes the line out of the basket. */\n onRemove?: () => void\n}\n\n/** Whether the register must ask before adding: a choice of variant or any modifier. */\nexport function posItemNeedsChoice(product: CommerceModel.HostProduct): boolean {\n return (\n (product.variants?.length ?? 0) > 1 ||\n CommerceModel.productModifierGroups(product).length > 0\n )\n}\n\n/** The variant whose options match every picked value, or undefined. */\nfunction variantFor(\n product: CommerceModel.HostProduct,\n picked: Record<string, string>,\n): CommerceModel.ProductVariant | undefined {\n return product.variants.find((variant) =>\n (product.options ?? []).every((option) => variant.options?.[option.name] === picked[option.name]),\n )\n}\n\nfunction variantLabel(variant: CommerceModel.ProductVariant): string {\n return Object.values(variant.options ?? {}).join(' / ') || 'Default'\n}\n\nfunction soldOut(variant: CommerceModel.ProductVariant | undefined): boolean {\n return variant?.inventory != null && variant.inventory <= 0\n}\n\n/**\n * The register's item sheet (AGL-3607): the size, color or other variant,\n * then each modifier group — required ones marked, \"pick one\" groups as a\n * single toggle row and \"pick up to N\" groups as chips — and the quantity,\n * with the line's price on the Add button as it changes. Full screen on a\n * phone, a dialog on a tablet; every target is at least 44px.\n *\n * The price shown is the register's estimate. The server prices the line\n * again from the product when the sale is charged.\n */\nexport function PosItemDialog(props: PosItemDialogProps) {\n const { product, initial } = props\n const theme = useTheme()\n const phone = useMediaQuery(theme.breakpoints.down('sm'))\n const groups = useMemo(\n () => (product ? CommerceModel.productModifierGroups(product) : []),\n [product],\n )\n const options = product?.options ?? []\n const [picked, setPicked] = useState<Record<string, string>>({})\n const [variantId, setVariantId] = useState('')\n const [modifiers, setModifiers] = useState<CommerceModel.ModifierSelection[]>([])\n const [quantity, setQuantity] = useState(1)\n\n useEffect(() => {\n if (!product) return\n const start =\n product.variants.find((variant) => variant.id === initial?.variantId) ??\n product.variants.find((variant) => !soldOut(variant)) ??\n product.variants[0]\n setVariantId(start?.id ?? '')\n setPicked({ ...(start?.options ?? {}) })\n setModifiers(initial?.modifiers ?? [])\n setQuantity(initial?.quantity ?? 1)\n }, [product, initial])\n\n if (!product) return null\n\n const variant =\n options.length > 0\n ? variantFor(product, picked)\n : product.variants.find((item) => item.id === variantId)\n const chosen = CommerceModel.resolveLineModifiers(product, modifiers)\n const unitCents = variant\n ? Math.round(Number(variant.priceUsd) * 100) + (chosen.ok ? chosen.extraCents : 0)\n : 0\n const missing = groups.find(\n (group) => modifiers.filter((pick) => pick.groupId === group.id).length < group.min,\n )\n const canAdd = Boolean(variant) && chosen.ok && quantity >= 1\n\n const toggle = (group: CommerceModel.ProductModifierGroup, optionId: string) => {\n setModifiers((current) => {\n const inGroup = current.filter((pick) => pick.groupId === group.id)\n const has = inGroup.some((pick) => pick.optionId === optionId)\n if (has) return current.filter((pick) => !(pick.groupId === group.id && pick.optionId === optionId))\n // A pick-one group swaps its choice; a pick-many group stops at its max.\n if (group.max === 1) {\n return [...current.filter((pick) => pick.groupId !== group.id), { groupId: group.id, optionId }]\n }\n if (inGroup.length >= group.max) return current\n return [...current, { groupId: group.id, optionId }]\n })\n }\n\n return (\n <Dialog open onClose={props.onClose} fullScreen={phone} fullWidth maxWidth=\"sm\">\n <DialogTitle>{product.name}</DialogTitle>\n <DialogContent dividers>\n <Stack spacing={3}>\n {options.length > 0\n ? options.map((option) => (\n <Box key={option.name} role=\"group\" aria-label={option.name}>\n <Typography variant=\"subtitle1\" component=\"h3\" sx={{ mb: 1 }}>\n {option.name}\n </Typography>\n <ToggleButtonGroup\n exclusive\n value={picked[option.name] ?? null}\n onChange={(_event, value: string | null) => {\n if (value) setPicked((current) => ({ ...current, [option.name]: value }))\n }}\n sx={{ flexWrap: 'wrap', gap: 1 }}\n >\n {option.values.map((value) => (\n <ToggleButton key={value} value={value} sx={{ minHeight: POS_TOUCH_PX, minWidth: 88 }}>\n {value}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n </Box>\n ))\n : product.variants.length > 1 ? (\n <Box role=\"group\" aria-label=\"Variant\">\n <ToggleButtonGroup\n exclusive\n value={variantId || null}\n onChange={(_event, value: string | null) => value && setVariantId(value)}\n sx={{ flexWrap: 'wrap', gap: 1 }}\n >\n {product.variants.map((item) => (\n <ToggleButton key={item.id} value={item.id} sx={{ minHeight: POS_TOUCH_PX, minWidth: 88 }}>\n {`${variantLabel(item)} · ${usd(Math.round(Number(item.priceUsd) * 100))}`}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n </Box>\n ) : null}\n {options.length > 0 && !variant ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {'That combination is not sold. Pick another.'}\n </Typography>\n ) : null}\n {variant && soldOut(variant) ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {'Out of stock: selling it takes the count below zero.'}\n </Typography>\n ) : null}\n {groups.map((group) => {\n const count = modifiers.filter((pick) => pick.groupId === group.id).length\n const rule = CommerceModel.modifierGroupRequired(group)\n ? group.max === 1\n ? 'Required · pick 1'\n : `Required · pick ${group.min} to ${group.max}`\n : group.max === 1\n ? 'Optional · pick 1'\n : `Optional · up to ${group.max}`\n return (\n <Box key={group.id} role=\"group\" aria-label={group.name}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline', mb: 1 }}>\n <Typography variant=\"subtitle1\" component=\"h3\">\n {group.name}\n </Typography>\n <Typography\n variant=\"caption\"\n color={count < group.min ? 'warning.main' : 'text.secondary'}\n >\n {rule}\n </Typography>\n </Stack>\n <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>\n {group.options.map((option) => {\n const selected = modifiers.some(\n (pick) => pick.groupId === group.id && pick.optionId === option.id,\n )\n return (\n <Chip\n key={option.id}\n label={option.priceCents ? `${option.name} +${usd(option.priceCents)}` : option.name}\n color={selected ? 'primary' : 'default'}\n variant={selected ? 'filled' : 'outlined'}\n aria-pressed={selected}\n onClick={() => toggle(group, option.id)}\n disabled={!selected && group.max > 1 && count >= group.max}\n sx={{ minHeight: POS_TOUCH_PX, px: 1 }}\n />\n )\n })}\n </Box>\n </Box>\n )\n })}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"subtitle1\" component=\"h3\" sx={{ flex: 1 }}>\n {'Quantity'}\n </Typography>\n <IconButton\n aria-label=\"One fewer\"\n onClick={() => setQuantity((value) => Math.max(1, value - 1))}\n sx={{ width: POS_TOUCH_PX, height: POS_TOUCH_PX, border: 1, borderColor: 'divider' }}\n >\n {'−'}\n </IconButton>\n <TextField\n value={quantity}\n onChange={(event) => {\n const next = Math.round(Number(event.target.value.replace(/\\D/g, '')))\n setQuantity(Math.max(1, Math.min(99, next || 1)))\n }}\n sx={{ width: 72 }}\n slotProps={{\n htmlInput: { inputMode: 'numeric', 'aria-label': 'Quantity', style: { textAlign: 'center' } },\n }}\n />\n <IconButton\n aria-label=\"One more\"\n onClick={() => setQuantity((value) => Math.min(99, value + 1))}\n sx={{ width: POS_TOUCH_PX, height: POS_TOUCH_PX, border: 1, borderColor: 'divider' }}\n >\n {'+'}\n </IconButton>\n </Stack>\n </Stack>\n </DialogContent>\n <DialogActions sx={{ p: 2, gap: 1 }}>\n {props.onRemove ? (\n <Button color=\"error\" onClick={props.onRemove} sx={{ minHeight: POS_TOUCH_PX, mr: 'auto' }}>\n {'Remove'}\n </Button>\n ) : null}\n <Button onClick={props.onClose} sx={{ minHeight: POS_TOUCH_PX }}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n disabled={!canAdd}\n onClick={() => {\n if (!variant || !chosen.ok) return\n props.onConfirm({ variant, modifiers, quantity })\n }}\n sx={{ minHeight: POS_TOUCH_PX, minWidth: 160 }}\n >\n {missing\n ? `Choose ${missing.name.toLowerCase()}`\n : `${props.onRemove ? 'Update' : 'Add'} · ${usd(unitCents * quantity)}`}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nPosItemDialog.displayName = 'PosItemDialog'\n"],"names":["Box","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","IconButton","Stack","TextField","ToggleButton","ToggleButtonGroup","Typography","useMediaQuery","useTheme","useEffect","useMemo","useState","CommerceModel","usd","POS_TOUCH_PX","posItemNeedsChoice","product","variants","length","productModifierGroups","variantFor","picked","find","variant","options","every","option","name","variantLabel","Object","values","join","soldOut","inventory","PosItemDialog","props","initial","theme","phone","breakpoints","down","groups","setPicked","variantId","setVariantId","modifiers","setModifiers","quantity","setQuantity","start","id","item","chosen","resolveLineModifiers","unitCents","Math","round","Number","priceUsd","ok","extraCents","missing","group","filter","pick","groupId","min","canAdd","Boolean","toggle","optionId","current","inGroup","has","some","max","open","onClose","fullScreen","fullWidth","maxWidth","dividers","spacing","map","role","aria-label","component","sx","mb","exclusive","value","onChange","_event","flexWrap","gap","minHeight","minWidth","color","count","rule","modifierGroupRequired","direction","alignItems","display","selected","label","priceCents","aria-pressed","onClick","disabled","px","flex","width","height","border","borderColor","event","next","target","replace","slotProps","htmlInput","inputMode","style","textAlign","p","onRemove","mr","onConfirm","toLowerCase","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos/pos-item-dialog.component.tsx"],"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'use client'\n\nimport {\n Box,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n IconButton,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { useEffect, useMemo, useState } from 'react'\nimport * as CommerceModel from '../../../model'\nimport { usd } from './pos-api'\nimport { POS_TOUCH_PX } from './pos-product-grid.component'\n\n/** What the dialog hands back: one basket line's worth of choices. */\nexport interface PosItemChoice {\n variant: CommerceModel.ProductVariant\n modifiers: CommerceModel.ModifierSelection[]\n quantity: number\n}\n\nexport interface PosItemDialogProps {\n /** The product being added or edited; `null` closes the dialog. */\n product: (CommerceModel.HostProduct & { $id: string }) | null\n /** An existing line's choices, when the cashier tapped a basket line. */\n initial?: { variantId?: string; modifiers?: CommerceModel.ModifierSelection[]; quantity: number }\n onClose: () => void\n onConfirm: (choice: PosItemChoice) => void\n /** Editing only: takes the line out of the basket. */\n onRemove?: () => void\n /**\n * How a price reads; the register's dollars by default. The self-service\n * kiosk (AGL-3623) passes the store's currency.\n */\n formatMoney?: (cents: number) => string\n /**\n * A sold-out variant cannot be added, rather than warned about: a kiosk's\n * customer is not holding the goods the way a cashier is (AGL-3623).\n */\n blockSoldOut?: boolean\n /** The most of one line that can be added; 99 at the register. */\n maxQuantity?: number\n}\n\n/** Whether the register must ask before adding: a choice of variant or any modifier. */\nexport function posItemNeedsChoice(product: CommerceModel.HostProduct): boolean {\n return (\n (product.variants?.length ?? 0) > 1 ||\n CommerceModel.productModifierGroups(product).length > 0\n )\n}\n\n/** The variant whose options match every picked value, or undefined. */\nfunction variantFor(\n product: CommerceModel.HostProduct,\n picked: Record<string, string>,\n): CommerceModel.ProductVariant | undefined {\n return product.variants.find((variant) =>\n (product.options ?? []).every((option) => variant.options?.[option.name] === picked[option.name]),\n )\n}\n\nfunction variantLabel(variant: CommerceModel.ProductVariant): string {\n return Object.values(variant.options ?? {}).join(' / ') || 'Default'\n}\n\nfunction soldOut(variant: CommerceModel.ProductVariant | undefined): boolean {\n return variant?.inventory != null && variant.inventory <= 0\n}\n\n/**\n * The register's item sheet (AGL-3607): the size, color or other variant,\n * then each modifier group — required ones marked, \"pick one\" groups as a\n * single toggle row and \"pick up to N\" groups as chips — and the quantity,\n * with the line's price on the Add button as it changes. Full screen on a\n * phone, a dialog on a tablet; every target is at least 44px.\n *\n * The price shown is the register's estimate. The server prices the line\n * again from the product when the sale is charged.\n */\nexport function PosItemDialog(props: PosItemDialogProps) {\n const { product, initial } = props\n const money = props.formatMoney ?? usd\n const maxQuantity = props.maxQuantity ?? 99\n const theme = useTheme()\n const phone = useMediaQuery(theme.breakpoints.down('sm'))\n const groups = useMemo(\n () => (product ? CommerceModel.productModifierGroups(product) : []),\n [product],\n )\n const options = product?.options ?? []\n const [picked, setPicked] = useState<Record<string, string>>({})\n const [variantId, setVariantId] = useState('')\n const [modifiers, setModifiers] = useState<CommerceModel.ModifierSelection[]>([])\n const [quantity, setQuantity] = useState(1)\n\n useEffect(() => {\n if (!product) return\n const start =\n product.variants.find((variant) => variant.id === initial?.variantId) ??\n product.variants.find((variant) => !soldOut(variant)) ??\n product.variants[0]\n setVariantId(start?.id ?? '')\n setPicked({ ...(start?.options ?? {}) })\n setModifiers(initial?.modifiers ?? [])\n setQuantity(initial?.quantity ?? 1)\n }, [product, initial])\n\n if (!product) return null\n\n const variant =\n options.length > 0\n ? variantFor(product, picked)\n : product.variants.find((item) => item.id === variantId)\n const chosen = CommerceModel.resolveLineModifiers(product, modifiers)\n const unitCents = variant\n ? Math.round(Number(variant.priceUsd) * 100) + (chosen.ok ? chosen.extraCents : 0)\n : 0\n const missing = groups.find(\n (group) => modifiers.filter((pick) => pick.groupId === group.id).length < group.min,\n )\n const canAdd =\n Boolean(variant) && chosen.ok && quantity >= 1 && !(props.blockSoldOut && soldOut(variant))\n\n const toggle = (group: CommerceModel.ProductModifierGroup, optionId: string) => {\n setModifiers((current) => {\n const inGroup = current.filter((pick) => pick.groupId === group.id)\n const has = inGroup.some((pick) => pick.optionId === optionId)\n if (has) return current.filter((pick) => !(pick.groupId === group.id && pick.optionId === optionId))\n // A pick-one group swaps its choice; a pick-many group stops at its max.\n if (group.max === 1) {\n return [...current.filter((pick) => pick.groupId !== group.id), { groupId: group.id, optionId }]\n }\n if (inGroup.length >= group.max) return current\n return [...current, { groupId: group.id, optionId }]\n })\n }\n\n return (\n <Dialog open onClose={props.onClose} fullScreen={phone} fullWidth maxWidth=\"sm\">\n <DialogTitle>{product.name}</DialogTitle>\n <DialogContent dividers>\n <Stack spacing={3}>\n {options.length > 0\n ? options.map((option) => (\n <Box key={option.name} role=\"group\" aria-label={option.name}>\n <Typography variant=\"subtitle1\" component=\"h3\" sx={{ mb: 1 }}>\n {option.name}\n </Typography>\n <ToggleButtonGroup\n exclusive\n value={picked[option.name] ?? null}\n onChange={(_event, value: string | null) => {\n if (value) setPicked((current) => ({ ...current, [option.name]: value }))\n }}\n sx={{ flexWrap: 'wrap', gap: 1 }}\n >\n {option.values.map((value) => (\n <ToggleButton key={value} value={value} sx={{ minHeight: POS_TOUCH_PX, minWidth: 88 }}>\n {value}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n </Box>\n ))\n : product.variants.length > 1 ? (\n <Box role=\"group\" aria-label=\"Variant\">\n <ToggleButtonGroup\n exclusive\n value={variantId || null}\n onChange={(_event, value: string | null) => value && setVariantId(value)}\n sx={{ flexWrap: 'wrap', gap: 1 }}\n >\n {product.variants.map((item) => (\n <ToggleButton key={item.id} value={item.id} sx={{ minHeight: POS_TOUCH_PX, minWidth: 88 }}>\n {`${variantLabel(item)} · ${money(Math.round(Number(item.priceUsd) * 100))}`}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n </Box>\n ) : null}\n {options.length > 0 && !variant ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {'That combination is not sold. Pick another.'}\n </Typography>\n ) : null}\n {variant && soldOut(variant) ? (\n <Typography variant=\"body2\" color=\"warning.main\">\n {props.blockSoldOut ? 'Sold out' : 'Out of stock: selling it takes the count below zero.'}\n </Typography>\n ) : null}\n {groups.map((group) => {\n const count = modifiers.filter((pick) => pick.groupId === group.id).length\n const rule = CommerceModel.modifierGroupRequired(group)\n ? group.max === 1\n ? 'Required · pick 1'\n : `Required · pick ${group.min} to ${group.max}`\n : group.max === 1\n ? 'Optional · pick 1'\n : `Optional · up to ${group.max}`\n return (\n <Box key={group.id} role=\"group\" aria-label={group.name}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'baseline', mb: 1 }}>\n <Typography variant=\"subtitle1\" component=\"h3\">\n {group.name}\n </Typography>\n <Typography\n variant=\"caption\"\n color={count < group.min ? 'warning.main' : 'text.secondary'}\n >\n {rule}\n </Typography>\n </Stack>\n <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>\n {group.options.map((option) => {\n const selected = modifiers.some(\n (pick) => pick.groupId === group.id && pick.optionId === option.id,\n )\n return (\n <Chip\n key={option.id}\n label={option.priceCents ? `${option.name} +${money(option.priceCents)}` : option.name}\n color={selected ? 'primary' : 'default'}\n variant={selected ? 'filled' : 'outlined'}\n aria-pressed={selected}\n onClick={() => toggle(group, option.id)}\n disabled={!selected && group.max > 1 && count >= group.max}\n sx={{ minHeight: POS_TOUCH_PX, px: 1 }}\n />\n )\n })}\n </Box>\n </Box>\n )\n })}\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"subtitle1\" component=\"h3\" sx={{ flex: 1 }}>\n {'Quantity'}\n </Typography>\n <IconButton\n aria-label=\"One fewer\"\n onClick={() => setQuantity((value) => Math.max(1, value - 1))}\n sx={{ width: POS_TOUCH_PX, height: POS_TOUCH_PX, border: 1, borderColor: 'divider' }}\n >\n {'−'}\n </IconButton>\n <TextField\n value={quantity}\n onChange={(event) => {\n const next = Math.round(Number(event.target.value.replace(/\\D/g, '')))\n setQuantity(Math.max(1, Math.min(maxQuantity, next || 1)))\n }}\n sx={{ width: 72 }}\n slotProps={{\n htmlInput: { inputMode: 'numeric', 'aria-label': 'Quantity', style: { textAlign: 'center' } },\n }}\n />\n <IconButton\n aria-label=\"One more\"\n onClick={() => setQuantity((value) => Math.min(maxQuantity, value + 1))}\n sx={{ width: POS_TOUCH_PX, height: POS_TOUCH_PX, border: 1, borderColor: 'divider' }}\n >\n {'+'}\n </IconButton>\n </Stack>\n </Stack>\n </DialogContent>\n <DialogActions sx={{ p: 2, gap: 1 }}>\n {props.onRemove ? (\n <Button color=\"error\" onClick={props.onRemove} sx={{ minHeight: POS_TOUCH_PX, mr: 'auto' }}>\n {'Remove'}\n </Button>\n ) : null}\n <Button onClick={props.onClose} sx={{ minHeight: POS_TOUCH_PX }}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n disabled={!canAdd}\n onClick={() => {\n if (!variant || !chosen.ok) return\n props.onConfirm({ variant, modifiers, quantity })\n }}\n sx={{ minHeight: POS_TOUCH_PX, minWidth: 160 }}\n >\n {missing\n ? `Choose ${missing.name.toLowerCase()}`\n : `${props.onRemove ? 'Update' : 'Add'} · ${money(unitCents * quantity)}`}\n </Button>\n </DialogActions>\n </Dialog>\n )\n}\nPosItemDialog.displayName = 'PosItemDialog'\n"],"names":["Box","Button","Chip","Dialog","DialogActions","DialogContent","DialogTitle","IconButton","Stack","TextField","ToggleButton","ToggleButtonGroup","Typography","useMediaQuery","useTheme","useEffect","useMemo","useState","CommerceModel","usd","POS_TOUCH_PX","posItemNeedsChoice","product","variants","length","productModifierGroups","variantFor","picked","find","variant","options","every","option","name","variantLabel","Object","values","join","soldOut","inventory","PosItemDialog","props","initial","money","formatMoney","maxQuantity","theme","phone","breakpoints","down","groups","setPicked","variantId","setVariantId","modifiers","setModifiers","quantity","setQuantity","start","id","item","chosen","resolveLineModifiers","unitCents","Math","round","Number","priceUsd","ok","extraCents","missing","group","filter","pick","groupId","min","canAdd","Boolean","blockSoldOut","toggle","optionId","current","inGroup","has","some","max","open","onClose","fullScreen","fullWidth","maxWidth","dividers","spacing","map","role","aria-label","component","sx","mb","exclusive","value","onChange","_event","flexWrap","gap","minHeight","minWidth","color","count","rule","modifierGroupRequired","direction","alignItems","display","selected","label","priceCents","aria-pressed","onClick","disabled","px","flex","width","height","border","borderColor","event","next","target","replace","slotProps","htmlInput","inputMode","style","textAlign","p","onRemove","mr","onConfirm","toLowerCase","displayName"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { RegisterLine } from './pos-cart-panel.component';
2
+ import { posKioskCall, type PosSaleSummary } from './pos-api';
3
+ type User = Parameters<typeof posKioskCall>[0];
4
+ /** How often the register looks for orders a kiosk sent to the counter. */
5
+ export declare const POS_KIOSK_QUEUE_POLL_MS = 10000;
6
+ /**
7
+ * "Pay at counter" orders from this register's self-service kiosks
8
+ * (AGL-3623), oldest first. Each is an open sale the kiosk already priced;
9
+ * Take payment loads it into the register's tender panel, where the cashier
10
+ * takes payment exactly as on a sale they rang themselves. Draws nothing
11
+ * when the queue is empty.
12
+ */
13
+ export declare function PosKioskQueue({ user, hostId, registerId, disabled, onTake, notify, }: {
14
+ user: User | null | undefined;
15
+ hostId: string;
16
+ registerId: string;
17
+ /** A sale is already open on the register. */
18
+ disabled: boolean;
19
+ onTake: (sale: PosSaleSummary, lines: RegisterLine[]) => void;
20
+ notify: (message: string, variant: 'error' | 'info') => void;
21
+ }): import("react").JSX.Element;
22
+ export {};
@@ -0,0 +1,168 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ 'use client';
17
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
+ import Button from "@mui/material/Button";
19
+ import Card from "@mui/material/Card";
20
+ import CardContent from "@mui/material/CardContent";
21
+ import CardHeader from "@mui/material/CardHeader";
22
+ import Divider from "@mui/material/Divider";
23
+ import Stack from "@mui/material/Stack";
24
+ import Typography from "@mui/material/Typography";
25
+ import { useCallback, useEffect, useRef, useState } from "react";
26
+ import { displayMoney } from "../pos-display/pos-display-api.js";
27
+ import { posKioskCall, PosRequestError } from "./pos-api.js";
28
+ /** How often the register looks for orders a kiosk sent to the counter. */ export const POS_KIOSK_QUEUE_POLL_MS = 10000;
29
+ /**
30
+ * "Pay at counter" orders from this register's self-service kiosks
31
+ * (AGL-3623), oldest first. Each is an open sale the kiosk already priced;
32
+ * Take payment loads it into the register's tender panel, where the cashier
33
+ * takes payment exactly as on a sale they rang themselves. Draws nothing
34
+ * when the queue is empty.
35
+ */ export function PosKioskQueue({ user, hostId, registerId, disabled, onTake, notify }) {
36
+ var _ref;
37
+ const [entries, setEntries] = useState([]);
38
+ const [currency, setCurrency] = useState('usd');
39
+ const [taking, setTaking] = useState(null);
40
+ const userRef = useRef(user);
41
+ userRef.current = user;
42
+ const uid = (_ref = user == null ? void 0 : user.uid) != null ? _ref : '';
43
+ const load = useCallback(async ()=>{
44
+ const signedIn = userRef.current;
45
+ if (!signedIn || !registerId) return;
46
+ try {
47
+ var _result_entries;
48
+ const result = await posKioskCall(signedIn, {
49
+ action: 'queue',
50
+ hostId,
51
+ registerId
52
+ }, 'GET');
53
+ setEntries((_result_entries = result.entries) != null ? _result_entries : []);
54
+ if (result.currency) setCurrency(result.currency);
55
+ } catch (unused) {
56
+ // A missed poll is retried on the next tick; the queue keeps what it showed.
57
+ }
58
+ }, [
59
+ hostId,
60
+ registerId
61
+ ]);
62
+ useEffect(()=>{
63
+ if (!uid || !registerId) {
64
+ setEntries([]);
65
+ return undefined;
66
+ }
67
+ void load();
68
+ const timer = setInterval(()=>{
69
+ if (typeof document !== 'undefined' && document.hidden) return;
70
+ void load();
71
+ }, POS_KIOSK_QUEUE_POLL_MS);
72
+ return ()=>clearInterval(timer);
73
+ }, [
74
+ uid,
75
+ registerId,
76
+ load
77
+ ]);
78
+ // A sale closing on the register is the moment a queued one may have gone.
79
+ useEffect(()=>{
80
+ if (!disabled) void load();
81
+ }, [
82
+ disabled,
83
+ load
84
+ ]);
85
+ if (!registerId || entries.length === 0) return null;
86
+ const take = async (entry)=>{
87
+ if (!user) return;
88
+ setTaking(entry.orderId);
89
+ try {
90
+ var _result_lines;
91
+ const result = await posKioskCall(user, {
92
+ action: 'take',
93
+ hostId,
94
+ registerId,
95
+ orderId: entry.orderId
96
+ });
97
+ onTake(result.sale, (_result_lines = result.lines) != null ? _result_lines : []);
98
+ } catch (error) {
99
+ notify(error instanceof PosRequestError ? error.message : 'That order could not be opened', 'error');
100
+ void load();
101
+ } finally{
102
+ setTaking(null);
103
+ }
104
+ };
105
+ return /*#__PURE__*/ _jsxs(Card, {
106
+ variant: "outlined",
107
+ sx: {
108
+ mb: 2
109
+ },
110
+ children: [
111
+ /*#__PURE__*/ _jsx(CardHeader, {
112
+ title: "Kiosk orders",
113
+ subheader: "Sent to the counter to pay",
114
+ slotProps: {
115
+ title: {
116
+ variant: 'subtitle1',
117
+ component: 'h2'
118
+ }
119
+ }
120
+ }),
121
+ /*#__PURE__*/ _jsx(CardContent, {
122
+ sx: {
123
+ pt: 0
124
+ },
125
+ children: /*#__PURE__*/ _jsx(Stack, {
126
+ divider: /*#__PURE__*/ _jsx(Divider, {
127
+ flexItem: true
128
+ }),
129
+ spacing: 1,
130
+ children: entries.map((entry)=>/*#__PURE__*/ _jsxs(Stack, {
131
+ direction: "row",
132
+ spacing: 2,
133
+ sx: {
134
+ alignItems: 'center'
135
+ },
136
+ children: [
137
+ /*#__PURE__*/ _jsx(Typography, {
138
+ variant: "h6",
139
+ component: "span",
140
+ sx: {
141
+ minWidth: (theme)=>theme.spacing(8)
142
+ },
143
+ children: `#${entry.number}`
144
+ }),
145
+ /*#__PURE__*/ _jsx(Typography, {
146
+ variant: "body2",
147
+ color: "text.secondary",
148
+ sx: {
149
+ flex: 1
150
+ },
151
+ children: `${entry.itemCount} ${entry.itemCount === 1 ? 'item' : 'items'} · ${displayMoney(entry.totalCents, currency)}`
152
+ }),
153
+ /*#__PURE__*/ _jsx(Button, {
154
+ variant: "contained",
155
+ size: "small",
156
+ disabled: disabled || Boolean(taking),
157
+ onClick: ()=>void take(entry),
158
+ children: taking === entry.orderId ? 'Opening…' : 'Take payment'
159
+ })
160
+ ]
161
+ }, entry.orderId))
162
+ })
163
+ })
164
+ ]
165
+ });
166
+ }
167
+
168
+ //# sourceMappingURL=pos-kiosk-queue.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../../libs/plugins/commerce/src/lib/components/console/pos/pos-kiosk-queue.component.tsx"],"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'use client'\n\nimport Button from '@mui/material/Button'\nimport Card from '@mui/material/Card'\nimport CardContent from '@mui/material/CardContent'\nimport CardHeader from '@mui/material/CardHeader'\nimport Divider from '@mui/material/Divider'\nimport Stack from '@mui/material/Stack'\nimport Typography from '@mui/material/Typography'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { displayMoney } from '../pos-display/pos-display-api'\nimport type { RegisterLine } from './pos-cart-panel.component'\nimport { posKioskCall, PosRequestError, type PosSaleSummary } from './pos-api'\n\ntype User = Parameters<typeof posKioskCall>[0]\n\ninterface QueueEntry {\n orderId: string\n number: number\n queuedAtMs: number\n totalCents: number\n itemCount: number\n}\n\n/** How often the register looks for orders a kiosk sent to the counter. */\nexport const POS_KIOSK_QUEUE_POLL_MS = 10_000\n\n/**\n * \"Pay at counter\" orders from this register's self-service kiosks\n * (AGL-3623), oldest first. Each is an open sale the kiosk already priced;\n * Take payment loads it into the register's tender panel, where the cashier\n * takes payment exactly as on a sale they rang themselves. Draws nothing\n * when the queue is empty.\n */\nexport function PosKioskQueue({\n user,\n hostId,\n registerId,\n disabled,\n onTake,\n notify,\n}: {\n user: User | null | undefined\n hostId: string\n registerId: string\n /** A sale is already open on the register. */\n disabled: boolean\n onTake: (sale: PosSaleSummary, lines: RegisterLine[]) => void\n notify: (message: string, variant: 'error' | 'info') => void\n}) {\n const [entries, setEntries] = useState<QueueEntry[]>([])\n const [currency, setCurrency] = useState('usd')\n const [taking, setTaking] = useState<string | null>(null)\n const userRef = useRef(user)\n userRef.current = user\n const uid = (user as { uid?: string } | null | undefined)?.uid ?? ''\n\n const load = useCallback(async () => {\n const signedIn = userRef.current\n if (!signedIn || !registerId) return\n try {\n const result = await posKioskCall<{ entries: QueueEntry[]; currency: string }>(\n signedIn,\n { action: 'queue', hostId, registerId },\n 'GET',\n )\n setEntries(result.entries ?? [])\n if (result.currency) setCurrency(result.currency)\n } catch {\n // A missed poll is retried on the next tick; the queue keeps what it showed.\n }\n }, [hostId, registerId])\n\n useEffect(() => {\n if (!uid || !registerId) {\n setEntries([])\n return undefined\n }\n void load()\n const timer = setInterval(() => {\n if (typeof document !== 'undefined' && document.hidden) return\n void load()\n }, POS_KIOSK_QUEUE_POLL_MS)\n return () => clearInterval(timer)\n }, [uid, registerId, load])\n\n // A sale closing on the register is the moment a queued one may have gone.\n useEffect(() => {\n if (!disabled) void load()\n }, [disabled, load])\n\n if (!registerId || entries.length === 0) return null\n\n const take = async (entry: QueueEntry) => {\n if (!user) return\n setTaking(entry.orderId)\n try {\n const result = await posKioskCall<{ sale: PosSaleSummary; lines: RegisterLine[] }>(user, {\n action: 'take',\n hostId,\n registerId,\n orderId: entry.orderId,\n })\n onTake(result.sale, result.lines ?? [])\n } catch (error) {\n notify(error instanceof PosRequestError ? error.message : 'That order could not be opened', 'error')\n void load()\n } finally {\n setTaking(null)\n }\n }\n\n return (\n <Card variant=\"outlined\" sx={{ mb: 2 }}>\n <CardHeader\n title=\"Kiosk orders\"\n subheader=\"Sent to the counter to pay\"\n slotProps={{ title: { variant: 'subtitle1', component: 'h2' } }}\n />\n <CardContent sx={{ pt: 0 }}>\n <Stack divider={<Divider flexItem />} spacing={1}>\n {entries.map((entry) => (\n <Stack key={entry.orderId} direction=\"row\" spacing={2} sx={{ alignItems: 'center' }}>\n <Typography variant=\"h6\" component=\"span\" sx={{ minWidth: (theme) => theme.spacing(8) }}>\n {`#${entry.number}`}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\" sx={{ flex: 1 }}>\n {`${entry.itemCount} ${entry.itemCount === 1 ? 'item' : 'items'} · ${displayMoney(entry.totalCents, currency)}`}\n </Typography>\n <Button\n variant=\"contained\"\n size=\"small\"\n disabled={disabled || Boolean(taking)}\n onClick={() => void take(entry)}\n >\n {taking === entry.orderId ? 'Opening…' : 'Take payment'}\n </Button>\n </Stack>\n ))}\n </Stack>\n </CardContent>\n </Card>\n )\n}\n"],"names":["Button","Card","CardContent","CardHeader","Divider","Stack","Typography","useCallback","useEffect","useRef","useState","displayMoney","posKioskCall","PosRequestError","POS_KIOSK_QUEUE_POLL_MS","PosKioskQueue","user","hostId","registerId","disabled","onTake","notify","entries","setEntries","currency","setCurrency","taking","setTaking","userRef","current","uid","load","signedIn","result","action","undefined","timer","setInterval","document","hidden","clearInterval","length","take","entry","orderId","sale","lines","error","message","variant","sx","mb","title","subheader","slotProps","component","pt","divider","flexItem","spacing","map","direction","alignItems","minWidth","theme","number","color","flex","itemCount","totalCents","size","Boolean","onClick"],"mappings":"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"}