Commit graph

9 commits

Author SHA1 Message Date
36ae6d9c97 docs(plan): the implementation plan for purchase orders raised from a quote
13 tasks, 73 steps. The pure core first (totals, reconciliation, the PO
series), then the collections, the AI key, the reader, the document, the
routes, the composer, and the page wiring.

Each task carries its own failing test, its implementation, its passing run
and its commit. The reader is proved against a real document before the
composer is built on it.
2026-11-05 22:28:32 +02:00
e0b9131f7e docs(spec): purchase orders raised from a supplier quote
The ERP import is one direction only. This adds the other: read a supplier
quote with AI, review the lines, raise a PO in Mega's house layout, and email
it to the supplier.

- AI read proven against two real documents: a scanned invoice and a native
  ERP PO. Both extracted exactly, the scan to the cent.
- The PO's totals are derived from the reviewed lines, never copied from the
  quote; the quote's stated total is kept and compared.
- PO number MEGPPO-0001: a hyphen before the digits, so it cannot collide
  with the MEGPPO49676 series during the hybrid period.
- The AI key lives in app_settings and is edited in Settings, not in the
  environment.
2026-11-05 22:12:25 +02:00
David
d055c344de feat(running): drive the charts and the profit from the running-cost register
The machine charts plotted the workbook's monthly_income.fixed_expenses, which
is NOT the app's running cost. So the charts and the /running Overview
disagreed about the same machine in the same month — CT01 by R 40 640, DT01 by
R 121 115. The workbook's figure also carries depreciation/ownership and an
operator allocation, which are capital recovery and overhead rather than
running cost.

Every cost value now comes from the running-cost register: fixed = insurance
+ financing, running = purchases. Profit is income - fixed - running, and it
is ONE formula on every surface, so /charts, /income and the dashboard quote
the same figure for the same machine.

  R 8 456 243  fleet income, Jan-Oct
- R 4 705 292  fixed   (insurance + financing, from the register)
- R   306 387  running (purchases, from the register)
= R 3 444 564  profit   (was R 2 510 908 on the workbook's fixed)

The legacy repairs sheet comes out completely. It was the old workbook's
"Plant Monthly Running Cost" — read by no page, maintained by nobody — and a
repair is bought on a purchase order, so its money is already in Purchases.
The collection is still seeded, so the history is not lost, but it is no
longer in any total, any chart or any profit.

Also:
  - the profit badge wears the CHART's own green, and profit is green and
    loss red on every surface. It was hard-coded to the teal brand colour,
    so the label and the line it labelled were two different colours.
  - September and October now state a profit instead of a dash: the dash
    existed because the workbook had not posted those months' fixed costs,
    and the register has the premium and the instalment for every month.
  - the containers and the water trailers show profit equal to their income,
    because the running-cost model carries no cost for them.

Overheads roll-up — an audit found three things out of order, and the roll-up
total itself reconciles exactly (R 580 485,37, unexplained R 0,00):

  - the dashboard's Overhead mix legend named only five of the six sections,
    so the figures it listed added up to R 565 489 against a headline of
    R 580 485, and the donut drew a slice nobody could name.
  - the /overheads note claimed machine instalments were excluded, while the
    Bastion Finance block on the same page carried R 115 575,94 of them and
    R 11 204,76 of insurance for the same machines. The note now says so,
    with the figures read off the rows at render time.
  - two Subscriptions rows had VAT that did not balance — the sheet had the
    TOTAL in the VAT cell (115%), and annual excl/VAT against a monthly
    total. Both are corrected on the client's instruction; the roll-up total
    does not move, only the VAT breakdown does, and the seed's VAT gate now
    passes instead of exiting non-zero.

Verified: 161 tests pass (4 new ones pin the register join), the production
build is clean, the reconciliation script passes, and the charts, the profit
label and the overhead legend were checked on screen.
2026-11-05 21:24:30 +02:00
7b3ec2fbd8 feat(income): posted income per site, and the LIVE month on the overview
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.
2026-11-05 00:19:06 +02:00
1429c8087b feat(plant): vehicle site history from Fuel Logs
/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.
2026-11-05 00:18:24 +02:00
David
1ec52961ee Cash float card, Office role + Slips tab, job card PDF, manual close/delete
- Cash Floats: ledger card (opening balance, top-ups, slips), manual entries, slip photos
- Office access level; clock app Slips tab (photograph a slip, popup for supplier/cost
  code/comments/amount); only Office sees it; per-role route table in lib/clock-access
- Job cards: Machine Service PDF (with Oil block), JC numbers, manual Close job, Delete
- Hide Expenses > Vehicle Costs and Finance (Bastion) from the menu

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 15:09:57 +02:00
David
df53130861 Add Workforce pages, Plant Clock tab bar and oil & diesel book-out
- Workforce: Employees register, per-employee dashboards, Attendance table
- Plant Clock: scan-only clock, bottom tab bar (Employees / Oil & Diesel / Job cards)
- Oil & Diesel stock: diesel tank (650 L, opening 589 L), level overwrite dialog,
  QR on each card, book-out screen with searchable vehicle picker,
  filterable "Booked out" table (today/week/month/all/range)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 11:39:18 +02:00
e3754613c7 feat(clock): add standalone employee clock PWA 2026-10-21 06:39:15 +02:00
2594dead84 feat: add user access and improve plant dashboard
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.
2026-10-06 04:02:08 +02:00