Dentolize · Treasury & Step Payments Fixes Walkthrough
On this pageBusiness viewTechnical view

Treasury on Diagnostic Fees

Business view

When a patient is booked for an examination, the clinic often collects the diagnostic fee immediately. In the New Appointment form the receptionist picks Collected, types the amount, and chooses which treasury the cash goes into — the physical drawer or bank account that money is now sitting in. Dentolize then auto-creates an invoice for that fee.

The bug: on the mobile app, after the receptionist tapped a treasury in the picker, the treasury field went back to empty. The selection did not stick. In practice that meant one of two bad outcomes:

  • The form could not be saved because a required field looked blank, or
  • It saved with no treasury, so the collected cash was not tied to a drawer and

the end-of-day reconciliation did not add up.

The fix: the picked treasury now lands in the field and stays there, so the fee is recorded against the correct treasury and the invoice is created cleanly.

Nothing about how you collect a diagnostic fee changes — you still choose Collected → amount → treasury. The field simply works now.

Technical view

The screen

packages/clinic-mobile/src/components/dashboard/calendar/newAppointment/NewAppointmentScreen.js

The appointment form uses a react-hook-form instance and reacts to route.params — the parameters passed back from a sub-selection screen. When the user returns from picking a treasury (room, doctor, etc.), an effect writes the returned value into the form with setValue(...).

The relevant effect is at NewAppointmentScreen.js:445-460:

if (route.params.name === 'doctor') {
  setDoctor(route.params.doctor.id)
} else if (route.params.name === 'room') {
  setRoom(route.params.room)
}
if (route.params?.name === 'treasury') {
  setValue('treasury', route.params.item, {   // <-- the fix
    shouldValidate: true
  })
} else {
  setValue(route.params.name, route.params[route.params.name], {
    shouldValidate: true
  })
}

Before the PR, every field went through the single generic branch:

setValue(route.params.name, route.params[route.params.name], { shouldValidate: true })

i.e. setValue('treasury', route.params.treasury, …).

Why the generic branch broke treasury

The picker is the shared SelectScreen. When an item is chosen, its handleSelect dispatches params back to the parent screen at packages/clinic-mobile/src/common/screens/SelectScreen.js:151-160:

const handleSelect = item => {
  navigation.dispatch({
    ...CommonActions.setParams({
      item,                        // the FULL object
      type,
      name,
      [name]: item?.name ?? item   // <-- for a treasury, this is the NAME STRING
    }),
    target: route.params.parentKey,
    source: key
  })
}

So for a treasury, the parent receives:

  • route.params.item → the treasury object { id, name, … }
  • route.params.treasury ([name]) → item?.name ?? item → the treasury's

name string

The old generic code read route.params.treasury, so it called setValue('treasury', "Main Cash") — a bare string.

The TreasuryField component (packages/clinic-mobile/src/common/controlledFields/TreasuryField.js) stores and renders treasuries as objects. Its wrapped SearchField renders the current value with renderTitle={value => value?.name}. Given a plain string, value?.name is undefined, so the field displays nothing — the "empty field" symptom. It also means the value submitted to the server was a string, not the treasury the rest of the code expects.

The fix

The new treasury branch reads route.params.item — the full object — so setValue('treasury', { id, name, … }) gives the field exactly the shape it renders and submits. shouldValidate: true re-runs validation so any "required" error clears once a real treasury is present.

Scope / honesty note

  • This fix is mobile-only. The identical field on the web New

Appointment form (shown in the walkthrough) uses an Ant Design dropdown that already keeps the object, so the web form was not affected. The web screenshot is included because it shows the same workflow and the same treasury form field name.

  • The default treasury is still pre-filled by TreasuryField from the branch's

configured defaultTreasury for the ${mainType}_${type} treasury type (TreasuryField.js, the BRANCH_DETAILS query onCompleted handler); the bug only affected changing it via the picker.