@aglyn/plugins-commerce 1.0.0-beta.289 → 1.0.0-beta.291

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +4 -2
  2. package/package.json +13 -13
  3. package/src/lib/components/commerce-console-page.js +2 -1
  4. package/src/lib/components/commerce-console-page.js.map +1 -1
  5. package/src/lib/components/console/commerce-pdf-download.d.ts +30 -0
  6. package/src/lib/components/console/commerce-pdf-download.js +53 -0
  7. package/src/lib/components/console/commerce-pdf-download.js.map +1 -0
  8. package/src/lib/components/console/host-orders-card.component.d.ts +6 -0
  9. package/src/lib/components/console/host-orders-card.component.js +3 -2
  10. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  11. package/src/lib/components/console/host-quotes-card.component.js +20 -4
  12. package/src/lib/components/console/host-quotes-card.component.js.map +1 -1
  13. package/src/lib/components/console/invoice-send.component.d.ts +44 -0
  14. package/src/lib/components/console/invoice-send.component.js +237 -0
  15. package/src/lib/components/console/invoice-send.component.js.map +1 -0
  16. package/src/lib/components/console/order-detail-dialog.component.d.ts +5 -0
  17. package/src/lib/components/console/order-detail-dialog.component.js +9 -2
  18. package/src/lib/components/console/order-detail-dialog.component.js.map +1 -1
  19. package/src/lib/components/console/order-invoice.component.d.ts +15 -4
  20. package/src/lib/components/console/order-invoice.component.js +93 -12
  21. package/src/lib/components/console/order-invoice.component.js.map +1 -1
  22. package/src/lib/components/console/quote-editor-dialog.component.d.ts +29 -0
  23. package/src/lib/components/console/quote-editor-dialog.component.js +154 -5
  24. package/src/lib/components/console/quote-editor-dialog.component.js.map +1 -1
  25. package/src/lib/components/console/quote-proofs.component.d.ts +48 -0
  26. package/src/lib/components/console/quote-proofs.component.js +694 -0
  27. package/src/lib/components/console/quote-proofs.component.js.map +1 -0
  28. package/src/lib/components/console/quote-send.component.d.ts +42 -0
  29. package/src/lib/components/console/quote-send.component.js +307 -0
  30. package/src/lib/components/console/quote-send.component.js.map +1 -0
  31. package/src/lib/components/console/quote-start.component.d.ts +45 -0
  32. package/src/lib/components/console/quote-start.component.js +182 -0
  33. package/src/lib/components/console/quote-start.component.js.map +1 -0
  34. package/src/lib/components/product-options-field-body.d.ts +44 -0
  35. package/src/lib/components/product-options-field-body.js +260 -0
  36. package/src/lib/components/product-options-field-body.js.map +1 -0
  37. package/src/lib/components/product-options-field.d.ts +32 -0
  38. package/src/lib/components/product-options-field.js +128 -0
  39. package/src/lib/components/product-options-field.js.map +1 -0
  40. package/src/lib/components/quote-page-body.js +67 -20
  41. package/src/lib/components/quote-page-body.js.map +1 -1
  42. package/src/lib/components/quote-proof-panel.d.ts +42 -0
  43. package/src/lib/components/quote-proof-panel.js +436 -0
  44. package/src/lib/components/quote-proof-panel.js.map +1 -0
  45. package/src/lib/model/commerce-invoices.d.ts +132 -0
  46. package/src/lib/model/commerce-invoices.js +332 -0
  47. package/src/lib/model/commerce-invoices.js.map +1 -0
  48. package/src/lib/model/commerce-orders.d.ts +29 -2
  49. package/src/lib/model/commerce-orders.js.map +1 -1
  50. package/src/lib/model/commerce-product-field.d.ts +23 -0
  51. package/src/lib/model/commerce-product-field.js +26 -0
  52. package/src/lib/model/commerce-product-field.js.map +1 -0
  53. package/src/lib/model/commerce-quote-email.d.ts +67 -0
  54. package/src/lib/model/commerce-quote-email.js +154 -0
  55. package/src/lib/model/commerce-quote-email.js.map +1 -0
  56. package/src/lib/model/commerce-quote-pay.d.ts +76 -6
  57. package/src/lib/model/commerce-quote-pay.js +105 -39
  58. package/src/lib/model/commerce-quote-pay.js.map +1 -1
  59. package/src/lib/model/commerce-quote-proofs.d.ts +159 -0
  60. package/src/lib/model/commerce-quote-proofs.js +348 -0
  61. package/src/lib/model/commerce-quote-proofs.js.map +1 -0
  62. package/src/lib/model/commerce-quote-request.d.ts +93 -0
  63. package/src/lib/model/commerce-quote-request.js +207 -0
  64. package/src/lib/model/commerce-quote-request.js.map +1 -0
  65. package/src/lib/model/commerce-quotes.d.ts +140 -3
  66. package/src/lib/model/commerce-quotes.js.map +1 -1
  67. package/src/lib/model/index.d.ts +4 -0
  68. package/src/lib/model/index.js +4 -0
  69. package/src/lib/model/index.js.map +1 -1
  70. package/src/lib/plugin.js +19 -0
  71. package/src/lib/plugin.js.map +1 -1
  72. package/src/lib/server/billing-webhook.js +115 -70
  73. package/src/lib/server/billing-webhook.js.map +1 -1
  74. package/src/lib/server/commerce-pdf.d.ts +104 -0
  75. package/src/lib/server/commerce-pdf.js +310 -0
  76. package/src/lib/server/commerce-pdf.js.map +1 -0
  77. package/src/lib/server/draft-order.js +23 -3
  78. package/src/lib/server/draft-order.js.map +1 -1
  79. package/src/lib/server/invoice-email.d.ts +76 -0
  80. package/src/lib/server/invoice-email.js +183 -0
  81. package/src/lib/server/invoice-email.js.map +1 -0
  82. package/src/lib/server/order-invoice-send.d.ts +43 -0
  83. package/src/lib/server/order-invoice-send.js +218 -0
  84. package/src/lib/server/order-invoice-send.js.map +1 -0
  85. package/src/lib/server/order-print.d.ts +6 -0
  86. package/src/lib/server/order-print.js +22 -5
  87. package/src/lib/server/order-print.js.map +1 -1
  88. package/src/lib/server/order-status.d.ts +11 -0
  89. package/src/lib/server/order-status.js +44 -8
  90. package/src/lib/server/order-status.js.map +1 -1
  91. package/src/lib/server/pdf-response.d.ts +31 -0
  92. package/src/lib/server/pdf-response.js +30 -0
  93. package/src/lib/server/pdf-response.js.map +1 -0
  94. package/src/lib/server/person-eraser.d.ts +3 -2
  95. package/src/lib/server/person-eraser.js +25 -6
  96. package/src/lib/server/person-eraser.js.map +1 -1
  97. package/src/lib/server/pos-reader-splash.d.ts +2 -18
  98. package/src/lib/server/pos-reader-splash.js +8 -93
  99. package/src/lib/server/pos-reader-splash.js.map +1 -1
  100. package/src/lib/server/quote-email.d.ts +85 -0
  101. package/src/lib/server/quote-email.js +211 -0
  102. package/src/lib/server/quote-email.js.map +1 -0
  103. package/src/lib/server/quote-link-token.d.ts +11 -0
  104. package/src/lib/server/quote-link-token.js +21 -0
  105. package/src/lib/server/quote-link-token.js.map +1 -1
  106. package/src/lib/server/quote-pay.d.ts +38 -6
  107. package/src/lib/server/quote-pay.js +115 -53
  108. package/src/lib/server/quote-pay.js.map +1 -1
  109. package/src/lib/server/quote-proofs.d.ts +51 -0
  110. package/src/lib/server/quote-proofs.js +500 -0
  111. package/src/lib/server/quote-proofs.js.map +1 -0
  112. package/src/lib/server/quote-uploads.d.ts +33 -0
  113. package/src/lib/server/quote-uploads.js +29 -0
  114. package/src/lib/server/quote-uploads.js.map +1 -0
  115. package/src/lib/server/quote.d.ts +25 -0
  116. package/src/lib/server/quote.js +638 -8
  117. package/src/lib/server/quote.js.map +1 -1
  118. package/src/lib/server/site-image-source.d.ts +38 -0
  119. package/src/lib/server/site-image-source.js +130 -0
  120. package/src/lib/server/site-image-source.js.map +1 -0
  121. package/src/lib/server/site-page-resolver.js +2 -1
  122. package/src/lib/server/site-page-resolver.js.map +1 -1
  123. package/src/lib/server.js +24 -1
  124. package/src/lib/server.js.map +1 -1
  125. package/src/lib/site.js +8 -0
  126. package/src/lib/site.js.map +1 -1
  127. package/src/lib/tenant-emails.js +243 -0
  128. package/src/lib/tenant-emails.js.map +1 -1
  129. package/src/lib/utils/commerce-document-pdf.d.ts +82 -0
  130. package/src/lib/utils/commerce-document-pdf.js +613 -0
  131. package/src/lib/utils/commerce-document-pdf.js.map +1 -0
  132. package/src/lib/utils/invoice-email-content.d.ts +45 -0
  133. package/src/lib/utils/invoice-email-content.js +126 -0
  134. package/src/lib/utils/invoice-email-content.js.map +1 -0
  135. package/src/lib/utils/order-invoice.d.ts +52 -3
  136. package/src/lib/utils/order-invoice.js +124 -64
  137. package/src/lib/utils/order-invoice.js.map +1 -1
  138. package/src/lib/utils/quote-email-content.d.ts +62 -0
  139. package/src/lib/utils/quote-email-content.js +154 -0
  140. package/src/lib/utils/quote-email-content.js.map +1 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-quotes.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { HostProduct, ProductType } from './commerce'\nimport { liftLegacyProduct, variantHasPrice } from './commerce'\nimport type {\n HostOrder,\n OrderAddress,\n OrderLineItem,\n OrderStatus,\n OrderTotals,\n} from './commerce-orders'\nimport { computeTaxCents, resolveTaxRate, type TaxSettings } from './commerce-tax'\nimport { storefrontTaxDecision, type StorefrontTaxMode } from './commerce-tax-decision'\nimport { allocateCentsByWeight } from './commerce-tax-engine'\nimport {\n lineLabelWithModifiers,\n resolveLineModifiers,\n type ModifierSelection,\n} from './product-modifiers'\n\n/*==========================================\n * QUOTES (AGL-4093).\n *\n * A quote is an order that has not been paid yet: a document in\n * `hosts/{hostId}/orders` with `channel: 'quote'`, `status: 'pending'` and a\n * `quote` section holding what only a quote has (the customer it is for, its\n * notes and terms, its discount and shipping, where it stands with the\n * business). Its `number` comes from the store's order counter, so the quote\n * and the order it becomes are one number and one document: paying it flips\n * `status` the way a paid draft order flips.\n *\n * WHAT KEEPS AN UNPAID QUOTE OUT OF EVERY ORDER LIST. A quote document\n * carries none of the fields the order readers ask their queries about:\n * no `createdAtMs` (the Orders list, its filters, the money tiles, the\n * analytics, the exports and the reports all order or range on it), none of\n * the list fields `withOrderListFields` stamps (`customerEmailLower`, the\n * search tokens, `requiresShipping`, `productIds`), and no top-level\n * `customerEmail`, `shippingAddress` or `billingAddress` (the customer's\n * account page, gated content and reviews look orders up by the buyer's\n * address). The customer lives in `quote.customer` until the quote is paid,\n * and whatever pays it writes those order fields then. `isUnpaidQuote` is the\n * test for the few readers that reach orders some other way (by number, by\n * document id, or by a header sort on `status`, `channel` or `number`).\n *\n * Pure: the console's live totals, the server's stored totals, and later the\n * quote's PDF and pay page all come from `computeQuoteTotals`.\n *=========================================*/\n\n/** Where a quote stands with the business. The customer side has no states. */\nexport type QuoteStage = 'draft' | 'sent' | 'changes_requested' | 'approved'\n\nexport const QUOTE_STAGE_LABELS: Record<QuoteStage, string> = {\n draft: 'Draft',\n sent: 'Sent',\n changes_requested: 'Changes requested',\n approved: 'Approved',\n}\n\n/** The person a quote is for: a record the workspace keeps, typed details, or both. */\nexport interface QuoteCustomer {\n /** The person record, as the person-records seam names it (`contact`, …). */\n record?: { kind: string; id: string } | null\n name?: string\n company?: string\n email?: string\n /** `email` lower-cased: what a person erasure finds the quote by. */\n emailLower?: string\n phone?: string\n}\n\n/** Money off the whole quote: an amount, or a percentage of the charged lines. */\nexport interface QuoteDiscount {\n kind: 'amount' | 'percent'\n /** With `amount`: the cents off. */\n amountCents?: number\n /** With `percent`: the percentage off. */\n pct?: number\n}\n\n/** Shipping the business prices by hand. */\nexport interface QuoteShipping {\n amountCents: number\n}\n\n/** Why a quote carries no tax. */\nexport type QuoteNoTaxReason =\n | 'not-collected'\n | 'prices-include-tax'\n | 'no-rate'\n | 'undecided'\n\n/** Where a quote's tax comes from. */\nexport type QuoteTaxKind = 'rate' | 'service' | 'at-payment' | 'none'\n\n/**\n * How a quote's tax was worked out, stored beside the figure so the quote can\n * say it: one of the store's own rates (`rate`, with `pct` and its `label`),\n * its connected tax service's answer (`service`, with `taxCents` and\n * `providerLabel`), Stripe Tax at payment (`at-payment`), or no tax and why\n * (`none`, with `reason`). One flat shape, so the apps can read it as is.\n */\nexport interface QuoteTaxBasis {\n kind: QuoteTaxKind\n pct?: number\n label?: string\n taxCents?: number\n providerLabel?: string\n reason?: QuoteNoTaxReason\n}\n\n/** A proof attached to one revision of a quote. Written by the proofs feature. */\nexport interface QuoteProofRef {\n id: string\n revision: number\n}\n\n/**\n * A payment that reached a quote it could not pay: one made through a pay\n * link the business replaced or for a price it has since changed\n * (`superseded`), or one made after the quote was already paid\n * (`already-paid`). The money is in Stripe and the quote records no sale for\n * it; the business refunds it or reconciles it by hand.\n */\nexport interface QuoteUnmatchedPayment {\n sessionId: string\n paymentIntentId: string | null\n amountCents: number\n atMs: number\n reason: 'superseded' | 'already-paid'\n}\n\n/** What only a quote has, on its order document. */\nexport interface OrderQuote {\n stage: QuoteStage\n /** Which version of the quote this is; versions are what proofs and approvals name. */\n revision: number\n customer: QuoteCustomer\n billTo?: OrderAddress | null\n shipTo?: OrderAddress | null\n notes?: string\n terms?: string\n discount?: QuoteDiscount | null\n shipping?: QuoteShipping | null\n tax: QuoteTaxBasis\n /** The customer approves the current revision's proof before they can pay. Absent reads as off. */\n requireProofApproval?: boolean\n proofs?: QuoteProofRef[]\n /** The revision the customer approved, when they have. */\n approvedRevision?: number | null\n /**\n * Which pay link opens the quote: a link signed for an older version shows\n * the customer that the quote was replaced, and nothing of it. Absent reads\n * as 1. Bumped when the business replaces the link.\n */\n linkVersion?: number\n /** When the customer paid it. */\n paidAtMs?: number\n /** Payments the quote could not take; see {@link QuoteUnmatchedPayment}. */\n unmatchedPayments?: QuoteUnmatchedPayment[]\n createdAtMs: number\n updatedAtMs: number\n /** Who created and last changed it: an email, else a uid. */\n createdBy: string\n updatedBy: string\n}\n\n/** The store-wide quote defaults, at `hosts/{hostId}/settings/store` under `quotes`. */\nexport interface QuoteStoreSettings {\n /** What `requireProofApproval` a new quote starts with. Absent reads as off. */\n requireProofApproval?: boolean\n}\n\n/** Whether new quotes start out asking the customer to approve the proof first. */\nexport function quoteRequiresProofApprovalByDefault(\n settings: QuoteStoreSettings | null | undefined,\n): boolean {\n return settings?.requireProofApproval === true\n}\n\n/**\n * Whether paying waits on the customer approving the proof: the quote asks for\n * approval, its current revision has a proof, and that revision is not the\n * one the customer approved. A revision with no proof never holds a payment.\n */\nexport function quotePaymentBlockedByApproval(\n quote: Pick<OrderQuote, 'requireProofApproval' | 'revision' | 'proofs' | 'approvedRevision'> | null | undefined,\n): boolean {\n if (!quote || quote.requireProofApproval !== true) return false\n const revision = Number(quote.revision ?? 1)\n const proofed = (quote.proofs ?? []).some((proof) => Number(proof?.revision) === revision)\n if (!proofed) return false\n return Number(quote.approvedRevision) !== revision\n}\n\n/** Whether an order document is a quote, paid or not. */\nexport function isQuote(order: Pick<Partial<HostOrder>, 'channel'> | null | undefined): boolean {\n return order?.channel === 'quote'\n}\n\n/** Whether an order document is a quote nobody has paid yet, which no order list counts. */\nexport function isUnpaidQuote(\n order: { channel?: unknown; status?: unknown } | null | undefined,\n): boolean {\n return order?.channel === 'quote' && order?.status === 'pending'\n}\n\n/** `Quote #1042`; an order number until it is paid. */\nexport function formatQuoteNumber(order: Pick<HostOrder, 'number'>, docId?: string): string {\n if (order.number != null) return `Quote #${order.number}`\n return docId ? `Quote #${docId.slice(-6).toUpperCase()}` : 'Quote'\n}\n\n/** A quote's status as its list shows it: its stage until it is paid. */\nexport function quoteDisplayStatus(order: {\n status?: OrderStatus | string\n quote?: Pick<OrderQuote, 'stage'> | null\n}): { key: QuoteStage | 'paid' | 'cancelled' | 'refunded'; label: string } {\n if (order.status === 'cancelled') return { key: 'cancelled', label: 'Canceled' }\n if (order.status === 'refunded') return { key: 'refunded', label: 'Refunded' }\n if (order.status && order.status !== 'pending') return { key: 'paid', label: 'Paid' }\n const stage = (order.quote?.stage ?? 'draft') as QuoteStage\n return { key: stage, label: QUOTE_STAGE_LABELS[stage] ?? QUOTE_STAGE_LABELS.draft }\n}\n\n/** Chip colors for {@link quoteDisplayStatus}, as the orders list colors its statuses. */\nexport const QUOTE_STATUS_COLOR: Record<\n ReturnType<typeof quoteDisplayStatus>['key'],\n 'default' | 'success' | 'info' | 'warning' | 'error'\n> = {\n draft: 'default',\n sent: 'info',\n changes_requested: 'warning',\n approved: 'info',\n paid: 'success',\n cancelled: 'default',\n refunded: 'error',\n}\n\n/*==========================================\n * TOTALS\n *=========================================*/\n\n/** A line as the totals read it. */\nexport type QuoteTotalsLine = Pick<OrderLineItem, 'unitAmountCents' | 'quantity'> & {\n included?: boolean\n taxExempt?: boolean\n}\n\nexport interface QuoteTotalsInput {\n lines: readonly QuoteTotalsLine[]\n discount?: QuoteDiscount | null\n shippingCents?: number\n tax: QuoteTaxBasis\n}\n\n/** A quote's totals: the order's, plus what the tax was computed on. */\nexport interface QuoteTotals extends OrderTotals {\n /** The taxable lines after their share of the discount. */\n taxableCents: number\n /** Stripe Tax works the tax out when the customer pays; `taxCents` is 0 until then. */\n taxAtPayment: boolean\n}\n\nconst whole = (value: unknown): number => {\n const number = Math.round(Number(value))\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n/** One line's charge: nothing for an included line. */\nexport function quoteLineCents(line: QuoteTotalsLine): number {\n if (line.included) return 0\n return whole(line.unitAmountCents) * whole(line.quantity)\n}\n\n/** The discount in cents on `itemsCents`, never more than it. */\nexport function quoteDiscountCents(\n itemsCents: number,\n discount: QuoteDiscount | null | undefined,\n): number {\n const items = whole(itemsCents)\n if (!discount) return 0\n if (discount.kind === 'amount') return Math.min(items, whole(discount.amountCents))\n if (discount.kind !== 'percent') return 0\n const pct = Math.min(100, Math.max(0, Number(discount.pct) || 0))\n return Math.min(items, Math.round((items * pct) / 100))\n}\n\n/**\n * Each line's charge, its share of the discount (in proportion to the\n * charges, as a checkout spreads a coupon) and what is left: the amounts a tax\n * service is asked about, line by line.\n */\nexport function quoteLineAmounts(\n lines: readonly QuoteTotalsLine[],\n discount: QuoteDiscount | null | undefined,\n): { grossCents: number[]; discountCents: number[]; netCents: number[]; itemsCents: number; totalDiscountCents: number } {\n const grossCents = lines.map(quoteLineCents)\n const itemsCents = grossCents.reduce((sum, cents) => sum + cents, 0)\n const totalDiscountCents = quoteDiscountCents(itemsCents, discount)\n const discountCents = allocateCentsByWeight(totalDiscountCents, grossCents)\n return {\n grossCents,\n discountCents,\n netCents: grossCents.map((cents, index) => cents - discountCents[index]),\n itemsCents,\n totalDiscountCents,\n }\n}\n\n/**\n * A quote's totals. The discount comes off before tax; a store rate taxes the\n * taxable lines (not shipping, as a draft order's rate does); a tax service's\n * answer is taken as given; Stripe Tax and no-tax add nothing now.\n */\nexport function computeQuoteTotals(input: QuoteTotalsInput): QuoteTotals {\n const amounts = quoteLineAmounts(input.lines, input.discount)\n const taxableCents = amounts.netCents.reduce(\n (sum, cents, index) => sum + (input.lines[index]?.taxExempt ? 0 : cents),\n 0,\n )\n const shippingCents = whole(input.shippingCents)\n const tax = input.tax\n const taxCents =\n tax.kind === 'rate'\n ? computeTaxCents(taxableCents, Number(tax.pct) || 0)\n : tax.kind === 'service'\n ? whole(tax.taxCents)\n : 0\n return {\n itemsCents: amounts.itemsCents,\n discountCents: amounts.totalDiscountCents,\n shippingCents,\n taxCents,\n // The platform fee is set when a payment is created, from what it charges.\n feeCents: 0,\n totalCents: amounts.itemsCents - amounts.totalDiscountCents + shippingCents + taxCents,\n taxableCents,\n taxAtPayment: tax.kind === 'at-payment',\n }\n}\n\n/** The order totals a quote document stores. */\nexport function quoteOrderTotals(totals: QuoteTotals): OrderTotals {\n return {\n itemsCents: totals.itemsCents,\n discountCents: totals.discountCents,\n shippingCents: totals.shippingCents,\n taxCents: totals.taxCents,\n feeCents: totals.feeCents,\n totalCents: totals.totalCents,\n }\n}\n\n/*==========================================\n * TAX\n *=========================================*/\n\n/**\n * The tax basis the store's own settings give a quote, before any tax service\n * is asked: Stripe Tax at payment, no tax (and why), or the store's rate for\n * where the quote ships — its ship-to when it has a country, else the store's\n * own address, as the storefront taxes a sale it cannot see the destination of.\n */\nexport function quoteTaxBasisFromSettings(\n settings: TaxSettings | null | undefined,\n shipTo: Pick<OrderAddress, 'country' | 'state'> | null | undefined,\n): QuoteTaxBasis {\n const decision = storefrontTaxDecision({ settings })\n if (decision.kind === 'undecided') return { kind: 'none', reason: 'undecided' }\n if (decision.kind === 'stripe-automatic') return { kind: 'at-payment' }\n if (decision.kind !== 'manual') return { kind: 'none', reason: 'not-collected' }\n if (settings?.pricesIncludeTax) return { kind: 'none', reason: 'prices-include-tax' }\n const address = String(shipTo?.country ?? '').trim()\n ? { country: shipTo?.country, state: shipTo?.state }\n : (settings?.origin ?? {})\n const rate = resolveTaxRate(settings ?? undefined, address)\n if (!rate || !(Number(rate.pct) > 0)) return { kind: 'none', reason: 'no-rate' }\n return { kind: 'rate', pct: Number(rate.pct), ...(rate.label ? { label: rate.label } : {}) }\n}\n\n/** The tax line's label on a quote. */\nexport function describeQuoteTax(basis: QuoteTaxBasis | null | undefined): string {\n if (!basis) return 'Tax'\n switch (basis.kind) {\n case 'rate':\n return basis.label ? `${basis.label} (${basis.pct ?? 0}%)` : `Tax (${basis.pct ?? 0}%)`\n case 'service':\n return basis.providerLabel ? `Tax (${basis.providerLabel})` : 'Tax'\n case 'at-payment':\n return 'Tax is added at payment'\n case 'none':\n return basis.reason === 'prices-include-tax'\n ? 'Prices include tax'\n : basis.reason === 'no-rate'\n ? 'No tax rate for this address'\n : basis.reason === 'undecided'\n ? 'Sales tax is not set up'\n : 'No sales tax'\n default:\n return 'Tax'\n }\n}\n\n/** The order's `taxMode` for a quote's basis; absent while nobody has decided. */\nexport function quoteTaxMode(basis: QuoteTaxBasis, taxCents: number): StorefrontTaxMode | undefined {\n if (basis.kind === 'at-payment') return 'stripe-automatic'\n if (basis.kind === 'none' && basis.reason === 'undecided') return undefined\n return taxCents > 0 ? 'manual' : 'none'\n}\n\n/*==========================================\n * WHAT THE CONSOLE SENDS\n *=========================================*/\n\nexport const QUOTE_MAX_LINES = 100\nexport const QUOTE_MAX_QUANTITY = 9_999\n/** $1,000,000 a unit: the most a typed line may cost. */\nexport const QUOTE_MAX_UNIT_CENTS = 100_000_000\nexport const QUOTE_MAX_TEXT = 5_000\nconst SHORT_TEXT = 200\n\n/** A product line as the console asks for it: what it is, never what it costs. */\nexport interface QuoteCatalogLineRequest {\n kind: 'catalog'\n productId: string\n variantId?: string\n quantity: number\n modifiers?: ModifierSelection[]\n included?: boolean\n description?: string\n}\n\n/** A line typed on the quote, which no product stands behind. */\nexport interface QuoteCustomLineRequest {\n kind: 'custom'\n name: string\n description?: string\n sku?: string\n unitAmountCents: number\n quantity: number\n /** Whether it is something to ship (else a service, which never ships). */\n ships?: boolean\n included?: boolean\n}\n\nexport type QuoteLineRequest = QuoteCatalogLineRequest | QuoteCustomLineRequest\n\n/** Everything a business edits on a quote. */\nexport interface QuoteInput {\n lines: QuoteLineRequest[]\n customer: QuoteCustomer\n billTo: OrderAddress | null\n shipTo: OrderAddress | null\n notes: string\n terms: string\n discount: QuoteDiscount | null\n shipping: QuoteShipping | null\n /** Absent: the store's default (on create) or unchanged (on update). */\n requireProofApproval?: boolean\n}\n\nconst EMAIL = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\nconst DOC_ID = /^(?!__.*__$)[^/]{1,200}$/\n\nconst clean = (value: unknown, max = SHORT_TEXT): string =>\n typeof value === 'string' ? value.trim().slice(0, max) : ''\n\nconst intIn = (value: unknown, min: number, max: number): number | null => {\n const number = Number(value)\n return Number.isInteger(number) && number >= min && number <= max ? number : null\n}\n\nfunction parseAddress(raw: unknown): OrderAddress | null {\n if (!raw || typeof raw !== 'object') return null\n const source = raw as Record<string, unknown>\n const address: OrderAddress = {}\n for (const key of ['name', 'line1', 'line2', 'city', 'state', 'postalCode', 'phone'] as const) {\n const value = clean(source[key])\n if (value) address[key] = value\n }\n const country = clean(source['country'], 2).toUpperCase()\n if (/^[A-Z]{2}$/.test(country)) address.country = country\n return Object.keys(address).length ? address : null\n}\n\nfunction parseLine(raw: unknown, index: number): QuoteLineRequest | string {\n const source = (raw && typeof raw === 'object' ? raw : {}) as Record<string, unknown>\n const at = `Line ${index + 1}`\n const quantity = intIn(source['quantity'], 1, QUOTE_MAX_QUANTITY)\n if (quantity === null) return `${at}: enter a quantity from 1 to ${QUOTE_MAX_QUANTITY.toLocaleString('en-US')}.`\n const included = source['included'] === true\n const description = clean(source['description'], QUOTE_MAX_TEXT)\n if (source['kind'] === 'catalog') {\n const productId = clean(source['productId'])\n if (!DOC_ID.test(productId)) return `${at}: choose a product.`\n const variantId = clean(source['variantId'])\n const modifiers = Array.isArray(source['modifiers'])\n ? (source['modifiers'] as unknown[]).slice(0, 200).map((entry) => {\n const pick = (entry ?? {}) as Record<string, unknown>\n return { groupId: clean(pick['groupId']), optionId: clean(pick['optionId']) }\n })\n : []\n return {\n kind: 'catalog',\n productId,\n ...(variantId ? { variantId } : {}),\n quantity,\n ...(modifiers.length ? { modifiers } : {}),\n ...(included ? { included } : {}),\n ...(description ? { description } : {}),\n }\n }\n if (source['kind'] === 'custom') {\n const name = clean(source['name'])\n if (!name) return `${at}: name the item.`\n const unitAmountCents = included ? 0 : intIn(source['unitAmountCents'], 0, QUOTE_MAX_UNIT_CENTS)\n if (unitAmountCents === null) return `${at}: enter a price from $0 to $1,000,000.`\n const sku = clean(source['sku'], 100)\n return {\n kind: 'custom',\n name,\n ...(description ? { description } : {}),\n ...(sku ? { sku } : {}),\n unitAmountCents,\n quantity,\n ships: source['ships'] !== false,\n ...(included ? { included } : {}),\n }\n }\n return `${at}: add a product or type an item.`\n}\n\n/**\n * The console's quote, checked and trimmed, or what to fix. Prices on product\n * lines are not read from here: the server prices them from the product.\n */\nexport function parseQuoteInput(raw: unknown): { ok: true; input: QuoteInput } | { ok: false; error: string } {\n const source = (raw && typeof raw === 'object' ? raw : {}) as Record<string, unknown>\n const rawLines = Array.isArray(source['lines']) ? (source['lines'] as unknown[]) : []\n if (rawLines.length === 0) return { ok: false, error: 'Add at least one line to the quote.' }\n if (rawLines.length > QUOTE_MAX_LINES) {\n return { ok: false, error: `A quote holds up to ${QUOTE_MAX_LINES} lines.` }\n }\n const lines: QuoteLineRequest[] = []\n for (const [index, entry] of rawLines.entries()) {\n const line = parseLine(entry, index)\n if (typeof line === 'string') return { ok: false, error: line }\n lines.push(line)\n }\n\n const customerSource = (source['customer'] && typeof source['customer'] === 'object'\n ? source['customer']\n : {}) as Record<string, unknown>\n const email = clean(customerSource['email'])\n if (email && !EMAIL.test(email)) return { ok: false, error: 'Enter a valid email for the customer.' }\n const recordSource = (customerSource['record'] ?? null) as Record<string, unknown> | null\n const recordKind = clean(recordSource?.['kind'], 40)\n const recordId = clean(recordSource?.['id'])\n const customer: QuoteCustomer = {\n ...(recordKind && DOC_ID.test(recordId) ? { record: { kind: recordKind, id: recordId } } : {}),\n ...(clean(customerSource['name']) ? { name: clean(customerSource['name']) } : {}),\n ...(clean(customerSource['company']) ? { company: clean(customerSource['company']) } : {}),\n ...(email ? { email, emailLower: email.toLowerCase() } : {}),\n ...(clean(customerSource['phone'], 40) ? { phone: clean(customerSource['phone'], 40) } : {}),\n }\n\n let discount: QuoteDiscount | null = null\n const discountSource = (source['discount'] ?? null) as Record<string, unknown> | null\n if (discountSource?.['kind'] === 'amount') {\n const amountCents = intIn(discountSource['amountCents'], 0, QUOTE_MAX_UNIT_CENTS)\n if (amountCents === null) return { ok: false, error: 'Enter a discount from $0 to $1,000,000.' }\n if (amountCents > 0) discount = { kind: 'amount', amountCents }\n } else if (discountSource?.['kind'] === 'percent') {\n const pct = Number(discountSource['pct'])\n if (!Number.isFinite(pct) || pct < 0 || pct > 100) {\n return { ok: false, error: 'Enter a discount from 0% to 100%.' }\n }\n if (pct > 0) discount = { kind: 'percent', pct: Math.round(pct * 100) / 100 }\n }\n\n let shipping: QuoteShipping | null = null\n const shippingSource = (source['shipping'] ?? null) as Record<string, unknown> | null\n if (shippingSource) {\n const amountCents = intIn(shippingSource['amountCents'], 0, QUOTE_MAX_UNIT_CENTS)\n if (amountCents === null) return { ok: false, error: 'Enter shipping from $0 to $1,000,000.' }\n shipping = { amountCents }\n }\n\n return {\n ok: true,\n input: {\n lines,\n customer,\n billTo: parseAddress(source['billTo']),\n shipTo: parseAddress(source['shipTo']),\n notes: clean(source['notes'], QUOTE_MAX_TEXT),\n terms: clean(source['terms'], QUOTE_MAX_TEXT),\n discount,\n shipping,\n ...(typeof source['requireProofApproval'] === 'boolean'\n ? { requireProofApproval: source['requireProofApproval'] }\n : {}),\n },\n }\n}\n\n/** A priced quote line, and whether its product is tax-exempt. */\nexport type PricedQuoteLine = OrderLineItem & { taxExempt?: boolean }\n\n/** The variant's label as order lines print it: its option values, or nothing for a default. */\nfunction variantOptionsLabel(options: Record<string, string> | undefined): string | undefined {\n return options && Object.keys(options).length ? Object.values(options).join(' / ') : undefined\n}\n\n/**\n * A product line priced from the PRODUCT: its variant's price plus the chosen\n * add-ons, as the register prices one. An included line keeps what it is and\n * charges nothing.\n */\nexport function priceQuoteCatalogLine(\n rawProduct: Partial<HostProduct> | null | undefined,\n request: QuoteCatalogLineRequest,\n): { ok: true; line: PricedQuoteLine } | { ok: false; error: string } {\n if (!rawProduct || rawProduct.deletedAt) {\n return { ok: false, error: 'A product on this quote no longer exists. Remove it and add it again.' }\n }\n const product = liftLegacyProduct(rawProduct)\n const variant = request.variantId\n ? product.variants.find((entry) => entry.id === request.variantId)\n : product.variants[0]\n if (!variant) return { ok: false, error: `Choose an option of ${product.name} that still exists.` }\n if (!request.included && !variantHasPrice(variant)) {\n return { ok: false, error: `Set a price for ${product.name} before quoting it.` }\n }\n const chosen = resolveLineModifiers(product, request.modifiers)\n if (!chosen.ok) return { ok: false, error: chosen.error ?? `Check the choices on ${product.name}.` }\n const variantLabel = lineLabelWithModifiers(variantOptionsLabel(variant.options), chosen.modifiers)\n return {\n ok: true,\n line: {\n productId: request.productId,\n ...(variant.id !== 'default' ? { variantId: variant.id } : {}),\n name: product.name,\n ...(variantLabel ? { variantLabel } : {}),\n ...(variant.sku ? { sku: variant.sku } : {}),\n productType: (product.type ?? 'physical') as ProductType,\n quantity: request.quantity,\n unitAmountCents: request.included\n ? 0\n : Math.round(Number(variant.priceUsd) * 100) + chosen.extraCents,\n ...(chosen.modifiers.length ? { modifiers: chosen.modifiers } : {}),\n ...(request.included ? { included: true } : {}),\n ...(request.description ? { description: request.description } : {}),\n ...(product.taxExempt ? { taxExempt: true } : {}),\n },\n }\n}\n\n/** A typed line: no product, so nothing reads stock, a supplier or a download for it. */\nexport function priceQuoteCustomLine(request: QuoteCustomLineRequest): PricedQuoteLine {\n return {\n productId: '',\n custom: true,\n name: request.name,\n ...(request.sku ? { sku: request.sku } : {}),\n productType: request.ships === false ? 'service' : 'physical',\n quantity: request.quantity,\n unitAmountCents: request.included ? 0 : request.unitAmountCents,\n ...(request.included ? { included: true } : {}),\n ...(request.description ? { description: request.description } : {}),\n }\n}\n\n/** The request a stored line is edited as: what the console sends back on a save. */\nexport function quoteLineRequestFromLine(line: OrderLineItem): QuoteLineRequest {\n if (line.custom || !line.productId) {\n return {\n kind: 'custom',\n name: line.name,\n ...(line.description ? { description: line.description } : {}),\n ...(line.sku ? { sku: line.sku } : {}),\n unitAmountCents: line.unitAmountCents,\n quantity: line.quantity,\n ships: line.productType !== 'service' && line.productType !== 'digital',\n ...(line.included ? { included: true } : {}),\n }\n }\n return {\n kind: 'catalog',\n productId: line.productId,\n ...(line.variantId ? { variantId: line.variantId } : {}),\n quantity: line.quantity,\n ...(line.modifiers?.length\n ? { modifiers: line.modifiers.map(({ groupId, optionId }) => ({ groupId, optionId })) }\n : {}),\n ...(line.included ? { included: true } : {}),\n ...(line.description ? { description: line.description } : {}),\n }\n}\n"],"names":["liftLegacyProduct","variantHasPrice","computeTaxCents","resolveTaxRate","storefrontTaxDecision","allocateCentsByWeight","lineLabelWithModifiers","resolveLineModifiers","QUOTE_STAGE_LABELS","draft","sent","changes_requested","approved","quoteRequiresProofApprovalByDefault","settings","requireProofApproval","quotePaymentBlockedByApproval","quote","revision","Number","proofed","proofs","some","proof","approvedRevision","isQuote","order","channel","isUnpaidQuote","status","formatQuoteNumber","docId","number","slice","toUpperCase","quoteDisplayStatus","key","label","stage","QUOTE_STATUS_COLOR","paid","cancelled","refunded","whole","value","Math","round","isFinite","quoteLineCents","line","included","unitAmountCents","quantity","quoteDiscountCents","itemsCents","discount","items","kind","min","amountCents","pct","max","quoteLineAmounts","lines","grossCents","map","reduce","sum","cents","totalDiscountCents","discountCents","netCents","index","computeQuoteTotals","input","amounts","taxableCents","taxExempt","shippingCents","tax","taxCents","feeCents","totalCents","taxAtPayment","quoteOrderTotals","totals","quoteTaxBasisFromSettings","shipTo","decision","reason","pricesIncludeTax","address","String","country","trim","state","origin","rate","undefined","describeQuoteTax","basis","providerLabel","quoteTaxMode","QUOTE_MAX_LINES","QUOTE_MAX_QUANTITY","QUOTE_MAX_UNIT_CENTS","QUOTE_MAX_TEXT","SHORT_TEXT","EMAIL","DOC_ID","clean","intIn","isInteger","parseAddress","raw","source","test","Object","keys","length","parseLine","at","toLocaleString","description","productId","variantId","modifiers","Array","isArray","entry","pick","groupId","optionId","name","sku","ships","parseQuoteInput","customerSource","rawLines","ok","error","entries","push","email","recordSource","recordKind","recordId","customer","record","id","company","emailLower","toLowerCase","phone","discountSource","shipping","shippingSource","billTo","notes","terms","variantOptionsLabel","options","values","join","priceQuoteCatalogLine","rawProduct","request","chosen","product","deletedAt","variant","variants","find","variantLabel","productType","type","priceUsd","extraCents","priceQuoteCustomLine","custom","quoteLineRequestFromLine"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-quotes.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { HostProduct, ProductType } from './commerce'\nimport type { QuoteRequestRef } from './commerce-quote-request'\nimport { liftLegacyProduct, variantHasPrice } from './commerce'\nimport type {\n HostOrder,\n OrderAddress,\n OrderLineItem,\n OrderStatus,\n OrderTotals,\n} from './commerce-orders'\nimport { computeTaxCents, resolveTaxRate, type TaxSettings } from './commerce-tax'\nimport { storefrontTaxDecision, type StorefrontTaxMode } from './commerce-tax-decision'\nimport { allocateCentsByWeight } from './commerce-tax-engine'\nimport {\n lineLabelWithModifiers,\n resolveLineModifiers,\n type ModifierSelection,\n} from './product-modifiers'\n\n/*==========================================\n * QUOTES (AGL-4093).\n *\n * A quote is an order that has not been paid yet: a document in\n * `hosts/{hostId}/orders` with `channel: 'quote'`, `status: 'pending'` and a\n * `quote` section holding what only a quote has (the customer it is for, its\n * notes and terms, its discount and shipping, where it stands with the\n * business). Its `number` comes from the store's order counter, so the quote\n * and the order it becomes are one number and one document: paying it flips\n * `status` the way a paid draft order flips.\n *\n * WHAT KEEPS AN UNPAID QUOTE OUT OF EVERY ORDER LIST. A quote document\n * carries none of the fields the order readers ask their queries about:\n * no `createdAtMs` (the Orders list, its filters, the money tiles, the\n * analytics, the exports and the reports all order or range on it), none of\n * the list fields `withOrderListFields` stamps (`customerEmailLower`, the\n * search tokens, `requiresShipping`, `productIds`), and no top-level\n * `customerEmail`, `shippingAddress` or `billingAddress` (the customer's\n * account page, gated content and reviews look orders up by the buyer's\n * address). The customer lives in `quote.customer` until the quote is paid,\n * and whatever pays it writes those order fields then. `isUnpaidQuote` is the\n * test for the few readers that reach orders some other way (by number, by\n * document id, or by a header sort on `status`, `channel` or `number`).\n *\n * Pure: the console's live totals, the server's stored totals, and later the\n * quote's PDF and pay page all come from `computeQuoteTotals`.\n *=========================================*/\n\n/** Where a quote stands with the business. The customer side has no states. */\nexport type QuoteStage = 'draft' | 'sent' | 'changes_requested' | 'approved'\n\nexport const QUOTE_STAGE_LABELS: Record<QuoteStage, string> = {\n draft: 'Draft',\n sent: 'Sent',\n changes_requested: 'Changes requested',\n approved: 'Approved',\n}\n\n/** The person a quote is for: a record the workspace keeps, typed details, or both. */\nexport interface QuoteCustomer {\n /** The person record, as the person-records seam names it (`contact`, …). */\n record?: { kind: string; id: string } | null\n name?: string\n company?: string\n email?: string\n /** `email` lower-cased: what a person erasure finds the quote by. */\n emailLower?: string\n phone?: string\n}\n\n/** Money off the whole quote: an amount, or a percentage of the charged lines. */\nexport interface QuoteDiscount {\n kind: 'amount' | 'percent'\n /** With `amount`: the cents off. */\n amountCents?: number\n /** With `percent`: the percentage off. */\n pct?: number\n}\n\n/** Shipping the business prices by hand. */\nexport interface QuoteShipping {\n amountCents: number\n}\n\n/** Why a quote carries no tax. */\nexport type QuoteNoTaxReason =\n | 'not-collected'\n | 'prices-include-tax'\n | 'no-rate'\n | 'undecided'\n\n/** Where a quote's tax comes from. */\nexport type QuoteTaxKind = 'rate' | 'service' | 'at-payment' | 'none'\n\n/**\n * How a quote's tax was worked out, stored beside the figure so the quote can\n * say it: one of the store's own rates (`rate`, with `pct` and its `label`),\n * its connected tax service's answer (`service`, with `taxCents` and\n * `providerLabel`), Stripe Tax at payment (`at-payment`), or no tax and why\n * (`none`, with `reason`). One flat shape, so the apps can read it as is.\n */\nexport interface QuoteTaxBasis {\n kind: QuoteTaxKind\n pct?: number\n label?: string\n taxCents?: number\n providerLabel?: string\n reason?: QuoteNoTaxReason\n}\n\n/** A proof attached to one revision of a quote: what names it, and the revision it is for. */\nexport interface QuoteProofRef {\n id: string\n revision: number\n}\n\n/**\n * A proof the business attached to a revision (AGL-4096): an image or a\n * design file in the site's private storage, under the quote.\n */\nexport interface QuoteProof extends QuoteProofRef {\n name: string\n contentType: string\n size: number\n /** The private Storage object. Never sent to the customer. */\n path: string\n addedAtMs: number\n /** Who attached it: an email, else a uid. */\n addedBy: string\n}\n\n/** A file the customer sent with a response. */\nexport interface QuoteResponseFile {\n id: string\n name: string\n contentType: string\n size: number\n /** The private Storage object. Never sent to the customer. */\n path: string\n}\n\n/**\n * The customer's answer to a revision's proofs (AGL-4096): approved under a\n * typed name, or changes asked for in a comment, with artwork if they had\n * some. The address and browser are kept as the record of who answered.\n */\nexport interface QuoteCustomerResponse {\n action: 'approve' | 'changes'\n revision: number\n atMs: number\n /** With `approve`: the name they typed. */\n name?: string\n /** With `changes`: what to change. */\n comment?: string\n /** With `changes`: files they sent. */\n files?: QuoteResponseFile[]\n ip: string | null\n userAgent: string | null\n}\n\n/** The record of a revision's approval. */\nexport interface QuoteApproval {\n revision: number\n name: string\n atMs: number\n ip: string | null\n userAgent: string | null\n}\n\n/**\n * A payment that reached a quote it could not pay: one made through a pay\n * link the business replaced or for a price it has since changed\n * (`superseded`), or one made after the quote was already paid\n * (`already-paid`). The money is in Stripe and the quote records no sale for\n * it; the business refunds it or reconciles it by hand.\n */\nexport interface QuoteUnmatchedPayment {\n sessionId: string\n paymentIntentId: string | null\n amountCents: number\n atMs: number\n reason: 'superseded' | 'already-paid'\n}\n\n/**\n * The conversation the quote's emails keep in the customer's mailbox\n * (AGL-4097): the latest message's `Message-ID`, every id in the\n * conversation oldest first, and the subject it runs under. The shape of\n * `EmailThread` (`@aglyn/shared-util-email`), spelled out so the model needs\n * no email library. Started by the first email about the quote (a request's\n * confirmation, or the quote itself); every later one replies into it.\n */\nexport interface QuoteEmailThread {\n messageId: string\n references: string[]\n subject: string\n}\n\n/** One time the quote was emailed to the customer (AGL-4097). */\nexport interface QuoteSendRecord {\n /** The revision the email carried. */\n revision: number\n to: string\n subject: string\n atMs: number\n /** Who sent it: an email, else a uid. */\n by: string\n /** The `Message-ID` the email was sent with. */\n messageId: string\n /** The mail provider's own id for the message, when it answered one. */\n providerId: string | null\n /** The pay link version the email carried. */\n linkVersion: number\n}\n\n/** One shipping rate a draft order offered (AGL-4099), as checkout's resolved rates name it. */\nexport interface OrderDraftShippingRate {\n rateId: string\n name: string\n amountCents: number\n}\n\n/** The shipping a draft order offered when it was made (AGL-4099); see `HostOrder.draftShipping`. */\nexport interface OrderDraftShipping {\n /** Where its rates ship; empty when it offers none. */\n countries: string[]\n /** The rates the buyer picks from at payment; empty when it ships nothing. */\n options: OrderDraftShippingRate[]\n}\n\n/** The lasting pay link of an order that is not a quote (AGL-4099); see `HostOrder.payLink`. */\nexport interface OrderPayLink {\n /** The link's version, absent reading as 1. */\n version?: number\n /** Payments through the link the order could not take, as a quote records them. */\n unmatchedPayments?: QuoteUnmatchedPayment[]\n}\n\n/** One time an order's (or a quote's) invoice was emailed to the customer (AGL-4099). */\nexport interface OrderInvoiceSendRecord {\n to: string\n subject: string\n atMs: number\n /** Who sent it: an email, else a uid. */\n by: string\n /** The `Message-ID` the email was sent with. */\n messageId: string\n /** The mail provider's own id for the message, when it answered one. */\n providerId: string | null\n /** The invoice said it was paid (the record), rather than asking for payment. */\n paid: boolean\n /** The pay link version an unpaid invoice carried; null on a paid one. */\n linkVersion: number | null\n /** The total the invoice showed. */\n totalCents: number\n}\n\n/** What only a quote has, on its order document. */\nexport interface OrderQuote {\n stage: QuoteStage\n /** Which version of the quote this is; versions are what proofs and approvals name. */\n revision: number\n customer: QuoteCustomer\n billTo?: OrderAddress | null\n shipTo?: OrderAddress | null\n notes?: string\n terms?: string\n discount?: QuoteDiscount | null\n shipping?: QuoteShipping | null\n tax: QuoteTaxBasis\n /** The customer approves the current revision's proof before they can pay. Absent reads as off. */\n requireProofApproval?: boolean\n /** The current revision's proofs; earlier revisions keep theirs in `revisions/{n}`. */\n proofs?: QuoteProof[]\n /** The customer's answers to the current revision, oldest first; the last one counts. */\n responses?: QuoteCustomerResponse[]\n /** The current revision's approval, while its latest answer is one. */\n approval?: QuoteApproval | null\n /** The revision the customer approved, when they have. */\n approvedRevision?: number | null\n /**\n * Which pay link opens the quote: a link signed for an older version shows\n * the customer that the quote was replaced, and nothing of it. Absent reads\n * as 1. Bumped when the business replaces the link.\n */\n linkVersion?: number\n /** The conversation its emails keep in the customer's mailbox (AGL-4097). */\n emailThread?: QuoteEmailThread | null\n /** Every time it was emailed to the customer, oldest first (AGL-4097). */\n sends?: QuoteSendRecord[]\n /**\n * The request it was started from (AGL-4098): a \"Request a quote\" form's\n * submission, by id. Its files are listed on the quote as the customer's\n * ({@link requestFiles}), and its confirmation's conversation is the\n * quote's {@link emailThread} until the quote is first sent.\n */\n request?: QuoteRequestRef | null\n /** The request's files, where the request keeps them; downloaded through the quote's own route. */\n requestFiles?: QuoteResponseFile[]\n /** When the customer paid it. */\n paidAtMs?: number\n /** Payments the quote could not take; see {@link QuoteUnmatchedPayment}. */\n unmatchedPayments?: QuoteUnmatchedPayment[]\n createdAtMs: number\n updatedAtMs: number\n /** Who created and last changed it: an email, else a uid. */\n createdBy: string\n updatedBy: string\n}\n\n/** The store-wide quote defaults, at `hosts/{hostId}/settings/store` under `quotes`. */\nexport interface QuoteStoreSettings {\n /** What `requireProofApproval` a new quote starts with. Absent reads as off. */\n requireProofApproval?: boolean\n}\n\n/** Whether new quotes start out asking the customer to approve the proof first. */\nexport function quoteRequiresProofApprovalByDefault(\n settings: QuoteStoreSettings | null | undefined,\n): boolean {\n return settings?.requireProofApproval === true\n}\n\n/**\n * Whether paying waits on the customer approving the proof: the quote asks for\n * approval, its current revision has a proof, and that revision is not the\n * one the customer approved. A revision with no proof never holds a payment.\n */\nexport function quotePaymentBlockedByApproval(\n quote:\n | (Pick<OrderQuote, 'requireProofApproval' | 'revision' | 'approvedRevision'> & {\n proofs?: readonly QuoteProofRef[]\n })\n | null\n | undefined,\n): boolean {\n if (!quote || quote.requireProofApproval !== true) return false\n const revision = Number(quote.revision ?? 1)\n const proofed = (quote.proofs ?? []).some((proof) => Number(proof?.revision) === revision)\n if (!proofed) return false\n return Number(quote.approvedRevision) !== revision\n}\n\n/** Whether an order document is a quote, paid or not. */\nexport function isQuote(order: Pick<Partial<HostOrder>, 'channel'> | null | undefined): boolean {\n return order?.channel === 'quote'\n}\n\n/** Whether an order document is a quote nobody has paid yet, which no order list counts. */\nexport function isUnpaidQuote(\n order: { channel?: unknown; status?: unknown } | null | undefined,\n): boolean {\n return order?.channel === 'quote' && order?.status === 'pending'\n}\n\n/** `Quote #1042`; an order number until it is paid. */\nexport function formatQuoteNumber(order: Pick<HostOrder, 'number'>, docId?: string): string {\n if (order.number != null) return `Quote #${order.number}`\n return docId ? `Quote #${docId.slice(-6).toUpperCase()}` : 'Quote'\n}\n\n/** A quote's status as its list shows it: its stage until it is paid. */\nexport function quoteDisplayStatus(order: {\n status?: OrderStatus | string\n quote?: Pick<OrderQuote, 'stage'> | null\n}): { key: QuoteStage | 'paid' | 'cancelled' | 'refunded'; label: string } {\n if (order.status === 'cancelled') return { key: 'cancelled', label: 'Canceled' }\n if (order.status === 'refunded') return { key: 'refunded', label: 'Refunded' }\n if (order.status && order.status !== 'pending') return { key: 'paid', label: 'Paid' }\n const stage = (order.quote?.stage ?? 'draft') as QuoteStage\n return { key: stage, label: QUOTE_STAGE_LABELS[stage] ?? QUOTE_STAGE_LABELS.draft }\n}\n\n/** Chip colors for {@link quoteDisplayStatus}, as the orders list colors its statuses. */\nexport const QUOTE_STATUS_COLOR: Record<\n ReturnType<typeof quoteDisplayStatus>['key'],\n 'default' | 'success' | 'info' | 'warning' | 'error'\n> = {\n draft: 'default',\n sent: 'info',\n changes_requested: 'warning',\n approved: 'info',\n paid: 'success',\n cancelled: 'default',\n refunded: 'error',\n}\n\n/*==========================================\n * TOTALS\n *=========================================*/\n\n/** A line as the totals read it. */\nexport type QuoteTotalsLine = Pick<OrderLineItem, 'unitAmountCents' | 'quantity'> & {\n included?: boolean\n taxExempt?: boolean\n}\n\nexport interface QuoteTotalsInput {\n lines: readonly QuoteTotalsLine[]\n discount?: QuoteDiscount | null\n shippingCents?: number\n tax: QuoteTaxBasis\n}\n\n/** A quote's totals: the order's, plus what the tax was computed on. */\nexport interface QuoteTotals extends OrderTotals {\n /** The taxable lines after their share of the discount. */\n taxableCents: number\n /** Stripe Tax works the tax out when the customer pays; `taxCents` is 0 until then. */\n taxAtPayment: boolean\n}\n\nconst whole = (value: unknown): number => {\n const number = Math.round(Number(value))\n return Number.isFinite(number) && number > 0 ? number : 0\n}\n\n/** One line's charge: nothing for an included line. */\nexport function quoteLineCents(line: QuoteTotalsLine): number {\n if (line.included) return 0\n return whole(line.unitAmountCents) * whole(line.quantity)\n}\n\n/** The discount in cents on `itemsCents`, never more than it. */\nexport function quoteDiscountCents(\n itemsCents: number,\n discount: QuoteDiscount | null | undefined,\n): number {\n const items = whole(itemsCents)\n if (!discount) return 0\n if (discount.kind === 'amount') return Math.min(items, whole(discount.amountCents))\n if (discount.kind !== 'percent') return 0\n const pct = Math.min(100, Math.max(0, Number(discount.pct) || 0))\n return Math.min(items, Math.round((items * pct) / 100))\n}\n\n/**\n * Each line's charge, its share of the discount (in proportion to the\n * charges, as a checkout spreads a coupon) and what is left: the amounts a tax\n * service is asked about, line by line.\n */\nexport function quoteLineAmounts(\n lines: readonly QuoteTotalsLine[],\n discount: QuoteDiscount | null | undefined,\n): { grossCents: number[]; discountCents: number[]; netCents: number[]; itemsCents: number; totalDiscountCents: number } {\n const grossCents = lines.map(quoteLineCents)\n const itemsCents = grossCents.reduce((sum, cents) => sum + cents, 0)\n const totalDiscountCents = quoteDiscountCents(itemsCents, discount)\n const discountCents = allocateCentsByWeight(totalDiscountCents, grossCents)\n return {\n grossCents,\n discountCents,\n netCents: grossCents.map((cents, index) => cents - discountCents[index]),\n itemsCents,\n totalDiscountCents,\n }\n}\n\n/**\n * A quote's totals. The discount comes off before tax; a store rate taxes the\n * taxable lines (not shipping, as a draft order's rate does); a tax service's\n * answer is taken as given; Stripe Tax and no-tax add nothing now.\n */\nexport function computeQuoteTotals(input: QuoteTotalsInput): QuoteTotals {\n const amounts = quoteLineAmounts(input.lines, input.discount)\n const taxableCents = amounts.netCents.reduce(\n (sum, cents, index) => sum + (input.lines[index]?.taxExempt ? 0 : cents),\n 0,\n )\n const shippingCents = whole(input.shippingCents)\n const tax = input.tax\n const taxCents =\n tax.kind === 'rate'\n ? computeTaxCents(taxableCents, Number(tax.pct) || 0)\n : tax.kind === 'service'\n ? whole(tax.taxCents)\n : 0\n return {\n itemsCents: amounts.itemsCents,\n discountCents: amounts.totalDiscountCents,\n shippingCents,\n taxCents,\n // The platform fee is set when a payment is created, from what it charges.\n feeCents: 0,\n totalCents: amounts.itemsCents - amounts.totalDiscountCents + shippingCents + taxCents,\n taxableCents,\n taxAtPayment: tax.kind === 'at-payment',\n }\n}\n\n/** The order totals a quote document stores. */\nexport function quoteOrderTotals(totals: QuoteTotals): OrderTotals {\n return {\n itemsCents: totals.itemsCents,\n discountCents: totals.discountCents,\n shippingCents: totals.shippingCents,\n taxCents: totals.taxCents,\n feeCents: totals.feeCents,\n totalCents: totals.totalCents,\n }\n}\n\n/*==========================================\n * TAX\n *=========================================*/\n\n/**\n * The tax basis the store's own settings give a quote, before any tax service\n * is asked: Stripe Tax at payment, no tax (and why), or the store's rate for\n * where the quote ships — its ship-to when it has a country, else the store's\n * own address, as the storefront taxes a sale it cannot see the destination of.\n */\nexport function quoteTaxBasisFromSettings(\n settings: TaxSettings | null | undefined,\n shipTo: Pick<OrderAddress, 'country' | 'state'> | null | undefined,\n): QuoteTaxBasis {\n const decision = storefrontTaxDecision({ settings })\n if (decision.kind === 'undecided') return { kind: 'none', reason: 'undecided' }\n if (decision.kind === 'stripe-automatic') return { kind: 'at-payment' }\n if (decision.kind !== 'manual') return { kind: 'none', reason: 'not-collected' }\n if (settings?.pricesIncludeTax) return { kind: 'none', reason: 'prices-include-tax' }\n const address = String(shipTo?.country ?? '').trim()\n ? { country: shipTo?.country, state: shipTo?.state }\n : (settings?.origin ?? {})\n const rate = resolveTaxRate(settings ?? undefined, address)\n if (!rate || !(Number(rate.pct) > 0)) return { kind: 'none', reason: 'no-rate' }\n return { kind: 'rate', pct: Number(rate.pct), ...(rate.label ? { label: rate.label } : {}) }\n}\n\n/** The tax line's label on a quote. */\nexport function describeQuoteTax(basis: QuoteTaxBasis | null | undefined): string {\n if (!basis) return 'Tax'\n switch (basis.kind) {\n case 'rate':\n return basis.label ? `${basis.label} (${basis.pct ?? 0}%)` : `Tax (${basis.pct ?? 0}%)`\n case 'service':\n return basis.providerLabel ? `Tax (${basis.providerLabel})` : 'Tax'\n case 'at-payment':\n return 'Tax is added at payment'\n case 'none':\n return basis.reason === 'prices-include-tax'\n ? 'Prices include tax'\n : basis.reason === 'no-rate'\n ? 'No tax rate for this address'\n : basis.reason === 'undecided'\n ? 'Sales tax is not set up'\n : 'No sales tax'\n default:\n return 'Tax'\n }\n}\n\n/** The order's `taxMode` for a quote's basis; absent while nobody has decided. */\nexport function quoteTaxMode(basis: QuoteTaxBasis, taxCents: number): StorefrontTaxMode | undefined {\n if (basis.kind === 'at-payment') return 'stripe-automatic'\n if (basis.kind === 'none' && basis.reason === 'undecided') return undefined\n return taxCents > 0 ? 'manual' : 'none'\n}\n\n/*==========================================\n * WHAT THE CONSOLE SENDS\n *=========================================*/\n\nexport const QUOTE_MAX_LINES = 100\nexport const QUOTE_MAX_QUANTITY = 9_999\n/** $1,000,000 a unit: the most a typed line may cost. */\nexport const QUOTE_MAX_UNIT_CENTS = 100_000_000\nexport const QUOTE_MAX_TEXT = 5_000\nconst SHORT_TEXT = 200\n\n/** A product line as the console asks for it: what it is, never what it costs. */\nexport interface QuoteCatalogLineRequest {\n kind: 'catalog'\n productId: string\n variantId?: string\n quantity: number\n modifiers?: ModifierSelection[]\n included?: boolean\n description?: string\n}\n\n/** A line typed on the quote, which no product stands behind. */\nexport interface QuoteCustomLineRequest {\n kind: 'custom'\n name: string\n description?: string\n sku?: string\n unitAmountCents: number\n quantity: number\n /** Whether it is something to ship (else a service, which never ships). */\n ships?: boolean\n included?: boolean\n}\n\nexport type QuoteLineRequest = QuoteCatalogLineRequest | QuoteCustomLineRequest\n\n/** Everything a business edits on a quote. */\nexport interface QuoteInput {\n lines: QuoteLineRequest[]\n customer: QuoteCustomer\n billTo: OrderAddress | null\n shipTo: OrderAddress | null\n notes: string\n terms: string\n discount: QuoteDiscount | null\n shipping: QuoteShipping | null\n /** Absent: the store's default (on create) or unchanged (on update). */\n requireProofApproval?: boolean\n}\n\nconst EMAIL = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\nconst DOC_ID = /^(?!__.*__$)[^/]{1,200}$/\n\nconst clean = (value: unknown, max = SHORT_TEXT): string =>\n typeof value === 'string' ? value.trim().slice(0, max) : ''\n\nconst intIn = (value: unknown, min: number, max: number): number | null => {\n const number = Number(value)\n return Number.isInteger(number) && number >= min && number <= max ? number : null\n}\n\nfunction parseAddress(raw: unknown): OrderAddress | null {\n if (!raw || typeof raw !== 'object') return null\n const source = raw as Record<string, unknown>\n const address: OrderAddress = {}\n for (const key of ['name', 'line1', 'line2', 'city', 'state', 'postalCode', 'phone'] as const) {\n const value = clean(source[key])\n if (value) address[key] = value\n }\n const country = clean(source['country'], 2).toUpperCase()\n if (/^[A-Z]{2}$/.test(country)) address.country = country\n return Object.keys(address).length ? address : null\n}\n\nfunction parseLine(raw: unknown, index: number): QuoteLineRequest | string {\n const source = (raw && typeof raw === 'object' ? raw : {}) as Record<string, unknown>\n const at = `Line ${index + 1}`\n const quantity = intIn(source['quantity'], 1, QUOTE_MAX_QUANTITY)\n if (quantity === null) return `${at}: enter a quantity from 1 to ${QUOTE_MAX_QUANTITY.toLocaleString('en-US')}.`\n const included = source['included'] === true\n const description = clean(source['description'], QUOTE_MAX_TEXT)\n if (source['kind'] === 'catalog') {\n const productId = clean(source['productId'])\n if (!DOC_ID.test(productId)) return `${at}: choose a product.`\n const variantId = clean(source['variantId'])\n const modifiers = Array.isArray(source['modifiers'])\n ? (source['modifiers'] as unknown[]).slice(0, 200).map((entry) => {\n const pick = (entry ?? {}) as Record<string, unknown>\n return { groupId: clean(pick['groupId']), optionId: clean(pick['optionId']) }\n })\n : []\n return {\n kind: 'catalog',\n productId,\n ...(variantId ? { variantId } : {}),\n quantity,\n ...(modifiers.length ? { modifiers } : {}),\n ...(included ? { included } : {}),\n ...(description ? { description } : {}),\n }\n }\n if (source['kind'] === 'custom') {\n const name = clean(source['name'])\n if (!name) return `${at}: name the item.`\n const unitAmountCents = included ? 0 : intIn(source['unitAmountCents'], 0, QUOTE_MAX_UNIT_CENTS)\n if (unitAmountCents === null) return `${at}: enter a price from $0 to $1,000,000.`\n const sku = clean(source['sku'], 100)\n return {\n kind: 'custom',\n name,\n ...(description ? { description } : {}),\n ...(sku ? { sku } : {}),\n unitAmountCents,\n quantity,\n ships: source['ships'] !== false,\n ...(included ? { included } : {}),\n }\n }\n return `${at}: add a product or type an item.`\n}\n\n/**\n * The console's quote, checked and trimmed, or what to fix. Prices on product\n * lines are not read from here: the server prices them from the product.\n */\nexport function parseQuoteInput(raw: unknown): { ok: true; input: QuoteInput } | { ok: false; error: string } {\n const source = (raw && typeof raw === 'object' ? raw : {}) as Record<string, unknown>\n const rawLines = Array.isArray(source['lines']) ? (source['lines'] as unknown[]) : []\n if (rawLines.length === 0) return { ok: false, error: 'Add at least one line to the quote.' }\n if (rawLines.length > QUOTE_MAX_LINES) {\n return { ok: false, error: `A quote holds up to ${QUOTE_MAX_LINES} lines.` }\n }\n const lines: QuoteLineRequest[] = []\n for (const [index, entry] of rawLines.entries()) {\n const line = parseLine(entry, index)\n if (typeof line === 'string') return { ok: false, error: line }\n lines.push(line)\n }\n\n const customerSource = (source['customer'] && typeof source['customer'] === 'object'\n ? source['customer']\n : {}) as Record<string, unknown>\n const email = clean(customerSource['email'])\n if (email && !EMAIL.test(email)) return { ok: false, error: 'Enter a valid email for the customer.' }\n const recordSource = (customerSource['record'] ?? null) as Record<string, unknown> | null\n const recordKind = clean(recordSource?.['kind'], 40)\n const recordId = clean(recordSource?.['id'])\n const customer: QuoteCustomer = {\n ...(recordKind && DOC_ID.test(recordId) ? { record: { kind: recordKind, id: recordId } } : {}),\n ...(clean(customerSource['name']) ? { name: clean(customerSource['name']) } : {}),\n ...(clean(customerSource['company']) ? { company: clean(customerSource['company']) } : {}),\n ...(email ? { email, emailLower: email.toLowerCase() } : {}),\n ...(clean(customerSource['phone'], 40) ? { phone: clean(customerSource['phone'], 40) } : {}),\n }\n\n let discount: QuoteDiscount | null = null\n const discountSource = (source['discount'] ?? null) as Record<string, unknown> | null\n if (discountSource?.['kind'] === 'amount') {\n const amountCents = intIn(discountSource['amountCents'], 0, QUOTE_MAX_UNIT_CENTS)\n if (amountCents === null) return { ok: false, error: 'Enter a discount from $0 to $1,000,000.' }\n if (amountCents > 0) discount = { kind: 'amount', amountCents }\n } else if (discountSource?.['kind'] === 'percent') {\n const pct = Number(discountSource['pct'])\n if (!Number.isFinite(pct) || pct < 0 || pct > 100) {\n return { ok: false, error: 'Enter a discount from 0% to 100%.' }\n }\n if (pct > 0) discount = { kind: 'percent', pct: Math.round(pct * 100) / 100 }\n }\n\n let shipping: QuoteShipping | null = null\n const shippingSource = (source['shipping'] ?? null) as Record<string, unknown> | null\n if (shippingSource) {\n const amountCents = intIn(shippingSource['amountCents'], 0, QUOTE_MAX_UNIT_CENTS)\n if (amountCents === null) return { ok: false, error: 'Enter shipping from $0 to $1,000,000.' }\n shipping = { amountCents }\n }\n\n return {\n ok: true,\n input: {\n lines,\n customer,\n billTo: parseAddress(source['billTo']),\n shipTo: parseAddress(source['shipTo']),\n notes: clean(source['notes'], QUOTE_MAX_TEXT),\n terms: clean(source['terms'], QUOTE_MAX_TEXT),\n discount,\n shipping,\n ...(typeof source['requireProofApproval'] === 'boolean'\n ? { requireProofApproval: source['requireProofApproval'] }\n : {}),\n },\n }\n}\n\n/** A priced quote line, and whether its product is tax-exempt. */\nexport type PricedQuoteLine = OrderLineItem & { taxExempt?: boolean }\n\n/** The variant's label as order lines print it: its option values, or nothing for a default. */\nfunction variantOptionsLabel(options: Record<string, string> | undefined): string | undefined {\n return options && Object.keys(options).length ? Object.values(options).join(' / ') : undefined\n}\n\n/**\n * A product line priced from the PRODUCT: its variant's price plus the chosen\n * add-ons, as the register prices one. An included line keeps what it is and\n * charges nothing.\n */\nexport function priceQuoteCatalogLine(\n rawProduct: Partial<HostProduct> | null | undefined,\n request: QuoteCatalogLineRequest,\n): { ok: true; line: PricedQuoteLine } | { ok: false; error: string } {\n if (!rawProduct || rawProduct.deletedAt) {\n return { ok: false, error: 'A product on this quote no longer exists. Remove it and add it again.' }\n }\n const product = liftLegacyProduct(rawProduct)\n const variant = request.variantId\n ? product.variants.find((entry) => entry.id === request.variantId)\n : product.variants[0]\n if (!variant) return { ok: false, error: `Choose an option of ${product.name} that still exists.` }\n if (!request.included && !variantHasPrice(variant)) {\n return { ok: false, error: `Set a price for ${product.name} before quoting it.` }\n }\n const chosen = resolveLineModifiers(product, request.modifiers)\n if (!chosen.ok) return { ok: false, error: chosen.error ?? `Check the choices on ${product.name}.` }\n const variantLabel = lineLabelWithModifiers(variantOptionsLabel(variant.options), chosen.modifiers)\n return {\n ok: true,\n line: {\n productId: request.productId,\n ...(variant.id !== 'default' ? { variantId: variant.id } : {}),\n name: product.name,\n ...(variantLabel ? { variantLabel } : {}),\n ...(variant.sku ? { sku: variant.sku } : {}),\n productType: (product.type ?? 'physical') as ProductType,\n quantity: request.quantity,\n unitAmountCents: request.included\n ? 0\n : Math.round(Number(variant.priceUsd) * 100) + chosen.extraCents,\n ...(chosen.modifiers.length ? { modifiers: chosen.modifiers } : {}),\n ...(request.included ? { included: true } : {}),\n ...(request.description ? { description: request.description } : {}),\n ...(product.taxExempt ? { taxExempt: true } : {}),\n },\n }\n}\n\n/** A typed line: no product, so nothing reads stock, a supplier or a download for it. */\nexport function priceQuoteCustomLine(request: QuoteCustomLineRequest): PricedQuoteLine {\n return {\n productId: '',\n custom: true,\n name: request.name,\n ...(request.sku ? { sku: request.sku } : {}),\n productType: request.ships === false ? 'service' : 'physical',\n quantity: request.quantity,\n unitAmountCents: request.included ? 0 : request.unitAmountCents,\n ...(request.included ? { included: true } : {}),\n ...(request.description ? { description: request.description } : {}),\n }\n}\n\n/** The request a stored line is edited as: what the console sends back on a save. */\nexport function quoteLineRequestFromLine(line: OrderLineItem): QuoteLineRequest {\n if (line.custom || !line.productId) {\n return {\n kind: 'custom',\n name: line.name,\n ...(line.description ? { description: line.description } : {}),\n ...(line.sku ? { sku: line.sku } : {}),\n unitAmountCents: line.unitAmountCents,\n quantity: line.quantity,\n ships: line.productType !== 'service' && line.productType !== 'digital',\n ...(line.included ? { included: true } : {}),\n }\n }\n return {\n kind: 'catalog',\n productId: line.productId,\n ...(line.variantId ? { variantId: line.variantId } : {}),\n quantity: line.quantity,\n ...(line.modifiers?.length\n ? { modifiers: line.modifiers.map(({ groupId, optionId }) => ({ groupId, optionId })) }\n : {}),\n ...(line.included ? { included: true } : {}),\n ...(line.description ? { description: line.description } : {}),\n }\n}\n"],"names":["liftLegacyProduct","variantHasPrice","computeTaxCents","resolveTaxRate","storefrontTaxDecision","allocateCentsByWeight","lineLabelWithModifiers","resolveLineModifiers","QUOTE_STAGE_LABELS","draft","sent","changes_requested","approved","quoteRequiresProofApprovalByDefault","settings","requireProofApproval","quotePaymentBlockedByApproval","quote","revision","Number","proofed","proofs","some","proof","approvedRevision","isQuote","order","channel","isUnpaidQuote","status","formatQuoteNumber","docId","number","slice","toUpperCase","quoteDisplayStatus","key","label","stage","QUOTE_STATUS_COLOR","paid","cancelled","refunded","whole","value","Math","round","isFinite","quoteLineCents","line","included","unitAmountCents","quantity","quoteDiscountCents","itemsCents","discount","items","kind","min","amountCents","pct","max","quoteLineAmounts","lines","grossCents","map","reduce","sum","cents","totalDiscountCents","discountCents","netCents","index","computeQuoteTotals","input","amounts","taxableCents","taxExempt","shippingCents","tax","taxCents","feeCents","totalCents","taxAtPayment","quoteOrderTotals","totals","quoteTaxBasisFromSettings","shipTo","decision","reason","pricesIncludeTax","address","String","country","trim","state","origin","rate","undefined","describeQuoteTax","basis","providerLabel","quoteTaxMode","QUOTE_MAX_LINES","QUOTE_MAX_QUANTITY","QUOTE_MAX_UNIT_CENTS","QUOTE_MAX_TEXT","SHORT_TEXT","EMAIL","DOC_ID","clean","intIn","isInteger","parseAddress","raw","source","test","Object","keys","length","parseLine","at","toLocaleString","description","productId","variantId","modifiers","Array","isArray","entry","pick","groupId","optionId","name","sku","ships","parseQuoteInput","customerSource","rawLines","ok","error","entries","push","email","recordSource","recordKind","recordId","customer","record","id","company","emailLower","toLowerCase","phone","discountSource","shipping","shippingSource","billTo","notes","terms","variantOptionsLabel","options","values","join","priceQuoteCatalogLine","rawProduct","request","chosen","product","deletedAt","variant","variants","find","variantLabel","productType","type","priceUsd","extraCents","priceQuoteCustomLine","custom","quoteLineRequestFromLine"],"mappings":"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"}
@@ -32,6 +32,10 @@ export * from './commerce-gift-cards';
32
32
  export * from './commerce-orders';
33
33
  export * from './commerce-quotes';
34
34
  export * from './commerce-quote-pay';
35
+ export * from './commerce-quote-proofs';
36
+ export * from './commerce-quote-email';
37
+ export * from './commerce-quote-request';
38
+ export * from './commerce-invoices';
35
39
  export * from './commerce-printers';
36
40
  export * from './commerce-pos';
37
41
  export * from './commerce-pos-offline';
@@ -30,6 +30,10 @@ export * from "./commerce-gift-cards.js";
30
30
  export * from "./commerce-orders.js";
31
31
  export * from "./commerce-quotes.js";
32
32
  export * from "./commerce-quote-pay.js";
33
+ export * from "./commerce-quote-proofs.js";
34
+ export * from "./commerce-quote-email.js";
35
+ export * from "./commerce-quote-request.js";
36
+ export * from "./commerce-invoices.js";
33
37
  export * from "./commerce-printers.js";
34
38
  export * from "./commerce-pos.js";
35
39
  export * from "./commerce-pos-offline.js";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/index.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Commerce domain model (AGL-411): pure types + logic relocated from the\n * core `@aglyn/aglyn` app-utils — the commerce plugin owns its domain.\n * Context-free and framework-free, so both the plugin's client components\n * and its `/server` handlers (and other plugins/apps via\n * `@aglyn/plugins-commerce/model`) can import it.\n */\nexport * from './commerce'\nexport * from './commerce-cart'\nexport * from './commerce-check-in'\nexport * from './commerce-connect-transfer'\nexport * from './commerce-discounts'\nexport * from './commerce-dispute'\nexport * from './commerce-topics'\nexport * from './commerce-gift-cards'\nexport * from './commerce-orders'\nexport * from './commerce-quotes'\nexport * from './commerce-quote-pay'\nexport * from './commerce-printers'\nexport * from './commerce-pos'\nexport * from './commerce-pos-offline'\nexport * from './commerce-pos-kiosk'\nexport * from './product-modifiers'\nexport * from './commerce-promotions'\nexport * from './commerce-receipt'\nexport * from './commerce-reservations'\nexport * from './commerce-shipping'\nexport * from './commerce-stock-holds'\nexport * from './commerce-subscription-status'\nexport * from './commerce-tax'\nexport * from './commerce-tax-decision'\nexport * from './order-figures'\nexport * from './order-list-fields'\nexport * from './purchase-analytics'\nexport * from './tracking-url'\nexport * from './buyer-notifications'\nexport * from './order-fulfillment'\nexport * from './order-local-fulfillment'\nexport * from './local-fulfillment-settings'\nexport * from './order-events'\nexport * from './commerce-returns'\nexport * from './order-shipping-export'\nexport * from './order-status-view'\nexport * from './order-webhooks'\nexport * from './shipstation'\nexport * from './commerce-payment-methods'\nexport * from './product-channel'\nexport * from './order-annotations'\nexport * from './product-write'\nexport * from './product-reviews'\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;CAMC,GACD,cAAc,gBAAY;AAC1B,cAAc,qBAAiB;AAC/B,cAAc,yBAAqB;AACnC,cAAc,iCAA6B;AAC3C,cAAc,0BAAsB;AACpC,cAAc,wBAAoB;AAClC,cAAc,uBAAmB;AACjC,cAAc,2BAAuB;AACrC,cAAc,uBAAmB;AACjC,cAAc,uBAAmB;AACjC,cAAc,0BAAsB;AACpC,cAAc,yBAAqB;AACnC,cAAc,oBAAgB;AAC9B,cAAc,4BAAwB;AACtC,cAAc,0BAAsB;AACpC,cAAc,yBAAqB;AACnC,cAAc,2BAAuB;AACrC,cAAc,wBAAoB;AAClC,cAAc,6BAAyB;AACvC,cAAc,yBAAqB;AACnC,cAAc,4BAAwB;AACtC,cAAc,oCAAgC;AAC9C,cAAc,oBAAgB;AAC9B,cAAc,6BAAyB;AACvC,cAAc,qBAAiB;AAC/B,cAAc,yBAAqB;AACnC,cAAc,0BAAsB;AACpC,cAAc,oBAAgB;AAC9B,cAAc,2BAAuB;AACrC,cAAc,yBAAqB;AACnC,cAAc,+BAA2B;AACzC,cAAc,kCAA8B;AAC5C,cAAc,oBAAgB;AAC9B,cAAc,wBAAoB;AAClC,cAAc,6BAAyB;AACvC,cAAc,yBAAqB;AACnC,cAAc,sBAAkB;AAChC,cAAc,mBAAe;AAC7B,cAAc,gCAA4B;AAC1C,cAAc,uBAAmB;AACjC,cAAc,yBAAqB;AACnC,cAAc,qBAAiB;AAC/B,cAAc,uBAAmB"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/index.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Commerce domain model (AGL-411): pure types + logic relocated from the\n * core `@aglyn/aglyn` app-utils — the commerce plugin owns its domain.\n * Context-free and framework-free, so both the plugin's client components\n * and its `/server` handlers (and other plugins/apps via\n * `@aglyn/plugins-commerce/model`) can import it.\n */\nexport * from './commerce'\nexport * from './commerce-cart'\nexport * from './commerce-check-in'\nexport * from './commerce-connect-transfer'\nexport * from './commerce-discounts'\nexport * from './commerce-dispute'\nexport * from './commerce-topics'\nexport * from './commerce-gift-cards'\nexport * from './commerce-orders'\nexport * from './commerce-quotes'\nexport * from './commerce-quote-pay'\nexport * from './commerce-quote-proofs'\nexport * from './commerce-quote-email'\nexport * from './commerce-quote-request'\nexport * from './commerce-invoices'\nexport * from './commerce-printers'\nexport * from './commerce-pos'\nexport * from './commerce-pos-offline'\nexport * from './commerce-pos-kiosk'\nexport * from './product-modifiers'\nexport * from './commerce-promotions'\nexport * from './commerce-receipt'\nexport * from './commerce-reservations'\nexport * from './commerce-shipping'\nexport * from './commerce-stock-holds'\nexport * from './commerce-subscription-status'\nexport * from './commerce-tax'\nexport * from './commerce-tax-decision'\nexport * from './order-figures'\nexport * from './order-list-fields'\nexport * from './purchase-analytics'\nexport * from './tracking-url'\nexport * from './buyer-notifications'\nexport * from './order-fulfillment'\nexport * from './order-local-fulfillment'\nexport * from './local-fulfillment-settings'\nexport * from './order-events'\nexport * from './commerce-returns'\nexport * from './order-shipping-export'\nexport * from './order-status-view'\nexport * from './order-webhooks'\nexport * from './shipstation'\nexport * from './commerce-payment-methods'\nexport * from './product-channel'\nexport * from './order-annotations'\nexport * from './product-write'\nexport * from './product-reviews'\n"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;CAMC,GACD,cAAc,gBAAY;AAC1B,cAAc,qBAAiB;AAC/B,cAAc,yBAAqB;AACnC,cAAc,iCAA6B;AAC3C,cAAc,0BAAsB;AACpC,cAAc,wBAAoB;AAClC,cAAc,uBAAmB;AACjC,cAAc,2BAAuB;AACrC,cAAc,uBAAmB;AACjC,cAAc,uBAAmB;AACjC,cAAc,0BAAsB;AACpC,cAAc,6BAAyB;AACvC,cAAc,4BAAwB;AACtC,cAAc,8BAA0B;AACxC,cAAc,yBAAqB;AACnC,cAAc,yBAAqB;AACnC,cAAc,oBAAgB;AAC9B,cAAc,4BAAwB;AACtC,cAAc,0BAAsB;AACpC,cAAc,yBAAqB;AACnC,cAAc,2BAAuB;AACrC,cAAc,wBAAoB;AAClC,cAAc,6BAAyB;AACvC,cAAc,yBAAqB;AACnC,cAAc,4BAAwB;AACtC,cAAc,oCAAgC;AAC9C,cAAc,oBAAgB;AAC9B,cAAc,6BAAyB;AACvC,cAAc,qBAAiB;AAC/B,cAAc,yBAAqB;AACnC,cAAc,0BAAsB;AACpC,cAAc,oBAAgB;AAC9B,cAAc,2BAAuB;AACrC,cAAc,yBAAqB;AACnC,cAAc,+BAA2B;AACzC,cAAc,kCAA8B;AAC5C,cAAc,oBAAgB;AAC9B,cAAc,wBAAoB;AAClC,cAAc,6BAAyB;AACvC,cAAc,yBAAqB;AACnC,cAAc,sBAAkB;AAChC,cAAc,mBAAe;AAC7B,cAAc,gCAA4B;AAC1C,cAAc,uBAAmB;AACjC,cAAc,yBAAqB;AACnC,cAAc,qBAAiB;AAC/B,cAAc,uBAAmB"}
package/src/lib/plugin.js CHANGED
@@ -40,6 +40,10 @@ import { registerCommerceTransferUi } from "./transfer/register-transfer-ui.js";
40
40
  import { COMMERCE_SEARCH_SOURCES } from "./model/commerce-search-sources.js";
41
41
  import { COMMERCE_CONFIG_SCHEMA } from "./plugin-config.js";
42
42
  /** Code-split: the Products console page only loads when opened. */ const CommerceConsolePage = lazy(()=>import("./components/commerce-console-page.js"));
43
+ /** "Start a quote" on a quote request in the Inbox and on a contact in the CRM (AGL-4098). */ const QuoteStartFromRequest = lazy(()=>import("./components/console/quote-start.component.js"));
44
+ const QuoteStartFromContact = lazy(()=>import("./components/console/quote-start.component.js").then((module)=>({
45
+ default: module.QuoteStartFromContact
46
+ })));
43
47
  /**
44
48
  * Console half (AGL-395): registers the Products nav item in the
45
49
  * ConsoleExtension registry so the commerce console surface is owned by the
@@ -155,6 +159,21 @@ import { COMMERCE_CONFIG_SCHEMA } from "./plugin-config.js";
155
159
  widgetId: 'commerce-site-member-purchases',
156
160
  title: 'Purchases',
157
161
  Component: SiteMemberPurchases
162
+ },
163
+ // Start a quote from a quote request, among an open submission's
164
+ // actions in the Inbox, and from a contact, in its header in the CRM
165
+ // (AGL-4098). Each zone is its host's; neither host imports this plugin.
166
+ {
167
+ slot: 'inboxSubmissionActions',
168
+ widgetId: 'commerce-quote-from-request',
169
+ title: 'Start a quote',
170
+ Component: QuoteStartFromRequest
171
+ },
172
+ {
173
+ slot: 'crmRecordActions',
174
+ widgetId: 'commerce-quote-from-contact',
175
+ title: 'Start a quote',
176
+ Component: QuoteStartFromContact
158
177
  }
159
178
  ],
160
179
  navItems: [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/plugin.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 * as Aglyn from '@aglyn/aglyn'\nimport { mdiStorefrontOutline } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nconst PosConsolePage = lazy(() => import('./components/console/pos-page.component'))\n/** The customer display (AGL-3608): loads only on the public page that shows it. */\nconst PosDisplayPage = lazy(() => import('./components/console/pos-display-page.component'))\nconst PosKioskPage = lazy(() => import('./components/console/pos-kiosk-page.component'))\nconst PosHardwareTermsPage = lazy(() => import('./components/console/pos-hardware-terms-page.component'))\nconst PosHardwareTermsSignPage = lazy(() => import('./components/console/pos-hardware-terms-sign-page.component'))\n/** Staff → Hardware orders (AGL-4038): loads only on its staff page. */\nconst PosHardwareOrdersStaffPage = lazy(\n () => import('./components/console/pos-hardware-orders-staff-page.component'),\n)\n/** Staff → Reader start-up screen (AGL-4048): loads only on its staff page. */\nconst PosReaderSplashStaffPage = lazy(\n () => import('./components/console/pos-reader-splash-staff-page.component'),\n)\nconst CommerceGlanceCard = lazy(\n () => import('./components/console/commerce-glance-card.component'),\n)\nconst NewestSiteUsersCard = lazy(\n () => import('./components/console/newest-site-users-card.component'),\n)\nconst SiteMemberPurchases = lazy(\n () => import('./components/console/site-member-purchases.component'),\n)\nimport {\n COMMERCE_CONSOLE_SECTIONS,\n COMMERCE_RETIRED_SECTIONS,\n} from './components/commerce-console-sections'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { COMMERCE_PERMISSIONS } from './model/plugin-permissions'\nimport { registerCommerceRecordLists } from './model/commerce-record-lists'\nimport { registerCommerceRecordRoutes } from './model/commerce-record-routes'\nimport { registerCommerceZones } from './components/console/product-zones'\nimport { registerCommerceOrderZones } from './components/console/order-zones'\nimport { registerCommerceReturnZones } from './components/console/return-zones'\nimport { registerCommerceStoreZones } from './components/console/store-zones'\nimport { registerCommercePosZones } from './components/console/pos-zones'\nimport { registerCommerceTransferUi } from './transfer/register-transfer-ui'\nimport { COMMERCE_SEARCH_SOURCES } from './model/commerce-search-sources'\nimport { COMMERCE_CONFIG_SCHEMA } from './plugin-config'\n\n/** Code-split: the Products console page only loads when opened. */\nconst CommerceConsolePage = lazy(() => import('./components/commerce-console-page'))\n\n/**\n * Console half (AGL-395): registers the Products nav item in the\n * ConsoleExtension registry so the commerce console surface is owned by the\n * plugin, like events and email. The page body still lives in the console\n * app (`/[hostId]/products`): its product editor depends on the app's media\n * browser, which transitively pulls in the org/session context, so a full\n * body relocation needs a plugin-consumable media-picker context first —\n * tracked as follow-up. The nav item omits a `Component`, so the shell's\n * nav renders the link and the existing named route serves the page.\n */\nexport function registerCommerceConsole(): void {\n // Where a product is read, for surfaces outside this plugin (AGL-3080).\n registerCommerceRecordRoutes()\n registerCommerceRecordLists()\n // The zones its product editor, products hub and CSV import dialog host,\n // with the props each hands a widget (AGL-2916, AGL-3080).\n registerCommerceZones()\n // …and the order dialog's two (AGL-3611), where a shipping plugin's widget\n // buys a label and records the shipment through the dialog's own route.\n registerCommerceOrderZones()\n // …and the return dialog's (AGL-3611), where a shipping plugin's widget\n // buys the buyer a return label and attaches it through this plugin's route.\n registerCommerceReturnZones()\n // The orders list's bulk actions and the store's Settings (AGL-3612).\n registerCommerceStoreZones()\n // The register's orders from other channels — a delivery app's (AGL-3644).\n registerCommercePosZones()\n // Its import and export resources' names and the products wizard's own\n // step (AGL-3531); the server halves register from the console's server\n // declarations.\n registerCommerceTransferUi()\n // Plugin-declared permissions (AGL-435): tier defaults ride every\n // resolved role set; custom roles override key-by-key.\n Aglyn.registerPluginPermissions(COMMERCE_PERMISSIONS)\n // Register discount ceiling (AGL-2161) — the generic settings card renders\n // it from this schema, so there is no bespoke UI to keep in sync.\n Aglyn.registerPluginConfigSchema(COMMERCE_CONFIG_SCHEMA)\n Aglyn.registerConsoleExtension({\n pluginId: BUNDLE_ID,\n displayName: 'Commerce',\n searchSources: COMMERCE_SEARCH_SOURCES,\n // The screen a register turns toward its customer (AGL-3608), at\n // `/kiosk/commerce/pos-display`. No staff session: the device proves\n // itself with the display token it got by pairing, and\n // `server/pos-display.ts` refuses every read without one.\n // Every workspace's orders from the POS hardware shop (AGL-4038), where\n // staff record Stripe's order id and the tracking, move each order on,\n // and cancel or refund one that has not shipped. The staff area loads\n // this registrar through commerce's `staff` surface.\n staffPages: [\n {\n id: 'hardware-orders',\n label: 'Hardware orders',\n header: { title: 'Hardware Orders', docsTopic: 'posHardware' },\n Component: PosHardwareOrdersStaffPage,\n },\n // The platform's default card reader start-up screen (AGL-4048), which\n // every store that has not picked its own logo or image shows.\n {\n id: 'reader-start-up-screen',\n label: 'Reader start-up screen',\n header: { title: 'Reader Start-up Screen', docsTopic: 'posHardware' },\n Component: PosReaderSplashStaffPage,\n },\n ],\n publicPages: [\n { path: '/pos-display', title: 'Customer display', Component: PosDisplayPage },\n // The self-service kiosk (AGL-3623), paired the same way with a kiosk\n // code; `server/pos-kiosk.ts` refuses every call without its token.\n { path: '/pos-kiosk', title: 'Self-service kiosk', Component: PosKioskPage },\n // The terms of sale for a card reader sold at the platform's register\n // (AGL-4038): on its customer display, by the display's own token\n // (`server/pos-display.ts`), and the printable booth sign, which holds\n // only the published terms' address and summary.\n { path: '/hardware-terms', title: 'Terms of sale', Component: PosHardwareTermsPage },\n { path: '/hardware-terms-sign', title: 'Terms of sale sign', Component: PosHardwareTermsSignPage },\n ],\n // Dashboard/analytics glance card (AGL-419): rendered through the\n // shell's 'commerceGlance' widget slot.\n widgets: [\n {\n slot: 'commerceGlance',\n widgetId: 'commerce-glance',\n title: 'Commerce',\n Component: CommerceGlanceCard,\n },\n // What a site user bought — lifetime total, orders and subscriptions —\n // in their drawer on the site's Users page: the orders and\n // subscriptions are this plugin's.\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.siteMember,\n widgetId: 'commerce-site-member-purchases',\n title: 'Purchases',\n Component: SiteMemberPurchases,\n },\n ],\n navItems: [\n {\n label: 'Products',\n href: '/products',\n icon: { path: mdiStorefrontOutline.path },\n // Sections as ROUTES (AGL-2501): `/products/orders` and friends are\n // real URLs the shell resolves and gates, so the page mounts the one\n // being read instead of subscribing all six.\n sections: COMMERCE_CONSOLE_SECTIONS,\n // `/products/settings` was split into a page per setting (AGL-3713);\n // links to it open the first of them.\n retiredSections: COMMERCE_RETIRED_SECTIONS,\n header: {\n title: 'Products',\n icon: { path: mdiStorefrontOutline.path },\n // The overview, not `catalog`: this surface is the whole commerce\n // console — Catalog, Orders, Promotions and Reservations.\n docsTopic: 'commerce',\n },\n Component: CommerceConsolePage,\n },\n {\n // POS register (AGL-312/419): relocated from the app's /pos route —\n // the generic [pluginSlug] shell now serves it at the same URL.\n label: 'POS',\n href: '/pos',\n /*\n * WHO may open the register, declared on THIS nav item rather than on\n * the extension.\n *\n * A key here narrows one surface; a key on the extension would gate\n * every surface it registers, and Products is the sibling — a catalog\n * anyone who works on the site may open. The register is the one that\n * takes money and lists checked-in guests by name, so the two\n * genuinely have different answers and only this one is narrowed.\n *\n * `managePos` rather than a dotted catalog key because it is the key\n * the sale itself is refused by: `server/pos-order.ts` resolves the\n * member's permission map and returns 403 unless `managePos` holds.\n * The shell answers this declaration out of that same resolved map, so\n * the console offers a register exactly to the people the route will\n * accept a sale from — the gate and its server twin cannot disagree,\n * because they are reading one value.\n *\n * `pos-order.ts` stays the enforcement point regardless. This decides\n * whether the surface is built at all, which is the part a check\n * inside the page cannot do: by the time the page can refuse itself it\n * has mounted and its product, register, location and reservation\n * listeners are already open.\n */\n permission: 'managePos',\n icon: { path: mdiStorefrontOutline.path },\n header: { title: 'Point of Sale', docsTopic: 'pos' },\n Component: PosConsolePage,\n },\n ],\n })\n /*\n * The USER ACCOUNTS half, registered under its own id.\n *\n * `accounts` is a switch rather than a bundle: the member blocks and every\n * `membership/*` handler ship inside this package, which is why the id\n * carries no loader manifest entry of its own. A second extension is how a\n * card about visitor accounts is gated on the switch that decides whether\n * the site serves them — `listConsoleWidgets` filters by the workspace's\n * effective plugin ids, and `accounts` is default-OFF per site.\n *\n * A separate call rather than more widgets on the commerce extension: the\n * registry is keyed by `pluginId` and that key IS the gate, so folding this\n * card in above would have kept the dashboard defect it fixes — `Newest\n * site users` on a site whose /signin returns 404.\n */\n Aglyn.registerConsoleExtension({\n pluginId: Aglyn.ACCOUNTS_PLUGIN_ID,\n displayName: 'User Accounts',\n widgets: [\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n widgetId: 'accounts-newest-site-users',\n title: 'Newest site users',\n Component: NewestSiteUsersCard,\n },\n ],\n })\n}\n\nexport * from './site'\n"],"names":["Aglyn","mdiStorefrontOutline","lazy","PosConsolePage","PosDisplayPage","PosKioskPage","PosHardwareTermsPage","PosHardwareTermsSignPage","PosHardwareOrdersStaffPage","PosReaderSplashStaffPage","CommerceGlanceCard","NewestSiteUsersCard","SiteMemberPurchases","COMMERCE_CONSOLE_SECTIONS","COMMERCE_RETIRED_SECTIONS","BUNDLE_ID","COMMERCE_PERMISSIONS","registerCommerceRecordLists","registerCommerceRecordRoutes","registerCommerceZones","registerCommerceOrderZones","registerCommerceReturnZones","registerCommerceStoreZones","registerCommercePosZones","registerCommerceTransferUi","COMMERCE_SEARCH_SOURCES","COMMERCE_CONFIG_SCHEMA","CommerceConsolePage","registerCommerceConsole","registerPluginPermissions","registerPluginConfigSchema","registerConsoleExtension","pluginId","displayName","searchSources","staffPages","id","label","header","title","docsTopic","Component","publicPages","path","widgets","slot","widgetId","CONSOLE_WIDGET_SLOTS","siteMember","navItems","href","icon","sections","retiredSections","permission","ACCOUNTS_PLUGIN_ID","hostDashboard"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/commerce/src/lib/plugin.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 * as Aglyn from '@aglyn/aglyn'\nimport { mdiStorefrontOutline } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nconst PosConsolePage = lazy(() => import('./components/console/pos-page.component'))\n/** The customer display (AGL-3608): loads only on the public page that shows it. */\nconst PosDisplayPage = lazy(() => import('./components/console/pos-display-page.component'))\nconst PosKioskPage = lazy(() => import('./components/console/pos-kiosk-page.component'))\nconst PosHardwareTermsPage = lazy(() => import('./components/console/pos-hardware-terms-page.component'))\nconst PosHardwareTermsSignPage = lazy(() => import('./components/console/pos-hardware-terms-sign-page.component'))\n/** Staff → Hardware orders (AGL-4038): loads only on its staff page. */\nconst PosHardwareOrdersStaffPage = lazy(\n () => import('./components/console/pos-hardware-orders-staff-page.component'),\n)\n/** Staff → Reader start-up screen (AGL-4048): loads only on its staff page. */\nconst PosReaderSplashStaffPage = lazy(\n () => import('./components/console/pos-reader-splash-staff-page.component'),\n)\nconst CommerceGlanceCard = lazy(\n () => import('./components/console/commerce-glance-card.component'),\n)\nconst NewestSiteUsersCard = lazy(\n () => import('./components/console/newest-site-users-card.component'),\n)\nconst SiteMemberPurchases = lazy(\n () => import('./components/console/site-member-purchases.component'),\n)\nimport {\n COMMERCE_CONSOLE_SECTIONS,\n COMMERCE_RETIRED_SECTIONS,\n} from './components/commerce-console-sections'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { COMMERCE_PERMISSIONS } from './model/plugin-permissions'\nimport { registerCommerceRecordLists } from './model/commerce-record-lists'\nimport { registerCommerceRecordRoutes } from './model/commerce-record-routes'\nimport { registerCommerceZones } from './components/console/product-zones'\nimport { registerCommerceOrderZones } from './components/console/order-zones'\nimport { registerCommerceReturnZones } from './components/console/return-zones'\nimport { registerCommerceStoreZones } from './components/console/store-zones'\nimport { registerCommercePosZones } from './components/console/pos-zones'\nimport { registerCommerceTransferUi } from './transfer/register-transfer-ui'\nimport { COMMERCE_SEARCH_SOURCES } from './model/commerce-search-sources'\nimport { COMMERCE_CONFIG_SCHEMA } from './plugin-config'\n\n/** Code-split: the Products console page only loads when opened. */\nconst CommerceConsolePage = lazy(() => import('./components/commerce-console-page'))\n/** \"Start a quote\" on a quote request in the Inbox and on a contact in the CRM (AGL-4098). */\nconst QuoteStartFromRequest = lazy(() => import('./components/console/quote-start.component'))\nconst QuoteStartFromContact = lazy(() =>\n import('./components/console/quote-start.component').then((module) => ({ default: module.QuoteStartFromContact })),\n)\n\n/**\n * Console half (AGL-395): registers the Products nav item in the\n * ConsoleExtension registry so the commerce console surface is owned by the\n * plugin, like events and email. The page body still lives in the console\n * app (`/[hostId]/products`): its product editor depends on the app's media\n * browser, which transitively pulls in the org/session context, so a full\n * body relocation needs a plugin-consumable media-picker context first —\n * tracked as follow-up. The nav item omits a `Component`, so the shell's\n * nav renders the link and the existing named route serves the page.\n */\nexport function registerCommerceConsole(): void {\n // Where a product is read, for surfaces outside this plugin (AGL-3080).\n registerCommerceRecordRoutes()\n registerCommerceRecordLists()\n // The zones its product editor, products hub and CSV import dialog host,\n // with the props each hands a widget (AGL-2916, AGL-3080).\n registerCommerceZones()\n // …and the order dialog's two (AGL-3611), where a shipping plugin's widget\n // buys a label and records the shipment through the dialog's own route.\n registerCommerceOrderZones()\n // …and the return dialog's (AGL-3611), where a shipping plugin's widget\n // buys the buyer a return label and attaches it through this plugin's route.\n registerCommerceReturnZones()\n // The orders list's bulk actions and the store's Settings (AGL-3612).\n registerCommerceStoreZones()\n // The register's orders from other channels — a delivery app's (AGL-3644).\n registerCommercePosZones()\n // Its import and export resources' names and the products wizard's own\n // step (AGL-3531); the server halves register from the console's server\n // declarations.\n registerCommerceTransferUi()\n // Plugin-declared permissions (AGL-435): tier defaults ride every\n // resolved role set; custom roles override key-by-key.\n Aglyn.registerPluginPermissions(COMMERCE_PERMISSIONS)\n // Register discount ceiling (AGL-2161) — the generic settings card renders\n // it from this schema, so there is no bespoke UI to keep in sync.\n Aglyn.registerPluginConfigSchema(COMMERCE_CONFIG_SCHEMA)\n Aglyn.registerConsoleExtension({\n pluginId: BUNDLE_ID,\n displayName: 'Commerce',\n searchSources: COMMERCE_SEARCH_SOURCES,\n // The screen a register turns toward its customer (AGL-3608), at\n // `/kiosk/commerce/pos-display`. No staff session: the device proves\n // itself with the display token it got by pairing, and\n // `server/pos-display.ts` refuses every read without one.\n // Every workspace's orders from the POS hardware shop (AGL-4038), where\n // staff record Stripe's order id and the tracking, move each order on,\n // and cancel or refund one that has not shipped. The staff area loads\n // this registrar through commerce's `staff` surface.\n staffPages: [\n {\n id: 'hardware-orders',\n label: 'Hardware orders',\n header: { title: 'Hardware Orders', docsTopic: 'posHardware' },\n Component: PosHardwareOrdersStaffPage,\n },\n // The platform's default card reader start-up screen (AGL-4048), which\n // every store that has not picked its own logo or image shows.\n {\n id: 'reader-start-up-screen',\n label: 'Reader start-up screen',\n header: { title: 'Reader Start-up Screen', docsTopic: 'posHardware' },\n Component: PosReaderSplashStaffPage,\n },\n ],\n publicPages: [\n { path: '/pos-display', title: 'Customer display', Component: PosDisplayPage },\n // The self-service kiosk (AGL-3623), paired the same way with a kiosk\n // code; `server/pos-kiosk.ts` refuses every call without its token.\n { path: '/pos-kiosk', title: 'Self-service kiosk', Component: PosKioskPage },\n // The terms of sale for a card reader sold at the platform's register\n // (AGL-4038): on its customer display, by the display's own token\n // (`server/pos-display.ts`), and the printable booth sign, which holds\n // only the published terms' address and summary.\n { path: '/hardware-terms', title: 'Terms of sale', Component: PosHardwareTermsPage },\n { path: '/hardware-terms-sign', title: 'Terms of sale sign', Component: PosHardwareTermsSignPage },\n ],\n // Dashboard/analytics glance card (AGL-419): rendered through the\n // shell's 'commerceGlance' widget slot.\n widgets: [\n {\n slot: 'commerceGlance',\n widgetId: 'commerce-glance',\n title: 'Commerce',\n Component: CommerceGlanceCard,\n },\n // What a site user bought — lifetime total, orders and subscriptions —\n // in their drawer on the site's Users page: the orders and\n // subscriptions are this plugin's.\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.siteMember,\n widgetId: 'commerce-site-member-purchases',\n title: 'Purchases',\n Component: SiteMemberPurchases,\n },\n // Start a quote from a quote request, among an open submission's\n // actions in the Inbox, and from a contact, in its header in the CRM\n // (AGL-4098). Each zone is its host's; neither host imports this plugin.\n {\n slot: 'inboxSubmissionActions',\n widgetId: 'commerce-quote-from-request',\n title: 'Start a quote',\n Component: QuoteStartFromRequest,\n },\n {\n slot: 'crmRecordActions',\n widgetId: 'commerce-quote-from-contact',\n title: 'Start a quote',\n Component: QuoteStartFromContact,\n },\n ],\n navItems: [\n {\n label: 'Products',\n href: '/products',\n icon: { path: mdiStorefrontOutline.path },\n // Sections as ROUTES (AGL-2501): `/products/orders` and friends are\n // real URLs the shell resolves and gates, so the page mounts the one\n // being read instead of subscribing all six.\n sections: COMMERCE_CONSOLE_SECTIONS,\n // `/products/settings` was split into a page per setting (AGL-3713);\n // links to it open the first of them.\n retiredSections: COMMERCE_RETIRED_SECTIONS,\n header: {\n title: 'Products',\n icon: { path: mdiStorefrontOutline.path },\n // The overview, not `catalog`: this surface is the whole commerce\n // console — Catalog, Orders, Promotions and Reservations.\n docsTopic: 'commerce',\n },\n Component: CommerceConsolePage,\n },\n {\n // POS register (AGL-312/419): relocated from the app's /pos route —\n // the generic [pluginSlug] shell now serves it at the same URL.\n label: 'POS',\n href: '/pos',\n /*\n * WHO may open the register, declared on THIS nav item rather than on\n * the extension.\n *\n * A key here narrows one surface; a key on the extension would gate\n * every surface it registers, and Products is the sibling — a catalog\n * anyone who works on the site may open. The register is the one that\n * takes money and lists checked-in guests by name, so the two\n * genuinely have different answers and only this one is narrowed.\n *\n * `managePos` rather than a dotted catalog key because it is the key\n * the sale itself is refused by: `server/pos-order.ts` resolves the\n * member's permission map and returns 403 unless `managePos` holds.\n * The shell answers this declaration out of that same resolved map, so\n * the console offers a register exactly to the people the route will\n * accept a sale from — the gate and its server twin cannot disagree,\n * because they are reading one value.\n *\n * `pos-order.ts` stays the enforcement point regardless. This decides\n * whether the surface is built at all, which is the part a check\n * inside the page cannot do: by the time the page can refuse itself it\n * has mounted and its product, register, location and reservation\n * listeners are already open.\n */\n permission: 'managePos',\n icon: { path: mdiStorefrontOutline.path },\n header: { title: 'Point of Sale', docsTopic: 'pos' },\n Component: PosConsolePage,\n },\n ],\n })\n /*\n * The USER ACCOUNTS half, registered under its own id.\n *\n * `accounts` is a switch rather than a bundle: the member blocks and every\n * `membership/*` handler ship inside this package, which is why the id\n * carries no loader manifest entry of its own. A second extension is how a\n * card about visitor accounts is gated on the switch that decides whether\n * the site serves them — `listConsoleWidgets` filters by the workspace's\n * effective plugin ids, and `accounts` is default-OFF per site.\n *\n * A separate call rather than more widgets on the commerce extension: the\n * registry is keyed by `pluginId` and that key IS the gate, so folding this\n * card in above would have kept the dashboard defect it fixes — `Newest\n * site users` on a site whose /signin returns 404.\n */\n Aglyn.registerConsoleExtension({\n pluginId: Aglyn.ACCOUNTS_PLUGIN_ID,\n displayName: 'User Accounts',\n widgets: [\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n widgetId: 'accounts-newest-site-users',\n title: 'Newest site users',\n Component: NewestSiteUsersCard,\n },\n ],\n })\n}\n\nexport * from './site'\n"],"names":["Aglyn","mdiStorefrontOutline","lazy","PosConsolePage","PosDisplayPage","PosKioskPage","PosHardwareTermsPage","PosHardwareTermsSignPage","PosHardwareOrdersStaffPage","PosReaderSplashStaffPage","CommerceGlanceCard","NewestSiteUsersCard","SiteMemberPurchases","COMMERCE_CONSOLE_SECTIONS","COMMERCE_RETIRED_SECTIONS","BUNDLE_ID","COMMERCE_PERMISSIONS","registerCommerceRecordLists","registerCommerceRecordRoutes","registerCommerceZones","registerCommerceOrderZones","registerCommerceReturnZones","registerCommerceStoreZones","registerCommercePosZones","registerCommerceTransferUi","COMMERCE_SEARCH_SOURCES","COMMERCE_CONFIG_SCHEMA","CommerceConsolePage","QuoteStartFromRequest","QuoteStartFromContact","then","module","default","registerCommerceConsole","registerPluginPermissions","registerPluginConfigSchema","registerConsoleExtension","pluginId","displayName","searchSources","staffPages","id","label","header","title","docsTopic","Component","publicPages","path","widgets","slot","widgetId","CONSOLE_WIDGET_SLOTS","siteMember","navItems","href","icon","sections","retiredSections","permission","ACCOUNTS_PLUGIN_ID","hostDashboard"],"mappings":"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"}