@aglyn/plugins-commerce 1.0.0-beta.288 → 1.0.0-beta.290
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +13 -13
- package/src/lib/components/commerce-console-page.js +29 -2
- package/src/lib/components/commerce-console-page.js.map +1 -1
- package/src/lib/components/console/commerce-analytics-card.component.js +4 -2
- package/src/lib/components/console/commerce-analytics-card.component.js.map +1 -1
- package/src/lib/components/console/commerce-pdf-download.d.ts +30 -0
- package/src/lib/components/console/commerce-pdf-download.js +53 -0
- package/src/lib/components/console/commerce-pdf-download.js.map +1 -0
- package/src/lib/components/console/host-orders-card.component.js +5 -1
- package/src/lib/components/console/host-orders-card.component.js.map +1 -1
- package/src/lib/components/console/host-quotes-card.component.d.ts +34 -0
- package/src/lib/components/console/host-quotes-card.component.js +263 -0
- package/src/lib/components/console/host-quotes-card.component.js.map +1 -0
- package/src/lib/components/console/order-invoice.component.d.ts +6 -4
- package/src/lib/components/console/order-invoice.component.js +67 -11
- package/src/lib/components/console/order-invoice.component.js.map +1 -1
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js +33 -28
- package/src/lib/components/console/pos-ops/pos-check-ins-card.component.js.map +1 -1
- package/src/lib/components/console/quote-editor-dialog.component.d.ts +45 -0
- package/src/lib/components/console/quote-editor-dialog.component.js +1317 -0
- package/src/lib/components/console/quote-editor-dialog.component.js.map +1 -0
- package/src/lib/components/console/quote-pay-link.component.d.ts +38 -0
- package/src/lib/components/console/quote-pay-link.component.js +213 -0
- package/src/lib/components/console/quote-pay-link.component.js.map +1 -0
- package/src/lib/components/console/quote-proofs.component.d.ts +37 -0
- package/src/lib/components/console/quote-proofs.component.js +626 -0
- package/src/lib/components/console/quote-proofs.component.js.map +1 -0
- package/src/lib/components/console/quote-settings-card.component.d.ts +29 -0
- package/src/lib/components/console/quote-settings-card.component.js +121 -0
- package/src/lib/components/console/quote-settings-card.component.js.map +1 -0
- package/src/lib/components/console/tax-settings-card.component.js +4 -1
- package/src/lib/components/console/tax-settings-card.component.js.map +1 -1
- package/src/lib/components/quote-page-body.d.ts +33 -0
- package/src/lib/components/quote-page-body.js +724 -0
- package/src/lib/components/quote-page-body.js.map +1 -0
- package/src/lib/components/quote-page.d.ts +29 -0
- package/src/lib/components/quote-page.js +96 -0
- package/src/lib/components/quote-page.js.map +1 -0
- package/src/lib/components/quote-proof-panel.d.ts +42 -0
- package/src/lib/components/quote-proof-panel.js +436 -0
- package/src/lib/components/quote-proof-panel.js.map +1 -0
- package/src/lib/components/storefront-payment-element.d.ts +7 -1
- package/src/lib/components/storefront-payment-element.js +8 -3
- package/src/lib/components/storefront-payment-element.js.map +1 -1
- package/src/lib/constants/orders-list-query.js +6 -1
- package/src/lib/constants/orders-list-query.js.map +1 -1
- package/src/lib/constants/quote-page.d.ts +30 -0
- package/src/lib/constants/quote-page.js +35 -0
- package/src/lib/constants/quote-page.js.map +1 -0
- package/src/lib/model/commerce-orders.d.ts +25 -2
- package/src/lib/model/commerce-orders.js +10 -1
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/commerce-quote-pay.d.ts +256 -0
- package/src/lib/model/commerce-quote-pay.js +426 -0
- package/src/lib/model/commerce-quote-pay.js.map +1 -0
- package/src/lib/model/commerce-quote-proofs.d.ts +159 -0
- package/src/lib/model/commerce-quote-proofs.js +348 -0
- package/src/lib/model/commerce-quote-proofs.js.map +1 -0
- package/src/lib/model/commerce-quotes.d.ts +335 -0
- package/src/lib/model/commerce-quotes.js +522 -0
- package/src/lib/model/commerce-quotes.js.map +1 -0
- package/src/lib/model/index.d.ts +3 -0
- package/src/lib/model/index.js +3 -0
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/model/plugin-permissions.d.ts +1 -1
- package/src/lib/model/plugin-permissions.js +20 -0
- package/src/lib/model/plugin-permissions.js.map +1 -1
- package/src/lib/server/api-v1/orders-and-products.js +6 -2
- package/src/lib/server/api-v1/orders-and-products.js.map +1 -1
- package/src/lib/server/billing-webhook.js +127 -52
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/checkout-session-expiry.d.ts +36 -0
- package/src/lib/server/checkout-session-expiry.js +63 -0
- package/src/lib/server/checkout-session-expiry.js.map +1 -0
- package/src/lib/server/commerce-pdf.d.ts +100 -0
- package/src/lib/server/commerce-pdf.js +283 -0
- package/src/lib/server/commerce-pdf.js.map +1 -0
- package/src/lib/server/draft-order.js +16 -8
- package/src/lib/server/draft-order.js.map +1 -1
- package/src/lib/server/order-print.d.ts +6 -0
- package/src/lib/server/order-print.js +22 -5
- package/src/lib/server/order-print.js.map +1 -1
- package/src/lib/server/pdf-response.d.ts +31 -0
- package/src/lib/server/pdf-response.js +30 -0
- package/src/lib/server/pdf-response.js.map +1 -0
- package/src/lib/server/person-eraser.d.ts +7 -0
- package/src/lib/server/person-eraser.js +24 -2
- package/src/lib/server/person-eraser.js.map +1 -1
- package/src/lib/server/pos-reader-splash.d.ts +2 -18
- package/src/lib/server/pos-reader-splash.js +8 -93
- package/src/lib/server/pos-reader-splash.js.map +1 -1
- package/src/lib/server/pos-return.js +3 -1
- package/src/lib/server/pos-return.js.map +1 -1
- package/src/lib/server/quote-link-token.d.ts +39 -0
- package/src/lib/server/quote-link-token.js +97 -0
- package/src/lib/server/quote-link-token.js.map +1 -0
- package/src/lib/server/quote-pay.d.ts +110 -0
- package/src/lib/server/quote-pay.js +606 -0
- package/src/lib/server/quote-pay.js.map +1 -0
- package/src/lib/server/quote-proofs.d.ts +51 -0
- package/src/lib/server/quote-proofs.js +500 -0
- package/src/lib/server/quote-proofs.js.map +1 -0
- package/src/lib/server/quote-uploads.d.ts +33 -0
- package/src/lib/server/quote-uploads.js +29 -0
- package/src/lib/server/quote-uploads.js.map +1 -0
- package/src/lib/server/quote.d.ts +63 -0
- package/src/lib/server/quote.js +846 -0
- package/src/lib/server/quote.js.map +1 -0
- package/src/lib/server/related.js +2 -0
- package/src/lib/server/related.js.map +1 -1
- package/src/lib/server/site-image-source.d.ts +38 -0
- package/src/lib/server/site-image-source.js +130 -0
- package/src/lib/server/site-image-source.js.map +1 -0
- package/src/lib/server/site-page-resolver.d.ts +2 -0
- package/src/lib/server/site-page-resolver.js +75 -0
- package/src/lib/server/site-page-resolver.js.map +1 -1
- package/src/lib/server.js +33 -1
- package/src/lib/server.js.map +1 -1
- package/src/lib/site.js +6 -0
- package/src/lib/site.js.map +1 -1
- package/src/lib/transfer/tracking.server.js +6 -0
- package/src/lib/transfer/tracking.server.js.map +1 -1
- package/src/lib/utils/commerce-document-pdf.d.ts +77 -0
- package/src/lib/utils/commerce-document-pdf.js +608 -0
- package/src/lib/utils/commerce-document-pdf.js.map +1 -0
- package/src/lib/utils/order-invoice.d.ts +52 -3
- package/src/lib/utils/order-invoice.js +116 -64
- package/src/lib/utils/order-invoice.js.map +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/model/commerce-quote-proofs.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 { appendOrderEvent, type HostOrder, type OrderAddress, type OrderLineItem, type OrderTotals } from './commerce-orders'\nimport {\n formatQuoteNumber,\n isQuote,\n isUnpaidQuote,\n type OrderQuote,\n type QuoteApproval,\n type QuoteCustomer,\n type QuoteCustomerResponse,\n type QuoteDiscount,\n type QuoteProof,\n type QuoteResponseFile,\n type QuoteShipping,\n type QuoteStage,\n type QuoteTaxBasis,\n} from './commerce-quotes'\nimport type { StorefrontTaxMode } from './commerce-tax-decision'\n\n/*==========================================\n * PROOFS, REVISIONS AND THE CUSTOMER'S ANSWER (AGL-4096).\n *\n * A quote's REVISION is the version the customer is looking at. The business\n * attaches proofs to the current revision; the customer approves them under\n * a typed name or asks for changes; the business then starts the next\n * revision, which keeps the one before exactly as it stood in\n * `hosts/{hostId}/orders/{id}/revisions/{n}` and starts the new one with no\n * proofs and no answer. Every revision is kept.\n *\n * Whether paying waits on an approval is the business's control\n * (`requireProofApproval`, AGL-4093) and `quotePaymentBlockedByApproval`\n * answers it; nothing here makes paying wait when it is off.\n *\n * Pure: the console route, the customer's route and (slice 5) the send that\n * starts a revision all call these, and write what they answer.\n *=========================================*/\n\n/** The most proofs one revision holds. */\nexport const QUOTE_MAX_PROOFS = 10\n\n/** The most answers the customer gives one revision; plenty for a change of mind. */\nexport const QUOTE_MAX_RESPONSES = 20\n\n/** The longest comment a customer may leave. */\nexport const QUOTE_MAX_COMMENT = 5_000\n\n/** The longest name an approval is signed with. */\nexport const QUOTE_MAX_APPROVER_NAME = 120\n\n/** What the visitor uploads are called for a quote: its proofs, and the customer's artwork. */\nexport const QUOTE_PROOF_PURPOSE = 'quote-proof'\nexport const QUOTE_ARTWORK_PURPOSE = 'quote-artwork'\n\n/** A revision as it stood when the next one started. */\nexport interface QuoteRevisionSnapshot {\n revision: number\n /** Where it stood with the business when it closed. */\n stage: QuoteStage\n lineItems: OrderLineItem[]\n totals: OrderTotals | null\n taxMode: StorefrontTaxMode | null\n tax: QuoteTaxBasis | null\n discount: QuoteDiscount | null\n shipping: QuoteShipping | null\n notes: string\n terms: string\n customer: QuoteCustomer | null\n billTo: OrderAddress | null\n shipTo: OrderAddress | null\n proofs: QuoteProof[]\n responses: QuoteCustomerResponse[]\n approval: QuoteApproval | null\n /** When the revision was last written by the business. */\n updatedAtMs: number\n /** When the next revision started, and who started it. */\n closedAtMs: number\n closedBy: string\n}\n\n/** The current revision's proofs. */\nexport function currentQuoteProofs(quote: Pick<OrderQuote, 'proofs' | 'revision'> | null | undefined): QuoteProof[] {\n const revision = Number(quote?.revision ?? 1)\n return (quote?.proofs ?? []).filter((proof) => Number(proof?.revision) === revision)\n}\n\n/** The customer's latest answer to the current revision, if they gave one. */\nexport function latestQuoteResponse(\n quote: Pick<OrderQuote, 'responses' | 'revision'> | null | undefined,\n): QuoteCustomerResponse | null {\n const revision = Number(quote?.revision ?? 1)\n const answers = (quote?.responses ?? []).filter((response) => Number(response?.revision) === revision)\n return answers.length ? answers[answers.length - 1] : null\n}\n\n/** Whether the customer has answered the current revision. */\nexport function quoteRevisionAnswered(quote: Pick<OrderQuote, 'responses' | 'revision'> | null | undefined): boolean {\n return latestQuoteResponse(quote) !== null\n}\n\n/*------------------------------------------\n * The business's proofs\n *-----------------------------------------*/\n\nexport type QuoteProofRefusal = 'not-a-quote' | 'paid' | 'answered' | 'full' | 'unknown-proof'\n\n/** Why the current revision's proofs cannot be changed now, if they cannot. */\nexport function quoteProofChangeRefusal(\n order: Pick<HostOrder, 'channel' | 'status' | 'quote'> | null | undefined,\n change: 'add' | 'remove',\n): QuoteProofRefusal | null {\n if (!order || !isQuote(order) || !order.quote) return 'not-a-quote'\n if (!isUnpaidQuote(order)) return 'paid'\n if (quoteRevisionAnswered(order.quote)) return 'answered'\n if (change === 'add' && currentQuoteProofs(order.quote).length >= QUOTE_MAX_PROOFS) return 'full'\n return null\n}\n\nexport function quoteProofRefusalMessage(reason: QuoteProofRefusal): string {\n switch (reason) {\n case 'not-a-quote':\n return 'Unknown quote'\n case 'paid':\n return 'This quote has been paid, so its proofs can no longer be changed.'\n case 'answered':\n return 'The customer has answered this revision. Start a new revision to send different proofs.'\n case 'full':\n return `A revision holds up to ${QUOTE_MAX_PROOFS} proofs. Remove one first.`\n case 'unknown-proof':\n return 'That proof is no longer on this quote.'\n }\n}\n\n/** The quote with a proof added to its current revision, edited by `by` at `atMs`. */\nexport function withQuoteProofAdded(\n order: HostOrder,\n proof: Omit<QuoteProof, 'revision' | 'addedAtMs' | 'addedBy'>,\n options: { atMs: number; by: string },\n): { ok: true; order: HostOrder; proof: QuoteProof } | { ok: false; reason: QuoteProofRefusal } {\n const refused = quoteProofChangeRefusal(order, 'add')\n if (refused) return { ok: false, reason: refused }\n const quote = order.quote as OrderQuote\n const added: QuoteProof = {\n id: proof.id,\n revision: Number(quote.revision ?? 1),\n name: proof.name,\n contentType: proof.contentType,\n size: proof.size,\n path: proof.path,\n addedAtMs: options.atMs,\n addedBy: options.by,\n }\n return {\n ok: true,\n proof: added,\n order: {\n ...order,\n timeline: appendOrderEvent(\n order,\n 'quote-proof-added',\n `Proof ${added.name} added to revision ${added.revision} by ${options.by}`,\n options.atMs,\n ),\n quote: {\n ...quote,\n proofs: [...currentQuoteProofs(quote).filter((entry) => entry.id !== added.id), added],\n updatedAtMs: options.atMs,\n updatedBy: options.by,\n },\n },\n }\n}\n\n/** The quote with one of the current revision's proofs taken off. */\nexport function withQuoteProofRemoved(\n order: HostOrder,\n proofId: string,\n options: { atMs: number; by: string },\n): { ok: true; order: HostOrder; proof: QuoteProof } | { ok: false; reason: QuoteProofRefusal } {\n const refused = quoteProofChangeRefusal(order, 'remove')\n if (refused) return { ok: false, reason: refused }\n const quote = order.quote as OrderQuote\n const proofs = currentQuoteProofs(quote)\n const proof = proofs.find((entry) => entry.id === proofId)\n if (!proof) return { ok: false, reason: 'unknown-proof' }\n return {\n ok: true,\n proof,\n order: {\n ...order,\n timeline: appendOrderEvent(\n order,\n 'quote-proof-removed',\n `Proof ${proof.name} removed from revision ${proof.revision} by ${options.by}`,\n options.atMs,\n ),\n quote: {\n ...quote,\n proofs: proofs.filter((entry) => entry.id !== proofId),\n updatedAtMs: options.atMs,\n updatedBy: options.by,\n },\n },\n }\n}\n\n/*------------------------------------------\n * Revisions\n *-----------------------------------------*/\n\nexport type QuoteRevisionRefusal = 'not-a-quote' | 'paid'\n\n/**\n * Starts the quote's next revision. The current one is SNAPSHOTTED as it\n * stands (its lines, totals, notes, terms, proofs and the customer's\n * answers); the quote moves to revision N+1 with no proofs and no answer,\n * back at Draft, edited by `by` at `atMs`. A paid quote starts none.\n *\n * The caller writes `snapshot` to `hosts/{hostId}/orders/{id}/revisions/{revision}`\n * (`String(snapshot.revision)`, created, never overwritten) and `order` over\n * the quote, in one transaction. The pay link's version is not touched: the\n * send that hands the customer the new revision decides that.\n */\nexport function startQuoteRevision(\n order: HostOrder,\n options: { atMs: number; by: string },\n):\n | { ok: true; snapshot: QuoteRevisionSnapshot; order: HostOrder; revision: number }\n | { ok: false; reason: QuoteRevisionRefusal; error: string } {\n if (!isQuote(order) || !order.quote) return { ok: false, reason: 'not-a-quote', error: 'Unknown quote' }\n if (!isUnpaidQuote(order)) {\n return { ok: false, reason: 'paid', error: 'This quote has been paid, so it takes no new revision.' }\n }\n const quote = order.quote\n const current = Math.max(1, Math.floor(Number(quote.revision ?? 1)) || 1)\n const responses = (quote.responses ?? []).filter((response) => Number(response?.revision) === current)\n const approval = quote.approval && Number(quote.approval.revision) === current ? quote.approval : null\n const snapshot: QuoteRevisionSnapshot = {\n revision: current,\n stage: quote.stage ?? 'draft',\n lineItems: order.lineItems ?? [],\n totals: order.totals ?? null,\n taxMode: order.taxMode ?? null,\n tax: quote.tax ?? null,\n discount: quote.discount ?? null,\n shipping: quote.shipping ?? null,\n notes: quote.notes ?? '',\n terms: quote.terms ?? '',\n customer: quote.customer ?? null,\n billTo: quote.billTo ?? null,\n shipTo: quote.shipTo ?? null,\n proofs: currentQuoteProofs(quote),\n responses,\n approval,\n updatedAtMs: Number(quote.updatedAtMs) || 0,\n closedAtMs: options.atMs,\n closedBy: options.by,\n }\n const next = current + 1\n return {\n ok: true,\n revision: current,\n snapshot,\n order: {\n ...order,\n timeline: appendOrderEvent(\n order,\n 'quote-revision-started',\n `Revision ${next} started by ${options.by}; revision ${current} is kept with its proofs and answers`,\n options.atMs,\n ),\n quote: {\n ...quote,\n revision: next,\n stage: 'draft',\n proofs: [],\n responses: [],\n approval: null,\n updatedAtMs: options.atMs,\n updatedBy: options.by,\n },\n },\n }\n}\n\n/*------------------------------------------\n * The customer's answer\n *-----------------------------------------*/\n\nexport type QuoteRespondRefusal = 'not-a-quote' | 'paid' | 'no-proof' | 'too-many'\n\n/** Why the customer cannot answer the current revision now, if they cannot. */\nexport function quoteRespondRefusal(\n order: Pick<HostOrder, 'channel' | 'status' | 'quote'> | null | undefined,\n): QuoteRespondRefusal | null {\n if (!order || !isQuote(order) || !order.quote) return 'not-a-quote'\n if (!isUnpaidQuote(order)) return 'paid'\n if (currentQuoteProofs(order.quote).length === 0) return 'no-proof'\n const revision = Number(order.quote.revision ?? 1)\n const given = (order.quote.responses ?? []).filter((response) => Number(response?.revision) === revision)\n if (given.length >= QUOTE_MAX_RESPONSES) return 'too-many'\n return null\n}\n\nexport function quoteRespondRefusalMessage(reason: QuoteRespondRefusal, storeName: string): string {\n const business = storeName || 'the business'\n switch (reason) {\n case 'not-a-quote':\n return 'We could not find that quote. Check the link you were sent.'\n case 'paid':\n return 'This quote has been paid, so it takes no more answers.'\n case 'no-proof':\n return `There is no proof on this quote to answer yet. ${storeName || 'The business'} will send one.`\n case 'too-many':\n return `This proof has had a lot of answers already. Contact ${business} directly.`\n }\n}\n\n/** What the customer sends: approve under a name, or changes with a comment and any files. */\nexport type QuoteResponseInput =\n | { action: 'approve'; name: string }\n | { action: 'changes'; comment: string; fileIds: string[] }\n\n/** The customer's answer as the route received it, checked and trimmed, or what to fix. */\nexport function parseQuoteResponseInput(raw: unknown): { ok: true; input: QuoteResponseInput } | { ok: false; error: string } {\n const source = (raw && typeof raw === 'object' ? raw : {}) as Record<string, unknown>\n if (source['action'] === 'approve') {\n const name = typeof source['name'] === 'string' ? source['name'].replace(/\\s+/g, ' ').trim() : ''\n if (name.length < 2) return { ok: false, error: 'Type your name to approve the proof.' }\n return { ok: true, input: { action: 'approve', name: name.slice(0, QUOTE_MAX_APPROVER_NAME) } }\n }\n if (source['action'] === 'changes') {\n const comment = typeof source['comment'] === 'string' ? source['comment'].trim() : ''\n if (!comment) return { ok: false, error: 'Tell us what to change.' }\n const raws = Array.isArray(source['fileIds']) ? (source['fileIds'] as unknown[]) : []\n const named = raws.map((value) => String(value ?? '')).filter((value) => /^[A-Za-z0-9_-]{1,128}$/.test(value))\n if (named.length !== raws.length) return { ok: false, error: 'One of the files could not be attached. Send it again.' }\n const fileIds = [...new Set(named)]\n if (fileIds.length > QUOTE_MAX_PROOFS) return { ok: false, error: `Send up to ${QUOTE_MAX_PROOFS} files at a time.` }\n return { ok: true, input: { action: 'changes', comment: comment.slice(0, QUOTE_MAX_COMMENT), fileIds } }\n }\n return { ok: false, error: 'Approve the proof or ask for changes.' }\n}\n\n/**\n * The quote with the customer's answer recorded on its current revision.\n * Approving names the revision approved and moves the quote to Approved;\n * asking for changes moves it to Changes requested and withdraws an approval\n * of the same revision. The latest answer is the one that counts. The\n * business's edit stamp (`updatedAtMs`) is not touched: the quote's content\n * did not change, and the page that sent the answer can still pay it.\n */\nexport function applyQuoteResponse(\n order: HostOrder,\n input: QuoteResponseInput & { files?: QuoteResponseFile[] },\n context: { atMs: number; ip: string | null; userAgent: string | null },\n): { ok: true; order: HostOrder; response: QuoteCustomerResponse } | { ok: false; reason: QuoteRespondRefusal } {\n const refused = quoteRespondRefusal(order)\n if (refused) return { ok: false, reason: refused }\n const quote = order.quote as OrderQuote\n const revision = Number(quote.revision ?? 1)\n const ip = context.ip ? context.ip.slice(0, 64) : null\n const userAgent = context.userAgent ? context.userAgent.slice(0, 400) : null\n const response: QuoteCustomerResponse =\n input.action === 'approve'\n ? { action: 'approve', revision, atMs: context.atMs, name: input.name, ip, userAgent }\n : {\n action: 'changes',\n revision,\n atMs: context.atMs,\n comment: input.comment,\n ...(input.files?.length ? { files: input.files } : {}),\n ip,\n userAgent,\n }\n const responses = [...(quote.responses ?? []).filter((entry) => Number(entry?.revision) === revision), response]\n const number = formatQuoteNumber(order)\n const next: OrderQuote =\n response.action === 'approve'\n ? {\n ...quote,\n responses,\n stage: 'approved',\n approvedRevision: revision,\n approval: { revision, name: response.name ?? '', atMs: context.atMs, ip, userAgent },\n }\n : {\n ...quote,\n responses,\n stage: 'changes_requested',\n approvedRevision: Number(quote.approvedRevision) === revision ? null : (quote.approvedRevision ?? null),\n approval: null,\n }\n return {\n ok: true,\n response,\n order: {\n ...order,\n timeline: appendOrderEvent(\n order,\n response.action === 'approve' ? 'quote-approved' : 'quote-changes-requested',\n response.action === 'approve'\n ? `${number} revision ${revision} approved by the customer as ${response.name}`\n : `The customer asked for changes to ${number} revision ${revision}` +\n (response.files?.length ? ` and sent ${response.files.length} file${response.files.length === 1 ? '' : 's'}` : ''),\n context.atMs,\n ),\n quote: next,\n },\n }\n}\n\n/** One line on what the customer last said: `Approved by Jordan Lee`, `Changes requested`. */\nexport function describeQuoteResponse(response: Pick<QuoteCustomerResponse, 'action' | 'name'>): string {\n return response.action === 'approve' ? `Approved by ${response.name || 'the customer'}` : 'Changes requested'\n}\n"],"names":["appendOrderEvent","formatQuoteNumber","isQuote","isUnpaidQuote","QUOTE_MAX_PROOFS","QUOTE_MAX_RESPONSES","QUOTE_MAX_COMMENT","QUOTE_MAX_APPROVER_NAME","QUOTE_PROOF_PURPOSE","QUOTE_ARTWORK_PURPOSE","currentQuoteProofs","quote","revision","Number","proofs","filter","proof","latestQuoteResponse","answers","responses","response","length","quoteRevisionAnswered","quoteProofChangeRefusal","order","change","quoteProofRefusalMessage","reason","withQuoteProofAdded","options","refused","ok","added","id","name","contentType","size","path","addedAtMs","atMs","addedBy","by","timeline","entry","updatedAtMs","updatedBy","withQuoteProofRemoved","proofId","find","startQuoteRevision","error","current","Math","max","floor","approval","snapshot","stage","lineItems","totals","taxMode","tax","discount","shipping","notes","terms","customer","billTo","shipTo","closedAtMs","closedBy","next","quoteRespondRefusal","given","quoteRespondRefusalMessage","storeName","business","parseQuoteResponseInput","raw","source","replace","trim","input","action","slice","comment","raws","Array","isArray","named","map","value","String","test","fileIds","Set","applyQuoteResponse","context","ip","userAgent","files","number","approvedRevision","describeQuoteResponse"],"mappings":"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"}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { HostProduct } from './commerce';
|
|
18
|
+
import type { HostOrder, OrderAddress, OrderLineItem, OrderStatus, OrderTotals } from './commerce-orders';
|
|
19
|
+
import { type TaxSettings } from './commerce-tax';
|
|
20
|
+
import { type StorefrontTaxMode } from './commerce-tax-decision';
|
|
21
|
+
import { type ModifierSelection } from './product-modifiers';
|
|
22
|
+
/** Where a quote stands with the business. The customer side has no states. */
|
|
23
|
+
export type QuoteStage = 'draft' | 'sent' | 'changes_requested' | 'approved';
|
|
24
|
+
export declare const QUOTE_STAGE_LABELS: Record<QuoteStage, string>;
|
|
25
|
+
/** The person a quote is for: a record the workspace keeps, typed details, or both. */
|
|
26
|
+
export interface QuoteCustomer {
|
|
27
|
+
/** The person record, as the person-records seam names it (`contact`, …). */
|
|
28
|
+
record?: {
|
|
29
|
+
kind: string;
|
|
30
|
+
id: string;
|
|
31
|
+
} | null;
|
|
32
|
+
name?: string;
|
|
33
|
+
company?: string;
|
|
34
|
+
email?: string;
|
|
35
|
+
/** `email` lower-cased: what a person erasure finds the quote by. */
|
|
36
|
+
emailLower?: string;
|
|
37
|
+
phone?: string;
|
|
38
|
+
}
|
|
39
|
+
/** Money off the whole quote: an amount, or a percentage of the charged lines. */
|
|
40
|
+
export interface QuoteDiscount {
|
|
41
|
+
kind: 'amount' | 'percent';
|
|
42
|
+
/** With `amount`: the cents off. */
|
|
43
|
+
amountCents?: number;
|
|
44
|
+
/** With `percent`: the percentage off. */
|
|
45
|
+
pct?: number;
|
|
46
|
+
}
|
|
47
|
+
/** Shipping the business prices by hand. */
|
|
48
|
+
export interface QuoteShipping {
|
|
49
|
+
amountCents: number;
|
|
50
|
+
}
|
|
51
|
+
/** Why a quote carries no tax. */
|
|
52
|
+
export type QuoteNoTaxReason = 'not-collected' | 'prices-include-tax' | 'no-rate' | 'undecided';
|
|
53
|
+
/** Where a quote's tax comes from. */
|
|
54
|
+
export type QuoteTaxKind = 'rate' | 'service' | 'at-payment' | 'none';
|
|
55
|
+
/**
|
|
56
|
+
* How a quote's tax was worked out, stored beside the figure so the quote can
|
|
57
|
+
* say it: one of the store's own rates (`rate`, with `pct` and its `label`),
|
|
58
|
+
* its connected tax service's answer (`service`, with `taxCents` and
|
|
59
|
+
* `providerLabel`), Stripe Tax at payment (`at-payment`), or no tax and why
|
|
60
|
+
* (`none`, with `reason`). One flat shape, so the apps can read it as is.
|
|
61
|
+
*/
|
|
62
|
+
export interface QuoteTaxBasis {
|
|
63
|
+
kind: QuoteTaxKind;
|
|
64
|
+
pct?: number;
|
|
65
|
+
label?: string;
|
|
66
|
+
taxCents?: number;
|
|
67
|
+
providerLabel?: string;
|
|
68
|
+
reason?: QuoteNoTaxReason;
|
|
69
|
+
}
|
|
70
|
+
/** A proof attached to one revision of a quote: what names it, and the revision it is for. */
|
|
71
|
+
export interface QuoteProofRef {
|
|
72
|
+
id: string;
|
|
73
|
+
revision: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* A proof the business attached to a revision (AGL-4096): an image or a
|
|
77
|
+
* design file in the site's private storage, under the quote.
|
|
78
|
+
*/
|
|
79
|
+
export interface QuoteProof extends QuoteProofRef {
|
|
80
|
+
name: string;
|
|
81
|
+
contentType: string;
|
|
82
|
+
size: number;
|
|
83
|
+
/** The private Storage object. Never sent to the customer. */
|
|
84
|
+
path: string;
|
|
85
|
+
addedAtMs: number;
|
|
86
|
+
/** Who attached it: an email, else a uid. */
|
|
87
|
+
addedBy: string;
|
|
88
|
+
}
|
|
89
|
+
/** A file the customer sent with a response. */
|
|
90
|
+
export interface QuoteResponseFile {
|
|
91
|
+
id: string;
|
|
92
|
+
name: string;
|
|
93
|
+
contentType: string;
|
|
94
|
+
size: number;
|
|
95
|
+
/** The private Storage object. Never sent to the customer. */
|
|
96
|
+
path: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The customer's answer to a revision's proofs (AGL-4096): approved under a
|
|
100
|
+
* typed name, or changes asked for in a comment, with artwork if they had
|
|
101
|
+
* some. The address and browser are kept as the record of who answered.
|
|
102
|
+
*/
|
|
103
|
+
export interface QuoteCustomerResponse {
|
|
104
|
+
action: 'approve' | 'changes';
|
|
105
|
+
revision: number;
|
|
106
|
+
atMs: number;
|
|
107
|
+
/** With `approve`: the name they typed. */
|
|
108
|
+
name?: string;
|
|
109
|
+
/** With `changes`: what to change. */
|
|
110
|
+
comment?: string;
|
|
111
|
+
/** With `changes`: files they sent. */
|
|
112
|
+
files?: QuoteResponseFile[];
|
|
113
|
+
ip: string | null;
|
|
114
|
+
userAgent: string | null;
|
|
115
|
+
}
|
|
116
|
+
/** The record of a revision's approval. */
|
|
117
|
+
export interface QuoteApproval {
|
|
118
|
+
revision: number;
|
|
119
|
+
name: string;
|
|
120
|
+
atMs: number;
|
|
121
|
+
ip: string | null;
|
|
122
|
+
userAgent: string | null;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* A payment that reached a quote it could not pay: one made through a pay
|
|
126
|
+
* link the business replaced or for a price it has since changed
|
|
127
|
+
* (`superseded`), or one made after the quote was already paid
|
|
128
|
+
* (`already-paid`). The money is in Stripe and the quote records no sale for
|
|
129
|
+
* it; the business refunds it or reconciles it by hand.
|
|
130
|
+
*/
|
|
131
|
+
export interface QuoteUnmatchedPayment {
|
|
132
|
+
sessionId: string;
|
|
133
|
+
paymentIntentId: string | null;
|
|
134
|
+
amountCents: number;
|
|
135
|
+
atMs: number;
|
|
136
|
+
reason: 'superseded' | 'already-paid';
|
|
137
|
+
}
|
|
138
|
+
/** What only a quote has, on its order document. */
|
|
139
|
+
export interface OrderQuote {
|
|
140
|
+
stage: QuoteStage;
|
|
141
|
+
/** Which version of the quote this is; versions are what proofs and approvals name. */
|
|
142
|
+
revision: number;
|
|
143
|
+
customer: QuoteCustomer;
|
|
144
|
+
billTo?: OrderAddress | null;
|
|
145
|
+
shipTo?: OrderAddress | null;
|
|
146
|
+
notes?: string;
|
|
147
|
+
terms?: string;
|
|
148
|
+
discount?: QuoteDiscount | null;
|
|
149
|
+
shipping?: QuoteShipping | null;
|
|
150
|
+
tax: QuoteTaxBasis;
|
|
151
|
+
/** The customer approves the current revision's proof before they can pay. Absent reads as off. */
|
|
152
|
+
requireProofApproval?: boolean;
|
|
153
|
+
/** The current revision's proofs; earlier revisions keep theirs in `revisions/{n}`. */
|
|
154
|
+
proofs?: QuoteProof[];
|
|
155
|
+
/** The customer's answers to the current revision, oldest first; the last one counts. */
|
|
156
|
+
responses?: QuoteCustomerResponse[];
|
|
157
|
+
/** The current revision's approval, while its latest answer is one. */
|
|
158
|
+
approval?: QuoteApproval | null;
|
|
159
|
+
/** The revision the customer approved, when they have. */
|
|
160
|
+
approvedRevision?: number | null;
|
|
161
|
+
/**
|
|
162
|
+
* Which pay link opens the quote: a link signed for an older version shows
|
|
163
|
+
* the customer that the quote was replaced, and nothing of it. Absent reads
|
|
164
|
+
* as 1. Bumped when the business replaces the link.
|
|
165
|
+
*/
|
|
166
|
+
linkVersion?: number;
|
|
167
|
+
/** When the customer paid it. */
|
|
168
|
+
paidAtMs?: number;
|
|
169
|
+
/** Payments the quote could not take; see {@link QuoteUnmatchedPayment}. */
|
|
170
|
+
unmatchedPayments?: QuoteUnmatchedPayment[];
|
|
171
|
+
createdAtMs: number;
|
|
172
|
+
updatedAtMs: number;
|
|
173
|
+
/** Who created and last changed it: an email, else a uid. */
|
|
174
|
+
createdBy: string;
|
|
175
|
+
updatedBy: string;
|
|
176
|
+
}
|
|
177
|
+
/** The store-wide quote defaults, at `hosts/{hostId}/settings/store` under `quotes`. */
|
|
178
|
+
export interface QuoteStoreSettings {
|
|
179
|
+
/** What `requireProofApproval` a new quote starts with. Absent reads as off. */
|
|
180
|
+
requireProofApproval?: boolean;
|
|
181
|
+
}
|
|
182
|
+
/** Whether new quotes start out asking the customer to approve the proof first. */
|
|
183
|
+
export declare function quoteRequiresProofApprovalByDefault(settings: QuoteStoreSettings | null | undefined): boolean;
|
|
184
|
+
/**
|
|
185
|
+
* Whether paying waits on the customer approving the proof: the quote asks for
|
|
186
|
+
* approval, its current revision has a proof, and that revision is not the
|
|
187
|
+
* one the customer approved. A revision with no proof never holds a payment.
|
|
188
|
+
*/
|
|
189
|
+
export declare function quotePaymentBlockedByApproval(quote: (Pick<OrderQuote, 'requireProofApproval' | 'revision' | 'approvedRevision'> & {
|
|
190
|
+
proofs?: readonly QuoteProofRef[];
|
|
191
|
+
}) | null | undefined): boolean;
|
|
192
|
+
/** Whether an order document is a quote, paid or not. */
|
|
193
|
+
export declare function isQuote(order: Pick<Partial<HostOrder>, 'channel'> | null | undefined): boolean;
|
|
194
|
+
/** Whether an order document is a quote nobody has paid yet, which no order list counts. */
|
|
195
|
+
export declare function isUnpaidQuote(order: {
|
|
196
|
+
channel?: unknown;
|
|
197
|
+
status?: unknown;
|
|
198
|
+
} | null | undefined): boolean;
|
|
199
|
+
/** `Quote #1042`; an order number until it is paid. */
|
|
200
|
+
export declare function formatQuoteNumber(order: Pick<HostOrder, 'number'>, docId?: string): string;
|
|
201
|
+
/** A quote's status as its list shows it: its stage until it is paid. */
|
|
202
|
+
export declare function quoteDisplayStatus(order: {
|
|
203
|
+
status?: OrderStatus | string;
|
|
204
|
+
quote?: Pick<OrderQuote, 'stage'> | null;
|
|
205
|
+
}): {
|
|
206
|
+
key: QuoteStage | 'paid' | 'cancelled' | 'refunded';
|
|
207
|
+
label: string;
|
|
208
|
+
};
|
|
209
|
+
/** Chip colors for {@link quoteDisplayStatus}, as the orders list colors its statuses. */
|
|
210
|
+
export declare const QUOTE_STATUS_COLOR: Record<ReturnType<typeof quoteDisplayStatus>['key'], 'default' | 'success' | 'info' | 'warning' | 'error'>;
|
|
211
|
+
/** A line as the totals read it. */
|
|
212
|
+
export type QuoteTotalsLine = Pick<OrderLineItem, 'unitAmountCents' | 'quantity'> & {
|
|
213
|
+
included?: boolean;
|
|
214
|
+
taxExempt?: boolean;
|
|
215
|
+
};
|
|
216
|
+
export interface QuoteTotalsInput {
|
|
217
|
+
lines: readonly QuoteTotalsLine[];
|
|
218
|
+
discount?: QuoteDiscount | null;
|
|
219
|
+
shippingCents?: number;
|
|
220
|
+
tax: QuoteTaxBasis;
|
|
221
|
+
}
|
|
222
|
+
/** A quote's totals: the order's, plus what the tax was computed on. */
|
|
223
|
+
export interface QuoteTotals extends OrderTotals {
|
|
224
|
+
/** The taxable lines after their share of the discount. */
|
|
225
|
+
taxableCents: number;
|
|
226
|
+
/** Stripe Tax works the tax out when the customer pays; `taxCents` is 0 until then. */
|
|
227
|
+
taxAtPayment: boolean;
|
|
228
|
+
}
|
|
229
|
+
/** One line's charge: nothing for an included line. */
|
|
230
|
+
export declare function quoteLineCents(line: QuoteTotalsLine): number;
|
|
231
|
+
/** The discount in cents on `itemsCents`, never more than it. */
|
|
232
|
+
export declare function quoteDiscountCents(itemsCents: number, discount: QuoteDiscount | null | undefined): number;
|
|
233
|
+
/**
|
|
234
|
+
* Each line's charge, its share of the discount (in proportion to the
|
|
235
|
+
* charges, as a checkout spreads a coupon) and what is left: the amounts a tax
|
|
236
|
+
* service is asked about, line by line.
|
|
237
|
+
*/
|
|
238
|
+
export declare function quoteLineAmounts(lines: readonly QuoteTotalsLine[], discount: QuoteDiscount | null | undefined): {
|
|
239
|
+
grossCents: number[];
|
|
240
|
+
discountCents: number[];
|
|
241
|
+
netCents: number[];
|
|
242
|
+
itemsCents: number;
|
|
243
|
+
totalDiscountCents: number;
|
|
244
|
+
};
|
|
245
|
+
/**
|
|
246
|
+
* A quote's totals. The discount comes off before tax; a store rate taxes the
|
|
247
|
+
* taxable lines (not shipping, as a draft order's rate does); a tax service's
|
|
248
|
+
* answer is taken as given; Stripe Tax and no-tax add nothing now.
|
|
249
|
+
*/
|
|
250
|
+
export declare function computeQuoteTotals(input: QuoteTotalsInput): QuoteTotals;
|
|
251
|
+
/** The order totals a quote document stores. */
|
|
252
|
+
export declare function quoteOrderTotals(totals: QuoteTotals): OrderTotals;
|
|
253
|
+
/**
|
|
254
|
+
* The tax basis the store's own settings give a quote, before any tax service
|
|
255
|
+
* is asked: Stripe Tax at payment, no tax (and why), or the store's rate for
|
|
256
|
+
* where the quote ships — its ship-to when it has a country, else the store's
|
|
257
|
+
* own address, as the storefront taxes a sale it cannot see the destination of.
|
|
258
|
+
*/
|
|
259
|
+
export declare function quoteTaxBasisFromSettings(settings: TaxSettings | null | undefined, shipTo: Pick<OrderAddress, 'country' | 'state'> | null | undefined): QuoteTaxBasis;
|
|
260
|
+
/** The tax line's label on a quote. */
|
|
261
|
+
export declare function describeQuoteTax(basis: QuoteTaxBasis | null | undefined): string;
|
|
262
|
+
/** The order's `taxMode` for a quote's basis; absent while nobody has decided. */
|
|
263
|
+
export declare function quoteTaxMode(basis: QuoteTaxBasis, taxCents: number): StorefrontTaxMode | undefined;
|
|
264
|
+
export declare const QUOTE_MAX_LINES = 100;
|
|
265
|
+
export declare const QUOTE_MAX_QUANTITY = 9999;
|
|
266
|
+
/** $1,000,000 a unit: the most a typed line may cost. */
|
|
267
|
+
export declare const QUOTE_MAX_UNIT_CENTS = 100000000;
|
|
268
|
+
export declare const QUOTE_MAX_TEXT = 5000;
|
|
269
|
+
/** A product line as the console asks for it: what it is, never what it costs. */
|
|
270
|
+
export interface QuoteCatalogLineRequest {
|
|
271
|
+
kind: 'catalog';
|
|
272
|
+
productId: string;
|
|
273
|
+
variantId?: string;
|
|
274
|
+
quantity: number;
|
|
275
|
+
modifiers?: ModifierSelection[];
|
|
276
|
+
included?: boolean;
|
|
277
|
+
description?: string;
|
|
278
|
+
}
|
|
279
|
+
/** A line typed on the quote, which no product stands behind. */
|
|
280
|
+
export interface QuoteCustomLineRequest {
|
|
281
|
+
kind: 'custom';
|
|
282
|
+
name: string;
|
|
283
|
+
description?: string;
|
|
284
|
+
sku?: string;
|
|
285
|
+
unitAmountCents: number;
|
|
286
|
+
quantity: number;
|
|
287
|
+
/** Whether it is something to ship (else a service, which never ships). */
|
|
288
|
+
ships?: boolean;
|
|
289
|
+
included?: boolean;
|
|
290
|
+
}
|
|
291
|
+
export type QuoteLineRequest = QuoteCatalogLineRequest | QuoteCustomLineRequest;
|
|
292
|
+
/** Everything a business edits on a quote. */
|
|
293
|
+
export interface QuoteInput {
|
|
294
|
+
lines: QuoteLineRequest[];
|
|
295
|
+
customer: QuoteCustomer;
|
|
296
|
+
billTo: OrderAddress | null;
|
|
297
|
+
shipTo: OrderAddress | null;
|
|
298
|
+
notes: string;
|
|
299
|
+
terms: string;
|
|
300
|
+
discount: QuoteDiscount | null;
|
|
301
|
+
shipping: QuoteShipping | null;
|
|
302
|
+
/** Absent: the store's default (on create) or unchanged (on update). */
|
|
303
|
+
requireProofApproval?: boolean;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* The console's quote, checked and trimmed, or what to fix. Prices on product
|
|
307
|
+
* lines are not read from here: the server prices them from the product.
|
|
308
|
+
*/
|
|
309
|
+
export declare function parseQuoteInput(raw: unknown): {
|
|
310
|
+
ok: true;
|
|
311
|
+
input: QuoteInput;
|
|
312
|
+
} | {
|
|
313
|
+
ok: false;
|
|
314
|
+
error: string;
|
|
315
|
+
};
|
|
316
|
+
/** A priced quote line, and whether its product is tax-exempt. */
|
|
317
|
+
export type PricedQuoteLine = OrderLineItem & {
|
|
318
|
+
taxExempt?: boolean;
|
|
319
|
+
};
|
|
320
|
+
/**
|
|
321
|
+
* A product line priced from the PRODUCT: its variant's price plus the chosen
|
|
322
|
+
* add-ons, as the register prices one. An included line keeps what it is and
|
|
323
|
+
* charges nothing.
|
|
324
|
+
*/
|
|
325
|
+
export declare function priceQuoteCatalogLine(rawProduct: Partial<HostProduct> | null | undefined, request: QuoteCatalogLineRequest): {
|
|
326
|
+
ok: true;
|
|
327
|
+
line: PricedQuoteLine;
|
|
328
|
+
} | {
|
|
329
|
+
ok: false;
|
|
330
|
+
error: string;
|
|
331
|
+
};
|
|
332
|
+
/** A typed line: no product, so nothing reads stock, a supplier or a download for it. */
|
|
333
|
+
export declare function priceQuoteCustomLine(request: QuoteCustomLineRequest): PricedQuoteLine;
|
|
334
|
+
/** The request a stored line is edited as: what the console sends back on a save. */
|
|
335
|
+
export declare function quoteLineRequestFromLine(line: OrderLineItem): QuoteLineRequest;
|