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
TreasuryFieldfrom 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.