For Quality
What to test
Tax calculation
- Patient with no insurance: tax should equal the old flat-rate calculation,
(subtotal - discount) * patientTaxPercent / 100. - Patient with insurance where insurance company tax rate == patient tax rate: total should match the flat-rate calculation exactly — this is the "equal rates" collapse path (
quotationUtils.jsper-op branch,QuotationDrawer.js:379). - Patient with insurance where insurance company tax rate != patient tax rate: tax should be the sum of
insurance-covered part × insurance rateandpatient-paid part × patient rate, computed separately — verify against manual arithmetic, not just "the number changed." - Mixed quotation: some procedures insured, some not, within the same quotation — confirm only the insured lines get the insurance-rate treatment.
- A procedure with
taxApplied: falseshould contribute0tax regardless of insurance status. - Manually typing into the quotation-level Tax or Tax% field should force
patientTaxPercent === insuranceTaxPercentgoing forward for that quotation (QuotationDrawer.js:621-622,653-654) — confirm the per-line split visibly collapses and stays collapsed after save/reload. - Editing a procedure's discount via the Discount modal should recompute that line's insurance amount, tax, and total, and the quotation-level total should update via
updateValuesFromLineItems.
Recalculation triggers (server-side)
handleUpdateQuotations in quotationUtils.js runs on several indirect triggers — each is worth its own test (confirmed call sites):
- Converting a quotation to an invoice —
packages/server/src/resolvers/mutations/actions/invoices/createNewInvoice.js. - Deleting an invoice that was converted from a quotation —
packages/server/src/resolvers/mutations/actions/invoices/deleteInvoice.js. - Adding/removing operations on an existing invoice tied to a quotation —
addOperationsToInvoice.js,removeOperationFromInvoice.js. - Editing or deleting chart operations referenced by a quotation —
patient/saveOperations.js,patient/deleteOperations.js(e.g. changing a procedure's price ortaxAppliedflag after it's already on a quotation). - Confirm old quotations (pre-migration,
patientTaxPercent/insuranceTaxPercent=0) recalculate using the legacy flattaxPercentrather than treating0%as a real insurance tax rate — this is thepatientTaxPercent || topTaxPercent/insuranceTaxPercent || topTaxPercentfallback inquotationUtils.js(lines 33-34). A real insurance tax rate of exactly0%is indistinguishable from "not set" in this logic — worth a dedicated edge-case test.
Insurance limit display — verified regression, not a pre-existing gap
This is new, not carried over from before: the diff shows the old code computed insuranceLeftToUse as patientDetails.insuranceLimit - totalInsuranceUsed where totalInsuranceUsed = patientDetails?.dailyInsurancePaid ?? 0. The new code drops that subtraction entirely — totalInsuranceUsed is now hardcoded to 0 (QuotationDrawer.js:1319), and the mobile insuranceLeftToUse (NewQuotationScreen.js:144) is likewise just Math.max(insuranceDetails.insuranceLimit || 0, 0) with no usage subtracted.
- Test: create a patient, use some of their daily insurance limit via an invoice, then open a new quotation for them — the quotation's "insurance remaining" should reflect the reduced amount but currently will not.
- File this as a bug for a product/engineering decision rather than assuming it's intentional — see For Stakeholders.
- Confirm the Invoice screen's equivalent figure is unaffected (out of scope for this PR) — if it also regressed, that's a more urgent escalation.
- Test the Saudi-only max-limit checkbox: toggling it per procedure should not, by itself, recompute totals — verify this matches intended behavior rather than being an oversight.
Print design
- The dropdown's only real option (value
newDesign) is labeled "New Customer" in the UI, not "New Design" — it reuses the genericapp.newtranslation key (en.json:"app": { "new": "New Customer" }), which is presumably meant for a customer/lead-registration flow elsewhere in the app. Confirmed live in the sandbox. This isn't a functional bug, but it's confusing enough to flag for a copy fix — file it rather than assume it's intentional. - With "Quotation Design" unset: printed quotation uses the legacy layout, unchanged.
- With "Quotation Design" = New: verify the Insurance column only appears when at least one procedure has an insurance amount (
hasInsurancecheck inNewQuotationDesign.js) — a fully non-insured quotation shouldn't show an empty Insurance column. - Verify per-procedure notes print correctly under their row, and rows respect the manually-set Order value from the editor.
- Verify the Quotation Details free-text box only appears when text has been entered in branch settings, and respects the 2000-character limit.
- Verify the printed tax line shows one combined figure even when the quotation has a patient/insurance split — confirm this is accepted as intentional scope for this PR (the split doesn't reach the printout) rather than a missed requirement.
- Test RTL rendering (Arabic) —
NewQuotationDesign.jshas explicit.rtlstyling branches for the table and signature blocks (lines 98-99, 165-166, 226, and thertlclass toggle based onlng === 'ar'at lines 358, 482, 497). - Multi-branch: confirm the setting is genuinely per-branch and doesn't leak to other branches.
Cross-platform parity
- Mobile (
NewQuotationScreen.js) reimplements the same tax-split logic independently rather than sharing code with web — run the same tax-split test matrix on mobile and confirm identical totals for the same input data. - Confirm a quotation created on mobile and edited on web (or vice versa) round-trips correctly. Note: mobile's internal field name for a line item's procedure ID is
operationIdwhile web's isid, but both serialize to the sameidkey in the persistedoperationInsuranceJSON (QuotationDrawer.js:1194-1208,NewQuotationScreen.js:960-975) — so this is an internal naming difference only, not expected to cause a cross-platform data bug, but still worth a round-trip test given how much of this logic was rewritten independently on each platform.
Unused plumbing to be aware of (not a bug, but don't assume it works)
pendingPaymentPercent is a declared GraphQL argument on both mutations and read server-side (patientMutations.js), but neither QuotationDrawer.js nor NewQuotationScreen.js sends it — confirm no test relies on setting a custom pending-payment percentage from either app, since there's currently no UI path to do so.
Edges likely to be under-tested
- Insurance company
taxPercentexplicitly set to0vs. left unset — these should behave differently (explicit0%insurance tax vs. "use patient rate") per thetypeof ... === 'number'style check used when derivinginsuranceTaxPercent, but it's an easy distinction to get wrong in manual testing. - A quotation edited enough times that its
operationInsuranceJSON mixes "legacy" entries (nopriceDiscount) and "new" entries (priceDiscountpresent) — the legacy-fallback logic in the per-op build loop is a plausible source of subtle discount miscalculation if the two shapes are mixed within one quotation. - Floating-point rounding: the totals aggregator subtracts a small epsilon (~0.001) before rounding in places — worth a test with many low-value line items to confirm totals don't drift by a cent.