# TechTitans POS — Professional Invoice PDF Redesign (with the uploaded business logo)

**Date:** 2026-11-12 · **Branch:** `techtitans/integration` · Local commit only (no push/deploy). NH POS / legacy portal untouched; no legacy PHP executed; no real payments; no invoice/payment amounts changed.

Redesigns the **shared** POS invoice PDF (used by IPTV, repairs and retail — one renderer, `App\Invoices\InvoiceService` → DomPDF → `resources/views/invoices/pdf.blade.php`) into a branded, professional document that automatically uses the tenant's uploaded logo and business settings. No separate IPTV generator.

## 1. Root cause — why the logo looked "missing"
It was **not a code defect**. The template already embedded the logo (`$logo`) and `InvoiceService::currentLogoDataUri()` already resolves it via `TenantLogoService::dataUri($tenant)`. Verified on the dev DB: the **techtitans tenant has a logo** (`tenants.logo_path` set, file present, a 512×97 PNG) and the resolver returns a valid `data:image/png;base64,…`. Two real reasons the logo appeared absent:
1. **Frozen historical PDFs** — invoices issued *before* the logo was uploaded have their PDF bytes stored once on the private disk; `getOrIssue()` reuses them, so an old invoice never shows a later-uploaded logo (correct accounting behaviour — see §5).
2. **Minimal template** — the old layout rendered the logo small/left in an otherwise plain black-and-white document, so it didn't read as "branded".
The redesign fixes (2) and makes the logo prominent; (1) is preserved by design (new invoices render with the logo).

## 2. Shared settings mapping (reused — no duplication)
Branding is **per-tenant** (logo) with per-location contact details, assembled by `App\Invoices\BusinessIdentity::for($order)` and snapshotted into `invoices.metadata['business']` at issue time:
| Invoice field | Source |
|---|---|
| Logo (PNG) | `tenants.logo_path` on the **private `local` disk** → `TenantLogoService::read()/dataUri()` (always re-encoded to PNG ≤512px on upload; JPEG/PNG/WEBP accepted, SVG rejected) |
| Business display name / number / phone / email / website / country | `tenants` (+ profile fields) |
| Tax registration number | `tenant_settings.tax_registration_number` |
| Address lines / location phone·email / receipt footer / currency | `locations` |
No Restaurant-Settings data was duplicated or renamed; the invoice reads the existing settings.

## 3. Template redesign (`resources/views/invoices/pdf.blade.php`)
Restrained **navy `#123B55` / teal `#008C83`** design, A4/Letter-friendly (relative widths), all CSS inline, DejaVu Sans:
- **Header band** — LEFT: prominent logo (`max-height:72px`, aspect preserved, only when present), bold navy business name, address lines, phone, email, website, business + tax-registration numbers (each only when present). RIGHT: a strong title — **TAX INVOICE** when `document_type==='invoice'`, else **PRO FORMA INVOICE**; invoice number; issue date; "Due on receipt" terms; order number; a **colour-coded payment pill** (PAID → teal, PARTIALLY_PAID → amber, PENDING/AUTHORIZED → navy, REFUNDED → grey, VOIDED/FAILED → red). Subtle (not oversized) PRO FORMA watermark for non-invoices.
- **Bill To / Fulfillment** — customer name, billing address, email + phone when available.
- **Line table** — **Description · Qty · Unit price · Discount · Tax · Line total** (per-line `line_tax_minor`/`tax_rate_bps` now passed; discount shows "–" when 0). IPTV lines render their service description ("IPTV renewal — N months") — no credentials/secrets. `thead` repeats on page breaks; description/address wrap (`overflow-wrap:anywhere`).
- **Financial summary** — Subtotal · Discounts (if >0) · per-tax rows (name + rate% + amount) · service/shipping (if >0) · **Grand total** · **Amount paid** · **Balance due** (teal at 0, red when owing).
- **Footer** — payment instructions / `receipt_footer`, business contact, a teal thank-you, and **"Page X of Y"** via a fixed-position footer using DomPDF `counter(page)`/`counter(pages)`, with `@page` bottom margin reserved so it never overlaps content and totals are never cut off.
- `InvoiceService::renderPdf()` now also passes `line_subtotal_minor`, `line_tax_minor`, `tax_rate_bps` and a `has_logo` flag.

## 4. Status / document-type correctness (preserved)
Title and pill are driven strictly from `document_type` / `payment_status_snapshot`: a proforma is never labelled a finalized INVOICE and an unpaid document is never shown as paid. Invoice numbering (INV-/PF- series), status semantics, the metadata snapshot, idempotent `getOrIssue`, and the `invoices.view`-gated download route are unchanged.

## 5. Historical invoice behaviour
The **text identity is snapshotted** into `invoices.metadata` at issue time; the **logo is embedded live at render**, but each invoice's PDF bytes are stored once and reused — so **already-issued invoices keep their original PDF unchanged** (no retroactive change to historical financial or issuer data). The redesign + logo therefore apply to **newly issued invoices** only. We deliberately did **not** snapshot the logo data-URI into `invoices.metadata` (it would bloat the JSON row by ~16 KB per invoice, and the frozen PDF already preserves branding). Recommended additive future option, if identical re-rendering is ever needed: store the logo path (not bytes) in metadata — not required today.

## 6. Logo handling / safety
Loaded server-side from the private disk and embedded as a **base64 PNG data URI** (DomPDF renders it natively, no `isRemoteEnabled`, no chroot issue). Aspect ratio preserved. Missing logo → clean header with the business name (no broken image); corrupt/unreadable bytes → `dataUri()` returns null → `@if` hides the img. No filesystem paths, credentials, or remote URLs are exposed. Only the per-tenant logo is used (isolation verified).

## 7. Testing
- **Backend:** new `tests/Feature/Orders/InvoicePdfTest.php` → **8 passed (30 assertions)**: logo data-URI embed; no-logo tenant still a valid `%PDF`; **two-tenant logo isolation** (each PDF carries only its own tenant's bytes); unpaid → PRO FORMA + `PF-` + PENDING pill; paid → TAX INVOICE + `INV-` + PAID pill; partial → proforma + amount_paid>0 + balance>0 + PARTIALLY PAID pill; discount + tax columns/summary match the frozen totals (named HST row @13%); long description + 61-line pagination renders a valid PDF. Existing `OrderInvoiceNotificationTest` → **13 passed** (unchanged). Pint clean; PHPStan `app/Invoices` → no errors.
- **Live:** `GET /orders/{id}/invoice` (Sanctum-authed) → **HTTP 200, application/pdf, ~880 KB**, `%PDF-`, filename `invoice-INV-2026-0008.pdf`.

## 8. Before / after (screenshots in `docs/techtitans/screenshots/invoice-pdf/`)
Same data (techtitans PAID invoice INV-2026-0008, "IPTV renewal — 3 months", CAD 50.85) through both templates:
- **Before** (`before-techtitans.png`): monochrome, 4-column table (no Discount/Tax), outlined "PAID" box, small logo.
- **After** (`after-techtitans.png`): navy/teal brand, **prominent TechTitans logo** in a header band, uppercased "TAX INVOICE" + metadata block, teal **PAID** pill, 6-column table (Discount + Tax), zebra rows, emphasised Grand total / Balance due.
Plus `after-nologo.png` (Kings Biryani — name only, no img), `after-proforma.png` (PRO FORMA + PENDING + red balance + watermark), `after-discount.png` (Discount −CAD / Tax 11.5% columns + long-description wrap). *(The fixed footer/page-number renders in the paged PDF; it is off-canvas in the single-page HTML screenshots — visible in the live 880 KB PDF.)*

## 9. Commits (on `techtitans/integration`, no push)
- **Backend:** `a913a9e` — `InvoiceService.php` (line fields + `has_logo`), `resources/views/invoices/pdf.blade.php` (redesign), `tests/Feature/Orders/InvoicePdfTest.php`, this report + before/after screenshots.
- **Frontend:** none — the SPA already downloads via the unchanged `/orders/{id}/invoice` route.
