The consult fee gate, the fee cascade, and no credit book

By Abhishek Dogra, Founder, ElaiorLast verified

A consult cannot start until the fee for that visit is settled. The rule is one function of nineteen lines in the Go backend, called from two places, standing in front of the only production statement in the codebase that can move a patient into a consult room. What it checks, what sets the amount, and where it stops.

What the gate checks

assertConsultFeeCollected passes when the visit's consultFeePaise column is NULL, or zero or below. Otherwise it wants one Invoice row for the actor's clinic and this visit, scope CONSULT, status PAID. Nothing else counts: not an ISSUED invoice, not a part paid one, not a REFUNDED or VOID one, not a paid procedure or pharmacy bill. Without it the call returns "Collect the consult fee first".

SELECT 1 FROM "Invoice"
WHERE "clinicId" = $1 AND "visitId" = $2 AND scope = 'CONSULT' AND status = 'PAID'
LIMIT 1

Enforced, guarded, or only shown

UPDATE "QueueEntry"
SET status = 'IN_CONSULT'::"QueueStatus", "calledAt" = $2, "updatedAt" = $2
WHERE id = $1 AND status::text = ANY($3::text[])

That UPDATE is the only production write that puts a card into IN_CONSULT, in a helper reached from those two verbs alone. Grep IN_CONSULT across the backend and the claim settles in a minute.

The client is deliberately not told the rule

The web app never works out for itself whether a patient has paid; it reads a boolean the server computed. The evidence is the doctor's auto send loop: it picks the next callable patient by status, held slot and form purpose, never by consultPaid, fires queue.startConsult at them, then handles the refusal by parking that patient and showing the server's message. The only client side courtesy is one disabled button in the preview drawer; the consult room's inline Start consultation button is not gated on consultPaid at all, so it stays live and the server stops it. The front desk has no admit control, only a Collect button.

What sets the fee in the first place

  1. Resolve the free revisit window: the patient's override, then the doctor's on the Membership row, then the clinic default, then a fallback in the code. Every real number is per clinic.
  2. Choose the instant to measure from: now for a walk in, the booked slot for an appointment. A follow up booked today for next month is not free.
  3. If the last live visit with this doctor falls inside that window, the fee is zero and the amount cascade is never consulted.
  4. Otherwise the amount cascades: what was typed now, then the doctor's rate, then the clinic default.

The zero therefore beats a typed amount here, since the branch is taken before the cascade is reached. A soft deleted visit cannot anchor a free revisit: the read that decides the money filters deletedAt IS NULL, the desk's hint filters the same way, and a test fails if they disagree. Correcting our own docs: there is no stored per patient consult fee. The first cascade slot is what the desk types now; the stored patient level override is the window, Patient.freeRevisitDays. The fee is then a snapshot, written by four INSERT sites and updated by nothing.

Why there is no patient credit book

The settle function takes no amount. It reads the invoice's grand total, flips ISSUED to PAID under a guard where any row count but one is a refusal, sets paidPaise to that total, and inserts one Payment row for the remaining balance. No registered action adds a payment of a caller chosen amount. A zero fee creates no invoice, so a free revisit leaves nothing to settle.

Where the mechanism stops

A gate described only by what it catches is a slogan. One described with its edges is something an owner can plan a front desk around.