feat(quotes): bypass PDF ready filter when is_no_quote
Lets the PDF render with all active items (regardless of price) when the operator has marked the quote as a decline letter. The existing PDF diagnostic still flags data issues for priced quotes. Also adds the (unused) noQuote prop to QuoteDocumentProps so the route compiles. Task 8 will wire the rendering.
This commit is contained in:
parent
1d6f7538d8
commit
cfcbbc5f10
2 changed files with 39 additions and 2 deletions
|
|
@ -146,6 +146,14 @@ export async function POST(
|
|||
const quote = await pb.collection("quotes").getOne<Quote>(quoteId, {
|
||||
expand: "customer",
|
||||
});
|
||||
// No-quote / decline-letter flag. When true, we render every
|
||||
// active item through to the PDF regardless of price — this is
|
||||
// the PDF companion to the no-quote feature (Tasks 1-4 of the
|
||||
// plan). The PDF component itself will render "Not quoted" cells
|
||||
// for these rows (Task 8). DiagnosePdfReadiness was relaxed in
|
||||
// Task 5 so priced quotes still surface their gaps; for
|
||||
// is_no_quote quotes, we just render.
|
||||
const isNoQuote = !!quote.is_no_quote;
|
||||
const customer = quote.expand?.customer as Customer | undefined;
|
||||
if (!customer) {
|
||||
return NextResponse.json(
|
||||
|
|
@ -214,7 +222,22 @@ export async function POST(
|
|||
}
|
||||
}
|
||||
|
||||
if (ready.length === 0) {
|
||||
// itemsForPdf: what we hand to <QuoteDocument>. For priced quotes
|
||||
// this is the priced/processed subset (the `ready` array above).
|
||||
// For no-quote / decline letters we bypass the filter entirely
|
||||
// and render ALL active items — the PDF component itself decides
|
||||
// how to render the price/total cells (Task 8 wires the "Not
|
||||
// quoted" text in those columns). This is the third pricing-gate
|
||||
// relaxation of the no-quote plan; Tasks 4-6 covered submit /
|
||||
// diagnose / send.
|
||||
//
|
||||
// We keep `ready` populated for the downstream status-flip logic
|
||||
// (decides approval_pending vs partial_approval_pending) and the
|
||||
// partial-quote note below ("5 of 15 items"), both of which still
|
||||
// apply to priced quotes.
|
||||
const itemsForPdf = isNoQuote ? allItems : ready;
|
||||
|
||||
if (!isNoQuote && ready.length === 0) {
|
||||
const body: ValidationErrorBody = {
|
||||
error:
|
||||
allItems.length === 0
|
||||
|
|
@ -258,12 +281,13 @@ export async function POST(
|
|||
// Render
|
||||
const pdfBuffer = await renderQuotePdf({
|
||||
quote,
|
||||
items: ready,
|
||||
items: itemsForPdf,
|
||||
customer,
|
||||
letterhead,
|
||||
logoDataUri,
|
||||
partialNote,
|
||||
quoteNotes: quote.quote_notes ?? null,
|
||||
noQuote: isNoQuote,
|
||||
});
|
||||
|
||||
// Build a stable, human-readable filename
|
||||
|
|
|
|||
|
|
@ -106,6 +106,18 @@ export interface QuoteDocumentProps {
|
|||
* Added 2026-08-17.
|
||||
*/
|
||||
quoteNotes?: string | null;
|
||||
/**
|
||||
* When true, the quote is being rendered as a "decline / no-quote"
|
||||
* letter instead of a priced quotation. The PDF route bypasses the
|
||||
* normal price/processed/supplier filter when this flag is set, and
|
||||
* passes every active item through. The component itself decides
|
||||
* how to render the per-row cells (Task 8 will wire the
|
||||
* "Not quoted" text in the price/total columns).
|
||||
*
|
||||
* Default false = standard priced quotation. Added 2026-08-26
|
||||
* alongside the no-quote / decline-letter feature.
|
||||
*/
|
||||
noQuote?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -130,6 +142,7 @@ export function QuoteDocument({
|
|||
logoDataUri,
|
||||
partialNote,
|
||||
quoteNotes,
|
||||
noQuote: _noQuote,
|
||||
}: QuoteDocumentProps) {
|
||||
const subtotal = quoteSubtotal(items);
|
||||
const vat = vatAmount(subtotal);
|
||||
|
|
|
|||
Loading…
Reference in a new issue