Commit graph

5 commits

Author SHA1 Message Date
David
14ec899ca9 Sites: a real Sites list, with the unbooked lines bookable to a site
The container lines (MPLANT04/MPLANT05) have no matching plant item, so they
sat unbooked forever with no way to account for them. They can now be booked
to a SITE instead.

`sites` is a new PocketBase collection derived from the invoices — each
distinct invoice site becomes a site, created as invoices are imported. They
are never hand-typed, and never deleted: a site can carry notes and hold line
bookings, so removing one would orphan that history.

/sites lists every site with its revenue ex VAT, its VAT, the total billed,
how many invoices and machines it carries, and how many lines are still
outstanding. Each row links to a detail page listing the site's invoices and
every line item with its ex-VAT/VAT/total split. Nav: Dashboard is now a
group with Overview and Sites.

Booking a line to a site posts NO income — there is no machine to carry it —
it only marks the line accounted for, so it stops showing as unbooked. Exactly
one of booked_asset / booked_site is ever set, and a line with neither is what
surfaces as unbooked everywhere.

One container per site, as specified: once a machine-less line has taken a
site, that site is withheld from the next one's picker and shown disabled
("already has one") rather than hidden. Free sites sort to the top. The rule is
enforced server-side too, so it does not depend on the UI being the only way in —
re-posting a taken site is refused with 409. Verified: booking The Hills, then
trying again, then taking West Park, and the remaining line sees 8 free and 2
taken. Clearing a booking frees its site.

The site picker only appears on lines that need it — no machine, or one already
booked to a site — so fully-booked rows keep a single control.

Also fixed the Sidebar's group-active check, which derived its prefix from the
first child. For a group whose first child is "/" that yields "/" and matched
every route, so Dashboard would have been highlighted on every page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-10-02 16:24:06 +02:00
David
dc7024d98a Fix: the mobile table was mislabelled and clipping its own totals
A screenshot at phone width showed an "INVOICE | LINES | TOTAL | STATUS"
header row while the data under LINES was money. The header and its data cell
had drifted apart: the earlier pass hid the Lines <td> but not the matching
<th>, because the loop that added the hide classes only matched text-left
headers and Lines is text-right. So the Total values rendered under a heading
that named a column which no longer existed.

The same screenshot showed the totals clipped — "41,710.50" reading as
"710,50". With only three columns left, the 520px min-width was still forcing
a sideways scroll, and the opaque sticky first column then sat on top of the
left of the money values. The mobile floor is gone, so the three remaining
columns size to their content, and the sticky and status gutters are trimmed
at mobile widths to let them fit 375px.

Below sm the table is now exactly Invoice, Total, Status — and every header
matches a visible cell.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-10-02 15:34:19 +02:00
David
999feb37fa Mobile: make the invoice tables readable at 375px
Audited the invoice UI against the Round 4 checklist and fixed four things.

The summary table carried eight columns at a fixed 820px min-width, which at
375px is a long sideways scroll through three figures before reaching the
status badge — the one thing that needs acting on. Below sm it now keeps only
Invoice, Site, Total and Status, and drops to a 520px min-width, so the useful
columns are readable without scrolling. Period, line count and the ex-VAT/VAT
split are one tap away in the expanded detail, and the full eight columns
return from sm up where there is room.

The summary table also had no right-edge fade. Rule 4 asks for a visible
affordance on any wide table and it was simply missing, so scrolling sideways
looked like the table ended. Same for the month grid on /income, which was also
missing -webkit-overflow-scrolling. Both now share components/ScrollFade.

Two 10px labels in the app header ("Overheads 2026", "FY") were invisible at
375px but visible from 640/768px — and 768x1024 is one of the two sizes the
checklist tests, so they were a rule-2 violation at tablet width. Bumped to
12px. PlantRegisterTable's 10px filter count was checked and left alone: it
sits inside hidden lg:flex, so it never renders on mobile.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-10-02 15:29:00 +02:00
David
9e6235af92 Invoices: Add Invoice becomes a real dialog; quieten the invoice table
The upload panel expanded inline above the table, which pushed the list down
the page every time it opened. It is now a standalone modal: backdrop,
Escape to close, body scroll locked, sheet on small screens and a centred
dialog from sm up. The file is chosen or dropped into the modal and nothing
is sent until OK — a mis-drop can be cleared without leaving a stray record
in the inbox. Cancel, a busy state and inline size/type errors round it out.

The status column carried two extra lines on every single row — a multi-site
note and a "booked R X · R Y VAT" figure — which is repetition: the figures
are already in the Revenue/VAT/Total columns, and the note restated something
the client ruled on. Both are gone, leaving just the booked/unbooked badge,
which is the only thing that needs acting on. The per-row booking figures
stay available in the expanded detail.

The plumbing those two lines used — the multi-site cross-invoice lookup and
the per-row booked/VAT totals — is now unused and removed with them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-10-02 14:35:27 +02:00
David
baf0bddde6 Invoices: line-item extraction, booking to plant items, VAT-inclusive income
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>
2026-10-01 15:59:30 +02:00