Billing the job — quote → invoice → receipt
Every job ties back to one revenue stream: the original M10 quote (if there was one), the M11 tax invoice (drafted on completion or manually), the M11 payment(s). Advance-payment tracking, partial settlement, multi-invoice splits all supported.
The revenue path
Auto-draft on completion
When job status moves to completed AND m11.auto_invoice_on_completion=1, the system runs:
Resolve the parent quote
If the job was created from a converted quote (
job.parent_quote_id), use that as the source of fee + line items. Otherwise fall back tojob.estimated_fee+ a single line "Audit fee FY {year}".Draft the invoice
doc_type=tax_invoice, status=draft, auto-numbered
INV/{YYYY}/{NNNN}. Inherits client billing T&Cs (override or firm default), VAT category from client.vat_category, OMR currency.Apply pre-existing advances
If client has
advanceBalance > 0, system silently allocates against the new invoice up to the smaller of (advance, invoice total). Flash message shows the auto-allocated amount.Notify the partner
Internal notification with deep-link to the new draft invoice. Partner reviews, approves, sends.
Manual invoice from a job
From the job dashboard → Invoices tab → New invoice from this job. Same flow as auto-draft but partner-driven. Useful for:
- Phased billing — 50% on planning sign-off, 50% on report issue
- Out-of-pocket recovery — separate invoice for travel + per-diem
- Scope-creep billing — additional fee for extra work outside the quote
- Proforma issuance — for advance-payment requests
Proforma vs tax invoice
| Aspect | Proforma | Tax invoice |
|---|---|---|
| doc_type | proforma | tax_invoice |
| Number prefix | PI/{YYYY}/{NNNN} | INV/{YYYY}/{NNNN} |
| Affects revenue? | No — pre-supply quote | Yes — recognised on issuance |
| VAT applies? | No — informational only | Yes — RD 121/2020 |
| PDF watermark | "PROFORMA" | None |
| Conversion | Click Convert to tax invoice | — |
When a proforma is converted to tax invoice, the system clones the lines, stamps both lineage columns (proforma.converted_to_invoice_id + tax_invoice.parent_invoice_id), and walks the new tax invoice draft → sent in one shot if the proforma had advance payment(s).
Payment allocation
One payment row, multiple allocation rows (payment_allocations):
One allocation row. Invoice flips to paid. paid_in_full_at stamped.
One allocation row. Invoice flips to partially_paid. Balance recomputes.
Multiple allocation rows. Each invoice's balance recomputes. Useful for "client paid off 3 invoices in one transfer".
is_advance=1. Sits as un-allocated credit until applied via post-hoc allocation form.
The OMR money rules
- Decimals: 3 (Oman Rial = 1000 baisa)
- Math: bcmath, scale 3, never floats
- Storage: DECIMAL(18,3)
- Rounding: banker's rounding via
bcadd($a, '0', 3) - VAT: 5% on standard-rated lines, 0% zero-rated, none for exempt — picked up from client.vat_category
- OMR-equivalent: if invoice currency != OMR, system stores the original + converted-to-OMR for reporting
Open a job with status=completed → Invoices tab → New invoice. Verify the line item descriptions auto-populate from the job's tasks (datalist autocomplete). Default T&Cs come from firm settings or client override. Save as draft, then approve, then send. Check the M11 invoice show page — branded PDF rendered, audit trail captured.
Don't manually fudge balances. The bcmath math is the source of truth. If a line says 14,500.000 OMR you can't decide it's 14,500.001 to "make it round" — the audit log captures every line edit and you'd be the auditor of your own books, badly.
The Client → Billing card always shows current advance balance. If it's > OMR 1,000 there's money sitting un-allocated. Use the post-hoc allocate form on payment-show to apply it to the next invoice. Saves the client a transfer + reconciles your books cleanly.