Payments + allocations — receipts to balance
One payments row, one or many payment_allocations rows. Allocate one payment across multiple invoices in one go. Five payment methods. Cross-client allocation rejected. Branded green receipt PDF. Auto-numbered RCT/{YYYY}/{NNNN}. Live invoice balance recompute.
Payment row anatomy
Atomic counter. Issued on insert.
Whose payment. All allocations must be to this client's invoices.
Bank-statement date or cash-received date.
Total received. bcmath scale 3.
If non-OMR, FX captured + OMR-equivalent computed.
bank_transfer · cash · cheque · card · online (Mwasalat / NPCI).
Bank ref, cheque number, transaction ID. Searchable.
Which firm bank account received. Drives bank-rec downstream.
True if payment has zero allocations OR only proforma allocations OR partial tax with leftover. See chapter 7.
"Per email confirmation 12-Apr · advance for FY 2026 audit · client wants receipt before fieldwork."
Allocation table
payment_allocations is a many-to-many join: one payment row, N invoice rows, with each link carrying its own allocation amount.
| Field | Notes |
|---|---|
| payment_id | FK |
| invoice_id | FK · must belong to same client as the payment |
| amount_allocated | DECIMAL(18,3) · cannot exceed (invoice.balance_due) and cannot make Σ allocations exceed payment.amount |
| allocated_at + by | Audit fields |
The 4 allocation patterns
Pattern 1 — full settlement
payment.amount = invoice.balance_due. One allocation row. Invoice flips sent → paid. paid_in_full_at stamped.
Pattern 2 — partial settlement
payment.amount < invoice.balance_due. One allocation row. Invoice flips sent → partially_paid. balance_due recomputes.
Pattern 3 — multi-invoice settlement
One transfer covers 3 invoices. Three allocation rows. Each invoice's balance recomputes independently. Useful when a client says "here's OMR 12,500 covering invoices 39, 40, 41".
Pattern 4 — pure advance
Payment row created with zero allocations (or only against a proforma). Marked is_advance=1. Sits as un-allocated credit until applied via post-hoc allocation form. See chapter 7.
Step-by-step — record + allocate a payment
Open client → Billing card → Record Payment
Or M11 → Payments → New. Client picker if not pre-filled.
Header
Date, amount, currency, method, reference, account. Auto-numbered.
Allocations panel
System lists all open invoices for the client (sent / partially_paid / overdue). For each, an "Allocate" input. As you type allocation amounts, the running unallocated balance shows below.
Validate + save
Σ allocations ≤ payment.amount. Each allocation ≤ invoice.balance_due. If unallocated remainder > 0, payment marked is_advance=1. Save commits.
Side-effects
Each allocated invoice's balance recomputes. Status auto-flips: partially_paid (if remaining balance > 0) or paid (if 0). M17 email queued to client (template payment.received) with receipt PDF attached.
Receipt PDF
Branded green PDF generated automatically. Cached at
storage/payments/{id}/{number}.pdf. Cache busts on any allocation change.
Validation rules
- payment.amount > 0 (no zero-amount payments)
- All allocations to same client as payment (cross-client rejected with 422)
- Each allocation amount > 0 (no zero-amount lines)
- Each allocation ≤ invoice's current balance_due
- Σ allocations ≤ payment.amount
- Cannot allocate to written_off / cancelled / converted invoices
- Cannot allocate to fully-paid invoices (balance = 0)
Editing allocations
Allocations can be added later via the post-hoc form on the payment-show page. Useful for:
- Applying a previously-recorded advance to a new invoice
- Reallocating from one invoice to another (e.g. wrong invoice originally selected)
- Splitting an over-allocation
Each edit re-recomputes affected invoice balances. Audit-logged.
The receipt PDF
| Received from: | Al-Bahja Trading LLC |
| Method: | Bank Transfer |
| Reference: | NBO-TXN-20260412-78421 |
| Allocated to: | INV/2026/0042 — full settlement |
Record a payment of OMR 12,500 against a client with 3 open invoices (each OMR ~5,000). Allocate fully to invoice 1 + invoice 2, partial to invoice 3. Save. Watch invoice 1 + 2 flip to paid, invoice 3 to partially_paid, all live. Receipt PDF auto-generated covering all 3 allocations.
Reference number matters for bank reconciliation. Type the actual bank reference (NBO transaction ID, cheque number) — not "received from John". When M12 Bank Reconciliation runs, it matches by reference. Sloppy refs = manual matching pain.
If a client overpays (e.g. OMR 12,600 received against OMR 12,500 of open invoices), allocate OMR 12,500 across the invoices and the OMR 100 stays as advance. Don't try to refund automatically — apply to next invoice or issue a CN if returning. The advance balance is visible on the client Billing card.