Two changes to the income overview, sharing one rule for what "the month"
means (lib/income-month.ts).
POSTED INCOME PER SITE
/income gains Vehicles / Sites views, and /sites defaults to the SAME
posted-income view with a separate Internal billed invoices view.
buildSiteIncomeRows (lib/site-income.ts) is the shared calculation: actual
monthly_income supplies the amount, year and month, and the linked invoice
supplies its historical site. It never uses assets.site, fuel-log
locations or today's map assignment.
- Unposted invoice lines are billing, not posted income.
- Historical workbook income with no site evidence stays in Unallocated
historical income; external income, whose site field names the SUPPLIER,
stays in Unallocated external income.
- Missing source references stay visible as unallocated invoice income.
No group is dropped to make a site list look cleaner and no site is
guessed to force a total.
- No site profit is calculated without an explicit cost-allocation rule.
- Invoice writes use revalidateInvoicePages, which invalidates both
registers, Income, Sites/details, Charts and Dashboard.
THE LIVE MONTH
The tiles show the year-to-date income and profit AND the current month,
and every vehicle card shows the current month income beside its year
figure.
- The month is the live CALENDAR month and it is shown even at R0. Income
for a month is usually entered near the end of it, so the current month
is a figure that starts at R0 and grows.
- The month index is computed on the SERVER and passed down as a number.
The browser never works it out for itself: two clocks disagreeing would
show two different months on the same page.
- A month profit is a dash until that month costs are posted. September
and October carry invoice income with no posted fixed costs (their costs
exist only as forecast rows), so a profit there would be income minus
zero - a 100% margin that does not tie back to the year-to-date profit.
It becomes a number by itself the moment the workbook posts that month.
- The 12-month strip shows the live month as R0 rather than a dash, so a
card cannot contradict itself. Months after it stay a dash.
- The sparkline on a collapsed card is hidden below xl: the content column
is only ~700px wide between md and xl, and the vehicle name must not be
truncated to pay for a decorative trend.
/plant and /plant/map import Fuel Logs CSVs by exact QR and show each
vehicle's current and previous distinct site with the dates they were
observed.
- plant_site_observations is durable, QR-keyed history. It is never wiped
and a previous site is never manufactured.
- An older or repeated snapshot must not move a vehicle backwards; untimed
conflicting sites on one day are held for review.
- A manual site change shares the same history and the same lock.
- Current/previous metadata and the sticker QR are captured BEFORE the asset
rows are deleted in the seed, so a re-seed cannot wipe them. Explicit
empty values are preserved too: clearing a site is a deliberate manual
observation, not permission to restore an old sheet value.
- Imports change location metadata only. They never touch income, cost or
fuel measurements.
- A new named site is created only for a matched Plant vehicle. Missing
coordinates mean "awaiting a pin", never a headquarters fallback.
Owner-confirmed 2026-11-04: TLB06's QR is 1089, not the old meter reading
4704. lib/plant-site-policy.json holds the confirmed aliases (Equestria
Bastion Job -> Equestria 215, Cosmo City / Lion Park -> Cosmo City); no
other identity is guessed.
Add PocketBase login, first-sign-in password changes, account settings,
and user activation, reactivation and deletion with self-removal guards.
Protect application pages and APIs and keep private pages out of PWA
caches.
Resolve plant locations from site assignments and improve map controls,
profit chart views, navigation and mobile table layouts.
Validation: production build and TypeScript passed; 28 unit tests passed.
Backend access integration tests and mobile verification are documented.