WIP carried over from the working tree, committed separately so it does not
sit inside the invoice work.
- inline Edit cells for code / name / type and an active toggle
- GET /api/assets/:id/impact returns the cascade counts so the client can
confirm before a delete
- DELETE /api/assets/:id hard delete; PocketBase cascade clears the dependent
income / running / cost-line rows
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds an Invoices sub-menu under /income with a summary table (one row per
invoice, click to expand the full detail) and the data model behind it.
Data:
- invoices + invoice_lines PocketBase collections, created idempotently by
scripts/setup-invoice-collections.mjs
- line items transcribed from PHI 166/167/171 with per-invoice arithmetic
validation in the seed; a bad invoice is refused without blocking the rest
- invoice-*.json in data/source are auto-imported, so adding an invoice is
one file
Booking:
- manual book-in per line (Booked to), grouped by category
- POST /api/invoice-lines/:id/book moves the income row rather than adding a
second one, so a booking can be changed freely
- scripts/book-invoice.mjs is idempotent: rows are stamped with
monthly_income.invoice_line and replaced on re-run
- booked amount follows lib/invoice-booking-policy.json (bookInclusiveVat),
read by both the app and the CLI so they cannot disagree
Money:
- the R24,205.13 column on these invoices is PLUS VAT, not escalation. The
seed now rejects VAT that disagrees with the stated rate.
- booked amounts are VAT-inclusive per the client ruling of 2026-10-01; the
VAT stays visible rather than hidden.
Uploads:
- drag-and-drop PDF attached to invoices.source_pdf
- GET /api/invoices/:id/document proxies it — the collection is rule-locked,
so a direct PocketBase /api/files link 404s in a browser
Fixes a month-aggregation bug this introduced: buildIncomeRows assigned into
months[i], so a machine with two income rows in one month (one per invoice,
which is normal multi-site use) showed only the last. SS01 September read
R20,700 instead of R35,305 while its own YTD included both. Months now
accumulate, per-month profit is computed once after accumulation, and
lib/rows.test.ts pins it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Part A — Round 3 regressions:
- A.1: verify-cost-reconciliation.mjs now loads .env.local, wraps in try/catch, exits 1 on any failure
- A.2: Vehicle Costs restored Santam — back to R 14,996.31 (matches row-by-row sum)
- A.3: incl<excl / VAT math / VAT>20% now FATAL — seed exits 2 with full report (4 anomalies on Charl-CCS + Buildsmart)
- A.4: Reconciliation target R 694,962.93 with both deltas itemised (Personnel +R19,800, Santam -R5,322.44, unexplained R 0.00)
- A.5: 'Lines' view uses actual lines (recharts LineChart), no fills; fills only in 'Cost stack'
Part B — Mobile standard:
- B.1: header wraps to 2 rows on mobile; YTD badge cards stack; page-wide overflow eliminated on /income, /charts, /overheads, /running, /expenses
- B.2: IncomeView card row layout stacked — name gets its own line on mobile, never truncated to single char
- B.3: Plant type filter is a <select> dropdown below lg (was 18 pills wrapping to 13 rows); same for sort on /income and /running
- B.4: Plant table first column sticky; -webkit-overflow-scrolling: touch; tables scroll inside their own container
- B.5: All buttons/links use min-h-[44px] tap targets; text below 12px is bumped where it matters
- B.6: Charts legend wraps below title; chart min-height h-44 → h-52 on mobile
- B.7: paddingTop env(safe-area-inset-top) on sticky header for notch/home indicator clearance
AGENTS.md created with the standing mobile checklist rule.