Dentolize · Invoice Discount Percentage Fix Walkthrough
On this pageBusiness viewTechnical view

How invoice totals are built

This page explains the handful of money fields on an invoice and how they relate, because that relationship is the whole reason the discount fix is correct.

Business view

Think of a single invoice as a small receipt:

  1. Subtotal — add up the list price of every procedure on the invoice.
  2. Discount — subtract whatever the clinic knocked off.
  3. Tax — add any VAT that applies (many dental invoices are VAT‑exempt, so

this is often zero).

  1. Total — what's left. This is the figure that flows into the table and the

analytics tiles.

A real example straight from the sandbox (invoice #26):

LineAmount
Subtotal₺500.00
Discount− ₺75.00
Tax+ ₺63.76
Total₺488.76

The key takeaway: by the time you see "Total", the discount has already been taken off. That's why, to talk about the discount as a percentage, you first have to add it back.

The analytics bar simply adds up these per‑invoice figures across whatever set of invoices you're looking at, and shows each column as a tile.

Technical view

The canonical formula

packages/server/src/resolvers/mutations/actions/invoices/invoiceUtils.js:205

const total = Number(subtotal.toFixed(2)) - Number(discount.toFixed(2)) + Number(tax.toFixed(2))

The same invariant is enforced per line item at invoiceUtils.js:118:

if (Number(item.total.toFixed(2)) !== Number((item.subtotal - item.discount + item.tax).toFixed(2))) hasError = true

So total = subtotal − discount + tax holds at both the line and invoice level. These computed values are written straight to the invoice row in packages/server/src/resolvers/mutations/actions/invoices/createNewInvoice.js:232 (total, discount, subtotal, discountPercent, …).

The stored Invoice fields

packages/prisma/schema.prisma:2620 (Invoice model) carries, among others:

FieldMeaning
subtotallist price of all procedures, pre‑discount
discountmoney discounted
discountPercentstored percentage, computed as discount / subtotal
tax / taxPercentVAT amount / rate
totalsubtotal − discount + tax (net of discount)
paidamount paid so far
pendingPaymentamount awaiting confirmation
insuranceinsurer‑covered portion
diagnosticFeediagnostic fee component
releasedamount released to clinic revenue

What feeds the analytics bar

The tiles are fed by the invoicesInfo query.

  • Schema: packages/server/src/schema.graphql:226 → returns [InvoiceGroupInfo]!
  • Type: packages/server/src/types.graphql:4059InvoiceGroupInfo exposes

balanceInvoice, paid, tax, discount, diagnosticFee, pendingPayment, total, balance, released, insurance.

  • Resolver: packages/server/src/resolvers/queries/patientQueries.js:2881 — a

prisma.invoice.groupBy({ by: ['balanceInvoice'], _sum: { … } }) that sums each money field over the invoices matching the current filters.

The frontend then reduces those grouped sums into the numbers behind each tile (see the discount fix).

The relationship that makes the fix work

Because total = subtotal − discount + tax:

total + discount = subtotal + tax     ← the pre-discount, tax-inclusive base
total + discount − tax = subtotal      ← the pre-discount, pre-tax base

The new denominator total + discount is the first of these. It is the correct "before any discount" figure for a tax‑inclusive view, and it is exact for the many VAT‑exempt invoices where tax = 0.