Branded PDF templates — dompdf-rendered
Three colour-coded templates: tax invoice (navy) · proforma (orange watermark) · receipt (green). Rendered server-side via dompdf 3.1.5 (pure PHP, no native libs). Cached per doc, busted on edit. dompdf-safe layout rules — table-layout fixed, HTML width attrs, no position:fixed.
The 3 templates
Compliant with RD 121/2020. Firm letterhead, bill-from + bill-to, line table with VAT, totals, T&Cs, bank details, signature.
Same layout as tax invoice but watermarked + VAT shown as informational. Footer text: "Tax invoice will follow on service delivery".
Different shape: hero centre-box with amount in words, payment method, allocation summary, signature + firm-stamp blocks.
Cache strategy
| Aspect | Behaviour |
|---|---|
| Cache path | storage/invoices/{id}/{number}.pdf · storage/payments/{id}/{number}.pdf |
| First request | Renders fresh · ~600-1200ms |
| Subsequent | Streamed from cache · < 100ms |
| Bust on | Invoice line edit · header edit · status change · payment allocation |
| Bust mechanism | Delete cache file · next request re-renders |
| Manual bust | Admin "Clear PDF cache" button (super_admin only) |
What's on the tax invoice PDF
Header strip (navy gradient)
Firm logo (left) · "TAX INVOICE" prominent + invoice number + issue date (right). Logo loaded inline from
companies.logo_pathwith cache-busting mtime.Bill From / Bill To grid
Firm: name, address, VAT TRN, phone, email. Client: legal name, CR, VAT TRN, address. Two columns, equal width.
Subject + scope (optional)
Job description, fiscal year reference. Pulled from invoice.subject + linked job.
Line item table
Description · Qty · Rate · VAT · Total. Navy header. Zebra rows. Negative discount lines highlighted.
Totals block
Subtotal · VAT · Grand Total. Navy footer with gold accent on Grand Total. If advance applied, "Less: prior advance" row + new "Balance Due".
Bank details footer
Pulled from
company_banks. Up to 2 bank accounts shown. Hidden on proforma.Terms & Conditions
From client.invoice_terms_override OR firm default.
Signature block
"For [Firm Name] · Authorised signatory" with stamp space. Right-side: "Received in good order · Customer signature".
dompdf-safe layout rules
An early version of the template rendered the same invoice across 17 pages. The fix was to follow strict dompdf compatibility:
- Use
<table>withtable-layout: fixed(neverauto) - Use HTML
width=""attributes on cells, NOT CSS percentages - NO
position: fixed(renders weirdly across pages) - Avoid CSS Grid — use tables for layout
- Avoid CSS Flexbox at top level — limited dompdf support
- Use absolute pixel sizes for fonts, NOT em / rem
- Embed images inline as base64 OR via absolute file paths
- Keep CSS in
<style>tag, not external (faster, more reliable)
Customising the templates
Each template is a PHP view at:
views/invoices/pdf-tax.php— tax invoiceviews/invoices/pdf-proforma.php— proformaviews/payments/pdf-receipt.php— receipt
To customise: copy → edit → test render via InvoicePdfService::render($invoiceId, true) (force flag rebuilds). Don't change the structure that affects compliance fields (TAX INVOICE label, VAT TRN, sequential number, total VAT amount).
Per-firm branding
The firm's companies row drives:
- Logo (40 KB cap, PNG/JPG/WebP)
- Stamp PNG (transparent background recommended)
- Brand colour primary + secondary
- Address, phone, email, VAT TRN, CR
- Bank accounts (up to 2 shown on PDF)
Settings → Company Profile → upload logo + stamp. Templates auto-pick. No code changes needed for brand customisation.
Step-by-step — preview + send a tax invoice PDF
Open invoice detail
From M11 invoice list or job → Invoices tab.
Click "Preview PDF"
Opens in new tab. Either streamed from cache or freshly rendered. Inspect every block — letterhead correct, VAT visible, totals match.
If incorrect — edit
Status must allow edits (draft only typically). Fix lines, save. Cache busts. Re-preview.
Send
Click Send. M17 attaches the cached PDF + emails via firm SMTP. Status flips draft → sent.
Client downloads
Email arrives with PDF attached. Original cached server-side; same PDF served if client requests another copy via portal.
Open any invoice → Preview PDF. The first render is ~800ms (you'll see the spinner). Refresh the same URL — instant from cache. Now edit a line item, save. Re-preview — re-renders fresh because cache was busted on save. This is what live invoice editing should feel like.
Don't edit dompdf templates without testing. A misplaced <td> can throw the whole layout 17 pages wide. Always render at least 3 sample invoices (1 line, 5 lines, 20 lines) after any template change — long invoices break differently from short ones.
The stamp PNG should be transparent-background (not white-square-with-stamp). If white shows, scan with a transparent layer in Photoshop / GIMP, or use rembg.app to auto-remove background. Looks much more professional in the signature block.