Commit graph

121 commits

Author SHA1 Message Date
DEls
9f0fce988e feat(cash-floats): petty-cash cards on users, one log, clock Slips tab
Each cash float card lives on its holder's user record (card number, last
4-6 digits of the physical card, low limit, assigned date). Every movement
goes in one cash_float_log collection: opening, top-up, slip, handover and
correction rows with signed amounts. Balances are always summed from the log.

- Management -> Cash Floats: cards table (lowest balance first), add, edit,
  top up, hand over, correct balance, retire, movements sheet with slip
  photos and CSV export. Admin-only.
- Settings -> Users: card section in the edit form and the balance per holder.
- Clock app: Slips tab, shown only to a user who holds a card. Photo, site,
  supplier and amount; the slip comes off that user's own card.
- Sidebar needs-attention mark when a card is under its limit.
- Slips count as site spend ("Cash float slips" on the site page).
- Migration scripts/migrations/2026-10-24-cash-floats.py (already applied to
  production on 2026-10-24).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 13:17:06 +02:00
DEls
708e0b2742 feat(clock): a scan at another site moves workers and vehicles there automatically
A destination scan now ends the worker's previous site session at that moment
(transfer OUT, hours booked at the origin) and opens the new one, so nothing is
left for the office to review. Vehicle scans take a scanning site, move the unit
there and log the reading against it. Adds a script to close already-flagged moves.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 06:34:05 +02:00
DEls
36da7f9f20 feat(site-model): Nellmapius Ext. 25 roads, kerbs and benchmarks from the 100_ROADS and 400_KERBING drawings
Road areas (5,221 m²) in 20 m sections along the ring roads and the Igwali Street spur, 1.74 km of kerb measured once, stand lines, and the four survey benchmarks with an indicative ground plane. No services or ground levels are placed; the supplied survey and sewer sheets cannot yield them.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 05:29:17 +02:00
DEls
5141f7783a feat(site-map): Cedar Gate stormwater and sewer pipes and manholes from the long sections
Extract 23 stormwater structures / 22 pipes and 18 sewer manholes / 17 pipes with sizes, inverts, ground levels and positions from the C3277 long-section tables (superseded sewer copies dropped by matching the issued PDFs). New model folder 2026-10-21. Map 3 draws them as round pipes and manholes, adds status popups for pipes and manholes, and reuses Map 2's services panel; water stays plan routes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 05:04:42 +02:00
DEls
6036506d70 chore: tidy repo root and commit October data work
- Move loose September fuel exports, chat export and deploy log into archive/
- Move the Hills wages test spreadsheet into src/lib/__fixtures__/
- Add Buildsite fuel exports and the 18 Oct fuel review audit under reports/
- Add PocketBase schema backups from 17-18 Oct
- Ignore .playwright-mcp/ and duplicate report zips; stop tracking cooler.env (already in .gitignore)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 18:49:34 +02:00
DEls
cb4a1ce461 feat(site-map): Maps page with previews, double-click progress popup, Cedar Gate cut & fill
- Sites → Maps in the sidebar replaces the per-site Site map / Map 2 links.
  The Maps page shows each site's model as a plan preview (built sections
  solid) with % of road layerworks built and links to Site map and Map 2.
- Double-clicking a road section opens its progress tick-boxes over the map
  (Site map saves to the server; Map 2 stays a local scenario).
- Cedar Gate: 40 drawn cross-sections extracted from C3277-912-001 Rev A
  (extract_cedar_gate_xs.py), cut/fill limited to Cedar Gate Road, a Cut &
  fill workspace in Map 2 with a chainage chart, and pavement layers shown
  in their material colours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:55:51 +02:00
DEls
0dfb129696 feat: update management comparisons and Cedar Gate site model
Add planned vs actual turnover dashboards, monthly actual costs, editable retention forecasts and actuals, starting retention, payouts and running balances. Preserve manual values across workbook imports and include retention backfill tooling.

Add Cedar Gate model extraction and reports, measured service routes and ground surfaces, plus site-map section totals and quantity refinements.
2026-10-20 12:23:44 +02:00
DEls
a96f35366d feat(management): store planning data in PocketBase with editable income, add job and forecast vs actual chart
Move the Management planning snapshots into PocketBase collections
(plans, projects, per-year project plans, retention, edit log), loaded
with a bundled-JSON fallback. Admins can double-click an income cell to
enter forecast and actual, edit awarded jobs' planned amounts, change
status and costs, and add jobs, with revision checks and an audit trail.
Income chart now shows forecast and invoiced bars with not-yet-started
money stacked in red; Income is in the sidebar. Adds schema and import
scripts, and jsdom/Testing Library dev dependencies for UI tests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 03:04:01 +02:00
DEls
a738b890bc fix(auth): keep named user sessions for 60 days 2026-10-19 20:04:14 +02:00
DEls
0a7138f60c fix: harden workforce, plant and financials
Improve workforce scanning, site transfers and timesheet exports.
Audit plant imports, duplicate identities, rates and dashboard totals.
Repair delivery PO selection, periodic rechecks and receiving guards.
Align financial ledgers, supplier periods and mobile layouts.
Add the site map preview, shared theme controls and audit evidence.

Verify 3,455 application tests, six fuel export tests, TypeScript and
an optimized production build. Clean up a leaked test timer and keep
local worktrees and generated reports out of ESLint traversal.

Changed-file lint passes; repository-wide lint retains 33 existing
errors in unchanged files.
2026-10-19 13:18:31 +02:00
DEls
bb2d15a39b feat(workforce): add employer and broker management 2026-10-15 15:58:06 +02:00
DEls
f06d545e02 fix(site-map): allow tab-separated survey files
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 08:52:19 +02:00
DEls
300b1d2a07 feat(site-map): model upload script with progress-key parity 2026-10-14 07:06:13 +02:00
DEls
c4da9d4a65 feat(site-map): PocketBase collections for models, progress and survey imports 2026-10-14 07:05:11 +02:00
DEls
d66c213712 feat(site-map): model pipeline with 20 m chainage sections 2026-10-14 07:04:34 +02:00
DEls
337b77be3f feat(management): refresh workbook plans and reconcile cashflow 2026-10-14 05:48:01 +02:00
DEls
bab2ea7a43 feat(receiving): add Inbox 2 and strengthen duplicate checks 2026-10-12 20:20:27 +02:00
DEls
1fb65a9d9c chore(n8n): keep the stored PO-ingest callback in step with the published workflow
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:47:00 +02:00
DEls
c55676bff0 fix(po): photographed POs get their order date from the PO Creation Date
Text PDFs already get it from pdftotext; photos and scans have no text,
so 4 of 5 undated POs were photos that do print a PO Creation Date.
- n8n PO reader (stored patch): asked for order_date = 'PO Creation
  Date' (day/month/year), never Required By or Printed; the callback
  passes it on.
- process-batch accepts extracted.order_date (YYYY-MM-DD); the PDF text
  wins when both exist.
- create_purchase_order / update_purchase_order / move_to_purchase_orders
  take order_date, so agents record it too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:45:14 +02:00
DEls
a41653ce87 fix(dn): date deliveries by the slip and orders by the PO's order date
The reader always read the slip date (and receiver, site name, customer
VAT, supplier registration), but dn_batch_notes never had those fields,
so they were dropped on save and every receipt was dated the moment it
was posted. The Financial dashboard dated orders by upload date, so 24
of 41 POs (R7.08M) sat in no month and deliveries piled up in the month
they were booked: 'R1.1M ordered, R2.4M delivered'.

- scripts/pb-add-dn-dates.py: adds the five header fields,
  delivery_notes.posted_at and purchase_orders.order_date (applied).
- scripts/dn-backfill-dates.py: filled 270 documents from the stored
  readings, moved 192 receipts to their slip date (booking time kept in
  posted_at) and read 36 PO order dates from the PDFs (applied; replaced
  values in docs/audits/2026-10-11-dates-backfill-backup.json).
- Posting refuses a slip without a delivery date instead of using today;
  receipts record posted_at. The review page shows the date and locks
  Post with a plain message when it is missing.
- PO intake reads 'PO Creation Date' from the PDF text (pdftotext).
- Dashboard tile and monthly chart date orders by order_date only; an
  order without one is undated, never put on its upload month.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:35:00 +02:00
DEls
cca41d5bb1 chore(clock): keep the pre-recalc paid-hours backups
Row ids with the hours_worked values from before the 2026-10-09
paid-hours recalculation, so the run can be reversed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 00:03:35 +02:00
DEls
4493bbc0db feat(clock): pay against the real work schedule
Mon-Thu 07:00-16:30, Friday 07:00-15:00, last Friday of the month
07:00-12:00 (weekends use the Mon-Thu bands). Each shift end has a
10-minute band either side; arrivals up to 07:10 pay from 07:00.
Lunch now phases in from 5h to 5h30 instead of a 30-minute cliff, so
a normal day pays 9 / 7.5 / 5 hours.

The worker calendar judges each day against that day's expected hours,
so a normal Friday is green, and the worker page shows the schedule.
The recalc script recognises rows already rewritten by the first rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 19:51:24 +02:00
DEls
295f31fce6 fix(clock): recalc re-run counts already-paid rows as unchanged
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 19:32:50 +02:00
DEls
75b178dd33 feat(clock): store paid hours, not the raw scan span
A scan in up to 07:10 pays from 07:00; a scan out between 16:20 and
16:40 pays to 16:30; a session over five hours loses a 30-minute lunch.
A normal day is therefore exactly 9 hours (green on the calendar and
matrix); late arrivals, early exits and overtime use the real times.
Applied on clock-out, in missed-scan corrections and their preview.
The calendar keeps the raw scan times and labels hours "Paid hours".

Includes a dry-run-first script to recalculate existing QR entries; it
only rewrites rows still equal to their raw scan total.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 19:01:17 +02:00
DEls
b32ad0bc8b feat(clock): fix a missing clock-in from Needs attention
When nobody scanned a worker in, the evening scan was stored as the IN
and the correction dialog only allowed a leaving time after it. The
dialog now shows an editable arrival and leaving time, with a one-click
"this scan was actually the clock-out" swap. Late scans (16:00+) get a
hint on the card. A corrected arrival is stored as the optional
actual_in_at on the correction row (migration script included, not
applied); the original QR scan is untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:57:09 +02:00
DEls
cb463c0faa feat(workforce): add timesheet exports and duplicate protection
Add per-site CSV and PDF matrix reports, editable sick-note date ranges, and weekend calendar styling. Preserve verified duplicate uploads for audit while excluding their entries from hours and costs, and prevent repeat files and overlapping daily imports.
2026-10-07 23:53:47 +02:00
DEls
a21c728d61 feat(workforce): add calendar and sick notes 2026-10-07 14:27:44 +02:00
DEls
f72397259c feat(auth): add Office access and restrict Clock users 2026-10-07 14:22:57 +02:00
DEls
8d53e3688c feat(workforce): add active and inactive workers 2026-10-07 13:41:05 +02:00
DEls
0df711875c feat(workforce): add rate history and missing-rate editor 2026-10-07 13:19:50 +02:00
DEls
4922d649ae feat: add remote purchasing MCP to existing app 2026-10-05 02:46:36 +02:00
DEls
08becc6270 feat: journal MCP operations and fence purchasing writes 2026-10-05 01:38:15 +02:00
DEls
ed69b517e8 Integrate agent work and add dashboard attention indicators
Remove repeated Plant, Financial and Workforce shortcut cards. Add site-scoped Plant and Financial sidebar attention status with automatic refresh.

Include accumulated delivery processing, purchase order recovery, supplier reconciliation, plant data and user management changes and audit records.
2026-10-04 21:53:32 +02:00
DEls
32d4a82cdf feat: add employee search and save pending fleet updates 2026-09-30 00:42:38 +02:00
DEls
b700252932 fix(auth): verify PocketBase backup key before migration 2026-09-29 07:01:15 +02:00
DEls
f5259d852f feat(auth): add PocketBase logins and site access controls 2026-09-28 23:51:47 +02:00
DEls
e6772bfb1f fix(ask): migrate created/updated to autodate, sort ties by id
ask_threads/ask_messages/ask_log shipped with created/updated typed as
plain `date` fields, which PocketBase never populates — every row's
created has been empty since. pb-add-ask-collections.py now emits
autodate fields (onCreate, plus onUpdate for updated) and migrates the
three live collections in place under --apply, re-adding
idx_ask_messages_thread_created if PocketBase drops it with the field.

--backfill (apply-only) just reports affected row counts: PocketBase
can only set an autodate field to *now*, never a historical value, so
there's nothing safe to backfill.

Since old rows keep an empty created either way, the read side sorts
ask_messages by `created,id` instead of `created` alone, so tied
(all-empty) rows land in a stable, deterministic order rather than
whatever order PocketBase happens to return them in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 03:05:23 +02:00
DEls
b211ce1c8b feat(ask): PocketBase schema for threads, messages and log
Additive, dry-run-by-default script creating ask_threads, ask_messages
and ask_log (change/memory/request), following the
pb-add-dn-staged-ingestion.py pattern. --dry-run confirms all three
collections are new; --apply is a human step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 17:51:26 +02:00
DEls
5cdfaa3128 feat(clock): review missed scan-outs without changing phone flow 2026-09-25 07:27:12 +02:00
DEls
8bdf3754df Revert "feat(clock): add locked shift schema migration"
This reverts commit 5529c3f53d.
2026-09-25 07:06:28 +02:00
DEls
5d712b66c9 Revert "feat(clock): dry-run historical shift pairing"
This reverts commit e01c45765b.
2026-09-25 07:06:28 +02:00
DEls
e01c45765b feat(clock): dry-run historical shift pairing 2026-09-25 03:16:08 +02:00
DEls
5529c3f53d feat(clock): add locked shift schema migration 2026-09-25 03:13:06 +02:00
DEIs
87f75dd98f feat(plant): fleet map page with GPS-denormalised vehicles, clustering, and tile switcher
Adds /dashboard/plant/map: a Leaflet view of every vehicle scanned from
the MEGA Clock kiosk, plotted at its last known position.

Schema
- vehicles.last_latitude / last_longitude / last_scanned_at /
  last_location_accuracy_m denormalised off fuel_logs so the map page
  renders every vehicle in a single query. Same best-effort pattern as
  attention_dismissed_at; updated by /api/clock/vehicle-scan after every
  successful scan. PB init defaults new number cols to 0 (not null) —
  filter uses 'last_latitude > 0 || last_latitude < 0' to mean 'real fix'.
- Indexed via idx_vehicles_last_scanned.

Map UI
- Custom SVG markers per unit_type: car silhouette for fuel, tractor
  silhouette for plant, 24x24, drop-shadowed.
- Clustering via react-leaflet-cluster over Leaflet.markercluster.
  Cluster icons colour-ramp with size (green->sky->amber->rose) and
  scale 32->54px.
- Three-button base-layer switcher: Street (OSM), Dark (ESRI Dark
  Gray Canvas — CartoDB watermarks tiles without a key), Satellite
  (ESRI World Imagery). Last choice persists in localStorage.
- Site pins: small purple map-marker divIcon, click flies the map to
  zoom level 15.
- Empty-state is a floating chip on the map rather than a full-page
  card so the map is always visible.

APIs
- /api/clock/vehicle-scan: stores the GPS fix on the fuel_logs row
  AND denormalises onto the vehicle when present; missing-coord
  scan leaves last_* untouched.

Sidebar: new Map entry under Plant group.
2026-09-22 21:39:25 +02:00
DEls
a589b14ac9 feat(clock): enhance vehicle scan API with GPS support
Updated the vehicle scan API to include optional GPS coordinates (latitude, longitude) and location accuracy. The API now validates these fields, ensuring both latitude and longitude are provided together and within valid ranges. Enhanced error handling for invalid inputs and updated the API reference documentation accordingly. Additionally, modified the FleetScanner component to capture GPS data and reflect its status in the UI.
2026-09-22 19:59:44 +02:00
DEls
1031a103c0 chore(dn): add two read-only checks for the staged rollout
dn-snapshot prints row counts across the delivery-note collections. The
two that matter are delivery_notes and delivery_note_items: the staged
pipeline must never move them on its own, so a reading before and after a
test says plainly whether anything reached the ledger.

dn-board renders the inbox board as text, which is how the pipeline can
be read over SSH or while the app is mid-deploy.

Both are read-only. The other scripts written while diagnosing this work
were one-off probes and are not kept.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 01:00:44 +02:00
DEls
14be7e7e21 fix(dn): keep an operator's correction through the next run
Picking the right order did nothing. The route stored the corrected
number, the re-run rebuilt the candidate from its page reads, and
po_number_raw went back to the original misread — so the operator
answered the same question again, having been told it was saved.

Nothing in dn_batch_notes distinguished a value a person chose from one
the model read, so the runner could not tell a correction from stale OCR
and overwrote both. A manual_overrides field now records the decision,
and the runner applies it before resolving rather than after, so the
corrected number is what the PO is matched on.

Only fields a run rebuilds are recorded this way. Quantity, unit and the
masses survive on their own; page ranges are the assembler's answer and
are corrected through the assembly route, which keeps the whole batch
consistent. An override must carry a value — clearing a box is not a
decision that a document has no number.

Verified end to end on a slip whose order number was read with a zero for
the letter O: the correction survived the run, resolved the order, moved
the match from fuzzy to exact, and left the document ready to post.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 23:55:56 +02:00
DEls
252dd7eb6a fix(dn): let staged ingestion run against the real PocketBase schema
Three faults stopped the staged pipeline from ingesting anything once it
met a live PocketBase rather than a test double.

PocketBase treats 0 as blank for a required number field, and two staged
fields legitimately hold zero: dn_batch_sources.ordinal is 0 for the
first source in a batch, and dn_batch_pages.region is 0 for a whole-page
read. Both creates were rejected with validation_required, so the first
file of every upload failed and no page ever persisted. The migration now
declares them optional, and pb-relax-dn-zero-fields.py relaxes instances
that already have them required.

The staged runner verifies its batch lease and commits the protected
write in one transaction, which needs PocketBase's batch API. That is off
by default, so the runner failed closed with
dn_pipeline_transactions_required and no deployment note explained why.
pb-enable-batch-api.py turns it on and raises the 3s transaction timeout,
which is tight for a commit carrying page images.

compare-dn-pipelines used top-level await while resolving as CJS, so the
command in the rollout runbook always failed to transform. Renamed .mts.

The tests missed all of this because they mock PocketBase, and the mock
accepts what the real server rejects.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 23:49:58 +02:00
DEls
1a97d7db44 fix(migration): preserve quoted index semantics 2026-09-19 19:36:50 +02:00
DEls
bee2b3dd17 fix(migration): normalize PocketBase index definitions 2026-09-19 19:33:39 +02:00