Dentolize · Balance Invoice Webhooks & API Config Cleanup Walkthrough
On this pageBusiness viewTechnical view

Payload accuracy fixes

Business view

This PR fixes two bugs that would have silently produced wrong data in webhook payloads and API responses. Neither is visible in the Dentolize UI — they only affect what gets sent to an integration or what the invoice reference number ends up being — so they're the kind of bug that's easy to ship unnoticed and hard to debug after the fact.

It also adds a handful of fields to the existing webhook payloads (email, gender, birth date on patients; tooth number on invoice lines and operations; a referral-source block) that were already available in Dentolize but weren't being sent before.

Technical view

Bug: insurance policy name was always undefined

getOtherDataFromPayload builds an insurance_policy_data block for NEW_PATIENT and NEW_INVOICE (and now the balance variants) payloads. It read:

insurance_policy_data: payload.insurancePolicy
  ? { id: payload.insurancePolicy.id, name: payload.insurancePolicy.name }
  : {}

The InsurancePolicy Prisma model has no name column — it has holderName. So payload.insurancePolicy.name was always undefined, and every webhook payload that included an insurance policy sent name: undefined (dropped entirely once JSON-serialized) instead of the policyholder's actual name. This PR changes both occurrences to payload.insurancePolicy.holderName:

packages/server/src/apis/apiConfig/otherAPIs.js:35 (patient payload) and packages/server/src/apis/apiConfig/otherAPIs.js:120 (invoice payload).

The fix required the callers to actually select holderName from the database — previously several select clauses only fetched insurancePolicy: { select: { id: true, name: true } }, which would have returned name: undefined from Prisma too (selecting a non-existent field is simply ignored). This PR updates those selects to insurancePolicy: { select: { id: true, holderName: true } } in:

  • packages/server/src/resolvers/mutations/actions/appointments/addPatientBalance.js:160
  • packages/server/src/resolvers/mutations/mutationUtils/paymentUtils.js:1549

Bug: balance invoice reference number written to the wrong object

In the overpayment-to-balance flow (packages/server/src/resolvers/mutations/mutationUtils/paymentUtils.js), after the transaction commits, updateInvoiceReferenceId is called to assign a sequential reference number to the newly created balance invoice:

const updatedInvoiceReference = await updateInvoiceReferenceId({
  invoiceId: balanceInvoice.id,
  prisma,
  companyId: request.session.user.company.id
})

The call itself was always correct — invoiceId: balanceInvoice.id. The bug was in what happened to the result: it was assigned back onto updatedInvoice.referenceId (the original, already-paid invoice) instead of balanceInvoice.referenceId (the new balance invoice that the reference number was actually generated for):

// before
updatedInvoice.referenceId = updatedInvoiceReference.referenceId
// after (paymentUtils.js:1614)
balanceInvoice.referenceId = updatedInvoiceReference.referenceId

Practical effect of the bug: updatedInvoice becomes part of the mutation's GraphQL response (createdPaymentData.invoice), so the original invoice's referenceId in the API response would have been silently overwritten with the balance invoice's reference number — a value that belongs to a different invoice entirely. Meanwhile balanceInvoice.referenceId (used in the new NEW_BALANCE_INVOICE webhook payload added by this PR) would have stayed at its default/stale value. Fixing the assignment target fixes both: the original invoice keeps its own reference number, and the balance invoice webhook payload carries the correct one.

New fields added to existing payload shapes

None of these are new event types — they're additions to payloads that already existed, so any integration parsing them today just gets more data, not breaking changes:

packages/server/src/apis/apiConfig/otherAPIs.js:

  • Patient payload (NEW_PATIENT): adds email, gender, birthDate

(lines 17-19), and a referral_data: { id, name } block sourced from payload.referralSource (line 44).

  • Invoice line items (NEW_INVOICE / NEW_BALANCE_INVOICE): each entry

in invoice_line_ids gains a tooth field (line 80).

  • Operation payload (NEW_OPERATION): gains a tooth field (line 164).

These reflect data that already exists on the corresponding Prisma models (Patient.email, Patient.gender, Patient.birthDate, Invoice.referralSource, Operation.tooth) — the payload builder simply wasn't forwarding them before.