Vouchers
The Receipt, Payment and Contra forms under Finance → Vouchers. Each one posts a balanced double-entry journal.
A voucher written from these forms lands pending and waits for someone else (Maker-checker). Once approved it can never be edited or deleted (FIN-031); a mistake is corrected by a reversal and a new entry (Journals).
1. Types
| Voucher | Debit | Credit | Used for |
|---|---|---|---|
| Receipt | cash / bank account | customer, agent or supplier-refund source | money coming in |
| Payment | supplier, expense, agent commission or booking | cash / bank account | money going out |
| Contra | target cash/bank account | source cash/bank account | a transfer between two of our own accounts |
Prefixes: RV, PV, CT. Both forms refuse a group (non-leaf) account, an inactive
account, and an account on the wrong side of the entry.
A customer receipt against a booking is a payment, not a voucher
On the Receipt form, a customer receipt with a booking or invoice chosen is sent to
record a payment → verify it (POST /finance/payments, or
/finance/payments/batch for several bookings). That path mints a receipt number, dates
the voucher by the day the money arrived, and requires a second person. It needs
finance.payments.record, so a cashier can use it. The on-account receipt endpoints
below exist for money that is not yet applied to a booking.
What the form offers. Only the vouchers the user may post are shown, each by the permission its route asks for (ACC-021):
| On the form | Route | Permission |
|---|---|---|
| Receipt — customer, against a booking or invoice | /finance/payments, /finance/payments/batch |
finance.payments.record |
| Receipt — customer, on account (no booking or invoice) | /finance/vouchers/customer-on-account-receipt |
finance.edit |
| Receipt — supplier refund, business partner | /finance/vouchers/supplier-refund-receipt, /agent-on-account-receipt |
finance.create |
| Payment, Contra, Debit Note, Credit Note, Settlement, Journal | their own routes | finance.create |
A cashier (finance.view + finance.payments.record) sees only Receipt, with the party
type Customer. Post Voucher shows once a booking or invoice is chosen.
The preview says what happens next. A receipt against a booking stays pending until another finance user verifies it. An on-account, supplier refund or business partner receipt stays pending until a second finance user approves its voucher. None of them posts at once.
2. Party sub-ledgers
Every party posts to its own ledger account, never to a control account:
| Prefix | Party |
|---|---|
CUS-xxxxxxxx |
customer receivable |
AGR-xxxxxxxx / AGP-xxxxxxxx |
agent receivable / payable |
SUP-xxxxxxxx |
supplier creditor |
The suffix is the first eight characters of the party id. SUP-* accounts hang under
Sundry Creditors → Current Liabilities → Liabilities.
Supplier payments used to miss the sub-ledger
A supplier purchase posted to SUP-xxxxxxxx while the payment for it posted to the
2100 control account, so the supplier never showed as paid (finance audit 2026-09-17,
P1 #16). Purchases and payments now both settle the supplier's own ledger. The same
fix corrected the airline-block overpayment guard, which compares payments against the
block cost instead of subtracting the purchase invoice.
If a party ledger does not exist yet the posting is refused by name — "Receivable ledger X not found — open the party ledger first" — rather than falling back to a control account.
3. Foreign currency
When the amount is not in INR, the posting stores the rate on the header (fxRateUsed) and
the original amount, currency and rate on each line. Airline-block and FIT purchase,
initial-payment and adjustment vouchers refuse to post without a rate, and group P&L
refuses to count a foreign contract that has none
(FIN-034).
Pure-INR postings leave the original-currency columns null — nothing was converted.
The realised gain/loss lines carry the real difference and balance. This is still computed in the browser, not the database; FIN-034 records that as a gap.
4. Endpoints
handleFinanceVouchers in src/lib/api.ts.
| Method | Path | Permission |
|---|---|---|
| POST | /finance/vouchers/customer-on-account-receipt |
finance.edit |
| POST | /finance/vouchers/agent-on-account-receipt |
finance.create |
| POST | /finance/vouchers/agent-receipt-allocate |
finance.payments.record |
| POST | /finance/vouchers/supplier-refund-receipt |
finance.create |
| POST | /finance/vouchers/debit-note |
finance.create |
| POST | /finance/vouchers/credit-note |
finance.create |
| GET | /finance/vouchers/customer-on-account-receipts/:customerId |
finance.view |
| POST | /finance/vouchers/customer-on-account-receipts/:id/allocate |
finance.edit |
On-account receipts are idempotent on the combination of party, amount, method, source
account, reference and date — a retry returns the original with deduplicated: true.
5. Permissions
| Action | Permission |
|---|---|
| Post a receipt (supplier refund, business partner), payment or contra | finance.create |
| Post a customer receipt on account | finance.edit |
| Allocate an on-account receipt | finance.edit |
| Record a customer payment (a receipt against a booking or invoice) | finance.payments.record |
| Verify a recorded payment | finance.payments.verify |
| Reject a recorded payment | finance.payments.verify or finance.payments.reject |
| Raise a refund | finance.payments.refund |
| Approve a refund | finance.refunds.approve (plus the amount limit) |
| Approve or reject the resulting voucher | finance.journals.approve / .reject (plus maker-checker) |
6. Not built yet
Credit and debit notes are posted by hand here. FIN-012
(LAW, CGST s.34) asks for them to be produced automatically against the original invoice
when a price drops or a passenger cancels. An issued invoice is already protected —
Invoice_guard refuses any edit to its amount, GST, number, booking or issue date — but the
note that should follow a change is still a manual step.