Skip to content

Approvals

The operations half of the booking approval handshake, and the corrections queue.

Rules: LC-002 (allowed transitions), LC-010 (maker ≠ checker), ACC-030 (approval limits).


1. The two stages

A booking is approved twice, by two different people, neither of whom may be the person who created it:

Stage Who Function Route
Operations an approver holding approvals.approve ops_decide_booking POST /operations/bookings/:id/ops-approve
Finance an approver holding approvals.approve or finance.bookings.approve_finance finance_decide_booking POST /finance/bookings/:id/finance-approve

Operations checks that the booking is complete and the inventory exists. Finance then approves, rejects, or sends it back.

/approvals (src/pages/approvals/Approvals.tsx, gated on approvals.view) is the ops queue. The finance queue is Finance → Approvals.

The paging bar under the list shows rows per page (25, 50 or 100), which bookings are on screen out of how many ("1–25 of 312") and first, previous, next and last page — even when they all fit on one page (UX-030).

Not twin-tracked any more

These used to be two independent flags — opsApprovalStatus and financeApprovalStatus — that a screen could set in any order. They are now stages of one status. A booking reaches finance only after operations has approved it, and neither flag can be written from the browser.

2. What each decision does

Approve (ops) — re-runs the readiness check (Bookings §3) and re-checks group capacity under a lock, then moves the booking to PENDING_FINANCE. If the booking is not ready it is refused by name: "Booking cannot be approved: Date of birth missing for …".

Send back — moves the booking to NEEDS_CORRECTION and records which stage sent it back. A reason of at least three characters is required. resubmit_booking returns it to exactly that stage, not to the beginning.

Reject (finance) — REJECTED, with a reason. Whether sales may resubmit a rejected booking or must create a new one is an open question (LC-OPEN-2); today REJECTED is terminal apart from cancellation.

Both decisions are idempotent — a second click on an already-decided booking returns alreadyDecided rather than acting twice.

3. Maker ≠ checker

Both functions refuse the creator, for approve and for send-back:

Maker-checker: you cannot approve or send back a booking you created (LC-010)

The approver recorded is always auth.uid(). Booking.createdBy is stamped from the session and cannot be edited afterwards.

4. Corrections

/corrections (src/pages/approvals/CorrectionsQueue.tsx) lists bookings in NEEDS_CORRECTION so sales can fix and resubmit them. Assignment is POST /operations/bookings/:id/corrections/assign, written by assign_booking_correction, which checks approvals.approve itself. Before 20261008140000 the browser wrote the owner on the row, which needs bookings.edit, so an approver without it (finance) saw "Owner updated" while nothing changed (ACC-020).

Resubmitting needs bookings.edit and a reason, and re-runs the full readiness check. A partner resubmits its own sent-back booking from the partner portal instead (PTR-023).

The corrections queue has the same paging bar as the approvals queue. Its owner filter narrows the rows already loaded, so with an owner picked the bar keeps fetching until a page is full and counts "N+" until the end of the queue is reached.

5. Other things that need a second person

The approvals queue is not the only place segregation of duties applies. The full list is in Maker-checker; the ones an operations user meets are:

  • Cancellations — requested by one person, approved by a holder of bookings.cancel.approve (Bookings §6).
  • Seat releases — requested with inventory.release.request, approved with inventory.release.approve, and above the seat limit inventory.release.approve_large (Holds, deadlines and releases).
  • Ticket exceptions — requested with tickets.edit, decided with tickets.approve by someone else (Tickets).
  • Refunds and vouchers — Receipts and refunds, Journals. A reversal is approved only by someone who made neither the voucher nor the reversal (ACC-020). Above ₹25,000, every voucher a person writes — journal, contra, on-account receipt, reversal on custom lines — is approved by the CEO or GM; a finance manager sees "₹30,000 is above your approval limit for journals (₹25,000). It needs CEO or GM." (ACC-030, limits).
  • A partner over its credit limit — a booking that would take a partner over its limit is refused, for staff as for the partner. A finance manager, the CEO or the GM (partners.credit.override) may save it with a reason, which is recorded; there is no queue for it — the person saving asks one of them (PTR-030, Partners).
  • Pending Refunds (Finance → Reports → Pending Refunds) — two kinds of filed cancellation wait here for finance, each tagged in the list:

    • Airline — seats on an airline block or FIT cancelled with the airline, with the airline's refund still to come (AIR §19, FIN-044). Finance approves it when the money arrives, or takes it as a supplier credit note: the bank receipt (or the supplier credit) is posted and the Cancellation Refund Receivable is cleared; a difference from the expected refund goes to Cancellation Charges. finance.cancellations.approve_airline.
    • B2B Buyer — seats we sold to another agency, cancelled by the buyer (INV-014). Approving posts the reversal of the sale and credits the buyer's receivable with the refund; when the seats were also cancelled with the airline, the supplier side is posted too. The person who filed it cannot decide it. finance.cancellations.approve_b2b.

    Rejecting either one reverses the filing and puts the seats back. The banner on the screen says the same. Money the airline returns outside a filed cancellation is recorded on the block or the FIT instead (Record refund from the airline, Airline blocks). That refund is a pending voucher in the approvals inbox, not a row in Pending Refunds.

6. What is not built

  • Routing by value. ACC-030 says a request above a limit should be routed to the next approver. Refund and manual-journal limits are enforced at the moment of approval, but nothing routes a queue item to the right person. The approvals inbox shows everything the viewer may decide.
  • One unified approvals inbox across bookings, finance, cancellations, releases and ticket exceptions — Wave 4. Today each lives on its own screen, with the role dashboard's approvals.inbox widget pulling them together for a count.
  • "Last materially edited" as a bar to approving — only the creator is tracked (LC-010).

7. Permissions

Action Permission
See the queue approvals.view
Approve, reject, send back a booking approvals.approve
Finance decision on a booking approvals.approve or finance.bookings.approve_finance / .reject_finance
Approve a cancellation bookings.cancel.approve
Approve / reject an airline cancellation refund (Pending Refunds) finance.cancellations.approve_airline
Approve / reject a B2B buyer cancellation (Pending Refunds) finance.cancellations.approve_b2b
Resubmit after correction bookings.edit
Save a partner booking over the partner's credit limit, with a reason partners.credit.override

How the screen loads

The operations queue and its money tiles load with one request (PRF-010): approvals_screen, which is the bookings list's own page (booking_list_page, as you) with booking_stats over the same filters beside it (was 6 requests, 5 one after another). Opening a booking's detail still asks for it when the dialog opens. Routes: Operations screens API.

Dates and order

The operations approval queue and the corrections queue take the bookings list's search, a Booked date range and a sort (booked on, last updated, booking number, amount), oldest first by default because a queue is worked from the front. Both show the booking's own number (UX-030).