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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (266) hide show
  1. package/package.json +13 -13
  2. package/src/lib/components/cart-extras.d.ts +6 -0
  3. package/src/lib/components/cart-extras.js +4 -1
  4. package/src/lib/components/cart-extras.js.map +1 -1
  5. package/src/lib/components/cart-fulfillment.d.ts +117 -0
  6. package/src/lib/components/cart-fulfillment.js +367 -0
  7. package/src/lib/components/cart-fulfillment.js.map +1 -0
  8. package/src/lib/components/cart.js +52 -10
  9. package/src/lib/components/cart.js.map +1 -1
  10. package/src/lib/components/commerce-console-page.js +42 -1
  11. package/src/lib/components/commerce-console-page.js.map +1 -1
  12. package/src/lib/components/console/catalog-organization-card.component.js +1 -0
  13. package/src/lib/components/console/catalog-organization-card.component.js.map +1 -1
  14. package/src/lib/components/console/host-orders-card.component.js +42 -11
  15. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  16. package/src/lib/components/console/local-delivery-settings-card.component.d.ts +37 -0
  17. package/src/lib/components/console/local-delivery-settings-card.component.js +542 -0
  18. package/src/lib/components/console/local-delivery-settings-card.component.js.map +1 -0
  19. package/src/lib/components/console/locations-card.component.js +177 -3
  20. package/src/lib/components/console/locations-card.component.js.map +1 -1
  21. package/src/lib/components/console/order-detail-dialog.component.js +10 -0
  22. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  23. package/src/lib/components/console/order-local-fulfillment-panel.component.d.ts +44 -0
  24. package/src/lib/components/console/order-local-fulfillment-panel.component.js +265 -0
  25. package/src/lib/components/console/order-local-fulfillment-panel.component.js.map +1 -0
  26. package/src/lib/components/console/order-offline-notice.component.d.ts +12 -0
  27. package/src/lib/components/console/order-offline-notice.component.js +53 -0
  28. package/src/lib/components/console/order-offline-notice.component.js.map +1 -0
  29. package/src/lib/components/console/pickup-delivery-queue-card.component.d.ts +35 -0
  30. package/src/lib/components/console/pickup-delivery-queue-card.component.js +222 -0
  31. package/src/lib/components/console/pickup-delivery-queue-card.component.js.map +1 -0
  32. package/src/lib/components/console/pos/pos-api.d.ts +24 -1
  33. package/src/lib/components/console/pos/pos-api.js +13 -0
  34. package/src/lib/components/console/pos/pos-api.js.map +1 -1
  35. package/src/lib/components/console/pos/pos-item-dialog.component.d.ts +12 -0
  36. package/src/lib/components/console/pos/pos-item-dialog.component.js +10 -8
  37. package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -1
  38. package/src/lib/components/console/pos/pos-kiosk-queue.component.d.ts +22 -0
  39. package/src/lib/components/console/pos/pos-kiosk-queue.component.js +168 -0
  40. package/src/lib/components/console/pos/pos-kiosk-queue.component.js.map +1 -0
  41. package/src/lib/components/console/pos/pos-tender-dialogs.component.d.ts +18 -0
  42. package/src/lib/components/console/pos/pos-tender-dialogs.component.js +116 -1
  43. package/src/lib/components/console/pos/pos-tender-dialogs.component.js.map +1 -1
  44. package/src/lib/components/console/pos/pos-tender-panel.component.d.ts +8 -0
  45. package/src/lib/components/console/pos/pos-tender-panel.component.js +77 -18
  46. package/src/lib/components/console/pos/pos-tender-panel.component.js.map +1 -1
  47. package/src/lib/components/console/pos/use-pos-display.d.ts +5 -2
  48. package/src/lib/components/console/pos/use-pos-display.js +3 -2
  49. package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
  50. package/src/lib/components/console/pos-devices-card.component.js +2 -2
  51. package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
  52. package/src/lib/components/console/pos-display/pairing-screen.d.ts +5 -2
  53. package/src/lib/components/console/pos-display/pairing-screen.js +7 -5
  54. package/src/lib/components/console/pos-display/pairing-screen.js.map +1 -1
  55. package/src/lib/components/console/pos-display/pos-display-api.d.ts +7 -1
  56. package/src/lib/components/console/pos-display/pos-display-api.js +7 -2
  57. package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -1
  58. package/src/lib/components/console/pos-kiosk/kiosk-checkout.d.ts +41 -0
  59. package/src/lib/components/console/pos-kiosk/kiosk-checkout.js +275 -0
  60. package/src/lib/components/console/pos-kiosk/kiosk-checkout.js.map +1 -0
  61. package/src/lib/components/console/pos-kiosk/kiosk-menu.d.ts +32 -0
  62. package/src/lib/components/console/pos-kiosk/kiosk-menu.js +344 -0
  63. package/src/lib/components/console/pos-kiosk/kiosk-menu.js.map +1 -0
  64. package/src/lib/components/console/pos-kiosk/kiosk-staff.d.ts +12 -0
  65. package/src/lib/components/console/pos-kiosk/kiosk-staff.js +257 -0
  66. package/src/lib/components/console/pos-kiosk/kiosk-staff.js.map +1 -0
  67. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.d.ts +114 -0
  68. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js +189 -0
  69. package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js.map +1 -0
  70. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.d.ts +16 -0
  71. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js +80 -0
  72. package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js.map +1 -0
  73. package/src/lib/components/console/pos-kiosk-page.component.d.ts +28 -0
  74. package/src/lib/components/console/pos-kiosk-page.component.js +791 -0
  75. package/src/lib/components/console/pos-kiosk-page.component.js.map +1 -0
  76. package/src/lib/components/console/pos-offline/pos-offline-api.d.ts +44 -0
  77. package/src/lib/components/console/pos-offline/pos-offline-api.js +69 -0
  78. package/src/lib/components/console/pos-offline/pos-offline-api.js.map +1 -0
  79. package/src/lib/components/console/pos-offline/pos-offline-banner.component.d.ts +7 -0
  80. package/src/lib/components/console/pos-offline/pos-offline-banner.component.js +135 -0
  81. package/src/lib/components/console/pos-offline/pos-offline-banner.component.js.map +1 -0
  82. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.d.ts +32 -0
  83. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js +287 -0
  84. package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js.map +1 -0
  85. package/src/lib/components/console/pos-offline/pos-offline-store.d.ts +63 -0
  86. package/src/lib/components/console/pos-offline/pos-offline-store.js +145 -0
  87. package/src/lib/components/console/pos-offline/pos-offline-store.js.map +1 -0
  88. package/src/lib/components/console/pos-offline/register-offline.d.ts +24 -0
  89. package/src/lib/components/console/pos-offline/register-offline.js +24 -0
  90. package/src/lib/components/console/pos-offline/register-offline.js.map +1 -0
  91. package/src/lib/components/console/pos-offline/use-pos-offline.d.ts +85 -0
  92. package/src/lib/components/console/pos-offline/use-pos-offline.js +542 -0
  93. package/src/lib/components/console/pos-offline/use-pos-offline.js.map +1 -0
  94. package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js +72 -17
  95. package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js.map +1 -1
  96. package/src/lib/components/console/pos-page.component.js +228 -51
  97. package/src/lib/components/console/pos-page.component.js.map +1 -1
  98. package/src/lib/components/console/pos-zones.d.ts +23 -0
  99. package/src/lib/components/console/pos-zones.js +31 -0
  100. package/src/lib/components/console/pos-zones.js.map +1 -0
  101. package/src/lib/components/console/product-editor-dialog.component.js +10 -64
  102. package/src/lib/components/console/product-editor-dialog.component.js.map +1 -1
  103. package/src/lib/components/console/products-hub-card.component.js +46 -30
  104. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  105. package/src/lib/components/console/shipping-settings-card.component.js +1 -1
  106. package/src/lib/components/console/shipping-settings-card.component.js.map +1 -1
  107. package/src/lib/components/console/stock-movements-card.component.js +48 -16
  108. package/src/lib/components/console/stock-movements-card.component.js.map +1 -1
  109. package/src/lib/components/console/store-zones.d.ts +8 -1
  110. package/src/lib/components/console/store-zones.js +11 -1
  111. package/src/lib/components/console/store-zones.js.map +1 -1
  112. package/src/lib/components/console/storefront-tax-summary-card.component.js +1 -0
  113. package/src/lib/components/console/storefront-tax-summary-card.component.js.map +1 -1
  114. package/src/lib/components/order-status.js +66 -0
  115. package/src/lib/components/order-status.js.map +1 -1
  116. package/src/lib/components/product-detail.js +42 -15
  117. package/src/lib/components/product-detail.js.map +1 -1
  118. package/src/lib/constants/local-fulfillment-list-query.d.ts +48 -0
  119. package/src/lib/constants/local-fulfillment-list-query.js +84 -0
  120. package/src/lib/constants/local-fulfillment-list-query.js.map +1 -0
  121. package/src/lib/constants/orders-list-query.d.ts +7 -2
  122. package/src/lib/constants/orders-list-query.js +89 -8
  123. package/src/lib/constants/orders-list-query.js.map +1 -1
  124. package/src/lib/constants/pos-shift-list-query.d.ts +22 -0
  125. package/src/lib/constants/pos-shift-list-query.js +115 -0
  126. package/src/lib/constants/pos-shift-list-query.js.map +1 -0
  127. package/src/lib/constants/product-list-query.d.ts +6 -1
  128. package/src/lib/constants/product-list-query.js +77 -8
  129. package/src/lib/constants/product-list-query.js.map +1 -1
  130. package/src/lib/constants/stock-movement-query.d.ts +3 -2
  131. package/src/lib/constants/stock-movement-query.js +52 -8
  132. package/src/lib/constants/stock-movement-query.js.map +1 -1
  133. package/src/lib/declarations.server.js +21 -0
  134. package/src/lib/declarations.server.js.map +1 -1
  135. package/src/lib/model/buyer-notifications.d.ts +5 -3
  136. package/src/lib/model/buyer-notifications.js +25 -4
  137. package/src/lib/model/buyer-notifications.js.map +1 -1
  138. package/src/lib/model/commerce-orders.d.ts +103 -1
  139. package/src/lib/model/commerce-orders.js +2 -1
  140. package/src/lib/model/commerce-orders.js.map +1 -1
  141. package/src/lib/model/commerce-pos-kiosk.d.ts +175 -0
  142. package/src/lib/model/commerce-pos-kiosk.js +147 -0
  143. package/src/lib/model/commerce-pos-kiosk.js.map +1 -0
  144. package/src/lib/model/commerce-pos-offline.d.ts +234 -0
  145. package/src/lib/model/commerce-pos-offline.js +301 -0
  146. package/src/lib/model/commerce-pos-offline.js.map +1 -0
  147. package/src/lib/model/commerce-pos-ops.d.ts +16 -6
  148. package/src/lib/model/commerce-pos-ops.js +12 -4
  149. package/src/lib/model/commerce-pos-ops.js.map +1 -1
  150. package/src/lib/model/commerce-pos.d.ts +13 -1
  151. package/src/lib/model/commerce-pos.js +5 -4
  152. package/src/lib/model/commerce-pos.js.map +1 -1
  153. package/src/lib/model/commerce-stock-holds.d.ts +24 -3
  154. package/src/lib/model/commerce-stock-holds.js +26 -8
  155. package/src/lib/model/commerce-stock-holds.js.map +1 -1
  156. package/src/lib/model/commerce.d.ts +7 -1
  157. package/src/lib/model/commerce.js.map +1 -1
  158. package/src/lib/model/index.d.ts +5 -0
  159. package/src/lib/model/index.js +5 -0
  160. package/src/lib/model/index.js.map +1 -1
  161. package/src/lib/model/local-fulfillment-settings.d.ts +178 -0
  162. package/src/lib/model/local-fulfillment-settings.js +361 -0
  163. package/src/lib/model/local-fulfillment-settings.js.map +1 -0
  164. package/src/lib/model/order-list-fields.d.ts +10 -1
  165. package/src/lib/model/order-list-fields.js +1 -0
  166. package/src/lib/model/order-list-fields.js.map +1 -1
  167. package/src/lib/model/order-local-fulfillment.d.ts +131 -0
  168. package/src/lib/model/order-local-fulfillment.js +125 -0
  169. package/src/lib/model/order-local-fulfillment.js.map +1 -0
  170. package/src/lib/model/order-shipping-export.js +4 -0
  171. package/src/lib/model/order-shipping-export.js.map +1 -1
  172. package/src/lib/model/order-status-view.d.ts +20 -1
  173. package/src/lib/model/order-status-view.js +60 -6
  174. package/src/lib/model/order-status-view.js.map +1 -1
  175. package/src/lib/model/product-write.d.ts +186 -0
  176. package/src/lib/model/product-write.js +116 -0
  177. package/src/lib/model/product-write.js.map +1 -0
  178. package/src/lib/plugin-config.js +6 -2
  179. package/src/lib/plugin-config.js.map +1 -1
  180. package/src/lib/plugin.js +11 -0
  181. package/src/lib/plugin.js.map +1 -1
  182. package/src/lib/pos-kiosk-config.d.ts +26 -0
  183. package/src/lib/pos-kiosk-config.js +59 -0
  184. package/src/lib/pos-kiosk-config.js.map +1 -0
  185. package/src/lib/server/api-v1/openapi.js +8 -0
  186. package/src/lib/server/api-v1/openapi.js.map +1 -1
  187. package/src/lib/server/api-v1/order-view.d.ts +19 -0
  188. package/src/lib/server/api-v1/order-view.js +35 -0
  189. package/src/lib/server/api-v1/order-view.js.map +1 -1
  190. package/src/lib/server/billing-webhook.js +238 -87
  191. package/src/lib/server/billing-webhook.js.map +1 -1
  192. package/src/lib/server/cart-checkout.d.ts +2 -0
  193. package/src/lib/server/cart-checkout.js +173 -13
  194. package/src/lib/server/cart-checkout.js.map +1 -1
  195. package/src/lib/server/channel-order-updates.d.ts +50 -0
  196. package/src/lib/server/channel-order-updates.js +290 -0
  197. package/src/lib/server/channel-order-updates.js.map +1 -0
  198. package/src/lib/server/channel-orders.d.ts +85 -0
  199. package/src/lib/server/channel-orders.js +543 -0
  200. package/src/lib/server/channel-orders.js.map +1 -0
  201. package/src/lib/server/checkout-extras.js +18 -4
  202. package/src/lib/server/checkout-extras.js.map +1 -1
  203. package/src/lib/server/checkout.js +71 -14
  204. package/src/lib/server/checkout.js.map +1 -1
  205. package/src/lib/server/local-fulfillment-status.d.ts +86 -0
  206. package/src/lib/server/local-fulfillment-status.js +379 -0
  207. package/src/lib/server/local-fulfillment-status.js.map +1 -0
  208. package/src/lib/server/local-fulfillment.d.ts +190 -0
  209. package/src/lib/server/local-fulfillment.js +580 -0
  210. package/src/lib/server/local-fulfillment.js.map +1 -0
  211. package/src/lib/server/membership-register.js +3 -1
  212. package/src/lib/server/membership-register.js.map +1 -1
  213. package/src/lib/server/order-notifications.js +61 -0
  214. package/src/lib/server/order-notifications.js.map +1 -1
  215. package/src/lib/server/pos-display.d.ts +27 -1
  216. package/src/lib/server/pos-display.js +50 -9
  217. package/src/lib/server/pos-display.js.map +1 -1
  218. package/src/lib/server/pos-kiosk-principal.d.ts +29 -0
  219. package/src/lib/server/pos-kiosk-principal.js +42 -0
  220. package/src/lib/server/pos-kiosk-principal.js.map +1 -0
  221. package/src/lib/server/pos-kiosk.d.ts +54 -0
  222. package/src/lib/server/pos-kiosk.js +972 -0
  223. package/src/lib/server/pos-kiosk.js.map +1 -0
  224. package/src/lib/server/pos-offline-sync.d.ts +85 -0
  225. package/src/lib/server/pos-offline-sync.js +765 -0
  226. package/src/lib/server/pos-offline-sync.js.map +1 -0
  227. package/src/lib/server/pos-order.js +22 -4
  228. package/src/lib/server/pos-order.js.map +1 -1
  229. package/src/lib/server/pos-payment.d.ts +6 -0
  230. package/src/lib/server/pos-payment.js +225 -6
  231. package/src/lib/server/pos-payment.js.map +1 -1
  232. package/src/lib/server/pos-print.js +5 -0
  233. package/src/lib/server/pos-print.js.map +1 -1
  234. package/src/lib/server/pos-return.js +27 -0
  235. package/src/lib/server/pos-return.js.map +1 -1
  236. package/src/lib/server/pos-sale.d.ts +6 -0
  237. package/src/lib/server/pos-sale.js +2 -0
  238. package/src/lib/server/pos-sale.js.map +1 -1
  239. package/src/lib/server/pos-shift.js +8 -1
  240. package/src/lib/server/pos-shift.js.map +1 -1
  241. package/src/lib/server/product-catalog.js +5 -1
  242. package/src/lib/server/product-catalog.js.map +1 -1
  243. package/src/lib/server/product-drafts.d.ts +5 -1
  244. package/src/lib/server/product-drafts.js +3 -3
  245. package/src/lib/server/product-drafts.js.map +1 -1
  246. package/src/lib/server/product-writer.d.ts +19 -0
  247. package/src/lib/server/product-writer.js +261 -0
  248. package/src/lib/server/product-writer.js.map +1 -0
  249. package/src/lib/server/products-write.d.ts +61 -0
  250. package/src/lib/server/products-write.js +375 -0
  251. package/src/lib/server/products-write.js.map +1 -0
  252. package/src/lib/server/refund.js +3 -0
  253. package/src/lib/server/refund.js.map +1 -1
  254. package/src/lib/server/stock-hold.d.ts +9 -1
  255. package/src/lib/server/stock-hold.js +15 -7
  256. package/src/lib/server/stock-hold.js.map +1 -1
  257. package/src/lib/server.js +21 -0
  258. package/src/lib/server.js.map +1 -1
  259. package/src/lib/tenant-emails.js +161 -0
  260. package/src/lib/tenant-emails.js.map +1 -1
  261. package/src/lib/testing/memory-firestore.d.ts +16 -3
  262. package/src/lib/testing/memory-firestore.js +11 -3
  263. package/src/lib/testing/memory-firestore.js.map +1 -1
  264. package/src/lib/transfer/products.server.d.ts +5 -0
  265. package/src/lib/transfer/products.server.js +2 -2
  266. package/src/lib/transfer/products.server.js.map +1 -1
@@ -13,8 +13,10 @@
13
13
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
- */ import { fulfillmentIsActive, fulfillmentLineQuantities } from "./order-fulfillment.js";
16
+ */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
+ import { fulfillmentIsActive, fulfillmentLineQuantities } from "./order-fulfillment.js";
17
18
  import { carrierLabelFor, fulfillmentTrackingUrl } from "./tracking-url.js";
19
+ import { ORDER_LOCAL_DELIVERY_STATUS_LABELS, ORDER_PICKUP_STATUS_LABELS, orderLocalDeliveryStatus, orderPickupStatus } from "./order-local-fulfillment.js";
18
20
  const STATUS_LABELS = {
19
21
  pending: 'Awaiting payment',
20
22
  paid: 'Confirmed',
@@ -33,13 +35,22 @@ function eventAt(order, ...events) {
33
35
  return found ? Number(found.atMs) || null : null;
34
36
  }
35
37
  /** Builds the public projection. Pure; the caller has verified the link. */ export function buildOrderStatusView(input) {
36
- var _order_lineItems, _order_fulfillments, _order_refundedLineItemIds, _ref, _order_amountCents, _eventAt, _ref1, _STATUS_LABELS_status, _ref2, _ref3, _ref4, _ref5, _order_refundedCents, _order_extras, _input_actions;
38
+ var _order_lineItems, _order_fulfillments, _order_refundedLineItemIds, _ref, _order_amountCents, _eventAt, _ref1, _STATUS_LABELS_status, _ref2, _ref3, _ref4, _ref5, _order_refundedCents, _order_extras, _input_actions, _pickup_locationName, _pickup_address, _pickup_hours, _pickup_instructions, _delivery_windowLabel;
37
39
  var _shipments_;
38
40
  const { order } = input;
39
41
  const lineItems = (_order_lineItems = order.lineItems) != null ? _order_lineItems : [];
40
42
  const active = ((_order_fulfillments = order.fulfillments) != null ? _order_fulfillments : []).filter(fulfillmentIsActive);
43
+ // A handover is not a parcel (AGL-3624): it has no carrier or tracking,
44
+ // and the pickup or delivery block below says what happened.
45
+ const parcels = active.filter((fulfillment)=>!fulfillment.handover);
41
46
  const shippedByLine = new Map();
42
- const shipments = active.map((fulfillment)=>{
47
+ for (const fulfillment of active.filter((entry)=>entry.handover)){
48
+ for (const entry of fulfillmentLineQuantities(order, fulfillment)){
49
+ var _shippedByLine_get;
50
+ shippedByLine.set(entry.lineItemId, ((_shippedByLine_get = shippedByLine.get(entry.lineItemId)) != null ? _shippedByLine_get : 0) + entry.quantity);
51
+ }
52
+ }
53
+ const shipments = parcels.map((fulfillment)=>{
43
54
  var _fulfillment_id, _fulfillment_trackingNumber;
44
55
  const quantities = fulfillmentLineQuantities(order, fulfillment);
45
56
  for (const entry of quantities){
@@ -95,6 +106,8 @@ function eventAt(order, ...events) {
95
106
  done: status !== 'pending' && paidAtMs !== null
96
107
  }
97
108
  ];
109
+ const pickup = order.fulfillmentMethod === 'pickup' ? order.pickup : undefined;
110
+ const delivery = order.fulfillmentMethod === 'local_delivery' ? order.localDelivery : undefined;
98
111
  if (status === 'cancelled') {
99
112
  steps.push({
100
113
  key: 'cancelled',
@@ -102,6 +115,32 @@ function eventAt(order, ...events) {
102
115
  atMs: eventAt(order, 'cancelled'),
103
116
  done: true
104
117
  });
118
+ } else if (pickup) {
119
+ const pickupStatus = orderPickupStatus(pickup.status);
120
+ steps.push({
121
+ key: 'ready_for_pickup',
122
+ label: 'Ready for pickup',
123
+ atMs: Number(pickup.readyAtMs) || null,
124
+ done: pickupStatus !== 'preparing'
125
+ }, {
126
+ key: 'picked_up',
127
+ label: 'Picked up',
128
+ atMs: Number(pickup.pickedUpAtMs) || null,
129
+ done: pickupStatus === 'picked_up'
130
+ });
131
+ } else if (delivery) {
132
+ const deliveryStatus = orderLocalDeliveryStatus(delivery.status);
133
+ steps.push({
134
+ key: 'out_for_delivery',
135
+ label: 'Out for delivery',
136
+ atMs: Number(delivery.outForDeliveryAtMs) || null,
137
+ done: deliveryStatus === 'out_for_delivery' || deliveryStatus === 'delivered'
138
+ }, {
139
+ key: 'delivered',
140
+ label: 'Delivered',
141
+ atMs: Number(delivery.deliveredAtMs) || deliveredAt,
142
+ done: deliveryStatus === 'delivered' || status === 'delivered'
143
+ });
105
144
  } else {
106
145
  steps.push({
107
146
  key: 'shipped',
@@ -127,11 +166,11 @@ function eventAt(order, ...events) {
127
166
  done: true
128
167
  });
129
168
  }
130
- return {
169
+ return _extends({
131
170
  storeName: input.storeName,
132
171
  number: input.number,
133
172
  status,
134
- statusLabel: (_STATUS_LABELS_status = STATUS_LABELS[status]) != null ? _STATUS_LABELS_status : 'Confirmed',
173
+ statusLabel: pickup && status !== 'refunded' && status !== 'cancelled' ? ORDER_PICKUP_STATUS_LABELS[orderPickupStatus(pickup.status)] : delivery && status !== 'refunded' && status !== 'cancelled' ? ORDER_LOCAL_DELIVERY_STATUS_LABELS[orderLocalDeliveryStatus(delivery.status)] : (_STATUS_LABELS_status = STATUS_LABELS[status]) != null ? _STATUS_LABELS_status : 'Confirmed',
135
174
  createdAtMs,
136
175
  currency: input.currency || 'USD',
137
176
  lines,
@@ -150,7 +189,22 @@ function eventAt(order, ...events) {
150
189
  shipments,
151
190
  steps,
152
191
  actions: (_input_actions = input.actions) != null ? _input_actions : []
153
- };
192
+ }, pickup ? {
193
+ pickup: {
194
+ locationName: String((_pickup_locationName = pickup.locationName) != null ? _pickup_locationName : ''),
195
+ address: (_pickup_address = pickup.address) != null ? _pickup_address : null,
196
+ hours: (_pickup_hours = pickup.hours) != null ? _pickup_hours : null,
197
+ instructions: (_pickup_instructions = pickup.instructions) != null ? _pickup_instructions : null,
198
+ status: orderPickupStatus(pickup.status),
199
+ statusLabel: ORDER_PICKUP_STATUS_LABELS[orderPickupStatus(pickup.status)]
200
+ }
201
+ } : {}, delivery ? {
202
+ localDelivery: {
203
+ windowLabel: (_delivery_windowLabel = delivery.windowLabel) != null ? _delivery_windowLabel : null,
204
+ status: orderLocalDeliveryStatus(delivery.status),
205
+ statusLabel: ORDER_LOCAL_DELIVERY_STATUS_LABELS[orderLocalDeliveryStatus(delivery.status)]
206
+ }
207
+ } : {});
154
208
  }
155
209
 
156
210
  //# sourceMappingURL=order-status-view.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/order-status-view.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 type { HostOrder, OrderStatus } from './commerce-orders'\nimport { fulfillmentIsActive, fulfillmentLineQuantities } from './order-fulfillment'\nimport { carrierLabelFor, fulfillmentTrackingUrl } from './tracking-url'\n\n/**\n * What the guest order-status page shows (AGL-3610): the ONE projection of an\n * order that leaves the server for a visitor holding a signed link.\n *\n * Allow-listed, never spread: an order document carries the buyer's email,\n * phone and addresses, Stripe ids, risk signals and the merchant's notes, and\n * a forwarded link must disclose no more than the email it arrived in. Every\n * field below is one the buyer's own receipt or shipping email already\n * states. New fields are added here deliberately, or not at all.\n */\nexport interface OrderStatusLine {\n name: string\n variantLabel: string | null\n quantity: number\n amountCents: number\n /** Units of this line in an active shipment. */\n shippedQuantity: number\n refunded: boolean\n}\n\nexport interface OrderStatusShipment {\n id: string\n atMs: number\n carrier: string | null\n trackingNumber: string | null\n trackingUrl: string | null\n lines: Array<{ name: string; quantity: number }>\n}\n\nexport interface OrderStatusStep {\n key: 'placed' | 'paid' | 'shipped' | 'delivered' | 'cancelled' | 'refunded'\n label: string\n atMs: number | null\n done: boolean\n}\n\n/**\n * Something the buyer can do from the page — \"Request a return\" is the case\n * the order-processing lane (AGL-3611) adds through\n * `registerOrderStatusActions` on the server. The page renders each as a\n * button to `url`; it knows nothing of what the action is.\n */\nexport interface OrderStatusAction {\n id: string\n label: string\n url: string\n}\n\nexport interface OrderStatusView {\n storeName: string\n number: string\n status: OrderStatus\n /** The status as a buyer reads it. */\n statusLabel: string\n createdAtMs: number | null\n currency: string\n lines: OrderStatusLine[]\n totals: {\n itemsCents: number\n shippingCents: number\n taxCents: number\n discountCents: number\n totalCents: number\n refundedCents: number\n }\n /**\n * Optional lines the buyer took at checkout (AGL-3635) — \"Package\n * protection\" — by the name and price their receipt states. Optional so\n * an answer from before this field reads as none.\n */\n extras?: Array<{ label: string; amountCents: number }>\n shipments: OrderStatusShipment[]\n steps: OrderStatusStep[]\n actions: OrderStatusAction[]\n}\n\nconst STATUS_LABELS: Record<OrderStatus, string> = {\n pending: 'Awaiting payment',\n paid: 'Confirmed',\n partially_fulfilled: 'Partly shipped',\n fulfilled: 'Shipped',\n delivered: 'Delivered',\n cancelled: 'Canceled',\n refunded: 'Refunded',\n}\n\nfunction eventAt(order: HostOrder, ...events: string[]): number | null {\n const found = (order.timeline ?? []).find((entry) =>\n events.includes(String(entry?.event ?? '')),\n )\n return found ? Number(found.atMs) || null : null\n}\n\n/** Builds the public projection. Pure; the caller has verified the link. */\nexport function buildOrderStatusView(input: {\n order: HostOrder\n orderId: string\n storeName: string\n currency?: string\n number: string\n actions?: OrderStatusAction[]\n}): OrderStatusView {\n const { order } = input\n const lineItems = order.lineItems ?? []\n const active = (order.fulfillments ?? []).filter(fulfillmentIsActive)\n const shippedByLine = new Map<number, number>()\n const shipments: OrderStatusShipment[] = active\n .map((fulfillment) => {\n const quantities = fulfillmentLineQuantities(order, fulfillment)\n for (const entry of quantities) {\n shippedByLine.set(\n entry.lineItemId,\n (shippedByLine.get(entry.lineItemId) ?? 0) + entry.quantity,\n )\n }\n return {\n id: String(fulfillment.id ?? ''),\n atMs: Number(fulfillment.atMs) || 0,\n carrier: fulfillment.carrier ? carrierLabelFor(fulfillment.carrier) : null,\n trackingNumber: fulfillment.trackingNumber ?? null,\n trackingUrl: fulfillmentTrackingUrl(fulfillment),\n lines: quantities.map((entry) => ({\n name: String(lineItems[entry.lineItemId]?.name ?? 'Item'),\n quantity: entry.quantity,\n })),\n }\n })\n .sort((a, b) => a.atMs - b.atMs)\n const refundedLines = new Set(order.refundedLineItemIds ?? [])\n const lines: OrderStatusLine[] = lineItems.map((line, index) => ({\n name: String(line.name ?? 'Item'),\n variantLabel: line.variantLabel ?? null,\n quantity: Number(line.quantity) || 0,\n amountCents: (Number(line.unitAmountCents) || 0) * (Number(line.quantity) || 0),\n shippedQuantity: Math.min(Number(line.quantity) || 0, shippedByLine.get(index) ?? 0),\n refunded: refundedLines.has(index),\n }))\n const totals = order.totals\n const totalCents = totals?.totalCents ?? Number(order.amountCents ?? 0)\n const status = order.status\n const createdAtMs = Number(order.createdAtMs) || null\n const paidAtMs = eventAt(order, 'paid') ?? (status !== 'pending' ? createdAtMs : null)\n const firstShipAt = shipments[0]?.atMs ?? eventAt(order, 'fulfilled', 'partially_fulfilled')\n const deliveredAt = eventAt(order, 'delivered')\n const steps: OrderStatusStep[] = [\n { key: 'placed', label: 'Order placed', atMs: createdAtMs, done: true },\n {\n key: 'paid',\n label: 'Payment confirmed',\n atMs: paidAtMs,\n done: status !== 'pending' && paidAtMs !== null,\n },\n ]\n if (status === 'cancelled') {\n steps.push({ key: 'cancelled', label: 'Canceled', atMs: eventAt(order, 'cancelled'), done: true })\n } else {\n steps.push(\n {\n key: 'shipped',\n label: status === 'partially_fulfilled' ? 'Partly shipped' : 'Shipped',\n atMs: firstShipAt,\n done: ['partially_fulfilled', 'fulfilled', 'delivered'].includes(status) ||\n shipments.length > 0,\n },\n {\n key: 'delivered',\n label: 'Delivered',\n atMs: deliveredAt,\n done: status === 'delivered' || deliveredAt !== null,\n },\n )\n }\n if (status === 'refunded') {\n steps.push({ key: 'refunded', label: 'Refunded', atMs: eventAt(order, 'refund'), done: true })\n }\n return {\n storeName: input.storeName,\n number: input.number,\n status,\n statusLabel: STATUS_LABELS[status] ?? 'Confirmed',\n createdAtMs,\n currency: input.currency || 'USD',\n lines,\n totals: {\n itemsCents: totals?.itemsCents ?? lines.reduce((sum, line) => sum + line.amountCents, 0),\n shippingCents: totals?.shippingCents ?? 0,\n taxCents: totals?.taxCents ?? 0,\n discountCents: totals?.discountCents ?? 0,\n totalCents,\n refundedCents: Number(order.refundedCents ?? 0) || 0,\n },\n extras: (order.extras ?? [])\n .filter((extra) => extra && Number(extra.amountCents) > 0)\n .map((extra) => ({ label: String(extra.label || 'Extra'), amountCents: Number(extra.amountCents) })),\n shipments,\n steps,\n actions: input.actions ?? [],\n }\n}\n"],"names":["fulfillmentIsActive","fulfillmentLineQuantities","carrierLabelFor","fulfillmentTrackingUrl","STATUS_LABELS","pending","paid","partially_fulfilled","fulfilled","delivered","cancelled","refunded","eventAt","order","events","found","timeline","find","entry","includes","String","event","Number","atMs","buildOrderStatusView","input","shipments","lineItems","active","fulfillments","filter","shippedByLine","Map","map","fulfillment","quantities","set","lineItemId","get","quantity","id","carrier","trackingNumber","trackingUrl","lines","name","sort","a","b","refundedLines","Set","refundedLineItemIds","line","index","variantLabel","amountCents","unitAmountCents","shippedQuantity","Math","min","has","totals","totalCents","status","createdAtMs","paidAtMs","firstShipAt","deliveredAt","steps","key","label","done","push","length","storeName","number","statusLabel","currency","itemsCents","reduce","sum","shippingCents","taxCents","discountCents","refundedCents","extras","extra","actions"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAGD,SAASA,mBAAmB,EAAEC,yBAAyB,QAAQ,yBAAqB;AACpF,SAASC,eAAe,EAAEC,sBAAsB,QAAQ,oBAAgB;AA8ExE,MAAMC,gBAA6C;IACjDC,SAAS;IACTC,MAAM;IACNC,qBAAqB;IACrBC,WAAW;IACXC,WAAW;IACXC,WAAW;IACXC,UAAU;AACZ;AAEA,SAASC,QAAQC,KAAgB,EAAE,GAAGC,MAAgB;QACrCD;IAAf,MAAME,QAAQ,EAACF,kBAAAA,MAAMG,QAAQ,YAAdH,kBAAkB,EAAE,EAAEI,IAAI,CAAC,CAACC;;eACzCJ,OAAOK,QAAQ,CAACC,eAAOF,yBAAAA,MAAOG,KAAK,mBAAI;;IAEzC,OAAON,QAAQO,OAAOP,MAAMQ,IAAI,KAAK,OAAO;AAC9C;AAEA,0EAA0E,GAC1E,OAAO,SAASC,qBAAqBC,KAOpC;QAEmBZ,kBACFA,qBAwBcA,kCAUkBA,oBAG/BD,iBAsCFR,mDAUWS,sBAEfA,eAKAY;QAtDSC;IAxCpB,MAAM,EAAEb,KAAK,EAAE,GAAGY;IAClB,MAAME,aAAYd,mBAAAA,MAAMc,SAAS,YAAfd,mBAAmB,EAAE;IACvC,MAAMe,SAAS,EAACf,sBAAAA,MAAMgB,YAAY,YAAlBhB,sBAAsB,EAAE,EAAEiB,MAAM,CAAC9B;IACjD,MAAM+B,gBAAgB,IAAIC;IAC1B,MAAMN,YAAmCE,OACtCK,GAAG,CAAC,CAACC;YASSA,iBAGKA;QAXlB,MAAMC,aAAalC,0BAA0BY,OAAOqB;QACpD,KAAK,MAAMhB,SAASiB,WAAY;gBAG3BJ;YAFHA,cAAcK,GAAG,CACflB,MAAMmB,UAAU,EAChB,EAACN,qBAAAA,cAAcO,GAAG,CAACpB,MAAMmB,UAAU,aAAlCN,qBAAuC,KAAKb,MAAMqB,QAAQ;QAE/D;QACA,OAAO;YACLC,IAAIpB,QAAOc,kBAAAA,YAAYM,EAAE,YAAdN,kBAAkB;YAC7BX,MAAMD,OAAOY,YAAYX,IAAI,KAAK;YAClCkB,SAASP,YAAYO,OAAO,GAAGvC,gBAAgBgC,YAAYO,OAAO,IAAI;YACtEC,cAAc,GAAER,8BAAAA,YAAYQ,cAAc,YAA1BR,8BAA8B;YAC9CS,aAAaxC,uBAAuB+B;YACpCU,OAAOT,WAAWF,GAAG,CAAC,CAACf;;oBACRS;uBADmB;oBAChCkB,MAAMzB,gBAAOO,8BAAAA,SAAS,CAACT,MAAMmB,UAAU,CAAC,qBAA3BV,4BAA6BkB,IAAI,mBAAI;oBAClDN,UAAUrB,MAAMqB,QAAQ;gBAC1B;;QACF;IACF,GACCO,IAAI,CAAC,CAACC,GAAGC,IAAMD,EAAExB,IAAI,GAAGyB,EAAEzB,IAAI;IACjC,MAAM0B,gBAAgB,IAAIC,KAAIrC,6BAAAA,MAAMsC,mBAAmB,YAAzBtC,6BAA6B,EAAE;IAC7D,MAAM+B,QAA2BjB,UAAUM,GAAG,CAAC,CAACmB,MAAMC;YACvCD,YACCA,oBAGwCrB;eALS;YAC/Dc,MAAMzB,QAAOgC,aAAAA,KAAKP,IAAI,YAATO,aAAa;YAC1BE,YAAY,GAAEF,qBAAAA,KAAKE,YAAY,YAAjBF,qBAAqB;YACnCb,UAAUjB,OAAO8B,KAAKb,QAAQ,KAAK;YACnCgB,aAAa,AAACjC,CAAAA,OAAO8B,KAAKI,eAAe,KAAK,CAAA,IAAMlC,CAAAA,OAAO8B,KAAKb,QAAQ,KAAK,CAAA;YAC7EkB,iBAAiBC,KAAKC,GAAG,CAACrC,OAAO8B,KAAKb,QAAQ,KAAK,IAAGR,qBAAAA,cAAcO,GAAG,CAACe,kBAAlBtB,qBAA4B;YAClFpB,UAAUsC,cAAcW,GAAG,CAACP;QAC9B;;IACA,MAAMQ,SAAShD,MAAMgD,MAAM;IAC3B,MAAMC,qBAAaD,0BAAAA,OAAQC,UAAU,mBAAIxC,QAAOT,qBAAAA,MAAM0C,WAAW,YAAjB1C,qBAAqB;IACrE,MAAMkD,SAASlD,MAAMkD,MAAM;IAC3B,MAAMC,cAAc1C,OAAOT,MAAMmD,WAAW,KAAK;IACjD,MAAMC,YAAWrD,WAAAA,QAAQC,OAAO,mBAAfD,WAA2BmD,WAAW,YAAYC,cAAc;IACjF,MAAME,wBAAcxC,cAAAA,SAAS,CAAC,EAAE,qBAAZA,YAAcH,IAAI,oBAAIX,QAAQC,OAAO,aAAa;IACtE,MAAMsD,cAAcvD,QAAQC,OAAO;IACnC,MAAMuD,QAA2B;QAC/B;YAAEC,KAAK;YAAUC,OAAO;YAAgB/C,MAAMyC;YAAaO,MAAM;QAAK;QACtE;YACEF,KAAK;YACLC,OAAO;YACP/C,MAAM0C;YACNM,MAAMR,WAAW,aAAaE,aAAa;QAC7C;KACD;IACD,IAAIF,WAAW,aAAa;QAC1BK,MAAMI,IAAI,CAAC;YAAEH,KAAK;YAAaC,OAAO;YAAY/C,MAAMX,QAAQC,OAAO;YAAc0D,MAAM;QAAK;IAClG,OAAO;QACLH,MAAMI,IAAI,CACR;YACEH,KAAK;YACLC,OAAOP,WAAW,wBAAwB,mBAAmB;YAC7DxC,MAAM2C;YACNK,MAAM;gBAAC;gBAAuB;gBAAa;aAAY,CAACpD,QAAQ,CAAC4C,WAC/DrC,UAAU+C,MAAM,GAAG;QACvB,GACA;YACEJ,KAAK;YACLC,OAAO;YACP/C,MAAM4C;YACNI,MAAMR,WAAW,eAAeI,gBAAgB;QAClD;IAEJ;IACA,IAAIJ,WAAW,YAAY;QACzBK,MAAMI,IAAI,CAAC;YAAEH,KAAK;YAAYC,OAAO;YAAY/C,MAAMX,QAAQC,OAAO;YAAW0D,MAAM;QAAK;IAC9F;IACA,OAAO;QACLG,WAAWjD,MAAMiD,SAAS;QAC1BC,QAAQlD,MAAMkD,MAAM;QACpBZ;QACAa,WAAW,GAAExE,wBAAAA,aAAa,CAAC2D,OAAO,YAArB3D,wBAAyB;QACtC4D;QACAa,UAAUpD,MAAMoD,QAAQ,IAAI;QAC5BjC;QACAiB,QAAQ;YACNiB,UAAU,WAAEjB,0BAAAA,OAAQiB,UAAU,oBAAIlC,MAAMmC,MAAM,CAAC,CAACC,KAAK5B,OAAS4B,MAAM5B,KAAKG,WAAW,EAAE;YACtF0B,aAAa,WAAEpB,0BAAAA,OAAQoB,aAAa,oBAAI;YACxCC,QAAQ,WAAErB,0BAAAA,OAAQqB,QAAQ,oBAAI;YAC9BC,aAAa,WAAEtB,0BAAAA,OAAQsB,aAAa,oBAAI;YACxCrB;YACAsB,eAAe9D,QAAOT,uBAAAA,MAAMuE,aAAa,YAAnBvE,uBAAuB,MAAM;QACrD;QACAwE,QAAQ,EAACxE,gBAAAA,MAAMwE,MAAM,YAAZxE,gBAAgB,EAAE,EACxBiB,MAAM,CAAC,CAACwD,QAAUA,SAAShE,OAAOgE,MAAM/B,WAAW,IAAI,GACvDtB,GAAG,CAAC,CAACqD,QAAW,CAAA;gBAAEhB,OAAOlD,OAAOkE,MAAMhB,KAAK,IAAI;gBAAUf,aAAajC,OAAOgE,MAAM/B,WAAW;YAAE,CAAA;QACnG7B;QACA0C;QACAmB,OAAO,GAAE9D,iBAAAA,MAAM8D,OAAO,YAAb9D,iBAAiB,EAAE;IAC9B;AACF"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/order-status-view.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 type { HostOrder, OrderStatus } from './commerce-orders'\nimport { fulfillmentIsActive, fulfillmentLineQuantities } from './order-fulfillment'\nimport { carrierLabelFor, fulfillmentTrackingUrl } from './tracking-url'\nimport {\n ORDER_LOCAL_DELIVERY_STATUS_LABELS,\n ORDER_PICKUP_STATUS_LABELS,\n orderLocalDeliveryStatus,\n orderPickupStatus,\n type OrderLocalDeliveryStatus,\n type OrderPickupStatus,\n} from './order-local-fulfillment'\n\n/**\n * What the guest order-status page shows (AGL-3610): the ONE projection of an\n * order that leaves the server for a visitor holding a signed link.\n *\n * Allow-listed, never spread: an order document carries the buyer's email,\n * phone and addresses, Stripe ids, risk signals and the merchant's notes, and\n * a forwarded link must disclose no more than the email it arrived in. Every\n * field below is one the buyer's own receipt or shipping email already\n * states. New fields are added here deliberately, or not at all.\n */\nexport interface OrderStatusLine {\n name: string\n variantLabel: string | null\n quantity: number\n amountCents: number\n /** Units of this line in an active shipment. */\n shippedQuantity: number\n refunded: boolean\n}\n\nexport interface OrderStatusShipment {\n id: string\n atMs: number\n carrier: string | null\n trackingNumber: string | null\n trackingUrl: string | null\n lines: Array<{ name: string; quantity: number }>\n}\n\nexport interface OrderStatusStep {\n key:\n | 'placed'\n | 'paid'\n | 'shipped'\n | 'delivered'\n | 'cancelled'\n | 'refunded'\n // Pickup and the store's own delivery (AGL-3624).\n | 'ready_for_pickup'\n | 'picked_up'\n | 'out_for_delivery'\n label: string\n atMs: number | null\n done: boolean\n}\n\n/**\n * Something the buyer can do from the page — \"Request a return\" is the case\n * the order-processing lane (AGL-3611) adds through\n * `registerOrderStatusActions` on the server. The page renders each as a\n * button to `url`; it knows nothing of what the action is.\n */\nexport interface OrderStatusAction {\n id: string\n label: string\n url: string\n}\n\nexport interface OrderStatusView {\n storeName: string\n number: string\n status: OrderStatus\n /** The status as a buyer reads it. */\n statusLabel: string\n createdAtMs: number | null\n currency: string\n lines: OrderStatusLine[]\n totals: {\n itemsCents: number\n shippingCents: number\n taxCents: number\n discountCents: number\n totalCents: number\n refundedCents: number\n }\n /**\n * Optional lines the buyer took at checkout (AGL-3635) — \"Package\n * protection\" — by the name and price their receipt states. Optional so\n * an answer from before this field reads as none.\n */\n extras?: Array<{ label: string; amountCents: number }>\n shipments: OrderStatusShipment[]\n steps: OrderStatusStep[]\n actions: OrderStatusAction[]\n /**\n * Where to collect a pickup order (AGL-3624) — the location the buyer chose\n * and its hours, as the ready-for-pickup email states them.\n */\n pickup?: {\n locationName: string\n address: string | null\n hours: string | null\n instructions: string | null\n status: OrderPickupStatus\n statusLabel: string\n }\n /** When the store's own driver brings it (AGL-3624). */\n localDelivery?: {\n windowLabel: string | null\n status: OrderLocalDeliveryStatus\n statusLabel: string\n }\n}\n\nconst STATUS_LABELS: Record<OrderStatus, string> = {\n pending: 'Awaiting payment',\n paid: 'Confirmed',\n partially_fulfilled: 'Partly shipped',\n fulfilled: 'Shipped',\n delivered: 'Delivered',\n cancelled: 'Canceled',\n refunded: 'Refunded',\n}\n\nfunction eventAt(order: HostOrder, ...events: string[]): number | null {\n const found = (order.timeline ?? []).find((entry) =>\n events.includes(String(entry?.event ?? '')),\n )\n return found ? Number(found.atMs) || null : null\n}\n\n/** Builds the public projection. Pure; the caller has verified the link. */\nexport function buildOrderStatusView(input: {\n order: HostOrder\n orderId: string\n storeName: string\n currency?: string\n number: string\n actions?: OrderStatusAction[]\n}): OrderStatusView {\n const { order } = input\n const lineItems = order.lineItems ?? []\n const active = (order.fulfillments ?? []).filter(fulfillmentIsActive)\n // A handover is not a parcel (AGL-3624): it has no carrier or tracking,\n // and the pickup or delivery block below says what happened.\n const parcels = active.filter((fulfillment) => !fulfillment.handover)\n const shippedByLine = new Map<number, number>()\n for (const fulfillment of active.filter((entry) => entry.handover)) {\n for (const entry of fulfillmentLineQuantities(order, fulfillment)) {\n shippedByLine.set(entry.lineItemId, (shippedByLine.get(entry.lineItemId) ?? 0) + entry.quantity)\n }\n }\n const shipments: OrderStatusShipment[] = parcels\n .map((fulfillment) => {\n const quantities = fulfillmentLineQuantities(order, fulfillment)\n for (const entry of quantities) {\n shippedByLine.set(\n entry.lineItemId,\n (shippedByLine.get(entry.lineItemId) ?? 0) + entry.quantity,\n )\n }\n return {\n id: String(fulfillment.id ?? ''),\n atMs: Number(fulfillment.atMs) || 0,\n carrier: fulfillment.carrier ? carrierLabelFor(fulfillment.carrier) : null,\n trackingNumber: fulfillment.trackingNumber ?? null,\n trackingUrl: fulfillmentTrackingUrl(fulfillment),\n lines: quantities.map((entry) => ({\n name: String(lineItems[entry.lineItemId]?.name ?? 'Item'),\n quantity: entry.quantity,\n })),\n }\n })\n .sort((a, b) => a.atMs - b.atMs)\n const refundedLines = new Set(order.refundedLineItemIds ?? [])\n const lines: OrderStatusLine[] = lineItems.map((line, index) => ({\n name: String(line.name ?? 'Item'),\n variantLabel: line.variantLabel ?? null,\n quantity: Number(line.quantity) || 0,\n amountCents: (Number(line.unitAmountCents) || 0) * (Number(line.quantity) || 0),\n shippedQuantity: Math.min(Number(line.quantity) || 0, shippedByLine.get(index) ?? 0),\n refunded: refundedLines.has(index),\n }))\n const totals = order.totals\n const totalCents = totals?.totalCents ?? Number(order.amountCents ?? 0)\n const status = order.status\n const createdAtMs = Number(order.createdAtMs) || null\n const paidAtMs = eventAt(order, 'paid') ?? (status !== 'pending' ? createdAtMs : null)\n const firstShipAt = shipments[0]?.atMs ?? eventAt(order, 'fulfilled', 'partially_fulfilled')\n const deliveredAt = eventAt(order, 'delivered')\n const steps: OrderStatusStep[] = [\n { key: 'placed', label: 'Order placed', atMs: createdAtMs, done: true },\n {\n key: 'paid',\n label: 'Payment confirmed',\n atMs: paidAtMs,\n done: status !== 'pending' && paidAtMs !== null,\n },\n ]\n const pickup = order.fulfillmentMethod === 'pickup' ? order.pickup : undefined\n const delivery = order.fulfillmentMethod === 'local_delivery' ? order.localDelivery : undefined\n if (status === 'cancelled') {\n steps.push({ key: 'cancelled', label: 'Canceled', atMs: eventAt(order, 'cancelled'), done: true })\n } else if (pickup) {\n const pickupStatus = orderPickupStatus(pickup.status)\n steps.push(\n {\n key: 'ready_for_pickup',\n label: 'Ready for pickup',\n atMs: Number(pickup.readyAtMs) || null,\n done: pickupStatus !== 'preparing',\n },\n {\n key: 'picked_up',\n label: 'Picked up',\n atMs: Number(pickup.pickedUpAtMs) || null,\n done: pickupStatus === 'picked_up',\n },\n )\n } else if (delivery) {\n const deliveryStatus = orderLocalDeliveryStatus(delivery.status)\n steps.push(\n {\n key: 'out_for_delivery',\n label: 'Out for delivery',\n atMs: Number(delivery.outForDeliveryAtMs) || null,\n done: deliveryStatus === 'out_for_delivery' || deliveryStatus === 'delivered',\n },\n {\n key: 'delivered',\n label: 'Delivered',\n atMs: Number(delivery.deliveredAtMs) || deliveredAt,\n done: deliveryStatus === 'delivered' || status === 'delivered',\n },\n )\n } else {\n steps.push(\n {\n key: 'shipped',\n label: status === 'partially_fulfilled' ? 'Partly shipped' : 'Shipped',\n atMs: firstShipAt,\n done: ['partially_fulfilled', 'fulfilled', 'delivered'].includes(status) ||\n shipments.length > 0,\n },\n {\n key: 'delivered',\n label: 'Delivered',\n atMs: deliveredAt,\n done: status === 'delivered' || deliveredAt !== null,\n },\n )\n }\n if (status === 'refunded') {\n steps.push({ key: 'refunded', label: 'Refunded', atMs: eventAt(order, 'refund'), done: true })\n }\n return {\n storeName: input.storeName,\n number: input.number,\n status,\n statusLabel:\n pickup && status !== 'refunded' && status !== 'cancelled'\n ? ORDER_PICKUP_STATUS_LABELS[orderPickupStatus(pickup.status)]\n : delivery && status !== 'refunded' && status !== 'cancelled'\n ? ORDER_LOCAL_DELIVERY_STATUS_LABELS[orderLocalDeliveryStatus(delivery.status)]\n : (STATUS_LABELS[status] ?? 'Confirmed'),\n createdAtMs,\n currency: input.currency || 'USD',\n lines,\n totals: {\n itemsCents: totals?.itemsCents ?? lines.reduce((sum, line) => sum + line.amountCents, 0),\n shippingCents: totals?.shippingCents ?? 0,\n taxCents: totals?.taxCents ?? 0,\n discountCents: totals?.discountCents ?? 0,\n totalCents,\n refundedCents: Number(order.refundedCents ?? 0) || 0,\n },\n extras: (order.extras ?? [])\n .filter((extra) => extra && Number(extra.amountCents) > 0)\n .map((extra) => ({ label: String(extra.label || 'Extra'), amountCents: Number(extra.amountCents) })),\n shipments,\n steps,\n actions: input.actions ?? [],\n ...(pickup\n ? {\n pickup: {\n locationName: String(pickup.locationName ?? ''),\n address: pickup.address ?? null,\n hours: pickup.hours ?? null,\n instructions: pickup.instructions ?? null,\n status: orderPickupStatus(pickup.status),\n statusLabel: ORDER_PICKUP_STATUS_LABELS[orderPickupStatus(pickup.status)],\n },\n }\n : {}),\n ...(delivery\n ? {\n localDelivery: {\n windowLabel: delivery.windowLabel ?? null,\n status: orderLocalDeliveryStatus(delivery.status),\n statusLabel: ORDER_LOCAL_DELIVERY_STATUS_LABELS[orderLocalDeliveryStatus(delivery.status)],\n },\n }\n : {}),\n }\n}\n"],"names":["fulfillmentIsActive","fulfillmentLineQuantities","carrierLabelFor","fulfillmentTrackingUrl","ORDER_LOCAL_DELIVERY_STATUS_LABELS","ORDER_PICKUP_STATUS_LABELS","orderLocalDeliveryStatus","orderPickupStatus","STATUS_LABELS","pending","paid","partially_fulfilled","fulfilled","delivered","cancelled","refunded","eventAt","order","events","found","timeline","find","entry","includes","String","event","Number","atMs","buildOrderStatusView","input","pickup","delivery","shipments","lineItems","active","fulfillments","filter","parcels","fulfillment","handover","shippedByLine","Map","set","lineItemId","get","quantity","map","quantities","id","carrier","trackingNumber","trackingUrl","lines","name","sort","a","b","refundedLines","Set","refundedLineItemIds","line","index","variantLabel","amountCents","unitAmountCents","shippedQuantity","Math","min","has","totals","totalCents","status","createdAtMs","paidAtMs","firstShipAt","deliveredAt","steps","key","label","done","fulfillmentMethod","undefined","localDelivery","push","pickupStatus","readyAtMs","pickedUpAtMs","deliveryStatus","outForDeliveryAtMs","deliveredAtMs","length","storeName","number","statusLabel","currency","itemsCents","reduce","sum","shippingCents","taxCents","discountCents","refundedCents","extras","extra","actions","locationName","address","hours","instructions","windowLabel"],"mappings":"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"}
@@ -0,0 +1,186 @@
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
+ */
17
+ import { type HostProduct, type InventoryAdjustment, type InventoryAdjustmentReason, type SmartCollectionRules } from './commerce';
18
+ /** A product as the editor holds it: the stored fields plus keys the listener or a seed may carry. */
19
+ export type EditedProduct = HostProduct & {
20
+ $id?: string;
21
+ skus?: string[];
22
+ barcodes?: string[];
23
+ stockHolds?: Record<string, unknown>;
24
+ };
25
+ /**
26
+ * The fields a product save writes, before its collection membership and its
27
+ * timestamps: the product as edited, with
28
+ *
29
+ * - `$id` dropped (AGL-1374): the listener's synthetic key, never a field;
30
+ * - the seed's `skus` / `barcodes` dropped (AGL-3321): `productSearchFields`
31
+ * omits each when no variant has one, so a seeded copy would outlive the
32
+ * last SKU;
33
+ * - the trimmed name with the search keys derived from it, together, so the
34
+ * two cannot drift (AGL-2501);
35
+ * - a slug, the primary variant's price, the stock fields, the first image
36
+ * and `updatedAtMs`.
37
+ */
38
+ export declare function productSaveFields(current: EditedProduct, nowMs: number): {
39
+ imageUrl: string;
40
+ updatedAtMs: number;
41
+ inventory: number | null;
42
+ soldOut: boolean;
43
+ slug: string;
44
+ priceUsd: number;
45
+ name: string;
46
+ nameLower: string;
47
+ nameTokens: string[];
48
+ nameReversed: string;
49
+ skus?: string[];
50
+ barcodes?: string[];
51
+ priceFromCents?: number;
52
+ description?: string;
53
+ type: import("./commerce").ProductType;
54
+ status: import("./commerce").ProductStatus;
55
+ mediaUrls?: string[];
56
+ categoryIds?: string[];
57
+ collectionIds?: string[];
58
+ tags?: string[];
59
+ options?: import("./commerce").ProductOption[];
60
+ variants: import("./commerce").ProductVariant[];
61
+ seo?: {
62
+ title?: string;
63
+ description?: string;
64
+ imageUrl?: string;
65
+ };
66
+ channel?: import("./product-channel").ProductChannelFacts;
67
+ supplierId?: string;
68
+ oversellPolicy?: "deny" | "backorder";
69
+ taxExempt?: boolean;
70
+ digitalFiles?: Array<{
71
+ url: string;
72
+ fileName: string;
73
+ version?: string;
74
+ }>;
75
+ downloadLimit?: number;
76
+ subscription?: {
77
+ interval: "month" | "year";
78
+ trialDays?: number;
79
+ };
80
+ subscriptionOptional?: boolean;
81
+ gatedVideos?: Array<{
82
+ url: string;
83
+ title?: string;
84
+ }>;
85
+ relatedProductIds?: string[];
86
+ shipping?: import("./commerce").ProductShippingFacts;
87
+ giftCard?: boolean;
88
+ lowStockThreshold?: number;
89
+ modifierGroups?: import("./product-modifiers").ProductModifierGroup[];
90
+ posQuickKey?: boolean;
91
+ createdAtMs?: number;
92
+ deletedAt?: number | null;
93
+ stockHolds?: Record<string, unknown>;
94
+ };
95
+ /** What the smart-collection rules are matched against: the saved name with the product's own facts. */
96
+ export declare function productMembershipInput(fields: {
97
+ name: string;
98
+ }, current: Pick<HostProduct, 'type' | 'tags' | 'categoryIds' | 'variants'>): {
99
+ name: string;
100
+ type: import("./commerce").ProductType;
101
+ tags: string[];
102
+ categoryIds: string[];
103
+ variants: import("./commerce").ProductVariant[];
104
+ };
105
+ /**
106
+ * An EDIT's replacing write, without its membership and server timestamp:
107
+ * live stock holds are not the editor's to write (AGL-2356), so the seed's
108
+ * are dropped and the stored ones carried; the product stays live
109
+ * (`deletedAt: null`, AGL-3321).
110
+ */
111
+ export declare function productEditWrite(fields: ReturnType<typeof productSaveFields>, liveHolds: Record<string, unknown> | null | undefined): {
112
+ deletedAt: any;
113
+ stockHolds?: Record<string, unknown>;
114
+ imageUrl: string;
115
+ updatedAtMs: number;
116
+ inventory: number | null;
117
+ soldOut: boolean;
118
+ slug: string;
119
+ priceUsd: number;
120
+ name: string;
121
+ nameLower: string;
122
+ nameTokens: string[];
123
+ nameReversed: string;
124
+ skus?: string[];
125
+ barcodes?: string[];
126
+ priceFromCents?: number;
127
+ description?: string;
128
+ type: import("./commerce").ProductType;
129
+ status: import("./commerce").ProductStatus;
130
+ mediaUrls?: string[];
131
+ categoryIds?: string[];
132
+ collectionIds?: string[];
133
+ tags?: string[];
134
+ options?: import("./commerce").ProductOption[];
135
+ variants: import("./commerce").ProductVariant[];
136
+ seo?: {
137
+ title?: string;
138
+ description?: string;
139
+ imageUrl?: string;
140
+ };
141
+ channel?: import("./product-channel").ProductChannelFacts;
142
+ supplierId?: string;
143
+ oversellPolicy?: "deny" | "backorder";
144
+ taxExempt?: boolean;
145
+ digitalFiles?: Array<{
146
+ url: string;
147
+ fileName: string;
148
+ version?: string;
149
+ }>;
150
+ downloadLimit?: number;
151
+ subscription?: {
152
+ interval: "month" | "year";
153
+ trialDays?: number;
154
+ };
155
+ subscriptionOptional?: boolean;
156
+ gatedVideos?: Array<{
157
+ url: string;
158
+ title?: string;
159
+ }>;
160
+ relatedProductIds?: string[];
161
+ shipping?: import("./commerce").ProductShippingFacts;
162
+ giftCard?: boolean;
163
+ lowStockThreshold?: number;
164
+ modifierGroups?: import("./product-modifiers").ProductModifierGroup[];
165
+ posQuickKey?: boolean;
166
+ createdAtMs?: number;
167
+ };
168
+ /** A smart collection as `hosts/{hostId}/collections` stores it, as the membership rules read it. */
169
+ export declare function smartCollectionRulesOf(id: string, data: Record<string, unknown>): SmartCollectionRules;
170
+ /** One stock adjustment, as the Adjust stock dialog asks for it. */
171
+ export interface StockAdjustment {
172
+ variantId: string;
173
+ delta: number;
174
+ reason: InventoryAdjustmentReason;
175
+ locationId?: string;
176
+ }
177
+ /**
178
+ * The product update and the ledger row one stock adjustment writes: the
179
+ * variants with the count moved, the stock fields that move with it
180
+ * (AGL-3321), and the same `inventoryAdjustments` row the sale webhook writes
181
+ * (AGL-281). A zero or non-numeric delta writes nothing.
182
+ */
183
+ export declare function stockAdjustmentWrite(product: HostProduct, productId: string, adjustment: StockAdjustment, nowMs: number): {
184
+ update: Record<string, unknown>;
185
+ ledger: InventoryAdjustment;
186
+ } | null;
@@ -0,0 +1,116 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
3
+ /**
4
+ * @license
5
+ * Copyright 2026 Aglyn LLC
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */ import { adjustVariantInventory, commerceSlug, productSearchFields, productStockFields } from "./commerce.js";
19
+ /**
20
+ * The fields a product save writes, before its collection membership and its
21
+ * timestamps: the product as edited, with
22
+ *
23
+ * - `$id` dropped (AGL-1374): the listener's synthetic key, never a field;
24
+ * - the seed's `skus` / `barcodes` dropped (AGL-3321): `productSearchFields`
25
+ * omits each when no variant has one, so a seeded copy would outlive the
26
+ * last SKU;
27
+ * - the trimmed name with the search keys derived from it, together, so the
28
+ * two cannot drift (AGL-2501);
29
+ * - a slug, the primary variant's price, the stock fields, the first image
30
+ * and `updatedAtMs`.
31
+ */ export function productSaveFields(current, nowMs) {
32
+ var _ref, _ref1, _ref2;
33
+ var _current_mediaUrls;
34
+ const { $id: _syntheticId, skus: _seededSkus, barcodes: _seededBarcodes } = current, currentFields = _object_without_properties_loose(current, [
35
+ "$id",
36
+ "skus",
37
+ "barcodes"
38
+ ]);
39
+ const primaryVariant = current.variants[0];
40
+ return _extends({}, currentFields, productSearchFields({
41
+ name: current.name.trim().slice(0, 120),
42
+ variants: current.variants
43
+ }), {
44
+ slug: current.slug || commerceSlug(current.name),
45
+ priceUsd: (_ref = primaryVariant == null ? void 0 : primaryVariant.priceUsd) != null ? _ref : 0
46
+ }, productStockFields({
47
+ variants: current.variants,
48
+ oversellPolicy: current.oversellPolicy
49
+ }), {
50
+ imageUrl: (_ref1 = (_ref2 = (_current_mediaUrls = current.mediaUrls) == null ? void 0 : _current_mediaUrls[0]) != null ? _ref2 : current.imageUrl) != null ? _ref1 : null,
51
+ updatedAtMs: nowMs
52
+ });
53
+ }
54
+ /** What the smart-collection rules are matched against: the saved name with the product's own facts. */ export function productMembershipInput(fields, current) {
55
+ return {
56
+ name: fields.name,
57
+ type: current.type,
58
+ tags: current.tags,
59
+ categoryIds: current.categoryIds,
60
+ variants: current.variants
61
+ };
62
+ }
63
+ /**
64
+ * An EDIT's replacing write, without its membership and server timestamp:
65
+ * live stock holds are not the editor's to write (AGL-2356), so the seed's
66
+ * are dropped and the stored ones carried; the product stays live
67
+ * (`deletedAt: null`, AGL-3321).
68
+ */ export function productEditWrite(fields, liveHolds) {
69
+ const { stockHolds: _seededHolds } = fields, withoutHolds = _object_without_properties_loose(fields, [
70
+ "stockHolds"
71
+ ]);
72
+ return _extends({}, withoutHolds, liveHolds ? {
73
+ stockHolds: liveHolds
74
+ } : {}, {
75
+ deletedAt: null
76
+ });
77
+ }
78
+ /** A smart collection as `hosts/{hostId}/collections` stores it, as the membership rules read it. */ export function smartCollectionRulesOf(id, data) {
79
+ var _data_rules;
80
+ return {
81
+ id,
82
+ rules: (_data_rules = data['rules']) != null ? _data_rules : [],
83
+ matchAll: data['matchAll']
84
+ };
85
+ }
86
+ /**
87
+ * The product update and the ledger row one stock adjustment writes: the
88
+ * variants with the count moved, the stock fields that move with it
89
+ * (AGL-3321), and the same `inventoryAdjustments` row the sale webhook writes
90
+ * (AGL-281). A zero or non-numeric delta writes nothing.
91
+ */ export function stockAdjustmentWrite(product, productId, adjustment, nowMs) {
92
+ const delta = Math.round(Number(adjustment.delta));
93
+ if (!delta) return null;
94
+ const variants = adjustVariantInventory(product, adjustment.variantId, delta, adjustment.locationId || undefined);
95
+ return {
96
+ update: _extends({
97
+ variants
98
+ }, productStockFields(_extends({}, product, {
99
+ variants
100
+ })), {
101
+ updatedAtMs: nowMs
102
+ }),
103
+ ledger: _extends({
104
+ productId,
105
+ variantId: adjustment.variantId,
106
+ delta,
107
+ reason: adjustment.reason
108
+ }, adjustment.locationId ? {
109
+ locationId: adjustment.locationId
110
+ } : {}, {
111
+ atMs: nowMs
112
+ })
113
+ };
114
+ }
115
+
116
+ //# sourceMappingURL=product-write.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/product-write.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n adjustVariantInventory,\n commerceSlug,\n type HostProduct,\n type InventoryAdjustment,\n type InventoryAdjustmentReason,\n productSearchFields,\n productStockFields,\n type SmartCollectionRules,\n} from './commerce'\n\n/*\n * WHAT A PRODUCT SAVE AND A STOCK ADJUSTMENT WRITE, worked out once.\n *\n * The console's product editor and its Adjust stock dialog compute these\n * fields in the browser before their Firestore writes; the native apps save\n * through `commerce/products/save` and `commerce/products/stock`, which run\n * this same code on the server. One computation, so a product saved from a\n * phone carries exactly the search keys, stock verdict, price, image and\n * smart-collection membership the console would have written.\n */\n\n/** A product as the editor holds it: the stored fields plus keys the listener or a seed may carry. */\nexport type EditedProduct = HostProduct & {\n $id?: string\n skus?: string[]\n barcodes?: string[]\n stockHolds?: Record<string, unknown>\n}\n\n/**\n * The fields a product save writes, before its collection membership and its\n * timestamps: the product as edited, with\n *\n * - `$id` dropped (AGL-1374): the listener's synthetic key, never a field;\n * - the seed's `skus` / `barcodes` dropped (AGL-3321): `productSearchFields`\n * omits each when no variant has one, so a seeded copy would outlive the\n * last SKU;\n * - the trimmed name with the search keys derived from it, together, so the\n * two cannot drift (AGL-2501);\n * - a slug, the primary variant's price, the stock fields, the first image\n * and `updatedAtMs`.\n */\nexport function productSaveFields(current: EditedProduct, nowMs: number) {\n const {\n $id: _syntheticId,\n skus: _seededSkus,\n barcodes: _seededBarcodes,\n ...currentFields\n } = current\n const primaryVariant = current.variants[0]\n return {\n ...currentFields,\n ...productSearchFields({\n name: current.name.trim().slice(0, 120),\n variants: current.variants,\n }),\n slug: current.slug || commerceSlug(current.name),\n priceUsd: primaryVariant?.priceUsd ?? 0,\n ...productStockFields({\n variants: current.variants,\n oversellPolicy: current.oversellPolicy,\n }),\n imageUrl: current.mediaUrls?.[0] ?? current.imageUrl ?? null,\n updatedAtMs: nowMs,\n }\n}\n\n/** What the smart-collection rules are matched against: the saved name with the product's own facts. */\nexport function productMembershipInput(\n fields: { name: string },\n current: Pick<HostProduct, 'type' | 'tags' | 'categoryIds' | 'variants'>,\n) {\n return {\n name: fields.name,\n type: current.type,\n tags: current.tags,\n categoryIds: current.categoryIds,\n variants: current.variants,\n }\n}\n\n/**\n * An EDIT's replacing write, without its membership and server timestamp:\n * live stock holds are not the editor's to write (AGL-2356), so the seed's\n * are dropped and the stored ones carried; the product stays live\n * (`deletedAt: null`, AGL-3321).\n */\nexport function productEditWrite(\n fields: ReturnType<typeof productSaveFields>,\n liveHolds: Record<string, unknown> | null | undefined,\n) {\n const { stockHolds: _seededHolds, ...withoutHolds } = fields as typeof fields & {\n stockHolds?: Record<string, unknown>\n }\n return {\n ...withoutHolds,\n ...(liveHolds ? { stockHolds: liveHolds } : {}),\n deletedAt: null,\n }\n}\n\n/** A smart collection as `hosts/{hostId}/collections` stores it, as the membership rules read it. */\nexport function smartCollectionRulesOf(id: string, data: Record<string, unknown>): SmartCollectionRules {\n return {\n id,\n rules: (data['rules'] as SmartCollectionRules['rules']) ?? [],\n matchAll: data['matchAll'] as boolean | undefined,\n }\n}\n\n/** One stock adjustment, as the Adjust stock dialog asks for it. */\nexport interface StockAdjustment {\n variantId: string\n delta: number\n reason: InventoryAdjustmentReason\n locationId?: string\n}\n\n/**\n * The product update and the ledger row one stock adjustment writes: the\n * variants with the count moved, the stock fields that move with it\n * (AGL-3321), and the same `inventoryAdjustments` row the sale webhook writes\n * (AGL-281). A zero or non-numeric delta writes nothing.\n */\nexport function stockAdjustmentWrite(\n product: HostProduct,\n productId: string,\n adjustment: StockAdjustment,\n nowMs: number,\n): { update: Record<string, unknown>; ledger: InventoryAdjustment } | null {\n const delta = Math.round(Number(adjustment.delta))\n if (!delta) return null\n const variants = adjustVariantInventory(product, adjustment.variantId, delta, adjustment.locationId || undefined)\n return {\n update: {\n variants,\n ...productStockFields({ ...product, variants }),\n updatedAtMs: nowMs,\n },\n ledger: {\n productId,\n variantId: adjustment.variantId,\n delta,\n reason: adjustment.reason,\n ...(adjustment.locationId ? { locationId: adjustment.locationId } : {}),\n atMs: nowMs,\n },\n }\n}\n"],"names":["adjustVariantInventory","commerceSlug","productSearchFields","productStockFields","productSaveFields","current","nowMs","$id","_syntheticId","skus","_seededSkus","barcodes","_seededBarcodes","currentFields","primaryVariant","variants","name","trim","slice","slug","priceUsd","oversellPolicy","imageUrl","mediaUrls","updatedAtMs","productMembershipInput","fields","type","tags","categoryIds","productEditWrite","liveHolds","stockHolds","_seededHolds","withoutHolds","deletedAt","smartCollectionRulesOf","id","data","rules","matchAll","stockAdjustmentWrite","product","productId","adjustment","delta","Math","round","Number","variantId","locationId","undefined","update","ledger","reason","atMs"],"mappings":";;AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,sBAAsB,EACtBC,YAAY,EAIZC,mBAAmB,EACnBC,kBAAkB,QAEb,gBAAY;AAqBnB;;;;;;;;;;;;CAYC,GACD,OAAO,SAASC,kBAAkBC,OAAsB,EAAEC,KAAa;cAoBzDD;QAAAA;IAnBZ,MAAM,EACJE,KAAKC,YAAY,EACjBC,MAAMC,WAAW,EACjBC,UAAUC,eAAe,EAE1B,GAAGP,SADCQ,iDACDR;;;;;IACJ,MAAMS,iBAAiBT,QAAQU,QAAQ,CAAC,EAAE;IAC1C,OAAO,aACFF,eACAX,oBAAoB;QACrBc,MAAMX,QAAQW,IAAI,CAACC,IAAI,GAAGC,KAAK,CAAC,GAAG;QACnCH,UAAUV,QAAQU,QAAQ;IAC5B;QACAI,MAAMd,QAAQc,IAAI,IAAIlB,aAAaI,QAAQW,IAAI;QAC/CI,QAAQ,UAAEN,kCAAAA,eAAgBM,QAAQ,mBAAI;OACnCjB,mBAAmB;QACpBY,UAAUV,QAAQU,QAAQ;QAC1BM,gBAAgBhB,QAAQgB,cAAc;IACxC;QACAC,QAAQ,GAAEjB,kBAAAA,qBAAAA,QAAQkB,SAAS,qBAAjBlB,kBAAmB,CAAC,EAAE,oBAAIA,QAAQiB,QAAQ,YAA1CjB,QAA8C;QACxDmB,aAAalB;;AAEjB;AAEA,sGAAsG,GACtG,OAAO,SAASmB,uBACdC,MAAwB,EACxBrB,OAAwE;IAExE,OAAO;QACLW,MAAMU,OAAOV,IAAI;QACjBW,MAAMtB,QAAQsB,IAAI;QAClBC,MAAMvB,QAAQuB,IAAI;QAClBC,aAAaxB,QAAQwB,WAAW;QAChCd,UAAUV,QAAQU,QAAQ;IAC5B;AACF;AAEA;;;;;CAKC,GACD,OAAO,SAASe,iBACdJ,MAA4C,EAC5CK,SAAqD;IAErD,MAAM,EAAEC,YAAYC,YAAY,EAAmB,GAAGP,QAAjBQ,gDAAiBR;;;IAGtD,OAAO,aACFQ,cACCH,YAAY;QAAEC,YAAYD;IAAU,IAAI,CAAC;QAC7CI,WAAW;;AAEf;AAEA,mGAAmG,GACnG,OAAO,SAASC,uBAAuBC,EAAU,EAAEC,IAA6B;QAGpEA;IAFV,OAAO;QACLD;QACAE,KAAK,GAAGD,cAAAA,IAAI,CAAC,QAAQ,YAAbA,cAAmD,EAAE;QAC7DE,UAAUF,IAAI,CAAC,WAAW;IAC5B;AACF;AAUA;;;;;CAKC,GACD,OAAO,SAASG,qBACdC,OAAoB,EACpBC,SAAiB,EACjBC,UAA2B,EAC3BtC,KAAa;IAEb,MAAMuC,QAAQC,KAAKC,KAAK,CAACC,OAAOJ,WAAWC,KAAK;IAChD,IAAI,CAACA,OAAO,OAAO;IACnB,MAAM9B,WAAWf,uBAAuB0C,SAASE,WAAWK,SAAS,EAAEJ,OAAOD,WAAWM,UAAU,IAAIC;IACvG,OAAO;QACLC,QAAQ;YACNrC;WACGZ,mBAAmB,aAAKuC;YAAS3B;;YACpCS,aAAalB;;QAEf+C,QAAQ;YACNV;YACAM,WAAWL,WAAWK,SAAS;YAC/BJ;YACAS,QAAQV,WAAWU,MAAM;WACrBV,WAAWM,UAAU,GAAG;YAAEA,YAAYN,WAAWM,UAAU;QAAC,IAAI,CAAC;YACrEK,MAAMjD;;IAEV;AACF"}
@@ -16,6 +16,7 @@
16
16
  */ import { _ as _extends } from "@swc/helpers/_/_extends";
17
17
  import { posTipPercentages } from "./model/commerce-pos.js";
18
18
  import { POS_OPS_CONFIG_DEFAULTS, POS_OPS_CONFIG_FIELDS } from "./pos-ops-config.js";
19
+ import { POS_KIOSK_CONFIG_DEFAULTS, POS_KIOSK_CONFIG_FIELDS } from "./pos-kiosk-config.js";
19
20
  /**
20
21
  * The ceiling a register may discount to, when the merchant has not set one
21
22
  * (AGL-2161).
@@ -108,7 +109,10 @@ import { POS_OPS_CONFIG_DEFAULTS, POS_OPS_CONFIG_FIELDS } from "./pos-ops-config
108
109
  },
109
110
  // RUNNING THE REGISTER (AGL-3609): shifts, refund limits, the idle lock
110
111
  // and what a printed receipt carries.
111
- ...POS_OPS_CONFIG_FIELDS
112
+ ...POS_OPS_CONFIG_FIELDS,
113
+ // THE SELF-SERVICE KIOSK (AGL-3623): pay at counter, the idle reset and
114
+ // its welcome line.
115
+ ...POS_KIOSK_CONFIG_FIELDS
112
116
  ],
113
117
  defaults: _extends({
114
118
  posMaxDiscountPct: POS_MAX_DISCOUNT_PCT_DEFAULT,
@@ -117,7 +121,7 @@ import { POS_OPS_CONFIG_DEFAULTS, POS_OPS_CONFIG_FIELDS } from "./pos-ops-config
117
121
  posReceiptDefault: 'ask',
118
122
  posDisplayMessage: '',
119
123
  posDisplayMarketingOptIn: true
120
- }, POS_OPS_CONFIG_DEFAULTS)
124
+ }, POS_OPS_CONFIG_DEFAULTS, POS_KIOSK_CONFIG_DEFAULTS)
121
125
  };
122
126
  /**
123
127
  * The register settings out of a merged commerce config, with every value
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/plugin-config.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 type { PluginConfigSchema } from '@aglyn/aglyn'\nimport { posTipPercentages } from './model/commerce-pos'\nimport { POS_OPS_CONFIG_DEFAULTS, POS_OPS_CONFIG_FIELDS } from './pos-ops-config'\n\n/**\n * The ceiling a register may discount to, when the merchant has not set one\n * (AGL-2161).\n *\n * 100, i.e. today's behaviour exactly. That is deliberate and is the one\n * number in this fix that is NOT a safety judgement:\n *\n * - A full comp is a legitimate register operation — a damaged item, a\n * goodwill gesture, a staff meal — and every merchant on the platform has\n * had it since POS shipped.\n * - Any lower default would change what merchants charge their own customers\n * on the day it deployed, without them asking. Picking that number is a\n * merchant policy decision, not a bug fix, and inventing one here would be\n * the same class of mistake as inventing a billing rate.\n *\n * What AGL-2161 actually fixes is that there was no ceiling to set and no\n * refusal when one was exceeded: the request body's `discountPct` was clamped\n * into range and rung up, so a register sending `150` silently comped the\n * whole sale. The ceiling is now real, enforced server-side, refused rather\n * than clamped, and one config value away from being lower.\n */\nexport const POS_MAX_DISCOUNT_PCT_DEFAULT = 100\n\n/**\n * Commerce plugin config schema (AGL-428 framework, AGL-2161 adopter).\n *\n * PURE DATA (type-only aglyn import), the same shape as bookings': the client\n * barrel registers it via '@aglyn/aglyn' and the /server entry via\n * '@aglyn/aglyn/server', so neither bundle drags in the other's barrel.\n *\n * `min`/`max` are not decoration — `mergePluginConfig` coerces a stored value\n * into the declared range and falls back to the default on junk, so the\n * ceiling itself cannot be forged out of bounds by a manager editing the\n * settings doc directly.\n */\nexport const COMMERCE_CONFIG_SCHEMA: PluginConfigSchema = {\n pluginId: 'commerce',\n fields: [\n {\n key: 'posMaxDiscountPct',\n label: 'Maximum register discount (%)',\n type: 'number',\n min: 0,\n max: 100,\n description:\n 'The largest discount the point of sale will accept on a sale. ' +\n 'A register that asks for more is refused, and the sale is not rung ' +\n 'up. Set to 0 to stop staff discounting at the register entirely.',\n },\n // THE REGISTER'S TIPS, RECEIPTS AND CUSTOMER DISPLAY (AGL-3607, AGL-3608).\n // Off by default: a store that never asked for tips does not start\n // asking its customers for one on the day this ships.\n {\n key: 'posTippingEnabled',\n label: 'Ask for tips at the register',\n type: 'boolean',\n description:\n 'Offers the customer a tip on the card reader and on the customer ' +\n 'display. Tips are yours: they are not counted as sales and carry no ' +\n 'platform fee.',\n },\n {\n key: 'posTipPercentages',\n label: 'Tip choices (%)',\n type: 'string',\n description:\n 'Up to four percentages, separated by commas, shown to the customer ' +\n 'as tip buttons. The customer can also enter an amount or choose no tip.',\n },\n {\n key: 'posReceiptDefault',\n label: 'Receipts at the register',\n type: 'select',\n options: [\n { value: 'ask', label: 'Ask the customer' },\n { value: 'print', label: 'Always print' },\n { value: 'none', label: 'No receipt unless asked' },\n ],\n description:\n 'With a customer display paired, \"Ask the customer\" lets them choose ' +\n 'email, print or no receipt on their own screen.',\n },\n {\n key: 'posDisplayMessage',\n label: 'Customer display welcome',\n type: 'string',\n description:\n 'The line the customer display shows under your store name between ' +\n 'sales. Leave blank for \"Welcome\".',\n },\n {\n key: 'posDisplayMarketingOptIn',\n label: 'Offer email sign-up on the customer display',\n type: 'boolean',\n description:\n 'Shows an unticked \"Email me news and offers\" box when a customer ' +\n 'asks for an email receipt. Only a ticked box adds them to your ' +\n 'marketing audience.',\n },\n // RUNNING THE REGISTER (AGL-3609): shifts, refund limits, the idle lock\n // and what a printed receipt carries.\n ...POS_OPS_CONFIG_FIELDS,\n ],\n defaults: {\n posMaxDiscountPct: POS_MAX_DISCOUNT_PCT_DEFAULT,\n posTippingEnabled: false,\n posTipPercentages: '15, 18, 20, 25',\n posReceiptDefault: 'ask',\n posDisplayMessage: '',\n posDisplayMarketingOptIn: true,\n ...POS_OPS_CONFIG_DEFAULTS,\n },\n}\n\n/** The register's tip, receipt and display settings, resolved for one site. */\nexport interface PosRegisterSettings {\n tippingEnabled: boolean\n tipPercentages: number[]\n receiptDefault: 'ask' | 'print' | 'none'\n displayMessage: string\n displayMarketingOptIn: boolean\n}\n\n/**\n * The register settings out of a merged commerce config, with every value\n * typed and bounded so the register, the display and the server agree.\n */\nexport function posRegisterSettings(\n config: Record<string, unknown> | null | undefined,\n): PosRegisterSettings {\n const receipt = String(config?.['posReceiptDefault'] ?? 'ask')\n return {\n tippingEnabled: config?.['posTippingEnabled'] === true,\n tipPercentages: posTipPercentages(config?.['posTipPercentages']),\n receiptDefault:\n receipt === 'print' || receipt === 'none' ? receipt : 'ask',\n displayMessage: String(config?.['posDisplayMessage'] ?? '').trim().slice(0, 120),\n displayMarketingOptIn: config?.['posDisplayMarketingOptIn'] !== false,\n }\n}\n\n/**\n * The ceiling for one org, read defensively (AGL-2161).\n *\n * Spelled here rather than at the call site so the POS route and any later\n * reader cannot disagree about what a missing or malformed value means. The\n * framework has already clamped and defaulted by the time this runs; the\n * second `Number.isFinite` guard is for the schema-less case, where\n * `getPluginConfig` returns the raw doc untouched.\n */\nexport function posMaxDiscountPct(\n config: Record<string, unknown> | null | undefined,\n): number {\n const raw = Number(config?.['posMaxDiscountPct'])\n if (!Number.isFinite(raw)) return POS_MAX_DISCOUNT_PCT_DEFAULT\n return Math.min(100, Math.max(0, raw))\n}\n"],"names":["posTipPercentages","POS_OPS_CONFIG_DEFAULTS","POS_OPS_CONFIG_FIELDS","POS_MAX_DISCOUNT_PCT_DEFAULT","COMMERCE_CONFIG_SCHEMA","pluginId","fields","key","label","type","min","max","description","options","value","defaults","posMaxDiscountPct","posTippingEnabled","posReceiptDefault","posDisplayMessage","posDisplayMarketingOptIn","posRegisterSettings","config","receipt","String","tippingEnabled","tipPercentages","receiptDefault","displayMessage","trim","slice","displayMarketingOptIn","raw","Number","isFinite","Math"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SAASA,iBAAiB,QAAQ,0BAAsB;AACxD,SAASC,uBAAuB,EAAEC,qBAAqB,QAAQ,sBAAkB;AAEjF;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,MAAMC,+BAA+B,IAAG;AAE/C;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,yBAA6C;IACxDC,UAAU;IACVC,QAAQ;QACN;YACEC,KAAK;YACLC,OAAO;YACPC,MAAM;YACNC,KAAK;YACLC,KAAK;YACLC,aACE,mEACA,wEACA;QACJ;QACA,2EAA2E;QAC3E,mEAAmE;QACnE,sDAAsD;QACtD;YACEL,KAAK;YACLC,OAAO;YACPC,MAAM;YACNG,aACE,sEACA,yEACA;QACJ;QACA;YACEL,KAAK;YACLC,OAAO;YACPC,MAAM;YACNG,aACE,wEACA;QACJ;QACA;YACEL,KAAK;YACLC,OAAO;YACPC,MAAM;YACNI,SAAS;gBACP;oBAAEC,OAAO;oBAAON,OAAO;gBAAmB;gBAC1C;oBAAEM,OAAO;oBAASN,OAAO;gBAAe;gBACxC;oBAAEM,OAAO;oBAAQN,OAAO;gBAA0B;aACnD;YACDI,aACE,yEACA;QACJ;QACA;YACEL,KAAK;YACLC,OAAO;YACPC,MAAM;YACNG,aACE,uEACA;QACJ;QACA;YACEL,KAAK;YACLC,OAAO;YACPC,MAAM;YACNG,aACE,sEACA,oEACA;QACJ;QACA,wEAAwE;QACxE,sCAAsC;WACnCV;KACJ;IACDa,UAAU;QACRC,mBAAmBb;QACnBc,mBAAmB;QACnBjB,mBAAmB;QACnBkB,mBAAmB;QACnBC,mBAAmB;QACnBC,0BAA0B;OACvBnB;AAEP,EAAC;AAWD;;;CAGC,GACD,OAAO,SAASoB,oBACdC,MAAkD;;IAElD,MAAMC,UAAUC,eAAOF,0BAAAA,MAAQ,CAAC,oBAAoB,mBAAI;IACxD,OAAO;QACLG,gBAAgBH,CAAAA,0BAAAA,MAAQ,CAAC,oBAAoB,MAAK;QAClDI,gBAAgB1B,kBAAkBsB,0BAAAA,MAAQ,CAAC,oBAAoB;QAC/DK,gBACEJ,YAAY,WAAWA,YAAY,SAASA,UAAU;QACxDK,gBAAgBJ,gBAAOF,0BAAAA,MAAQ,CAAC,oBAAoB,oBAAI,IAAIO,IAAI,GAAGC,KAAK,CAAC,GAAG;QAC5EC,uBAAuBT,CAAAA,0BAAAA,MAAQ,CAAC,2BAA2B,MAAK;IAClE;AACF;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASN,kBACdM,MAAkD;IAElD,MAAMU,MAAMC,OAAOX,0BAAAA,MAAQ,CAAC,oBAAoB;IAChD,IAAI,CAACW,OAAOC,QAAQ,CAACF,MAAM,OAAO7B;IAClC,OAAOgC,KAAKzB,GAAG,CAAC,KAAKyB,KAAKxB,GAAG,CAAC,GAAGqB;AACnC"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/plugin-config.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 type { PluginConfigSchema } from '@aglyn/aglyn'\nimport { posTipPercentages } from './model/commerce-pos'\nimport { POS_OPS_CONFIG_DEFAULTS, POS_OPS_CONFIG_FIELDS } from './pos-ops-config'\nimport { POS_KIOSK_CONFIG_DEFAULTS, POS_KIOSK_CONFIG_FIELDS } from './pos-kiosk-config'\n\n/**\n * The ceiling a register may discount to, when the merchant has not set one\n * (AGL-2161).\n *\n * 100, i.e. today's behaviour exactly. That is deliberate and is the one\n * number in this fix that is NOT a safety judgement:\n *\n * - A full comp is a legitimate register operation — a damaged item, a\n * goodwill gesture, a staff meal — and every merchant on the platform has\n * had it since POS shipped.\n * - Any lower default would change what merchants charge their own customers\n * on the day it deployed, without them asking. Picking that number is a\n * merchant policy decision, not a bug fix, and inventing one here would be\n * the same class of mistake as inventing a billing rate.\n *\n * What AGL-2161 actually fixes is that there was no ceiling to set and no\n * refusal when one was exceeded: the request body's `discountPct` was clamped\n * into range and rung up, so a register sending `150` silently comped the\n * whole sale. The ceiling is now real, enforced server-side, refused rather\n * than clamped, and one config value away from being lower.\n */\nexport const POS_MAX_DISCOUNT_PCT_DEFAULT = 100\n\n/**\n * Commerce plugin config schema (AGL-428 framework, AGL-2161 adopter).\n *\n * PURE DATA (type-only aglyn import), the same shape as bookings': the client\n * barrel registers it via '@aglyn/aglyn' and the /server entry via\n * '@aglyn/aglyn/server', so neither bundle drags in the other's barrel.\n *\n * `min`/`max` are not decoration — `mergePluginConfig` coerces a stored value\n * into the declared range and falls back to the default on junk, so the\n * ceiling itself cannot be forged out of bounds by a manager editing the\n * settings doc directly.\n */\nexport const COMMERCE_CONFIG_SCHEMA: PluginConfigSchema = {\n pluginId: 'commerce',\n fields: [\n {\n key: 'posMaxDiscountPct',\n label: 'Maximum register discount (%)',\n type: 'number',\n min: 0,\n max: 100,\n description:\n 'The largest discount the point of sale will accept on a sale. ' +\n 'A register that asks for more is refused, and the sale is not rung ' +\n 'up. Set to 0 to stop staff discounting at the register entirely.',\n },\n // THE REGISTER'S TIPS, RECEIPTS AND CUSTOMER DISPLAY (AGL-3607, AGL-3608).\n // Off by default: a store that never asked for tips does not start\n // asking its customers for one on the day this ships.\n {\n key: 'posTippingEnabled',\n label: 'Ask for tips at the register',\n type: 'boolean',\n description:\n 'Offers the customer a tip on the card reader and on the customer ' +\n 'display. Tips are yours: they are not counted as sales and carry no ' +\n 'platform fee.',\n },\n {\n key: 'posTipPercentages',\n label: 'Tip choices (%)',\n type: 'string',\n description:\n 'Up to four percentages, separated by commas, shown to the customer ' +\n 'as tip buttons. The customer can also enter an amount or choose no tip.',\n },\n {\n key: 'posReceiptDefault',\n label: 'Receipts at the register',\n type: 'select',\n options: [\n { value: 'ask', label: 'Ask the customer' },\n { value: 'print', label: 'Always print' },\n { value: 'none', label: 'No receipt unless asked' },\n ],\n description:\n 'With a customer display paired, \"Ask the customer\" lets them choose ' +\n 'email, print or no receipt on their own screen.',\n },\n {\n key: 'posDisplayMessage',\n label: 'Customer display welcome',\n type: 'string',\n description:\n 'The line the customer display shows under your store name between ' +\n 'sales. Leave blank for \"Welcome\".',\n },\n {\n key: 'posDisplayMarketingOptIn',\n label: 'Offer email sign-up on the customer display',\n type: 'boolean',\n description:\n 'Shows an unticked \"Email me news and offers\" box when a customer ' +\n 'asks for an email receipt. Only a ticked box adds them to your ' +\n 'marketing audience.',\n },\n // RUNNING THE REGISTER (AGL-3609): shifts, refund limits, the idle lock\n // and what a printed receipt carries.\n ...POS_OPS_CONFIG_FIELDS,\n // THE SELF-SERVICE KIOSK (AGL-3623): pay at counter, the idle reset and\n // its welcome line.\n ...POS_KIOSK_CONFIG_FIELDS,\n ],\n defaults: {\n posMaxDiscountPct: POS_MAX_DISCOUNT_PCT_DEFAULT,\n posTippingEnabled: false,\n posTipPercentages: '15, 18, 20, 25',\n posReceiptDefault: 'ask',\n posDisplayMessage: '',\n posDisplayMarketingOptIn: true,\n ...POS_OPS_CONFIG_DEFAULTS,\n ...POS_KIOSK_CONFIG_DEFAULTS,\n },\n}\n\n/** The register's tip, receipt and display settings, resolved for one site. */\nexport interface PosRegisterSettings {\n tippingEnabled: boolean\n tipPercentages: number[]\n receiptDefault: 'ask' | 'print' | 'none'\n displayMessage: string\n displayMarketingOptIn: boolean\n}\n\n/**\n * The register settings out of a merged commerce config, with every value\n * typed and bounded so the register, the display and the server agree.\n */\nexport function posRegisterSettings(\n config: Record<string, unknown> | null | undefined,\n): PosRegisterSettings {\n const receipt = String(config?.['posReceiptDefault'] ?? 'ask')\n return {\n tippingEnabled: config?.['posTippingEnabled'] === true,\n tipPercentages: posTipPercentages(config?.['posTipPercentages']),\n receiptDefault:\n receipt === 'print' || receipt === 'none' ? receipt : 'ask',\n displayMessage: String(config?.['posDisplayMessage'] ?? '').trim().slice(0, 120),\n displayMarketingOptIn: config?.['posDisplayMarketingOptIn'] !== false,\n }\n}\n\n/**\n * The ceiling for one org, read defensively (AGL-2161).\n *\n * Spelled here rather than at the call site so the POS route and any later\n * reader cannot disagree about what a missing or malformed value means. The\n * framework has already clamped and defaulted by the time this runs; the\n * second `Number.isFinite` guard is for the schema-less case, where\n * `getPluginConfig` returns the raw doc untouched.\n */\nexport function posMaxDiscountPct(\n config: Record<string, unknown> | null | undefined,\n): number {\n const raw = Number(config?.['posMaxDiscountPct'])\n if (!Number.isFinite(raw)) return POS_MAX_DISCOUNT_PCT_DEFAULT\n return Math.min(100, Math.max(0, raw))\n}\n"],"names":["posTipPercentages","POS_OPS_CONFIG_DEFAULTS","POS_OPS_CONFIG_FIELDS","POS_KIOSK_CONFIG_DEFAULTS","POS_KIOSK_CONFIG_FIELDS","POS_MAX_DISCOUNT_PCT_DEFAULT","COMMERCE_CONFIG_SCHEMA","pluginId","fields","key","label","type","min","max","description","options","value","defaults","posMaxDiscountPct","posTippingEnabled","posReceiptDefault","posDisplayMessage","posDisplayMarketingOptIn","posRegisterSettings","config","receipt","String","tippingEnabled","tipPercentages","receiptDefault","displayMessage","trim","slice","displayMarketingOptIn","raw","Number","isFinite","Math"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC;AAGD,SAASA,iBAAiB,QAAQ,0BAAsB;AACxD,SAASC,uBAAuB,EAAEC,qBAAqB,QAAQ,sBAAkB;AACjF,SAASC,yBAAyB,EAAEC,uBAAuB,QAAQ,wBAAoB;AAEvF;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,MAAMC,+BAA+B,IAAG;AAE/C;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,yBAA6C;IACxDC,UAAU;IACVC,QAAQ;QACN;YACEC,KAAK;YACLC,OAAO;YACPC,MAAM;YACNC,KAAK;YACLC,KAAK;YACLC,aACE,mEACA,wEACA;QACJ;QACA,2EAA2E;QAC3E,mEAAmE;QACnE,sDAAsD;QACtD;YACEL,KAAK;YACLC,OAAO;YACPC,MAAM;YACNG,aACE,sEACA,yEACA;QACJ;QACA;YACEL,KAAK;YACLC,OAAO;YACPC,MAAM;YACNG,aACE,wEACA;QACJ;QACA;YACEL,KAAK;YACLC,OAAO;YACPC,MAAM;YACNI,SAAS;gBACP;oBAAEC,OAAO;oBAAON,OAAO;gBAAmB;gBAC1C;oBAAEM,OAAO;oBAASN,OAAO;gBAAe;gBACxC;oBAAEM,OAAO;oBAAQN,OAAO;gBAA0B;aACnD;YACDI,aACE,yEACA;QACJ;QACA;YACEL,KAAK;YACLC,OAAO;YACPC,MAAM;YACNG,aACE,uEACA;QACJ;QACA;YACEL,KAAK;YACLC,OAAO;YACPC,MAAM;YACNG,aACE,sEACA,oEACA;QACJ;QACA,wEAAwE;QACxE,sCAAsC;WACnCZ;QACH,wEAAwE;QACxE,oBAAoB;WACjBE;KACJ;IACDa,UAAU;QACRC,mBAAmBb;QACnBc,mBAAmB;QACnBnB,mBAAmB;QACnBoB,mBAAmB;QACnBC,mBAAmB;QACnBC,0BAA0B;OACvBrB,yBACAE;AAEP,EAAC;AAWD;;;CAGC,GACD,OAAO,SAASoB,oBACdC,MAAkD;;IAElD,MAAMC,UAAUC,eAAOF,0BAAAA,MAAQ,CAAC,oBAAoB,mBAAI;IACxD,OAAO;QACLG,gBAAgBH,CAAAA,0BAAAA,MAAQ,CAAC,oBAAoB,MAAK;QAClDI,gBAAgB5B,kBAAkBwB,0BAAAA,MAAQ,CAAC,oBAAoB;QAC/DK,gBACEJ,YAAY,WAAWA,YAAY,SAASA,UAAU;QACxDK,gBAAgBJ,gBAAOF,0BAAAA,MAAQ,CAAC,oBAAoB,oBAAI,IAAIO,IAAI,GAAGC,KAAK,CAAC,GAAG;QAC5EC,uBAAuBT,CAAAA,0BAAAA,MAAQ,CAAC,2BAA2B,MAAK;IAClE;AACF;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASN,kBACdM,MAAkD;IAElD,MAAMU,MAAMC,OAAOX,0BAAAA,MAAQ,CAAC,oBAAoB;IAChD,IAAI,CAACW,OAAOC,QAAQ,CAACF,MAAM,OAAO7B;IAClC,OAAOgC,KAAKzB,GAAG,CAAC,KAAKyB,KAAKxB,GAAG,CAAC,GAAGqB;AACnC"}
package/src/lib/plugin.js CHANGED
@@ -18,6 +18,7 @@ import { mdiStorefrontOutline } from "@aglyn/shared-data-mdi";
18
18
  import { lazy } from "react";
19
19
  const PosConsolePage = lazy(()=>import("./components/console/pos-page.component.js"));
20
20
  /** The customer display (AGL-3608): loads only on the public page that shows it. */ const PosDisplayPage = lazy(()=>import("./components/console/pos-display-page.component.js"));
21
+ const PosKioskPage = lazy(()=>import("./components/console/pos-kiosk-page.component.js"));
21
22
  const CommerceGlanceCard = lazy(()=>import("./components/console/commerce-glance-card.component.js"));
22
23
  const NewestSiteUsersCard = lazy(()=>import("./components/console/newest-site-users-card.component.js"));
23
24
  const SiteMemberPurchases = lazy(()=>import("./components/console/site-member-purchases.component.js"));
@@ -30,6 +31,7 @@ import { registerCommerceZones } from "./components/console/product-zones.js";
30
31
  import { registerCommerceOrderZones } from "./components/console/order-zones.js";
31
32
  import { registerCommerceReturnZones } from "./components/console/return-zones.js";
32
33
  import { registerCommerceStoreZones } from "./components/console/store-zones.js";
34
+ import { registerCommercePosZones } from "./components/console/pos-zones.js";
33
35
  import { registerCommerceTransferUi } from "./transfer/register-transfer-ui.js";
34
36
  import { COMMERCE_SEARCH_SOURCES } from "./model/commerce-search-sources.js";
35
37
  import { COMMERCE_CONFIG_SCHEMA } from "./plugin-config.js";
@@ -58,6 +60,8 @@ import { COMMERCE_CONFIG_SCHEMA } from "./plugin-config.js";
58
60
  registerCommerceReturnZones();
59
61
  // The orders list's bulk actions and the store's Settings (AGL-3612).
60
62
  registerCommerceStoreZones();
63
+ // The register's orders from other channels — a delivery app's (AGL-3644).
64
+ registerCommercePosZones();
61
65
  // Its import and export resources' names and the products wizard's own
62
66
  // step (AGL-3531); the server halves register from the console's server
63
67
  // declarations.
@@ -81,6 +85,13 @@ import { COMMERCE_CONFIG_SCHEMA } from "./plugin-config.js";
81
85
  path: '/pos-display',
82
86
  title: 'Customer display',
83
87
  Component: PosDisplayPage
88
+ },
89
+ // The self-service kiosk (AGL-3623), paired the same way with a kiosk
90
+ // code; `server/pos-kiosk.ts` refuses every call without its token.
91
+ {
92
+ path: '/pos-kiosk',
93
+ title: 'Self-service kiosk',
94
+ Component: PosKioskPage
84
95
  }
85
96
  ],
86
97
  // Dashboard/analytics glance card (AGL-419): rendered through the