New Quotation Print Design
Business view
Quotations get printed (or PDF'd) and handed to patients as a paper estimate before treatment starts. The old print layout was the same generic table used for invoices. This PR adds an alternative, purpose-built layout for quotations: one row per procedure with the tooth involved, quantity, unit price, price before and after discount, an insurance column when relevant, plus a totals block, an optional diagnosis/details box, and doctor + patient signature lines.
It's opt-in per branch — a clinic turns it on from Settings → Print → the quotation/invoice form, under a "Quotation Design" option. Clinics that don't change anything keep the existing print layout; nothing changes for them automatically. This makes it easy to roll the new layout out to one branch or clinic at a time and get feedback before it becomes the default.
One thing to know: the new layout shows the quotation's total tax as a single combined line, not broken out into the patient-tax / insurance-tax split described in Patient vs. Insurance Tax Split. The split exists in the data model and the editors, but this printout doesn't (yet) surface it separately.
Technical view
Turning it on
The toggle is a Form.Item named quotationDesign in the branch's print-settings form, only rendered for the invoice/quotation form type:
<Form.Item label={t('settings.forms.quotationDesign')} name="quotationDesign">
packages/clinic-web/src/components/dashboard/settings/Print/FormContent.js:746, watched via Form.useWatch('quotationDesign', form) (line 79). Its only real value is "newDesign"; the field is clearable, so unsetting it reverts to the old layout. There's no separate top-level company flag — the value is persisted inside the branch's invoiceJsonForm JSON blob via the existing editForm mutation.
The option's visible label is t('app.new') (FormContent.js:748-750), which resolves to "New Customer" in en.json — a generic string shared with a customer-registration flow elsewhere in the app, not a quotation-specific label like "New Design." Confirmed live in the sandbox (the dropdown genuinely reads "New Customer"). Harmless functionally, but worth a copy fix since it reads as a mismatched/confusing label in this context.
When quotationDesign === 'newDesign', a second field appears: a free-text quotationDetails box (up to 2000 characters, FormContent.js:754-766) that becomes an optional details block on the printed page.
Choosing which layout to render
PageToPrint.js destructures quotationDesign out of the parsed branch form data (branchFormData, PageToPrint.js:339) and switches on it:
) : forQuotation && quotationDesign === 'newDesign' ? (
<NewQuotationDesign lng={lng} quotation={invoice} branchFormData={branchFormData} qrCode={qrCode} user={...} />
) : (
// existing default invoice/quotation table layout
)
PageToPrint.js:415-422. The switch only fires for quotations (forQuotation) with the flag set; invoices always render through the existing default layout regardless of this setting.
What NewQuotationDesign.js renders
packages/clinic-web/src/components/dashboard/settings/Print/NewQuotationDesign.js (517 lines, new file):
- Header (lines 342-356): clinic logo, centered clinic name and date, optional QR code.
- Row data (
rowsmemo, lines 276-314): one row per procedure, built by merging the liveoperations/proceduresarray with the quotation's storedoperationInsuranceJSON (storedOps, lines 254-264) to recover each procedure's discount, insurance amount, and notes, and with the storedorderJSON (lines 266-273) to control display order. Handles both the legacy shape (stored.discountmeant "insurance amount") and the new shape (stored.priceDiscount,stored.insurance) — see the comment at line 279. - Table (lines 358-467): title row, patient ID and name, a header row with
#, service, tooth, qty, unit price before, total before, unit price after, total after, and — only when any row has an insurance amount — an insurance column (hasInsurance, line 324). Per-row notes render as an italic sub-row (lines 414-421). - Totals (lines 426-465): total teeth count (deduplicated across all procedures'
tootharrays, line 315), total before, total after (highlighted), a conditional tax row (hasTax, line 322 — true wheneverquotation.tax > 0, showing the combinedquotation.taxvalue, not a per-source breakdown), a conditional insurance row (summing per-row insurance or falling back toquotation.insurance), and a final grand total. - Diagnosis / quotation details (lines 469-479): optional free-text
quotation.diagnosisand the branch'squotationDetailssetting. - Signatures (lines 481-512): two blocks — Doctor and Patient — each with name/signature/date lines.
Related change: PageToPrint.js
Beyond the new-design branch, PageToPrint.js was adjusted to pass through the extra data NewQuotationDesign needs (branchFormData, qrCode, the resolved user) that the old layout's code path didn't previously need to plumb this far — see the diff around PageToPrint.js:339-422 if extending this layout further.