Dentolize · Quotation Tax Fix & New Design Walkthrough
On this pageBusiness viewTechnical view

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 (rows memo, lines 276-314): one row per procedure, built by merging the live operations/procedures array with the quotation's stored operationInsurance JSON (storedOps, lines 254-264) to recover each procedure's discount, insurance amount, and notes, and with the stored order JSON (lines 266-273) to control display order. Handles both the legacy shape (stored.discount meant "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' tooth arrays, line 315), total before, total after (highlighted), a conditional tax row (hasTax, line 322 — true whenever quotation.tax > 0, showing the combined quotation.tax value, not a per-source breakdown), a conditional insurance row (summing per-row insurance or falling back to quotation.insurance), and a final grand total.
  • Diagnosis / quotation details (lines 469-479): optional free-text quotation.diagnosis and the branch's quotationDetails setting.
  • Signatures (lines 481-512): two blocks — Doctor and Patient — each with name/signature/date lines.

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.