@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.
- package/package.json +13 -13
- package/src/lib/components/cart-extras.d.ts +6 -0
- package/src/lib/components/cart-extras.js +4 -1
- package/src/lib/components/cart-extras.js.map +1 -1
- package/src/lib/components/cart-fulfillment.d.ts +117 -0
- package/src/lib/components/cart-fulfillment.js +367 -0
- package/src/lib/components/cart-fulfillment.js.map +1 -0
- package/src/lib/components/cart.js +52 -10
- package/src/lib/components/cart.js.map +1 -1
- package/src/lib/components/commerce-console-page.js +42 -1
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/console/host-orders-card.component.js +38 -10
- package/src/lib/components/console/host-orders-card.component.js.map +1 -1
- package/src/lib/components/console/local-delivery-settings-card.component.d.ts +37 -0
- package/src/lib/components/console/local-delivery-settings-card.component.js +542 -0
- package/src/lib/components/console/local-delivery-settings-card.component.js.map +1 -0
- package/src/lib/components/console/locations-card.component.js +177 -3
- package/src/lib/components/console/locations-card.component.js.map +1 -1
- package/src/lib/components/console/order-detail-dialog.component.js +10 -0
- package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
- package/src/lib/components/console/order-local-fulfillment-panel.component.d.ts +44 -0
- package/src/lib/components/console/order-local-fulfillment-panel.component.js +265 -0
- package/src/lib/components/console/order-local-fulfillment-panel.component.js.map +1 -0
- package/src/lib/components/console/order-offline-notice.component.d.ts +12 -0
- package/src/lib/components/console/order-offline-notice.component.js +53 -0
- package/src/lib/components/console/order-offline-notice.component.js.map +1 -0
- package/src/lib/components/console/pickup-delivery-queue-card.component.d.ts +35 -0
- package/src/lib/components/console/pickup-delivery-queue-card.component.js +222 -0
- package/src/lib/components/console/pickup-delivery-queue-card.component.js.map +1 -0
- package/src/lib/components/console/pos/pos-api.d.ts +24 -1
- package/src/lib/components/console/pos/pos-api.js +13 -0
- package/src/lib/components/console/pos/pos-api.js.map +1 -1
- package/src/lib/components/console/pos/pos-item-dialog.component.d.ts +12 -0
- package/src/lib/components/console/pos/pos-item-dialog.component.js +10 -8
- package/src/lib/components/console/pos/pos-item-dialog.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-kiosk-queue.component.d.ts +22 -0
- package/src/lib/components/console/pos/pos-kiosk-queue.component.js +168 -0
- package/src/lib/components/console/pos/pos-kiosk-queue.component.js.map +1 -0
- package/src/lib/components/console/pos/pos-tender-dialogs.component.d.ts +18 -0
- package/src/lib/components/console/pos/pos-tender-dialogs.component.js +116 -1
- package/src/lib/components/console/pos/pos-tender-dialogs.component.js.map +1 -1
- package/src/lib/components/console/pos/pos-tender-panel.component.d.ts +8 -0
- package/src/lib/components/console/pos/pos-tender-panel.component.js +77 -18
- package/src/lib/components/console/pos/pos-tender-panel.component.js.map +1 -1
- package/src/lib/components/console/pos/use-pos-display.d.ts +5 -2
- package/src/lib/components/console/pos/use-pos-display.js +3 -2
- package/src/lib/components/console/pos/use-pos-display.js.map +1 -1
- package/src/lib/components/console/pos-devices-card.component.js +2 -2
- package/src/lib/components/console/pos-devices-card.component.js.map +1 -1
- package/src/lib/components/console/pos-display/pairing-screen.d.ts +5 -2
- package/src/lib/components/console/pos-display/pairing-screen.js +7 -5
- package/src/lib/components/console/pos-display/pairing-screen.js.map +1 -1
- package/src/lib/components/console/pos-display/pos-display-api.d.ts +7 -1
- package/src/lib/components/console/pos-display/pos-display-api.js +7 -2
- package/src/lib/components/console/pos-display/pos-display-api.js.map +1 -1
- package/src/lib/components/console/pos-kiosk/kiosk-checkout.d.ts +41 -0
- package/src/lib/components/console/pos-kiosk/kiosk-checkout.js +275 -0
- package/src/lib/components/console/pos-kiosk/kiosk-checkout.js.map +1 -0
- package/src/lib/components/console/pos-kiosk/kiosk-menu.d.ts +32 -0
- package/src/lib/components/console/pos-kiosk/kiosk-menu.js +344 -0
- package/src/lib/components/console/pos-kiosk/kiosk-menu.js.map +1 -0
- package/src/lib/components/console/pos-kiosk/kiosk-staff.d.ts +12 -0
- package/src/lib/components/console/pos-kiosk/kiosk-staff.js +257 -0
- package/src/lib/components/console/pos-kiosk/kiosk-staff.js.map +1 -0
- package/src/lib/components/console/pos-kiosk/pos-kiosk-api.d.ts +114 -0
- package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js +189 -0
- package/src/lib/components/console/pos-kiosk/pos-kiosk-api.js.map +1 -0
- package/src/lib/components/console/pos-kiosk/use-kiosk-idle.d.ts +16 -0
- package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js +80 -0
- package/src/lib/components/console/pos-kiosk/use-kiosk-idle.js.map +1 -0
- package/src/lib/components/console/pos-kiosk-page.component.d.ts +28 -0
- package/src/lib/components/console/pos-kiosk-page.component.js +791 -0
- package/src/lib/components/console/pos-kiosk-page.component.js.map +1 -0
- package/src/lib/components/console/pos-offline/pos-offline-api.d.ts +44 -0
- package/src/lib/components/console/pos-offline/pos-offline-api.js +69 -0
- package/src/lib/components/console/pos-offline/pos-offline-api.js.map +1 -0
- package/src/lib/components/console/pos-offline/pos-offline-banner.component.d.ts +7 -0
- package/src/lib/components/console/pos-offline/pos-offline-banner.component.js +135 -0
- package/src/lib/components/console/pos-offline/pos-offline-banner.component.js.map +1 -0
- package/src/lib/components/console/pos-offline/pos-offline-checkout.component.d.ts +32 -0
- package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js +287 -0
- package/src/lib/components/console/pos-offline/pos-offline-checkout.component.js.map +1 -0
- package/src/lib/components/console/pos-offline/pos-offline-store.d.ts +63 -0
- package/src/lib/components/console/pos-offline/pos-offline-store.js +145 -0
- package/src/lib/components/console/pos-offline/pos-offline-store.js.map +1 -0
- package/src/lib/components/console/pos-offline/register-offline.d.ts +24 -0
- package/src/lib/components/console/pos-offline/register-offline.js +24 -0
- package/src/lib/components/console/pos-offline/register-offline.js.map +1 -0
- package/src/lib/components/console/pos-offline/use-pos-offline.d.ts +85 -0
- package/src/lib/components/console/pos-offline/use-pos-offline.js +542 -0
- package/src/lib/components/console/pos-offline/use-pos-offline.js.map +1 -0
- package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js +72 -17
- package/src/lib/components/console/pos-ops/pos-shift-history-card.component.js.map +1 -1
- package/src/lib/components/console/pos-page.component.js +174 -27
- package/src/lib/components/console/pos-page.component.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +35 -10
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/components/console/shipping-settings-card.component.js +1 -1
- package/src/lib/components/console/shipping-settings-card.component.js.map +1 -1
- package/src/lib/components/console/stock-movements-card.component.js +48 -16
- package/src/lib/components/console/stock-movements-card.component.js.map +1 -1
- package/src/lib/components/console/store-zones.d.ts +8 -1
- package/src/lib/components/console/store-zones.js +11 -1
- package/src/lib/components/console/store-zones.js.map +1 -1
- package/src/lib/components/order-status.js +66 -0
- package/src/lib/components/order-status.js.map +1 -1
- package/src/lib/components/product-detail.js +42 -15
- package/src/lib/components/product-detail.js.map +1 -1
- package/src/lib/constants/local-fulfillment-list-query.d.ts +48 -0
- package/src/lib/constants/local-fulfillment-list-query.js +84 -0
- package/src/lib/constants/local-fulfillment-list-query.js.map +1 -0
- package/src/lib/constants/orders-list-query.d.ts +7 -2
- package/src/lib/constants/orders-list-query.js +89 -8
- package/src/lib/constants/orders-list-query.js.map +1 -1
- package/src/lib/constants/pos-shift-list-query.d.ts +22 -0
- package/src/lib/constants/pos-shift-list-query.js +115 -0
- package/src/lib/constants/pos-shift-list-query.js.map +1 -0
- package/src/lib/constants/product-list-query.d.ts +6 -1
- package/src/lib/constants/product-list-query.js +77 -8
- package/src/lib/constants/product-list-query.js.map +1 -1
- package/src/lib/constants/stock-movement-query.d.ts +3 -2
- package/src/lib/constants/stock-movement-query.js +52 -8
- package/src/lib/constants/stock-movement-query.js.map +1 -1
- package/src/lib/model/buyer-notifications.d.ts +5 -3
- package/src/lib/model/buyer-notifications.js +25 -4
- package/src/lib/model/buyer-notifications.js.map +1 -1
- package/src/lib/model/commerce-orders.d.ts +59 -0
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce-pos-kiosk.d.ts +175 -0
- package/src/lib/model/commerce-pos-kiosk.js +147 -0
- package/src/lib/model/commerce-pos-kiosk.js.map +1 -0
- package/src/lib/model/commerce-pos-offline.d.ts +234 -0
- package/src/lib/model/commerce-pos-offline.js +301 -0
- package/src/lib/model/commerce-pos-offline.js.map +1 -0
- package/src/lib/model/commerce-pos-ops.d.ts +16 -6
- package/src/lib/model/commerce-pos-ops.js +12 -4
- package/src/lib/model/commerce-pos-ops.js.map +1 -1
- package/src/lib/model/commerce-pos.d.ts +13 -1
- package/src/lib/model/commerce-pos.js +5 -4
- package/src/lib/model/commerce-pos.js.map +1 -1
- package/src/lib/model/commerce-stock-holds.d.ts +24 -3
- package/src/lib/model/commerce-stock-holds.js +26 -8
- package/src/lib/model/commerce-stock-holds.js.map +1 -1
- package/src/lib/model/commerce.d.ts +3 -0
- package/src/lib/model/commerce.js.map +1 -1
- package/src/lib/model/index.d.ts +4 -0
- package/src/lib/model/index.js +4 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/local-fulfillment-settings.d.ts +178 -0
- package/src/lib/model/local-fulfillment-settings.js +361 -0
- package/src/lib/model/local-fulfillment-settings.js.map +1 -0
- package/src/lib/model/order-list-fields.d.ts +10 -1
- package/src/lib/model/order-list-fields.js +1 -0
- package/src/lib/model/order-list-fields.js.map +1 -1
- package/src/lib/model/order-local-fulfillment.d.ts +131 -0
- package/src/lib/model/order-local-fulfillment.js +125 -0
- package/src/lib/model/order-local-fulfillment.js.map +1 -0
- package/src/lib/model/order-shipping-export.js +4 -0
- package/src/lib/model/order-shipping-export.js.map +1 -1
- package/src/lib/model/order-status-view.d.ts +20 -1
- package/src/lib/model/order-status-view.js +60 -6
- package/src/lib/model/order-status-view.js.map +1 -1
- package/src/lib/plugin-config.js +6 -2
- package/src/lib/plugin-config.js.map +1 -1
- package/src/lib/plugin.js +8 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/pos-kiosk-config.d.ts +26 -0
- package/src/lib/pos-kiosk-config.js +59 -0
- package/src/lib/pos-kiosk-config.js.map +1 -0
- package/src/lib/server/api-v1/openapi.js +8 -0
- package/src/lib/server/api-v1/openapi.js.map +1 -1
- package/src/lib/server/api-v1/order-view.d.ts +19 -0
- package/src/lib/server/api-v1/order-view.js +35 -0
- package/src/lib/server/api-v1/order-view.js.map +1 -1
- package/src/lib/server/billing-webhook.js +238 -87
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/cart-checkout.d.ts +2 -0
- package/src/lib/server/cart-checkout.js +173 -13
- package/src/lib/server/cart-checkout.js.map +1 -1
- package/src/lib/server/checkout-extras.js +18 -4
- package/src/lib/server/checkout-extras.js.map +1 -1
- package/src/lib/server/checkout.js +71 -14
- package/src/lib/server/checkout.js.map +1 -1
- package/src/lib/server/local-fulfillment-status.d.ts +86 -0
- package/src/lib/server/local-fulfillment-status.js +379 -0
- package/src/lib/server/local-fulfillment-status.js.map +1 -0
- package/src/lib/server/local-fulfillment.d.ts +190 -0
- package/src/lib/server/local-fulfillment.js +580 -0
- package/src/lib/server/local-fulfillment.js.map +1 -0
- package/src/lib/server/membership-register.js +3 -1
- package/src/lib/server/membership-register.js.map +1 -1
- package/src/lib/server/order-notifications.js +61 -0
- package/src/lib/server/order-notifications.js.map +1 -1
- package/src/lib/server/pos-display.d.ts +27 -1
- package/src/lib/server/pos-display.js +50 -9
- package/src/lib/server/pos-display.js.map +1 -1
- package/src/lib/server/pos-kiosk-principal.d.ts +29 -0
- package/src/lib/server/pos-kiosk-principal.js +42 -0
- package/src/lib/server/pos-kiosk-principal.js.map +1 -0
- package/src/lib/server/pos-kiosk.d.ts +54 -0
- package/src/lib/server/pos-kiosk.js +972 -0
- package/src/lib/server/pos-kiosk.js.map +1 -0
- package/src/lib/server/pos-offline-sync.d.ts +85 -0
- package/src/lib/server/pos-offline-sync.js +765 -0
- package/src/lib/server/pos-offline-sync.js.map +1 -0
- package/src/lib/server/pos-order.js +22 -4
- package/src/lib/server/pos-order.js.map +1 -1
- package/src/lib/server/pos-payment.d.ts +6 -0
- package/src/lib/server/pos-payment.js +225 -6
- package/src/lib/server/pos-payment.js.map +1 -1
- package/src/lib/server/pos-print.js +5 -0
- package/src/lib/server/pos-print.js.map +1 -1
- package/src/lib/server/pos-return.js +27 -0
- package/src/lib/server/pos-return.js.map +1 -1
- package/src/lib/server/pos-sale.d.ts +6 -0
- package/src/lib/server/pos-sale.js +2 -0
- package/src/lib/server/pos-sale.js.map +1 -1
- package/src/lib/server/pos-shift.js +8 -1
- package/src/lib/server/pos-shift.js.map +1 -1
- package/src/lib/server/refund.js +3 -0
- package/src/lib/server/refund.js.map +1 -1
- package/src/lib/server/stock-hold.d.ts +9 -1
- package/src/lib/server/stock-hold.js +15 -7
- package/src/lib/server/stock-hold.js.map +1 -1
- package/src/lib/server.js +16 -0
- package/src/lib/server.js.map +1 -1
- package/src/lib/tenant-emails.js +161 -0
- package/src/lib/tenant-emails.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/pos-sale.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 { createHash } from 'crypto'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport recordCapturedContact from '@aglyn/aglyn/plugin-manager/record-captured-contact'\nimport * as CommerceModel from '../model'\nimport { alertLowStockCrossing } from './low-stock'\nimport { decrementVariantStock } from './reserve-stock'\nimport { releasePromotionHold, settlePromotionSlot } from './promotion-hold'\nimport { offlineFeeMonthKey } from './pos-fee-month'\nimport { resetPosDisplay } from './pos-display'\nimport { notifyOrderBuyer, sendOrderReceipt } from './order-notifications'\nimport { raiseOrderEvent } from './order-events'\nimport { ORDER_PAID_EVENT } from '../model/order-events'\n\n/*==========================================\n * AN OPEN REGISTER SALE (AGL-3607).\n *\n * A split-tender sale is an order written `pending` with an empty\n * `payments[]`, priced once by `pos-order.ts` (`payment: 'open'`). Each tender\n * then adds one payment through {@link applyPosPayment}, which is the ONLY\n * writer of the ledger and runs every change in one transaction on the order\n * document. When the settled payments cover the total, the same transaction\n * flips the order to `paid` and accrues the invoiced part of the fee, so a\n * sale is completed exactly once however many webhooks, polls and retries\n * race to report its last payment. Stock, the promotion slot, the contact and\n * the receipt follow from that one flip ({@link completePosSale}).\n *=========================================*/\n\nexport type PosLiftedOrder = CommerceModel.HostOrder & {\n $id: string\n /** The sale's platform take, before any card processing (AGL-2111). */\n posTakeFeeCents?: number\n /** The org the invoiced part of the take accrues to. */\n posFeeOrgId?: string\n /** The promotion slot this sale holds until it completes (AGL-305). */\n discountId?: string\n discountHoldKey?: string\n paymentIntentIds?: string[]\n reservationId?: string\n}\n\n/**\n * A payment's id, derived from the register's attempt key so a retried tap\n * finds the payment it already started instead of starting a second one.\n */\nexport function posPaymentId(orderId: string, attemptKey: string): string {\n return `pay_${createHash('sha256')\n .update(`${orderId}:${attemptKey}`)\n .digest('hex')\n .slice(0, 24)}`\n}\n\n/** What one ledger change decided. */\nexport type PosPaymentStep =\n | { kind: 'refuse'; status: number; error: string }\n /** Nothing to change: the payment is already in the state asked for. */\n | { kind: 'keep' }\n | {\n kind: 'put'\n payment: CommerceModel.OrderPayment\n /** A timeline line for the order, when the change is worth one. */\n event?: { event: string; detail?: string }\n }\n\nexport type PosPaymentOutcome =\n | { ok: false; status: number; error: string }\n | {\n ok: true\n order: PosLiftedOrder\n payment: CommerceModel.OrderPayment | null\n /** True only on the call whose change completed the sale. */\n completed: boolean\n changed: boolean\n }\n\n/** The open sale's ledger, as the register reads it back after any change. */\nexport function posSaleSummary(order: PosLiftedOrder) {\n const payments = CommerceModel.orderPayments(order)\n const totalCents = Number(order.totals?.totalCents ?? 0)\n return {\n orderId: order.$id,\n status: order.status,\n totalCents,\n paidCents: CommerceModel.posSettledCents(payments),\n dueCents: CommerceModel.posBalanceDueCents(totalCents, payments),\n tenderableCents: CommerceModel.posTenderableCents(totalCents, payments),\n tipCents: CommerceModel.posTipCents(payments),\n payments: payments.map((payment) => ({\n id: payment.id,\n method: payment.method,\n amountCents: payment.amountCents,\n ...(payment.tipCents ? { tipCents: payment.tipCents } : {}),\n status: payment.status,\n ...(payment.cardBrand ? { cardBrand: payment.cardBrand } : {}),\n ...(payment.last4 ? { last4: payment.last4 } : {}),\n ...(payment.changeCents != null ? { changeCents: payment.changeCents } : {}),\n ...(payment.checkoutUrl && payment.status === 'pending'\n ? { checkoutUrl: payment.checkoutUrl }\n : {}),\n ...(payment.readerId ? { readerId: payment.readerId } : {}),\n ...(payment.failureMessage ? { failureMessage: payment.failureMessage } : {}),\n ...(payment.livemode != null ? { livemode: payment.livemode } : {}),\n })),\n }\n}\n\n/**\n * The fields that complete a sale, when its settled payments now cover the\n * total; null when the balance is still open or the sale is not pending.\n *\n * Pure, so the spec can hold the arithmetic: the fee is every card payment's\n * own `application_fee_amount` plus the take the cards did not carry, the tip\n * is summed beside the total and never inside it.\n */\nexport function posCompletionPatch(\n order: PosLiftedOrder,\n payments: CommerceModel.OrderPayment[],\n nowMs: number,\n): {\n patch: Record<string, unknown>\n invoiceTakeCents: number\n} | null {\n if (order.status !== 'pending') return null\n const totalCents = Number(order.totals?.totalCents ?? 0)\n if (CommerceModel.posBalanceDueCents(totalCents, payments) > 0) return null\n const takeFeeCents = Math.max(0, Math.round(Number(order.posTakeFeeCents ?? 0)))\n const invoiceTakeCents = CommerceModel.posInvoiceTakeCents({ takeFeeCents, payments })\n const settled = payments.filter((payment) => payment.status === 'succeeded')\n const cardFeeCents = settled\n .filter((payment) => CommerceModel.isCardPaymentMethod(payment.method))\n .reduce((sum, payment) => sum + Math.max(0, Math.round(Number(payment.feeCents ?? 0))), 0)\n const feeCents = cardFeeCents + invoiceTakeCents\n const tipCents = CommerceModel.posTipCents(payments)\n const intents = settled\n .map((payment) => payment.paymentIntentId)\n .filter((id): id is string => Boolean(id))\n const tenders = settled.map((payment) => CommerceModel.describeOrderPayment(payment))\n const overpaid = CommerceModel.posSettledCents(payments) - totalCents\n return {\n invoiceTakeCents,\n patch: {\n status: 'paid',\n paidAtMs: nowMs,\n totals: {\n ...(order.totals ?? {}),\n feeCents,\n ...(tipCents > 0 ? { tipCents } : {}),\n },\n ...(feeCents > 0 ? { feeCollection: CommerceModel.posFeeCollection(payments) } : {}),\n ...(intents.length ? { paymentIntentIds: intents } : {}),\n // The single-charge readers (refund, dispute lookup) key on this; a\n // sale with one card payment keeps working with every one of them.\n ...(intents.length === 1 ? { paymentIntentId: intents[0] } : {}),\n ...(settled.some((payment) => payment.livemode === false) ? { livemode: false } : {}),\n timeline: CommerceModel.appendOrderEvent(\n order,\n 'paid',\n `Paid — ${tenders.join(', ')}` +\n (overpaid > 0\n ? `. Overpaid by $${(overpaid / 100).toFixed(2)}: refund the difference.`\n : ''),\n nowMs,\n ),\n },\n }\n}\n\n/** The order document, lifted, with its id. */\nfunction liftOrder(id: string, data: unknown): PosLiftedOrder {\n return {\n ...(CommerceModel.liftLegacyOrder((data ?? {}) as any) as any),\n $id: id,\n }\n}\n\n/**\n * The ONE writer of a sale's ledger. Runs `prepare` (extra reads) and\n * `decide` inside one transaction on the order, writes the payment, and\n * completes the sale in the same commit when the balance reaches zero.\n */\nexport async function applyPosPayment<C = undefined>(options: {\n hostId: string\n orderId: string\n paymentId: string\n /** Reads beyond the order (a gift card, a stay), before any write. */\n prepare?: (transaction: any) => Promise<C>\n decide: (sale: {\n order: PosLiftedOrder\n payments: CommerceModel.OrderPayment[]\n existing: CommerceModel.OrderPayment | undefined\n context: C\n }) => PosPaymentStep\n /** Writes beyond the order, in the same transaction, for a `put`. */\n write?: (\n transaction: any,\n context: C,\n payment: CommerceModel.OrderPayment,\n previous: CommerceModel.OrderPayment | undefined,\n ) => void\n nowMs?: number\n}): Promise<PosPaymentOutcome> {\n const firestore = firebaseAdmin.app().firestore()\n const orderRef = firestore\n .collection('hosts')\n .doc(options.hostId)\n .collection('orders')\n .doc(options.orderId)\n const nowMs = options.nowMs ?? Date.now()\n const outcome = await firestore.runTransaction(async (transaction: any) => {\n const snapshot = await transaction.get(orderRef)\n if (!snapshot.exists) {\n return { ok: false as const, status: 404, error: 'Unknown sale' }\n }\n const order = liftOrder(orderRef.id, snapshot.data())\n if (order.channel !== 'pos' || !Array.isArray(order.payments)) {\n return { ok: false as const, status: 409, error: 'This order is not an open register sale' }\n }\n const context = (options.prepare\n ? await options.prepare(transaction)\n : undefined) as C\n const payments = CommerceModel.orderPayments(order)\n const existing = payments.find((payment) => payment.id === options.paymentId)\n const step = options.decide({ order, payments, existing, context })\n if (step.kind === 'refuse') {\n return { ok: false as const, status: step.status, error: step.error }\n }\n if (step.kind === 'keep') {\n return {\n ok: true as const,\n order,\n payment: existing ?? null,\n completed: false,\n changed: false,\n }\n }\n const nextPayments = existing\n ? payments.map((payment) => (payment.id === step.payment.id ? step.payment : payment))\n : [...payments, step.payment]\n let next: PosLiftedOrder = { ...order, payments: nextPayments }\n if (step.event) {\n next = {\n ...next,\n timeline: CommerceModel.appendOrderEvent(\n order,\n step.event.event,\n step.event.detail,\n nowMs,\n ),\n }\n }\n const completion = posCompletionPatch(next, nextPayments, nowMs)\n const write: Record<string, unknown> = {\n payments: nextPayments,\n ...(next.timeline !== order.timeline ? { timeline: next.timeline } : {}),\n }\n if (completion) {\n Object.assign(write, completion.patch)\n next = { ...next, ...(completion.patch as Partial<PosLiftedOrder>) }\n Object.assign(write, CommerceModel.orderListFields(next, orderRef.id))\n // ACCRUED IN THE SAME COMMIT AS THE FLIP (the AGL-2111 rule): the part\n // of the take no card payout carried goes on the org's invoice, and an\n // order and the fee it owes cannot diverge.\n const feeOrgId = String(order.posFeeOrgId ?? '')\n if (completion.invoiceTakeCents > 0 && feeOrgId) {\n const month = offlineFeeMonthKey(new Date(nowMs))\n transaction.set(\n firestore.collection('orgs').doc(feeOrgId).collection('offlineFees').doc(month),\n {\n month,\n feeCents: firebaseAdmin.firestore.FieldValue.increment(completion.invoiceTakeCents),\n orders: firebaseAdmin.firestore.FieldValue.increment(1),\n },\n { merge: true },\n )\n }\n } else if (\n order.status === 'paid' &&\n step.payment.status === 'succeeded' &&\n existing?.status !== 'succeeded'\n ) {\n // MONEY THAT ARRIVED AFTER THE SALE WAS ALREADY PAID: a QR page paid\n // after the cashier took another tender. It is recorded, never\n // dropped, and said on the order so the merchant can refund it.\n write['timeline'] = CommerceModel.appendOrderEvent(\n next,\n 'pos-overpaid',\n `${CommerceModel.describeOrderPayment(step.payment)} arrived after the ` +\n 'sale was already paid. Refund it from Stripe.',\n nowMs,\n )\n }\n transaction.set(orderRef, write, { merge: true })\n options.write?.(transaction, context, step.payment, existing)\n return {\n ok: true as const,\n order: next,\n payment: step.payment,\n completed: Boolean(completion),\n changed: true,\n }\n })\n if (outcome.ok && outcome.completed) {\n await completePosSale(options.hostId, outcome.order)\n }\n return outcome\n}\n\n/*==========================================\n * \"A REGISTER SALE COMPLETED\" — the one point every POS sale passes through\n * (AGL-3607), whichever way it was paid: the ledger's last payment\n * ({@link completePosSale}), a single-tender cash or room sale\n * (`pos-order.ts`), or a legacy QR sale paid through the `commerce-draft`\n * webhook branch. Peripherals (a cloud receipt printer, a cash drawer kick)\n * and anything else that must follow a sale subscribe here rather than\n * patching each path.\n *=========================================*/\n\nexport interface PosSaleCompletedEvent {\n hostId: string\n orderId: string\n order: CommerceModel.HostOrder\n}\n\nexport type PosSaleCompletedListener = (event: PosSaleCompletedEvent) => void | Promise<void>\n\nconst saleCompletedListeners = new Set<PosSaleCompletedListener>()\n\n/** Subscribes to completed register sales; returns the unsubscribe. */\nexport function onPosSaleCompleted(listener: PosSaleCompletedListener): () => void {\n saleCompletedListeners.add(listener)\n return () => saleCompletedListeners.delete(listener)\n}\n\n/**\n * Tells every listener a register sale completed. Awaited, and never throws:\n * the money is already taken, so a listener's failure is logged, not\n * returned to a cashier who would ring the sale again.\n */\nexport async function notifyPosSaleCompleted(event: PosSaleCompletedEvent): Promise<void> {\n for (const listener of [...saleCompletedListeners]) {\n try {\n await listener(event)\n } catch (error) {\n console.error('[pos-sale] sale-completed listener failed', event.orderId, error)\n }\n }\n}\n\n/** Reads the sale as it stands, for the register's status polls. */\nexport async function readPosSale(\n hostId: string,\n orderId: string,\n): Promise<PosLiftedOrder | null> {\n const snapshot = await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('orders')\n .doc(orderId)\n .get()\n if (!snapshot.exists) return null\n return liftOrder(snapshot.id, snapshot.data())\n}\n\n/**\n * Everything that follows a sale being paid, run once by the call whose\n * commit completed it. Each step is guarded on its own and none can undo the\n * sale: the money is taken, so a failure here is logged and said on the order\n * rather than thrown back at a cashier who would ring it again.\n */\nexport async function completePosSale(\n hostId: string,\n order: PosLiftedOrder,\n): Promise<void> {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const orderRef = hostRef.collection('orders').doc(order.$id)\n const notes: Array<{ event: string; detail: string }> = []\n\n // The promotion's slot becomes a redemption only now: a sale that was\n // voided half-paid never counted against the merchant's cap (AGL-305).\n if (order.discountId && order.discountHoldKey) {\n const settled = await settlePromotionSlot({\n firestore,\n ref: hostRef.collection('discounts').doc(order.discountId),\n holdKey: order.discountHoldKey,\n label: `pos discount ${order.discountId}`,\n })\n if (settled === 'missing' || settled === 'error') {\n notes.push({\n event: 'redemption-orphaned',\n detail:\n `Discount ${order.discountId} was applied to this sale but its ` +\n 'redemption could not be counted against its limit.',\n })\n }\n }\n\n // Stock comes off exactly once, here, compounding across lines of one\n // product (AGL-1828) and atomically against other channels (AGL-2320).\n const products = new Map<string, CommerceModel.HostProduct>()\n for (const line of order.lineItems ?? []) {\n try {\n if (!products.has(line.productId)) {\n const snapshot = await hostRef.collection('products').doc(line.productId).get()\n if (!snapshot.exists) continue\n products.set(line.productId, CommerceModel.liftLegacyProduct(snapshot.data() as any))\n }\n const product = products.get(line.productId) as CommerceModel.HostProduct\n const variantId = line.variantId ?? product.variants[0]?.id\n const tracked = product.variants.some(\n (variant) => variant.id === variantId && variant.inventory != null,\n )\n if (!variantId || !tracked) continue\n const moved = await decrementVariantStock({\n firestore,\n hostRef,\n hostId,\n productId: line.productId,\n variantId,\n quantity: line.quantity,\n locationId: order.locationId || undefined,\n ledger: { reason: 'sale', orderId: order.$id },\n })\n if (!moved.before || !moved.after) continue\n products.set(line.productId, moved.after)\n alertLowStockCrossing(hostId, moved.before, moved.after)\n } catch (error) {\n console.error('[pos-sale] stock decrement failed', order.$id, line.productId, error)\n }\n }\n\n const receipt = order.receiptRequest\n const receiptEmail = receipt?.channel === 'email' ? String(receipt.to ?? '') : ''\n const contactEmail = receiptEmail || String(order.customerEmail ?? '')\n const marketingOptIn = Boolean((receipt as { marketingOptIn?: boolean } | undefined)?.marketingOptIn)\n const totalCents = Number(order.totals?.totalCents ?? 0)\n if (contactEmail) {\n await recordCapturedContact({\n orgId: '',\n hostId,\n identity: { email: contactEmail },\n surface: 'relationship',\n lifecycleFloor: 'customer',\n // The customer's own tick on the display, the same opt-in the online\n // checkout records (AGL-3608); an unticked box records nothing.\n ...(marketingOptIn ? { marketingConsent: true } : {}),\n purchaseCents: totalCents,\n interaction: {\n source: 'order',\n refId: order.$id,\n summary: `In-store purchase ($${(totalCents / 100).toFixed(2)})`,\n },\n }).catch((error: unknown) => {\n console.error('[pos-sale] contact capture failed', error)\n })\n }\n\n // The receipt and the paid event, through the same doors every sale uses\n // (AGL-3610, AGL-3611): the buyer-notification door claims each message\n // per channel, so a second completion path can never mail twice, and it\n // texts the receipt when the customer chose a text and a provider is on.\n if ((receipt?.channel === 'email' || receipt?.channel === 'sms') && receipt.to) {\n // The customer named where it goes: it goes there, whatever else the\n // order holds (AGL-3608).\n const sent = await deliverChosenPosReceipt(\n { hostId, orderId: order.$id },\n { channel: receipt.channel, to: receipt.to, orderEmail: String(order.customerEmail ?? '') },\n )\n if (sent !== 'sent') {\n notes.push({\n event: 'receipt-unsent',\n detail: `The ${receipt.channel === 'sms' ? 'text' : 'email'} receipt could not be sent.`,\n })\n }\n } else if (contactEmail) {\n await notifyOrderBuyer({ hostId, orderId: order.$id }, 'receipt', { email: contactEmail })\n }\n await raiseOrderEvent(ORDER_PAID_EVENT, { hostId, orderId: order.$id, key: 'paid' }).catch(\n (error: unknown) => console.error('[pos-sale] order.paid event failed', error),\n )\n\n if (notes.length) {\n await firestore\n .runTransaction(async (transaction: any) => {\n const fresh = liftOrder(orderRef.id, (await transaction.get(orderRef)).data())\n let timeline = fresh.timeline ?? []\n for (const note of notes) {\n timeline = CommerceModel.appendOrderEvent({ timeline }, note.event, note.detail)\n }\n transaction.set(orderRef, { timeline }, { merge: true })\n })\n .catch((error: unknown) => console.error('[pos-sale] note failed', error))\n }\n\n await notifyPosSaleCompleted({ hostId, orderId: order.$id, order })\n\n // The customer screen says thank you and forgets the customer: the state\n // is rewritten whole, so no address or phone outlives the sale (AGL-3608).\n if (order.registerId) {\n await resetPosDisplay(hostId, order.registerId).catch((error: unknown) =>\n console.error('[pos-sale] display reset failed', error),\n )\n }\n}\n\n/**\n * Sends a receipt the customer (or the cashier for them) explicitly asked\n * for, to the address or number they gave (AGL-3607, AGL-3608).\n *\n * It goes through the buyer-notification door first, so the order's\n * once-per-channel marker is claimed and a completion that races this call\n * cannot send a second copy. The door can skip it, though, for reasons that\n * do not apply to a request made at the counter: the store switched\n * automatic receipts or texts off, a receipt already went out on that\n * channel to an earlier address, or the door would use the email the order\n * was opened with rather than the one just typed. In each of those cases the\n * receipt is sent explicitly, as the order dialog's \"Resend receipt\" does.\n */\nexport async function deliverChosenPosReceipt(\n ref: { hostId: string; orderId: string },\n input: { channel: 'email' | 'sms'; to: string; orderEmail?: string },\n): Promise<'sent' | 'failed' | 'not_configured'> {\n const orderEmail = String(input.orderEmail ?? '').trim().toLowerCase()\n const to = input.to.trim()\n const doorAddressesIt = input.channel === 'sms' || !orderEmail || orderEmail === to.toLowerCase()\n if (doorAddressesIt) {\n const door = await notifyOrderBuyer(ref, 'receipt', input.channel === 'email' ? { email: to } : {})\n const entry =\n door.outcome === 'handled'\n ? door.channels.find((channel) => channel.channel === input.channel)\n : undefined\n if (entry?.outcome === 'sent') return 'sent'\n if (entry?.outcome === 'failed') return 'failed'\n // Already sent to this very address: nothing more to do.\n if (entry?.outcome === 'already' && input.channel === 'email') return 'sent'\n }\n const explicit = await sendOrderReceipt(ref, { channel: input.channel, to })\n if (explicit.outcome === 'sent') return 'sent'\n return explicit.outcome === 'not_configured' ? 'not_configured' : 'failed'\n}\n\n/**\n * Hands back a promotion slot an open sale was holding, when the sale is\n * voided rather than paid.\n */\nexport async function releasePosSaleDiscount(\n hostId: string,\n order: PosLiftedOrder,\n): Promise<void> {\n if (!order.discountId || !order.discountHoldKey) return\n await releasePromotionHold(\n firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('discounts')\n .doc(order.discountId),\n order.discountHoldKey,\n )\n}\n"],"names":["createHash","firebaseAdmin","recordCapturedContact","CommerceModel","alertLowStockCrossing","decrementVariantStock","releasePromotionHold","settlePromotionSlot","offlineFeeMonthKey","resetPosDisplay","notifyOrderBuyer","sendOrderReceipt","raiseOrderEvent","ORDER_PAID_EVENT","posPaymentId","orderId","attemptKey","update","digest","slice","posSaleSummary","order","payments","orderPayments","totalCents","Number","totals","$id","status","paidCents","posSettledCents","dueCents","posBalanceDueCents","tenderableCents","posTenderableCents","tipCents","posTipCents","map","payment","id","method","amountCents","cardBrand","last4","changeCents","checkoutUrl","readerId","failureMessage","livemode","posCompletionPatch","nowMs","takeFeeCents","Math","max","round","posTakeFeeCents","invoiceTakeCents","posInvoiceTakeCents","settled","filter","cardFeeCents","isCardPaymentMethod","reduce","sum","feeCents","intents","paymentIntentId","Boolean","tenders","describeOrderPayment","overpaid","patch","paidAtMs","feeCollection","posFeeCollection","length","paymentIntentIds","some","timeline","appendOrderEvent","join","toFixed","liftOrder","data","liftLegacyOrder","applyPosPayment","options","firestore","app","orderRef","collection","doc","hostId","Date","now","outcome","runTransaction","transaction","snapshot","get","exists","ok","error","channel","Array","isArray","context","prepare","undefined","existing","find","paymentId","step","decide","kind","completed","changed","nextPayments","next","event","detail","completion","write","Object","assign","orderListFields","feeOrgId","String","posFeeOrgId","month","set","FieldValue","increment","orders","merge","completePosSale","saleCompletedListeners","Set","onPosSaleCompleted","listener","add","delete","notifyPosSaleCompleted","console","readPosSale","receipt","hostRef","notes","discountId","discountHoldKey","ref","holdKey","label","push","products","Map","line","lineItems","product","has","productId","liftLegacyProduct","variantId","variants","tracked","variant","inventory","moved","quantity","locationId","ledger","reason","before","after","receiptRequest","receiptEmail","to","contactEmail","customerEmail","marketingOptIn","orgId","identity","email","surface","lifecycleFloor","marketingConsent","purchaseCents","interaction","source","refId","summary","catch","sent","deliverChosenPosReceipt","orderEmail","key","fresh","note","registerId","input","trim","toLowerCase","doorAddressesIt","door","entry","channels","explicit","releasePosSaleDiscount"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/pos-sale.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 { createHash } from 'crypto'\nimport { firebaseAdmin } from '@aglyn/tenant-data-admin'\nimport recordCapturedContact from '@aglyn/aglyn/plugin-manager/record-captured-contact'\nimport * as CommerceModel from '../model'\nimport { alertLowStockCrossing } from './low-stock'\nimport { decrementVariantStock } from './reserve-stock'\nimport { releasePromotionHold, settlePromotionSlot } from './promotion-hold'\nimport { offlineFeeMonthKey } from './pos-fee-month'\nimport { resetPosDisplay } from './pos-display'\nimport { notifyOrderBuyer, sendOrderReceipt } from './order-notifications'\nimport { raiseOrderEvent } from './order-events'\nimport { ORDER_PAID_EVENT } from '../model/order-events'\n\n/*==========================================\n * AN OPEN REGISTER SALE (AGL-3607).\n *\n * A split-tender sale is an order written `pending` with an empty\n * `payments[]`, priced once by `pos-order.ts` (`payment: 'open'`). Each tender\n * then adds one payment through {@link applyPosPayment}, which is the ONLY\n * writer of the ledger and runs every change in one transaction on the order\n * document. When the settled payments cover the total, the same transaction\n * flips the order to `paid` and accrues the invoiced part of the fee, so a\n * sale is completed exactly once however many webhooks, polls and retries\n * race to report its last payment. Stock, the promotion slot, the contact and\n * the receipt follow from that one flip ({@link completePosSale}).\n *=========================================*/\n\nexport type PosLiftedOrder = CommerceModel.HostOrder & {\n $id: string\n /** The sale's platform take, before any card processing (AGL-2111). */\n posTakeFeeCents?: number\n /** The org the invoiced part of the take accrues to. */\n posFeeOrgId?: string\n /** The promotion slot this sale holds until it completes (AGL-305). */\n discountId?: string\n discountHoldKey?: string\n paymentIntentIds?: string[]\n reservationId?: string\n}\n\n/**\n * A payment's id, derived from the register's attempt key so a retried tap\n * finds the payment it already started instead of starting a second one.\n */\nexport function posPaymentId(orderId: string, attemptKey: string): string {\n return `pay_${createHash('sha256')\n .update(`${orderId}:${attemptKey}`)\n .digest('hex')\n .slice(0, 24)}`\n}\n\n/** What one ledger change decided. */\nexport type PosPaymentStep =\n | { kind: 'refuse'; status: number; error: string }\n /** Nothing to change: the payment is already in the state asked for. */\n | { kind: 'keep' }\n | {\n kind: 'put'\n payment: CommerceModel.OrderPayment\n /** A timeline line for the order, when the change is worth one. */\n event?: { event: string; detail?: string }\n }\n\nexport type PosPaymentOutcome =\n | { ok: false; status: number; error: string }\n | {\n ok: true\n order: PosLiftedOrder\n payment: CommerceModel.OrderPayment | null\n /** True only on the call whose change completed the sale. */\n completed: boolean\n changed: boolean\n }\n\n/** The open sale's ledger, as the register reads it back after any change. */\nexport function posSaleSummary(order: PosLiftedOrder) {\n const payments = CommerceModel.orderPayments(order)\n const totalCents = Number(order.totals?.totalCents ?? 0)\n return {\n orderId: order.$id,\n status: order.status,\n totalCents,\n paidCents: CommerceModel.posSettledCents(payments),\n dueCents: CommerceModel.posBalanceDueCents(totalCents, payments),\n tenderableCents: CommerceModel.posTenderableCents(totalCents, payments),\n tipCents: CommerceModel.posTipCents(payments),\n payments: payments.map((payment) => ({\n id: payment.id,\n method: payment.method,\n amountCents: payment.amountCents,\n ...(payment.tipCents ? { tipCents: payment.tipCents } : {}),\n status: payment.status,\n ...(payment.cardBrand ? { cardBrand: payment.cardBrand } : {}),\n ...(payment.last4 ? { last4: payment.last4 } : {}),\n ...(payment.changeCents != null ? { changeCents: payment.changeCents } : {}),\n ...(payment.checkoutUrl && payment.status === 'pending'\n ? { checkoutUrl: payment.checkoutUrl }\n : {}),\n ...(payment.readerId ? { readerId: payment.readerId } : {}),\n // What a store-credit payment's provider calls it (AGL-3640).\n ...(payment.creditLabel ? { creditLabel: payment.creditLabel } : {}),\n ...(payment.failureMessage ? { failureMessage: payment.failureMessage } : {}),\n ...(payment.livemode != null ? { livemode: payment.livemode } : {}),\n })),\n }\n}\n\n/**\n * The fields that complete a sale, when its settled payments now cover the\n * total; null when the balance is still open or the sale is not pending.\n *\n * Pure, so the spec can hold the arithmetic: the fee is every card payment's\n * own `application_fee_amount` plus the take the cards did not carry, the tip\n * is summed beside the total and never inside it.\n */\nexport function posCompletionPatch(\n order: PosLiftedOrder,\n payments: CommerceModel.OrderPayment[],\n nowMs: number,\n): {\n patch: Record<string, unknown>\n invoiceTakeCents: number\n} | null {\n if (order.status !== 'pending') return null\n const totalCents = Number(order.totals?.totalCents ?? 0)\n if (CommerceModel.posBalanceDueCents(totalCents, payments) > 0) return null\n const takeFeeCents = Math.max(0, Math.round(Number(order.posTakeFeeCents ?? 0)))\n const invoiceTakeCents = CommerceModel.posInvoiceTakeCents({ takeFeeCents, payments })\n const settled = payments.filter((payment) => payment.status === 'succeeded')\n const cardFeeCents = settled\n .filter((payment) => CommerceModel.isCardPaymentMethod(payment.method))\n .reduce((sum, payment) => sum + Math.max(0, Math.round(Number(payment.feeCents ?? 0))), 0)\n const feeCents = cardFeeCents + invoiceTakeCents\n const tipCents = CommerceModel.posTipCents(payments)\n const intents = settled\n .map((payment) => payment.paymentIntentId)\n .filter((id): id is string => Boolean(id))\n const tenders = settled.map((payment) => CommerceModel.describeOrderPayment(payment))\n const overpaid = CommerceModel.posSettledCents(payments) - totalCents\n return {\n invoiceTakeCents,\n patch: {\n status: 'paid',\n paidAtMs: nowMs,\n totals: {\n ...(order.totals ?? {}),\n feeCents,\n ...(tipCents > 0 ? { tipCents } : {}),\n },\n ...(feeCents > 0 ? { feeCollection: CommerceModel.posFeeCollection(payments) } : {}),\n ...(intents.length ? { paymentIntentIds: intents } : {}),\n // The single-charge readers (refund, dispute lookup) key on this; a\n // sale with one card payment keeps working with every one of them.\n ...(intents.length === 1 ? { paymentIntentId: intents[0] } : {}),\n ...(settled.some((payment) => payment.livemode === false) ? { livemode: false } : {}),\n timeline: CommerceModel.appendOrderEvent(\n order,\n 'paid',\n `Paid — ${tenders.join(', ')}` +\n (overpaid > 0\n ? `. Overpaid by $${(overpaid / 100).toFixed(2)}: refund the difference.`\n : ''),\n nowMs,\n ),\n },\n }\n}\n\n/** The order document, lifted, with its id. */\nfunction liftOrder(id: string, data: unknown): PosLiftedOrder {\n return {\n ...(CommerceModel.liftLegacyOrder((data ?? {}) as any) as any),\n $id: id,\n }\n}\n\n/**\n * The ONE writer of a sale's ledger. Runs `prepare` (extra reads) and\n * `decide` inside one transaction on the order, writes the payment, and\n * completes the sale in the same commit when the balance reaches zero.\n */\nexport async function applyPosPayment<C = undefined>(options: {\n hostId: string\n orderId: string\n paymentId: string\n /** Reads beyond the order (a gift card, a stay), before any write. */\n prepare?: (transaction: any) => Promise<C>\n decide: (sale: {\n order: PosLiftedOrder\n payments: CommerceModel.OrderPayment[]\n existing: CommerceModel.OrderPayment | undefined\n context: C\n }) => PosPaymentStep\n /** Writes beyond the order, in the same transaction, for a `put`. */\n write?: (\n transaction: any,\n context: C,\n payment: CommerceModel.OrderPayment,\n previous: CommerceModel.OrderPayment | undefined,\n ) => void\n nowMs?: number\n}): Promise<PosPaymentOutcome> {\n const firestore = firebaseAdmin.app().firestore()\n const orderRef = firestore\n .collection('hosts')\n .doc(options.hostId)\n .collection('orders')\n .doc(options.orderId)\n const nowMs = options.nowMs ?? Date.now()\n const outcome = await firestore.runTransaction(async (transaction: any) => {\n const snapshot = await transaction.get(orderRef)\n if (!snapshot.exists) {\n return { ok: false as const, status: 404, error: 'Unknown sale' }\n }\n const order = liftOrder(orderRef.id, snapshot.data())\n if (order.channel !== 'pos' || !Array.isArray(order.payments)) {\n return { ok: false as const, status: 409, error: 'This order is not an open register sale' }\n }\n const context = (options.prepare\n ? await options.prepare(transaction)\n : undefined) as C\n const payments = CommerceModel.orderPayments(order)\n const existing = payments.find((payment) => payment.id === options.paymentId)\n const step = options.decide({ order, payments, existing, context })\n if (step.kind === 'refuse') {\n return { ok: false as const, status: step.status, error: step.error }\n }\n if (step.kind === 'keep') {\n return {\n ok: true as const,\n order,\n payment: existing ?? null,\n completed: false,\n changed: false,\n }\n }\n const nextPayments = existing\n ? payments.map((payment) => (payment.id === step.payment.id ? step.payment : payment))\n : [...payments, step.payment]\n let next: PosLiftedOrder = { ...order, payments: nextPayments }\n if (step.event) {\n next = {\n ...next,\n timeline: CommerceModel.appendOrderEvent(\n order,\n step.event.event,\n step.event.detail,\n nowMs,\n ),\n }\n }\n const completion = posCompletionPatch(next, nextPayments, nowMs)\n const write: Record<string, unknown> = {\n payments: nextPayments,\n ...(next.timeline !== order.timeline ? { timeline: next.timeline } : {}),\n }\n if (completion) {\n Object.assign(write, completion.patch)\n next = { ...next, ...(completion.patch as Partial<PosLiftedOrder>) }\n Object.assign(write, CommerceModel.orderListFields(next, orderRef.id))\n // ACCRUED IN THE SAME COMMIT AS THE FLIP (the AGL-2111 rule): the part\n // of the take no card payout carried goes on the org's invoice, and an\n // order and the fee it owes cannot diverge.\n const feeOrgId = String(order.posFeeOrgId ?? '')\n if (completion.invoiceTakeCents > 0 && feeOrgId) {\n const month = offlineFeeMonthKey(new Date(nowMs))\n transaction.set(\n firestore.collection('orgs').doc(feeOrgId).collection('offlineFees').doc(month),\n {\n month,\n feeCents: firebaseAdmin.firestore.FieldValue.increment(completion.invoiceTakeCents),\n orders: firebaseAdmin.firestore.FieldValue.increment(1),\n },\n { merge: true },\n )\n }\n } else if (\n order.status === 'paid' &&\n step.payment.status === 'succeeded' &&\n existing?.status !== 'succeeded'\n ) {\n // MONEY THAT ARRIVED AFTER THE SALE WAS ALREADY PAID: a QR page paid\n // after the cashier took another tender. It is recorded, never\n // dropped, and said on the order so the merchant can refund it.\n write['timeline'] = CommerceModel.appendOrderEvent(\n next,\n 'pos-overpaid',\n `${CommerceModel.describeOrderPayment(step.payment)} arrived after the ` +\n 'sale was already paid. Refund it from Stripe.',\n nowMs,\n )\n }\n transaction.set(orderRef, write, { merge: true })\n options.write?.(transaction, context, step.payment, existing)\n return {\n ok: true as const,\n order: next,\n payment: step.payment,\n completed: Boolean(completion),\n changed: true,\n }\n })\n if (outcome.ok && outcome.completed) {\n await completePosSale(options.hostId, outcome.order)\n }\n return outcome\n}\n\n/*==========================================\n * \"A REGISTER SALE COMPLETED\" — the one point every POS sale passes through\n * (AGL-3607), whichever way it was paid: the ledger's last payment\n * ({@link completePosSale}), a single-tender cash or room sale\n * (`pos-order.ts`), or a legacy QR sale paid through the `commerce-draft`\n * webhook branch. Peripherals (a cloud receipt printer, a cash drawer kick)\n * and anything else that must follow a sale subscribe here rather than\n * patching each path.\n *=========================================*/\n\nexport interface PosSaleCompletedEvent {\n hostId: string\n orderId: string\n order: CommerceModel.HostOrder\n /**\n * Rung while the register was offline and synced later (AGL-3625): the\n * customer has already left, so nothing at the counter should react now.\n */\n offline?: boolean\n}\n\nexport type PosSaleCompletedListener = (event: PosSaleCompletedEvent) => void | Promise<void>\n\nconst saleCompletedListeners = new Set<PosSaleCompletedListener>()\n\n/** Subscribes to completed register sales; returns the unsubscribe. */\nexport function onPosSaleCompleted(listener: PosSaleCompletedListener): () => void {\n saleCompletedListeners.add(listener)\n return () => saleCompletedListeners.delete(listener)\n}\n\n/**\n * Tells every listener a register sale completed. Awaited, and never throws:\n * the money is already taken, so a listener's failure is logged, not\n * returned to a cashier who would ring the sale again.\n */\nexport async function notifyPosSaleCompleted(event: PosSaleCompletedEvent): Promise<void> {\n for (const listener of [...saleCompletedListeners]) {\n try {\n await listener(event)\n } catch (error) {\n console.error('[pos-sale] sale-completed listener failed', event.orderId, error)\n }\n }\n}\n\n/** Reads the sale as it stands, for the register's status polls. */\nexport async function readPosSale(\n hostId: string,\n orderId: string,\n): Promise<PosLiftedOrder | null> {\n const snapshot = await firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('orders')\n .doc(orderId)\n .get()\n if (!snapshot.exists) return null\n return liftOrder(snapshot.id, snapshot.data())\n}\n\n/**\n * Everything that follows a sale being paid, run once by the call whose\n * commit completed it. Each step is guarded on its own and none can undo the\n * sale: the money is taken, so a failure here is logged and said on the order\n * rather than thrown back at a cashier who would ring it again.\n */\nexport async function completePosSale(\n hostId: string,\n order: PosLiftedOrder,\n): Promise<void> {\n const firestore = firebaseAdmin.app().firestore()\n const hostRef = firestore.collection('hosts').doc(hostId)\n const orderRef = hostRef.collection('orders').doc(order.$id)\n const notes: Array<{ event: string; detail: string }> = []\n\n // The promotion's slot becomes a redemption only now: a sale that was\n // voided half-paid never counted against the merchant's cap (AGL-305).\n if (order.discountId && order.discountHoldKey) {\n const settled = await settlePromotionSlot({\n firestore,\n ref: hostRef.collection('discounts').doc(order.discountId),\n holdKey: order.discountHoldKey,\n label: `pos discount ${order.discountId}`,\n })\n if (settled === 'missing' || settled === 'error') {\n notes.push({\n event: 'redemption-orphaned',\n detail:\n `Discount ${order.discountId} was applied to this sale but its ` +\n 'redemption could not be counted against its limit.',\n })\n }\n }\n\n // Stock comes off exactly once, here, compounding across lines of one\n // product (AGL-1828) and atomically against other channels (AGL-2320).\n const products = new Map<string, CommerceModel.HostProduct>()\n for (const line of order.lineItems ?? []) {\n try {\n if (!products.has(line.productId)) {\n const snapshot = await hostRef.collection('products').doc(line.productId).get()\n if (!snapshot.exists) continue\n products.set(line.productId, CommerceModel.liftLegacyProduct(snapshot.data() as any))\n }\n const product = products.get(line.productId) as CommerceModel.HostProduct\n const variantId = line.variantId ?? product.variants[0]?.id\n const tracked = product.variants.some(\n (variant) => variant.id === variantId && variant.inventory != null,\n )\n if (!variantId || !tracked) continue\n const moved = await decrementVariantStock({\n firestore,\n hostRef,\n hostId,\n productId: line.productId,\n variantId,\n quantity: line.quantity,\n locationId: order.locationId || undefined,\n ledger: { reason: 'sale', orderId: order.$id },\n })\n if (!moved.before || !moved.after) continue\n products.set(line.productId, moved.after)\n alertLowStockCrossing(hostId, moved.before, moved.after)\n } catch (error) {\n console.error('[pos-sale] stock decrement failed', order.$id, line.productId, error)\n }\n }\n\n const receipt = order.receiptRequest\n const receiptEmail = receipt?.channel === 'email' ? String(receipt.to ?? '') : ''\n const contactEmail = receiptEmail || String(order.customerEmail ?? '')\n const marketingOptIn = Boolean((receipt as { marketingOptIn?: boolean } | undefined)?.marketingOptIn)\n const totalCents = Number(order.totals?.totalCents ?? 0)\n if (contactEmail) {\n await recordCapturedContact({\n orgId: '',\n hostId,\n identity: { email: contactEmail },\n surface: 'relationship',\n lifecycleFloor: 'customer',\n // The customer's own tick on the display, the same opt-in the online\n // checkout records (AGL-3608); an unticked box records nothing.\n ...(marketingOptIn ? { marketingConsent: true } : {}),\n purchaseCents: totalCents,\n interaction: {\n source: 'order',\n refId: order.$id,\n summary: `In-store purchase ($${(totalCents / 100).toFixed(2)})`,\n },\n }).catch((error: unknown) => {\n console.error('[pos-sale] contact capture failed', error)\n })\n }\n\n // The receipt and the paid event, through the same doors every sale uses\n // (AGL-3610, AGL-3611): the buyer-notification door claims each message\n // per channel, so a second completion path can never mail twice, and it\n // texts the receipt when the customer chose a text and a provider is on.\n if ((receipt?.channel === 'email' || receipt?.channel === 'sms') && receipt.to) {\n // The customer named where it goes: it goes there, whatever else the\n // order holds (AGL-3608).\n const sent = await deliverChosenPosReceipt(\n { hostId, orderId: order.$id },\n { channel: receipt.channel, to: receipt.to, orderEmail: String(order.customerEmail ?? '') },\n )\n if (sent !== 'sent') {\n notes.push({\n event: 'receipt-unsent',\n detail: `The ${receipt.channel === 'sms' ? 'text' : 'email'} receipt could not be sent.`,\n })\n }\n } else if (contactEmail) {\n await notifyOrderBuyer({ hostId, orderId: order.$id }, 'receipt', { email: contactEmail })\n }\n await raiseOrderEvent(ORDER_PAID_EVENT, { hostId, orderId: order.$id, key: 'paid' }).catch(\n (error: unknown) => console.error('[pos-sale] order.paid event failed', error),\n )\n\n if (notes.length) {\n await firestore\n .runTransaction(async (transaction: any) => {\n const fresh = liftOrder(orderRef.id, (await transaction.get(orderRef)).data())\n let timeline = fresh.timeline ?? []\n for (const note of notes) {\n timeline = CommerceModel.appendOrderEvent({ timeline }, note.event, note.detail)\n }\n transaction.set(orderRef, { timeline }, { merge: true })\n })\n .catch((error: unknown) => console.error('[pos-sale] note failed', error))\n }\n\n await notifyPosSaleCompleted({ hostId, orderId: order.$id, order })\n\n // The customer screen says thank you and forgets the customer: the state\n // is rewritten whole, so no address or phone outlives the sale (AGL-3608).\n if (order.registerId) {\n await resetPosDisplay(hostId, order.registerId).catch((error: unknown) =>\n console.error('[pos-sale] display reset failed', error),\n )\n }\n}\n\n/**\n * Sends a receipt the customer (or the cashier for them) explicitly asked\n * for, to the address or number they gave (AGL-3607, AGL-3608).\n *\n * It goes through the buyer-notification door first, so the order's\n * once-per-channel marker is claimed and a completion that races this call\n * cannot send a second copy. The door can skip it, though, for reasons that\n * do not apply to a request made at the counter: the store switched\n * automatic receipts or texts off, a receipt already went out on that\n * channel to an earlier address, or the door would use the email the order\n * was opened with rather than the one just typed. In each of those cases the\n * receipt is sent explicitly, as the order dialog's \"Resend receipt\" does.\n */\nexport async function deliverChosenPosReceipt(\n ref: { hostId: string; orderId: string },\n input: { channel: 'email' | 'sms'; to: string; orderEmail?: string },\n): Promise<'sent' | 'failed' | 'not_configured'> {\n const orderEmail = String(input.orderEmail ?? '').trim().toLowerCase()\n const to = input.to.trim()\n const doorAddressesIt = input.channel === 'sms' || !orderEmail || orderEmail === to.toLowerCase()\n if (doorAddressesIt) {\n const door = await notifyOrderBuyer(ref, 'receipt', input.channel === 'email' ? { email: to } : {})\n const entry =\n door.outcome === 'handled'\n ? door.channels.find((channel) => channel.channel === input.channel)\n : undefined\n if (entry?.outcome === 'sent') return 'sent'\n if (entry?.outcome === 'failed') return 'failed'\n // Already sent to this very address: nothing more to do.\n if (entry?.outcome === 'already' && input.channel === 'email') return 'sent'\n }\n const explicit = await sendOrderReceipt(ref, { channel: input.channel, to })\n if (explicit.outcome === 'sent') return 'sent'\n return explicit.outcome === 'not_configured' ? 'not_configured' : 'failed'\n}\n\n/**\n * Hands back a promotion slot an open sale was holding, when the sale is\n * voided rather than paid.\n */\nexport async function releasePosSaleDiscount(\n hostId: string,\n order: PosLiftedOrder,\n): Promise<void> {\n if (!order.discountId || !order.discountHoldKey) return\n await releasePromotionHold(\n firebaseAdmin\n .app()\n .firestore()\n .collection('hosts')\n .doc(hostId)\n .collection('discounts')\n .doc(order.discountId),\n order.discountHoldKey,\n )\n}\n"],"names":["createHash","firebaseAdmin","recordCapturedContact","CommerceModel","alertLowStockCrossing","decrementVariantStock","releasePromotionHold","settlePromotionSlot","offlineFeeMonthKey","resetPosDisplay","notifyOrderBuyer","sendOrderReceipt","raiseOrderEvent","ORDER_PAID_EVENT","posPaymentId","orderId","attemptKey","update","digest","slice","posSaleSummary","order","payments","orderPayments","totalCents","Number","totals","$id","status","paidCents","posSettledCents","dueCents","posBalanceDueCents","tenderableCents","posTenderableCents","tipCents","posTipCents","map","payment","id","method","amountCents","cardBrand","last4","changeCents","checkoutUrl","readerId","creditLabel","failureMessage","livemode","posCompletionPatch","nowMs","takeFeeCents","Math","max","round","posTakeFeeCents","invoiceTakeCents","posInvoiceTakeCents","settled","filter","cardFeeCents","isCardPaymentMethod","reduce","sum","feeCents","intents","paymentIntentId","Boolean","tenders","describeOrderPayment","overpaid","patch","paidAtMs","feeCollection","posFeeCollection","length","paymentIntentIds","some","timeline","appendOrderEvent","join","toFixed","liftOrder","data","liftLegacyOrder","applyPosPayment","options","firestore","app","orderRef","collection","doc","hostId","Date","now","outcome","runTransaction","transaction","snapshot","get","exists","ok","error","channel","Array","isArray","context","prepare","undefined","existing","find","paymentId","step","decide","kind","completed","changed","nextPayments","next","event","detail","completion","write","Object","assign","orderListFields","feeOrgId","String","posFeeOrgId","month","set","FieldValue","increment","orders","merge","completePosSale","saleCompletedListeners","Set","onPosSaleCompleted","listener","add","delete","notifyPosSaleCompleted","console","readPosSale","receipt","hostRef","notes","discountId","discountHoldKey","ref","holdKey","label","push","products","Map","line","lineItems","product","has","productId","liftLegacyProduct","variantId","variants","tracked","variant","inventory","moved","quantity","locationId","ledger","reason","before","after","receiptRequest","receiptEmail","to","contactEmail","customerEmail","marketingOptIn","orgId","identity","email","surface","lifecycleFloor","marketingConsent","purchaseCents","interaction","source","refId","summary","catch","sent","deliverChosenPosReceipt","orderEmail","key","fresh","note","registerId","input","trim","toLowerCase","doorAddressesIt","door","entry","channels","explicit","releasePosSaleDiscount"],"mappings":"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"}
|
|
@@ -179,7 +179,13 @@ export async function handlePosShift(deps, req) {
|
|
|
179
179
|
openedByName,
|
|
180
180
|
openedAtMs: now,
|
|
181
181
|
openingFloatCents: float,
|
|
182
|
-
cashEvents: []
|
|
182
|
+
cashEvents: [],
|
|
183
|
+
// Null until the close: the history orders by each (AGL-3680).
|
|
184
|
+
closedAtMs: null,
|
|
185
|
+
countedCashCents: null,
|
|
186
|
+
expectedCashCents: null,
|
|
187
|
+
varianceCents: null,
|
|
188
|
+
netSalesCents: null
|
|
183
189
|
};
|
|
184
190
|
transaction.create(shiftRef, shift);
|
|
185
191
|
transaction.update(register.ref, {
|
|
@@ -423,6 +429,7 @@ export async function handlePosShift(deps, req) {
|
|
|
423
429
|
countedCashCents: counted,
|
|
424
430
|
expectedCashCents: report.expectedCashCents,
|
|
425
431
|
varianceCents: posCashVarianceCents(counted, report.expectedCashCents),
|
|
432
|
+
netSalesCents: report.netSalesCents,
|
|
426
433
|
report
|
|
427
434
|
}, note ? {
|
|
428
435
|
closingNote: note
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/pos-shift.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 { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport type { PluginApiHandler, PluginApiRequest } from '@aglyn/aglyn/server'\nimport {\n computePosShiftReport,\n posShiftPrintReport,\n posCashVarianceCents,\n type PosCashEvent,\n type PosCashEventType,\n type PosShift,\n type PosShiftRefund,\n type PosShiftReport,\n type PosShiftSale,\n} from '../model/commerce-pos-ops'\nimport {\n authorizePosOps,\n defaultPosOpsDeps,\n posOpsBody,\n posOpsCleanId,\n readPosRegister,\n resolvePosCashier,\n type PosOpsDeps,\n type PosOpsStaff,\n} from './pos-ops-gate'\n\n/*==========================================\n * SHIFTS AND THE CASH DRAWER (AGL-3609): `POST /api/commerce/pos-shift`.\n *\n * open a starting float; refused while the register has a shift open\n * cash-event paid in, paid out or a safe drop, with a reason\n * x-report the open shift's figures so far, changing nothing\n * close the counted cash, the Z report frozen onto the shift\n * current the register's open shift, or none\n *\n * ONE OPEN SHIFT PER REGISTER is the register document's `openShiftId`,\n * claimed and released inside the same transaction as the shift it names.\n * Two tablets opening the same till at once both read the register; the\n * transaction that commits second re-reads it, finds the first one's shift\n * and refuses — there is no window in which both stand.\n *=========================================*/\n\n/** The most sales one report reads; past it the report says it is partial. */\nexport const POS_SHIFT_SALES_CEILING = 5000\n/** The largest float or cash movement the drawer records in one entry. */\nexport const POS_CASH_ENTRY_MAX_CENTS = 10_000_000\n\nconst CASH_EVENT_TYPES: ReadonlySet<PosCashEventType> = new Set(['paid_in', 'paid_out', 'drop'])\n\nfunction cents(value: unknown): number | null {\n const number = Number(value)\n if (!Number.isFinite(number)) return null\n const rounded = Math.round(number)\n return rounded >= 0 && rounded <= POS_CASH_ENTRY_MAX_CENTS ? rounded : null\n}\n\n/**\n * The figures for a shift, read from the sales stamped with it and the\n * returns rung against it. Inside a transaction when one is given, so a Z\n * report and the close it freezes see the same sales.\n */\nexport async function readPosShiftReport(\n registerRef: FirebaseFirestore.DocumentReference,\n hostRef: FirebaseFirestore.DocumentReference,\n shiftId: string,\n shift: Pick<PosShift, 'openingFloatCents' | 'cashEvents'>,\n transaction?: FirebaseFirestore.Transaction,\n): Promise<PosShiftReport> {\n const salesQuery = hostRef\n .collection('orders')\n .where('shiftId', '==', shiftId)\n .limit(POS_SHIFT_SALES_CEILING + 1)\n const returnsQuery = registerRef.collection('returns').where('shiftId', '==', shiftId).limit(2000)\n const [salesSnapshot, returnsSnapshot] = transaction\n ? await Promise.all([transaction.get(salesQuery), transaction.get(returnsQuery)])\n : await Promise.all([salesQuery.get(), returnsQuery.get()])\n const sales = salesSnapshot.docs\n .slice(0, POS_SHIFT_SALES_CEILING)\n .map((doc) => (doc.data() ?? {}) as PosShiftSale)\n const refunds: PosShiftRefund[] = returnsSnapshot.docs.map((doc) => {\n const data = (doc.data() ?? {}) as Record<string, any>\n const tenders = Array.isArray(data['tenders']) ? data['tenders'] : []\n return {\n refundedCents: Number(data['refundedCents'] ?? 0),\n tenders: tenders\n .filter((tender: any) => tender?.status !== 'failed')\n .map((tender: any) => ({ method: tender.method, amountCents: Number(tender.amountCents ?? 0) })),\n }\n })\n return computePosShiftReport({\n shift,\n sales,\n refunds,\n truncated: salesSnapshot.docs.length > POS_SHIFT_SALES_CEILING,\n })\n}\n\nfunction shiftOf(data: Record<string, any> | undefined): PosShift | null {\n if (!data) return null\n return {\n ...(data as PosShift),\n cashEvents: Array.isArray(data['cashEvents']) ? (data['cashEvents'] as PosCashEvent[]) : [],\n openingFloatCents: Number(data['openingFloatCents'] ?? 0),\n }\n}\n\ntype Outcome = { status: number; body: Record<string, unknown> }\n\nexport async function handlePosShift(\n deps: PosOpsDeps,\n req: PluginApiRequest,\n): Promise<Outcome> {\n if (req.method !== 'POST') return { status: 405, body: { error: 'Method not allowed' } }\n const body = posOpsBody(req)\n const gate = await authorizePosOps(deps, req, body['hostId'])\n if ('error' in gate) return { status: gate.status, body: { error: gate.error } }\n const staff = gate.staff\n const register = await readPosRegister(staff, body['registerId'])\n if ('error' in register) return { status: register.status, body: { error: register.error } }\n const registerId = register.ref.id\n const { cashierId } = await resolvePosCashier(deps, staff, registerId, body['cashierAssertion'])\n const action = String(body['action'] ?? '')\n const firestore = deps.firestore()\n const shifts = register.ref.collection('shifts')\n\n switch (action) {\n case 'current': {\n const openShiftId = String(register.data['openShiftId'] ?? '')\n if (!openShiftId) return { status: 200, body: { shift: null } }\n const shift = shiftOf((await shifts.doc(openShiftId).get()).data())\n return {\n status: 200,\n body: { shift: shift && shift.status === 'open' ? { id: openShiftId, ...shift } : null },\n }\n }\n\n case 'open': {\n const float = cents(body['openingFloatCents'] ?? 0)\n if (float == null) return { status: 400, body: { error: 'Enter the starting cash as an amount.' } }\n const shiftRef = shifts.doc(createResourceUid())\n const now = deps.now()\n const openedByName = await deps.memberName(cashierId)\n return await firestore.runTransaction(async (transaction) => {\n const fresh = await transaction.get(register.ref)\n const standing = String(fresh.get('openShiftId') ?? '')\n if (standing) {\n const standingShift = await transaction.get(shifts.doc(standing))\n if (standingShift.exists && standingShift.get('status') === 'open') {\n return {\n status: 409,\n body: {\n error: 'This register already has a shift open. Close it before opening another.',\n shiftId: standing,\n },\n }\n }\n }\n const shift: PosShift = {\n hostId: staff.hostId,\n registerId,\n status: 'open',\n openedBy: cashierId,\n openedByName,\n openedAtMs: now,\n openingFloatCents: float,\n cashEvents: [],\n }\n transaction.create(shiftRef, shift)\n transaction.update(register.ref, { openShiftId: shiftRef.id })\n return { status: 200, body: { shift: { id: shiftRef.id, ...shift } } }\n })\n }\n\n case 'cash-event': {\n const type = String(body['type'] ?? '') as PosCashEventType\n if (!CASH_EVENT_TYPES.has(type)) {\n return { status: 400, body: { error: 'Choose paid in, paid out or a safe drop.' } }\n }\n const amount = cents(body['amountCents'])\n if (!amount) return { status: 400, body: { error: 'Enter an amount above zero.' } }\n const reason = String(body['reason'] ?? '').trim().slice(0, 200)\n if (!reason && type !== 'drop') {\n return { status: 400, body: { error: 'Say what the cash was for.' } }\n }\n const eventId = posOpsCleanId(body['eventId']) || createResourceUid()\n const recorded: Outcome = await firestore.runTransaction(async (transaction) => {\n const fresh = await transaction.get(register.ref)\n const shiftId = String(fresh.get('openShiftId') ?? '')\n if (!shiftId) return { status: 409, body: { error: 'Open a shift first.' } }\n const shiftRef = shifts.doc(shiftId)\n const shift = shiftOf((await transaction.get(shiftRef)).data())\n if (!shift || shift.status !== 'open') {\n return { status: 409, body: { error: 'Open a shift first.' } }\n }\n // A retried tap carries the same id: recorded once, answered twice.\n if (shift.cashEvents.some((event) => event.id === eventId)) {\n return { status: 200, body: { shift: { id: shiftId, ...shift }, replayed: true } }\n }\n const event: PosCashEvent = {\n id: eventId,\n type,\n amountCents: amount,\n reason,\n by: cashierId,\n atMs: deps.now(),\n }\n const cashEvents = [...shift.cashEvents, event]\n transaction.update(shiftRef, { cashEvents })\n return { status: 200, body: { shift: { id: shiftId, ...shift, cashEvents } } }\n })\n // The drawer opens for the cash once it is on the record; a replayed\n // tap kicked it the first time.\n if (recorded.status === 200 && !recorded.body['replayed']) {\n await deps.printer.kickDrawer({\n hostId: staff.hostId,\n registerId,\n reason: type as Exclude<PosCashEventType, 'refund'>,\n causeId: eventId,\n createdBy: cashierId,\n })\n }\n return recorded\n }\n\n case 'print-report': {\n // An X report of the open shift, or the Z report a closed one froze, on\n // the register's receipt printer. `printed: false` tells the register\n // there is no cloud printer, and it prints through the browser instead.\n const requested = posOpsCleanId(body['shiftId']) || String(register.data['openShiftId'] ?? '')\n if (!requested) return { status: 409, body: { error: 'No shift is open on this register.' } }\n const shift = shiftOf((await shifts.doc(requested).get()).data())\n if (!shift) return { status: 404, body: { error: 'Unknown shift' } }\n const closed = shift.status === 'closed' && shift.report\n const report = closed\n ? (shift.report as PosShiftReport)\n : await readPosShiftReport(register.ref, staff.hostRef, requested, shift)\n const host = await staff.hostRef.get()\n const by = closed ? shift.closedByName : shift.openedByName\n const printed = await deps.printer.printReport({\n hostId: staff.hostId,\n registerId,\n shiftId: requested,\n report: posShiftPrintReport({\n title: closed ? 'Z REPORT' : 'X REPORT',\n storeName: String(host.get('displayName') ?? '') || 'Register',\n ...(register.data['name'] ? { registerName: String(register.data['name']) } : {}),\n ...(by ? { subtitle: `${closed ? 'Closed' : 'Opened'} by ${by}` } : {}),\n report,\n ...(closed ? { shift } : {}),\n }),\n createdBy: cashierId,\n attemptKey: posOpsCleanId(body['attemptKey']) || String(deps.now()),\n })\n return { status: 200, body: { printed: printed.jobIds.length > 0, jobIds: printed.jobIds } }\n }\n\n case 'x-report': {\n const shiftId = String(register.data['openShiftId'] ?? '')\n const shift = shiftId ? shiftOf((await shifts.doc(shiftId).get()).data()) : null\n if (!shift || shift.status !== 'open') {\n return { status: 409, body: { error: 'No shift is open on this register.' } }\n }\n const report = await readPosShiftReport(register.ref, staff.hostRef, shiftId, shift)\n return { status: 200, body: { shiftId, shift: { id: shiftId, ...shift }, report } }\n }\n\n case 'close': {\n const counted = cents(body['countedCashCents'])\n if (counted == null) return { status: 400, body: { error: 'Enter the cash you counted.' } }\n const note = String(body['note'] ?? '').trim().slice(0, 500)\n const expectedShiftId = posOpsCleanId(body['shiftId'])\n const closedByName = await deps.memberName(cashierId)\n return await firestore.runTransaction(async (transaction) => {\n const fresh = await transaction.get(register.ref)\n const shiftId = String(fresh.get('openShiftId') ?? '')\n if (!shiftId) return { status: 409, body: { error: 'No shift is open on this register.' } }\n // A stale tablet closing a shift another tablet already closed and\n // reopened must not close the new one with the old count.\n if (expectedShiftId && expectedShiftId !== shiftId) {\n return {\n status: 409,\n body: { error: 'That shift is already closed. Refresh to see the open one.' },\n }\n }\n const shiftRef = shifts.doc(shiftId)\n const shift = shiftOf((await transaction.get(shiftRef)).data())\n if (!shift || shift.status !== 'open') {\n return { status: 409, body: { error: 'No shift is open on this register.' } }\n }\n const report = await readPosShiftReport(\n register.ref,\n staff.hostRef,\n shiftId,\n shift,\n transaction,\n )\n const closed: Partial<PosShift> = {\n status: 'closed',\n closedBy: cashierId,\n closedByName,\n closedAtMs: deps.now(),\n countedCashCents: counted,\n expectedCashCents: report.expectedCashCents,\n varianceCents: posCashVarianceCents(counted, report.expectedCashCents),\n report,\n ...(note ? { closingNote: note } : {}),\n }\n transaction.update(shiftRef, closed)\n transaction.update(register.ref, { openShiftId: null })\n return { status: 200, body: { shift: { id: shiftId, ...shift, ...closed }, report } }\n })\n }\n\n default:\n return { status: 400, body: { error: 'Unknown action' } }\n }\n}\n\n/** Whether a register may take a sale or pay cash out, by the site's shift rule. */\nexport async function posShiftForSale(\n staff: Pick<PosOpsStaff, 'settings'>,\n registerRef: FirebaseFirestore.DocumentReference,\n): Promise<{ ok: true; shiftId: string | null } | { ok: false; error: string }> {\n const register = await registerRef.get()\n const shiftId = String(register.get('openShiftId') ?? '') || null\n if (!shiftId && staff.settings.requireOpenShift) {\n return { ok: false, error: 'Open a shift on this register before ringing a sale.' }\n }\n return { ok: true, shiftId }\n}\n\nexport function createPosShiftHandler(deps: () => PosOpsDeps = defaultPosOpsDeps): PluginApiHandler {\n return async (req, res) => {\n try {\n const outcome = await handlePosShift(deps(), req)\n return res.status(outcome.status).json(outcome.body)\n } catch (error) {\n console.error('[pos-shift] failed', error)\n return res.status(500).json({ error: 'The shift could not be updated' })\n }\n }\n}\n\nexport const posShiftHandler = createPosShiftHandler()\n"],"names":["createResourceUid","computePosShiftReport","posShiftPrintReport","posCashVarianceCents","authorizePosOps","defaultPosOpsDeps","posOpsBody","posOpsCleanId","readPosRegister","resolvePosCashier","POS_SHIFT_SALES_CEILING","POS_CASH_ENTRY_MAX_CENTS","CASH_EVENT_TYPES","Set","cents","value","number","Number","isFinite","rounded","Math","round","readPosShiftReport","registerRef","hostRef","shiftId","shift","transaction","salesQuery","collection","where","limit","returnsQuery","salesSnapshot","returnsSnapshot","Promise","all","get","sales","docs","slice","map","doc","data","refunds","tenders","Array","isArray","refundedCents","filter","tender","status","method","amountCents","truncated","length","shiftOf","cashEvents","openingFloatCents","handlePosShift","deps","req","body","error","gate","staff","register","registerId","ref","id","cashierId","action","String","firestore","shifts","openShiftId","float","shiftRef","now","openedByName","memberName","runTransaction","fresh","standing","standingShift","exists","hostId","openedBy","openedAtMs","create","update","type","has","amount","reason","trim","eventId","recorded","some","event","replayed","by","atMs","printer","kickDrawer","causeId","createdBy","host","requested","closed","report","closedByName","printed","printReport","title","storeName","registerName","subtitle","attemptKey","jobIds","counted","note","expectedShiftId","closedBy","closedAtMs","countedCashCents","expectedCashCents","varianceCents","closingNote","posShiftForSale","settings","requireOpenShift","ok","createPosShiftHandler","res","outcome","json","console","posShiftHandler"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/server/pos-shift.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 { createResourceUid } from '@aglyn/aglyn/app-utils/create-resource-uid'\nimport type { PluginApiHandler, PluginApiRequest } from '@aglyn/aglyn/server'\nimport {\n computePosShiftReport,\n posShiftPrintReport,\n posCashVarianceCents,\n type PosCashEvent,\n type PosCashEventType,\n type PosShift,\n type PosShiftRefund,\n type PosShiftReport,\n type PosShiftSale,\n} from '../model/commerce-pos-ops'\nimport {\n authorizePosOps,\n defaultPosOpsDeps,\n posOpsBody,\n posOpsCleanId,\n readPosRegister,\n resolvePosCashier,\n type PosOpsDeps,\n type PosOpsStaff,\n} from './pos-ops-gate'\n\n/*==========================================\n * SHIFTS AND THE CASH DRAWER (AGL-3609): `POST /api/commerce/pos-shift`.\n *\n * open a starting float; refused while the register has a shift open\n * cash-event paid in, paid out or a safe drop, with a reason\n * x-report the open shift's figures so far, changing nothing\n * close the counted cash, the Z report frozen onto the shift\n * current the register's open shift, or none\n *\n * ONE OPEN SHIFT PER REGISTER is the register document's `openShiftId`,\n * claimed and released inside the same transaction as the shift it names.\n * Two tablets opening the same till at once both read the register; the\n * transaction that commits second re-reads it, finds the first one's shift\n * and refuses — there is no window in which both stand.\n *=========================================*/\n\n/** The most sales one report reads; past it the report says it is partial. */\nexport const POS_SHIFT_SALES_CEILING = 5000\n/** The largest float or cash movement the drawer records in one entry. */\nexport const POS_CASH_ENTRY_MAX_CENTS = 10_000_000\n\nconst CASH_EVENT_TYPES: ReadonlySet<PosCashEventType> = new Set(['paid_in', 'paid_out', 'drop'])\n\nfunction cents(value: unknown): number | null {\n const number = Number(value)\n if (!Number.isFinite(number)) return null\n const rounded = Math.round(number)\n return rounded >= 0 && rounded <= POS_CASH_ENTRY_MAX_CENTS ? rounded : null\n}\n\n/**\n * The figures for a shift, read from the sales stamped with it and the\n * returns rung against it. Inside a transaction when one is given, so a Z\n * report and the close it freezes see the same sales.\n */\nexport async function readPosShiftReport(\n registerRef: FirebaseFirestore.DocumentReference,\n hostRef: FirebaseFirestore.DocumentReference,\n shiftId: string,\n shift: Pick<PosShift, 'openingFloatCents' | 'cashEvents'>,\n transaction?: FirebaseFirestore.Transaction,\n): Promise<PosShiftReport> {\n const salesQuery = hostRef\n .collection('orders')\n .where('shiftId', '==', shiftId)\n .limit(POS_SHIFT_SALES_CEILING + 1)\n const returnsQuery = registerRef.collection('returns').where('shiftId', '==', shiftId).limit(2000)\n const [salesSnapshot, returnsSnapshot] = transaction\n ? await Promise.all([transaction.get(salesQuery), transaction.get(returnsQuery)])\n : await Promise.all([salesQuery.get(), returnsQuery.get()])\n const sales = salesSnapshot.docs\n .slice(0, POS_SHIFT_SALES_CEILING)\n .map((doc) => (doc.data() ?? {}) as PosShiftSale)\n const refunds: PosShiftRefund[] = returnsSnapshot.docs.map((doc) => {\n const data = (doc.data() ?? {}) as Record<string, any>\n const tenders = Array.isArray(data['tenders']) ? data['tenders'] : []\n return {\n refundedCents: Number(data['refundedCents'] ?? 0),\n tenders: tenders\n .filter((tender: any) => tender?.status !== 'failed')\n .map((tender: any) => ({ method: tender.method, amountCents: Number(tender.amountCents ?? 0) })),\n }\n })\n return computePosShiftReport({\n shift,\n sales,\n refunds,\n truncated: salesSnapshot.docs.length > POS_SHIFT_SALES_CEILING,\n })\n}\n\nfunction shiftOf(data: Record<string, any> | undefined): PosShift | null {\n if (!data) return null\n return {\n ...(data as PosShift),\n cashEvents: Array.isArray(data['cashEvents']) ? (data['cashEvents'] as PosCashEvent[]) : [],\n openingFloatCents: Number(data['openingFloatCents'] ?? 0),\n }\n}\n\ntype Outcome = { status: number; body: Record<string, unknown> }\n\nexport async function handlePosShift(\n deps: PosOpsDeps,\n req: PluginApiRequest,\n): Promise<Outcome> {\n if (req.method !== 'POST') return { status: 405, body: { error: 'Method not allowed' } }\n const body = posOpsBody(req)\n const gate = await authorizePosOps(deps, req, body['hostId'])\n if ('error' in gate) return { status: gate.status, body: { error: gate.error } }\n const staff = gate.staff\n const register = await readPosRegister(staff, body['registerId'])\n if ('error' in register) return { status: register.status, body: { error: register.error } }\n const registerId = register.ref.id\n const { cashierId } = await resolvePosCashier(deps, staff, registerId, body['cashierAssertion'])\n const action = String(body['action'] ?? '')\n const firestore = deps.firestore()\n const shifts = register.ref.collection('shifts')\n\n switch (action) {\n case 'current': {\n const openShiftId = String(register.data['openShiftId'] ?? '')\n if (!openShiftId) return { status: 200, body: { shift: null } }\n const shift = shiftOf((await shifts.doc(openShiftId).get()).data())\n return {\n status: 200,\n body: { shift: shift && shift.status === 'open' ? { id: openShiftId, ...shift } : null },\n }\n }\n\n case 'open': {\n const float = cents(body['openingFloatCents'] ?? 0)\n if (float == null) return { status: 400, body: { error: 'Enter the starting cash as an amount.' } }\n const shiftRef = shifts.doc(createResourceUid())\n const now = deps.now()\n const openedByName = await deps.memberName(cashierId)\n return await firestore.runTransaction(async (transaction) => {\n const fresh = await transaction.get(register.ref)\n const standing = String(fresh.get('openShiftId') ?? '')\n if (standing) {\n const standingShift = await transaction.get(shifts.doc(standing))\n if (standingShift.exists && standingShift.get('status') === 'open') {\n return {\n status: 409,\n body: {\n error: 'This register already has a shift open. Close it before opening another.',\n shiftId: standing,\n },\n }\n }\n }\n const shift: PosShift = {\n hostId: staff.hostId,\n registerId,\n status: 'open',\n openedBy: cashierId,\n openedByName,\n openedAtMs: now,\n openingFloatCents: float,\n cashEvents: [],\n // Null until the close: the history orders by each (AGL-3680).\n closedAtMs: null,\n countedCashCents: null,\n expectedCashCents: null,\n varianceCents: null,\n netSalesCents: null,\n }\n transaction.create(shiftRef, shift)\n transaction.update(register.ref, { openShiftId: shiftRef.id })\n return { status: 200, body: { shift: { id: shiftRef.id, ...shift } } }\n })\n }\n\n case 'cash-event': {\n const type = String(body['type'] ?? '') as PosCashEventType\n if (!CASH_EVENT_TYPES.has(type)) {\n return { status: 400, body: { error: 'Choose paid in, paid out or a safe drop.' } }\n }\n const amount = cents(body['amountCents'])\n if (!amount) return { status: 400, body: { error: 'Enter an amount above zero.' } }\n const reason = String(body['reason'] ?? '').trim().slice(0, 200)\n if (!reason && type !== 'drop') {\n return { status: 400, body: { error: 'Say what the cash was for.' } }\n }\n const eventId = posOpsCleanId(body['eventId']) || createResourceUid()\n const recorded: Outcome = await firestore.runTransaction(async (transaction) => {\n const fresh = await transaction.get(register.ref)\n const shiftId = String(fresh.get('openShiftId') ?? '')\n if (!shiftId) return { status: 409, body: { error: 'Open a shift first.' } }\n const shiftRef = shifts.doc(shiftId)\n const shift = shiftOf((await transaction.get(shiftRef)).data())\n if (!shift || shift.status !== 'open') {\n return { status: 409, body: { error: 'Open a shift first.' } }\n }\n // A retried tap carries the same id: recorded once, answered twice.\n if (shift.cashEvents.some((event) => event.id === eventId)) {\n return { status: 200, body: { shift: { id: shiftId, ...shift }, replayed: true } }\n }\n const event: PosCashEvent = {\n id: eventId,\n type,\n amountCents: amount,\n reason,\n by: cashierId,\n atMs: deps.now(),\n }\n const cashEvents = [...shift.cashEvents, event]\n transaction.update(shiftRef, { cashEvents })\n return { status: 200, body: { shift: { id: shiftId, ...shift, cashEvents } } }\n })\n // The drawer opens for the cash once it is on the record; a replayed\n // tap kicked it the first time.\n if (recorded.status === 200 && !recorded.body['replayed']) {\n await deps.printer.kickDrawer({\n hostId: staff.hostId,\n registerId,\n reason: type as Exclude<PosCashEventType, 'refund'>,\n causeId: eventId,\n createdBy: cashierId,\n })\n }\n return recorded\n }\n\n case 'print-report': {\n // An X report of the open shift, or the Z report a closed one froze, on\n // the register's receipt printer. `printed: false` tells the register\n // there is no cloud printer, and it prints through the browser instead.\n const requested = posOpsCleanId(body['shiftId']) || String(register.data['openShiftId'] ?? '')\n if (!requested) return { status: 409, body: { error: 'No shift is open on this register.' } }\n const shift = shiftOf((await shifts.doc(requested).get()).data())\n if (!shift) return { status: 404, body: { error: 'Unknown shift' } }\n const closed = shift.status === 'closed' && shift.report\n const report = closed\n ? (shift.report as PosShiftReport)\n : await readPosShiftReport(register.ref, staff.hostRef, requested, shift)\n const host = await staff.hostRef.get()\n const by = closed ? shift.closedByName : shift.openedByName\n const printed = await deps.printer.printReport({\n hostId: staff.hostId,\n registerId,\n shiftId: requested,\n report: posShiftPrintReport({\n title: closed ? 'Z REPORT' : 'X REPORT',\n storeName: String(host.get('displayName') ?? '') || 'Register',\n ...(register.data['name'] ? { registerName: String(register.data['name']) } : {}),\n ...(by ? { subtitle: `${closed ? 'Closed' : 'Opened'} by ${by}` } : {}),\n report,\n ...(closed ? { shift } : {}),\n }),\n createdBy: cashierId,\n attemptKey: posOpsCleanId(body['attemptKey']) || String(deps.now()),\n })\n return { status: 200, body: { printed: printed.jobIds.length > 0, jobIds: printed.jobIds } }\n }\n\n case 'x-report': {\n const shiftId = String(register.data['openShiftId'] ?? '')\n const shift = shiftId ? shiftOf((await shifts.doc(shiftId).get()).data()) : null\n if (!shift || shift.status !== 'open') {\n return { status: 409, body: { error: 'No shift is open on this register.' } }\n }\n const report = await readPosShiftReport(register.ref, staff.hostRef, shiftId, shift)\n return { status: 200, body: { shiftId, shift: { id: shiftId, ...shift }, report } }\n }\n\n case 'close': {\n const counted = cents(body['countedCashCents'])\n if (counted == null) return { status: 400, body: { error: 'Enter the cash you counted.' } }\n const note = String(body['note'] ?? '').trim().slice(0, 500)\n const expectedShiftId = posOpsCleanId(body['shiftId'])\n const closedByName = await deps.memberName(cashierId)\n return await firestore.runTransaction(async (transaction) => {\n const fresh = await transaction.get(register.ref)\n const shiftId = String(fresh.get('openShiftId') ?? '')\n if (!shiftId) return { status: 409, body: { error: 'No shift is open on this register.' } }\n // A stale tablet closing a shift another tablet already closed and\n // reopened must not close the new one with the old count.\n if (expectedShiftId && expectedShiftId !== shiftId) {\n return {\n status: 409,\n body: { error: 'That shift is already closed. Refresh to see the open one.' },\n }\n }\n const shiftRef = shifts.doc(shiftId)\n const shift = shiftOf((await transaction.get(shiftRef)).data())\n if (!shift || shift.status !== 'open') {\n return { status: 409, body: { error: 'No shift is open on this register.' } }\n }\n const report = await readPosShiftReport(\n register.ref,\n staff.hostRef,\n shiftId,\n shift,\n transaction,\n )\n const closed: Partial<PosShift> = {\n status: 'closed',\n closedBy: cashierId,\n closedByName,\n closedAtMs: deps.now(),\n countedCashCents: counted,\n expectedCashCents: report.expectedCashCents,\n varianceCents: posCashVarianceCents(counted, report.expectedCashCents),\n netSalesCents: report.netSalesCents,\n report,\n ...(note ? { closingNote: note } : {}),\n }\n transaction.update(shiftRef, closed)\n transaction.update(register.ref, { openShiftId: null })\n return { status: 200, body: { shift: { id: shiftId, ...shift, ...closed }, report } }\n })\n }\n\n default:\n return { status: 400, body: { error: 'Unknown action' } }\n }\n}\n\n/** Whether a register may take a sale or pay cash out, by the site's shift rule. */\nexport async function posShiftForSale(\n staff: Pick<PosOpsStaff, 'settings'>,\n registerRef: FirebaseFirestore.DocumentReference,\n): Promise<{ ok: true; shiftId: string | null } | { ok: false; error: string }> {\n const register = await registerRef.get()\n const shiftId = String(register.get('openShiftId') ?? '') || null\n if (!shiftId && staff.settings.requireOpenShift) {\n return { ok: false, error: 'Open a shift on this register before ringing a sale.' }\n }\n return { ok: true, shiftId }\n}\n\nexport function createPosShiftHandler(deps: () => PosOpsDeps = defaultPosOpsDeps): PluginApiHandler {\n return async (req, res) => {\n try {\n const outcome = await handlePosShift(deps(), req)\n return res.status(outcome.status).json(outcome.body)\n } catch (error) {\n console.error('[pos-shift] failed', error)\n return res.status(500).json({ error: 'The shift could not be updated' })\n }\n }\n}\n\nexport const posShiftHandler = createPosShiftHandler()\n"],"names":["createResourceUid","computePosShiftReport","posShiftPrintReport","posCashVarianceCents","authorizePosOps","defaultPosOpsDeps","posOpsBody","posOpsCleanId","readPosRegister","resolvePosCashier","POS_SHIFT_SALES_CEILING","POS_CASH_ENTRY_MAX_CENTS","CASH_EVENT_TYPES","Set","cents","value","number","Number","isFinite","rounded","Math","round","readPosShiftReport","registerRef","hostRef","shiftId","shift","transaction","salesQuery","collection","where","limit","returnsQuery","salesSnapshot","returnsSnapshot","Promise","all","get","sales","docs","slice","map","doc","data","refunds","tenders","Array","isArray","refundedCents","filter","tender","status","method","amountCents","truncated","length","shiftOf","cashEvents","openingFloatCents","handlePosShift","deps","req","body","error","gate","staff","register","registerId","ref","id","cashierId","action","String","firestore","shifts","openShiftId","float","shiftRef","now","openedByName","memberName","runTransaction","fresh","standing","standingShift","exists","hostId","openedBy","openedAtMs","closedAtMs","countedCashCents","expectedCashCents","varianceCents","netSalesCents","create","update","type","has","amount","reason","trim","eventId","recorded","some","event","replayed","by","atMs","printer","kickDrawer","causeId","createdBy","host","requested","closed","report","closedByName","printed","printReport","title","storeName","registerName","subtitle","attemptKey","jobIds","counted","note","expectedShiftId","closedBy","closingNote","posShiftForSale","settings","requireOpenShift","ok","createPosShiftHandler","res","outcome","json","console","posShiftHandler"],"mappings":"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"}
|
package/src/lib/server/refund.js
CHANGED
|
@@ -342,6 +342,9 @@ import { posRegisterRefundAuthority } from "./pos-refund-authority.js";
|
|
|
342
342
|
hostId,
|
|
343
343
|
orderId,
|
|
344
344
|
kind: 'commerce-refund',
|
|
345
|
+
// The site, as every claim names its scope: the staff claims list
|
|
346
|
+
// sorts by `scopeId`, and an `orderBy` drops a claim without it.
|
|
347
|
+
scopeId: hostId,
|
|
345
348
|
status: 'pending',
|
|
346
349
|
createdAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),
|
|
347
350
|
createdAtMs: Date.now(),
|