Commit graph

64 commits

Author SHA1 Message Date
DEls
fca95ef4bd feat(dashboard): add QR code support to fuel attention items and alerts
- Updated FuelAttentionItem type to include qrCodeId for better tracking.
- Enhanced loadOverview function to fetch and include qrCodeId in vehicle data.
- Modified UI components in dashboard and fuel-view to display QR code information when available, improving visibility for users.
2026-08-25 08:29:21 +02:00
DEls
488f38ad38 feat(dashboard): enhance fuel monitoring and attention alerts
- Introduced a new section for flagged vehicles based on fuel variance, providing alerts and warnings for each vehicle.
- Updated data fetching to include fuel logs and compute variance for better tracking of fuel consumption.
- Improved site-level aggregation of fuel issues, allowing for a clearer overview of vehicles requiring attention.
- Refactored utility functions for fuel variance calculations and streamlined the data structure for better maintainability.
- Enhanced UI components to display alerts and warnings effectively, ensuring better visibility for users.
2026-08-25 08:14:20 +02:00
DEls
c6fed247e1 feat(fuel-view): enhance vehicle alerts and pagination
- Introduced a new section for flagged vehicles requiring attention, sorted by alert severity.
- Added pagination for fleet vehicles with a separate fleet page size.
- Updated UI components to improve visibility of flagged vehicles and their statuses.
- Adjusted vehicle detail view layout for better readability of project and QR code information.
- Ensured mobile-friendly display of vehicle data with responsive design adjustments.
2026-08-25 08:01:17 +02:00
DEls
1d06baafee fix(data-import-batches): allow xlsx MIME on original_file
When the new fuel-import route started accepting .xlsx uploads, the
real xlsx MIME type (application/vnd.openxmlformats-officedocument
.spreadsheetml.sheet) hit PB's mimeTypes allowlist on data_import_
batches.original_file and got rejected with:

    validation_invalid_mime_type: mime type must be one of:
    application/pdf, image/jpeg, image/png, image/webp, text/csv,
    text/plain, application/vnd.ms-excel, application/csv.

The old CSV-only route never hit this (text/csv was already on the
list), so the audit-trail batch attach worked fine.

Adds the OOXML xlsx MIME to the allowlist via the same migration
pattern as 2026-08-19 and 2026-08-24-fuel-logs. Idempotent: re-run
is a no-op once applied. No app-code changes - the route was
already sending the correct MIME for the file extension.

Verified: batch create succeeds against live PB with the exact
xlsx payload the route sends.
2026-08-24 22:17:45 +02:00
DEls
3e6e7eae99 fix(fuel-import): surface PB field-level errors instead of 'Failed to create record.'
When PB rejects a create, the SDK's ClientResponseError has:
  err.message = 'Failed to create record.'
  err.data    = { <fieldName>: { message: 'The string did not match...' } }

We were throwing away err.data and only logging err.message, so
operators saw the generic top-level message with no clue which field
was rejecting (latest_consumption? site? vehicle? batch_reference?).

Two changes:
  1. Add formatPbError() that flattens err.data into a single string
     like 'Failed to create record. (latest_consumption: The string
     did not match the expected pattern.)'.
  2. Wrap the data_import_batches create in a try/catch so batch-
     level failures (e.g. a field name mismatch after the migration)
     surface as a useful 400 instead of bubbling as a 500.

No behaviour change on success. Tests + tsc clean.
2026-08-24 22:15:38 +02:00
DEls
0402c73a08 fix(fuel-import): send date_logged as full ISO datetime
PB's date field validator runs an internal regex that requires a
full ISO 8601 datetime (e.g. '2026-08-24T00:00:00.000Z'), not a
bare 'YYYY-MM-DD' string. The new parser returns YYYY-MM-DD which
is correct for our internal use, but the route was passing it
straight through to PB and getting rejected with:

    'The string did not match the expected pattern.'

Anchor at UTC midnight (the pump sheet is date-only) before
writing to PB. Same shape as the original route's
'new Date(date).toISOString()' output.

Triggered by importing 'The Hills Diesel 1-24 August.xlsx'
through the new xlsx route after the migration ran.
2026-08-24 22:13:17 +02:00
DEls
7bce0a4e80 feat(fuel): drop-in xlsx/csv import for pump sheets
The Aug 2026 pump sheet from The Hills (and any future sheet from
the same source) uses different column names and a different date
format than the legacy CSV the import route was built for. Adapts
the route so the same endpoint accepts either Excel (.xlsx) or CSV,
maps the new column names onto the existing FUEL_LOGS schema, and
persists their 'Latest Consumption' column verbatim.

What's new
  • SheetJS (xlsx) replaces PapaParse for parsing. Single code path
    handles both .xlsx and .csv uploads.
  • Resolves columns case-insensitively against an alias table so
    legacy CSVs (vehicle_description, odo_reading) still work
    alongside the new shape (Date Logged, Vehicle QR, Km's/ Hours,
    Litres Pumped, Current Site, Latest Consumption).
  • Date parser: DD/MM/YYYY first (with leading-space tolerance),
    falls back to YYYY-MM-DD, then to Excel serial numbers.
  • Vehicle match: QR code first (clean identifier in the source
    data), falls back to description text for legacy CSV.
  • Site match: by sites.name (one round-trip, cached for the
    import). project_code is derived from the resolved site; falls
    back to the vehicle's project_code.
  • Latest Consumption persisted as a new FUEL_LOGS field
    (latest_consumption, number, optional). Empty source cells
    stay empty rather than getting coerced to 0.

PocketBase change
  • scripts/migrations/2026-08-24-fuel-logs-latest-consumption.py
    adds the new field. Idempotent; follows the pattern of the
    2026-08-19 migration. Run it with PB_ADMIN_EMAIL +
    PB_ADMIN_PASSWORD before importing the new sheets, or the
    import will fail with 'latest_consumption is not a field'.

UI
  • Import button now accepts .xlsx and CSV; SAMPLE_HEADERS +
    SAMPLE_OPTIONAL show the new column expectations and the
    DD/MM/YYYY date format hint.
  • fuel-view.tsx shows a 'Consumption (src)' column on the row
    table so operators can eyeball what their fuel team sent
    against the app's recomputed variance.

Out of scope (intentionally untouched)
  • vehicles, sites, data_import_batches and every other
    collection — no schema changes there.
  • PO / timesheets / delivery-notes imports.
  • fuel/page.tsx business logic (variance calc, vehicle summaries).

Verified
  • Dry-run against 'The Hills Diesel 1-24 August.xlsx': 134/134
    rows parse cleanly, 0 errors. 12/134 rows correctly carry an
    empty Latest Consumption (matches the source).
  • vitest: 20/20 fuel-import-parse + 35/35 PO-schema tests pass.
  • tsc --noEmit clean.
  • One pre-existing failure in
    src/app/api/purchase-orders/process-batch/route.test.ts
    unrelated to fuel; confirmed failing on main before this change.
2026-08-24 22:03:08 +02:00
DEls
e0417e4560 fix(po-schema): allow qty = 0 on line items
PO ingestion was rejecting valid line items with qty = 0 (base
corrections, pending test rows) because the schema strictly required
qty > 0. Loosen the guard to qty >= 0 while keeping rate, disc_percent
and vat_percent strict. Also tighten against non-finite numbers
(NaN, +/-Infinity) via Number.isFinite — the previous !(qty > 0) guard
let Infinity slip through.

- Schema: qty >= 0, finite; rate/disc/vat rules unchanged
- Tests: add positive case for qty = 0; add negative + NaN rejection
- 35/35 schema tests pass; tsc clean
2026-08-24 21:32:13 +02:00
DEls
aa8b1d61f6 fix(api): use err.status for PB 404 detection in process-batch
PB-SDK puts the HTTP status on err.status (e.g. 404), not in
err.message. The previous /404/.test(err.message) check never matched
PB's actual 404 message ('The requested resource wasn't found.'), so
every 404 fell through to the generic 'lookup_failed' 500.

This wouldn't have caused the user's current 500 since their batch row
exists, but it would mask any future 'batch row not found' race as
'lookup_failed' and prevent the correct 404 + retry flow.
2026-08-24 18:42:14 +02:00
DEls
18ebf6d217 diag: include step + detail in process-batch lookup_failed
When the route returns 500 with 'lookup_failed', include the failing
step name and the underlying error string in the response body so we
can pinpoint which catch block is firing (get_batch / dedup / match_site).
Production diagnostic - safe to revert once root cause is found.
2026-08-24 18:39:15 +02:00
DEls
7fd46d9ebf feat(fuel): add QR code and cost per hour to vehicle and plant summaries
- Enhanced VehicleSummary and PlantRow interfaces to include qrCodeId and costPerHour properties.
- Updated FuelView and PlantView components to display QR code information in the respective tables.
- Adjusted data fetching in FuelPage and VehicleDetailPage to retrieve and handle new fields from the API.
- Modified site detail views to reflect the unified VEHICLES collection, ensuring accurate data representation for plant utilization.

This update improves the visibility of QR codes and cost information for better tracking and management of vehicles and equipment.
2026-08-20 01:31:15 +02:00
DEls
65b06277eb feat(api): normalise po_number in process-batch + preserve file on dedup
- Normalise po_number (strip PO- prefix, uppercase, collapse whitespace)
  via the new normalizePoNumber helper on the dedup compare AND on the
  stored purchase_order_number. All new POs go forward in canonical form.

- Add a clearFile option to finalizeBatchAsProcessed and call it with
  { clearFile: false } on the dedup path. The duplicate upload's bytes
  stay on the batch row for audit / debugging; the original PO's file
  is unchanged. The success path still moves the file from batch -> PO
  and clears the batch (current canonical-home behaviour).

- Response surfaces both po_number (normalised, used for dedup/storage)
  and po_number_extracted (the raw AI output) for downstream debugging.

Tests: 15 in this file; full suite still 88/88 green.
2026-08-20 00:51:15 +02:00
DEls
d5b691e2ad feat(api): hash-based dedup in /purchase-orders/upload
SHA-256 the file bytes, look for a recent (30-day) processed
data_import_batches row with the same hash, and short-circuit with
200 { duplicate: true, existing_batch_id, existing_po_id } when found.

Saves an AI extraction call + a cron tick for byte-identical re-uploads.
Stores file_hash on the new batch row so future re-uploads can match
against it.

Backed by a new index (idx_batches_file_hash on file_hash, status)
and a new file_hash column (text, 64 chars) on data_import_batches
applied via PB admin API.

Tests: 11 in this file (covering happy path, byte-identical re-uploads,
hash collisions, dedup-query failures, and existing edge cases).
2026-08-20 00:51:15 +02:00
DEls
7b25a1f32c feat(lib): add normalizePoNumber helper for canonical PO numbers
Strips vendor prefixes (PO-, PO#, P.O., etc.), collapses whitespace,
uppercases. Used by the upload-route hash dedup and the process-batch
dedup-by-po_number check so OCR variants ('PO-1234' vs 'po 1234') fold
to the same canonical form.

Tests: 33 in this file (23 pre-existing for validatePurchaseOrderPayload,
matchSite, computeTotalValue + 10 new for normalizePoNumber).
2026-08-20 00:51:15 +02:00
DEls
8b7b1ac2c7 chore: ignore kilo.json IDE config (contains MCP bearer tokens) 2026-08-20 00:51:15 +02:00
DEls
097d2fdcf4 fix(api): surface real PocketBase error in /purchase-orders/upload
The catch block on batch_create only logged the PB error server-side
and returned a generic 500 'batch_create_failed', which forced
operators to dig through Coolify logs to find the real cause (e.g.
mime type not allowed, missing field). Now the response includes
pb_status, pb_message, and pb_data so the actual reason is visible
in the browser's Network tab and the dashboard's error toast.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 22:19:21 +02:00
DEls
84ff4c25b2 docs: add PO import queue spec/plan, ignore cooler.env, rewrite n8n ingest impl
Captures the async-queue redesign of the PO/DN import pipeline (see
spec + plan) and rewrites the n8n implementation doc to match the
new cron-driven architecture (workflow 005iI4jX5mq9DsTR). Also
gitignores cooler.env so the local-only deployment reference is no
longer tracked.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 22:03:39 +02:00
DEls
34f272f520 docs(n8n): mark old webhook-sync flow as superseded
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 20:03:11 +02:00
DEls
e45fa11c74 docs(deploy): document POCKETBASE_ADMIN_TOKEN for process-batch + n8n
Both Next.js /api/purchase-orders/process-batch and the n8n cron
workflow need a PocketBase admin token (generated via PocketBase
admin > Settings > Admins). The existing template only documented
email+password auth.

Add a new 'POCKETBASE (Next.js process-batch + n8n cron)' section
with the token variable, the generation instructions, and the
warning that Next.js env and the n8n workflow's hardcoded token
value must match.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 20:01:45 +02:00
DEls
0802afccf1 refactor(ui): tighten upload-button status pill (DRY, a11y, error UX)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 19:58:14 +02:00
DEls
029b9b3bf9 feat(ui): poll batch status + retry button on PO upload
After Task 7's upload returns 202 { batch_id, status: 'queued' }, the
button now polls /api/data-import-batches/[id]/status every 2s, renders
a state pill (queued/processing/processed/failed/dead), and provides a
Retry button that calls POST /retry for failed/dead batches. Polling
stops on terminal states and on unmount; no double-submit.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 19:52:53 +02:00
DEls
4627a2a7f5 feat(api): mark sync imports as processed on create
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 19:49:42 +02:00
DEls
f0f7c82a09 feat(api): rewrite upload route to write data_import_batches
POST /api/purchase-orders/upload no longer forwards the file to n8n
synchronously. It now validates size/MIME, writes a data_import_batches
row (batch_type='purchase_orders', status='pending', attempts=0) with the
uploaded file attached, and returns 202 { batch_id, status: "queued",
filename } immediately. n8n's 5-minute cron claims pending rows and calls
back to /api/purchase-orders/process-batch; the browser polls
/api/data-import-batches/[id]/status.

Adds route tests covering validation guards, the queued 202 contract,
the batch-create failure path, and that no synchronous fetch to n8n
happens.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 19:47:24 +02:00
DEls
f1c285a149 refactor(api): drop unused PurchaseOrderItemPayload import
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 19:43:17 +02:00
DEls
0fff2d1f2a feat(api): add /purchase-orders/process-batch (the new callback)
n8n POSTs extracted PO JSON + batch_id after downloading the file
from PocketBase. The route validates the payload, dedups by po_number
(idempotent), resolves the site, moves the batch's file onto a new
purchase_orders row + items, and finalizes the batch with
status=processed, processed_record_id, processed_at, original_file
cleared.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 19:40:45 +02:00
DEls
c39124c637 feat(api): add data-import-batches manual retry endpoint
POST /api/data-import-batches/[id]/retry resets a failed or dead batch
back to pending (attempts=0, last_error/next_retry_at cleared) so the
next cron sweep picks it up. Non-retryable states return 409 with the
current status; legacy rows with no status default to processed.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 19:36:29 +02:00
DEls
c12c66a20a feat(api): add data-import-batches status polling endpoint
GET /api/data-import-batches/[id]/status returns the current batch
state for the upload button's 2-second polling loop. Returns 400 on
bad id format, 404 if missing, 500 on lookup failure. Defaults
missing status to 'processed' for legacy timesheets/fuel-logs rows.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 19:32:33 +02:00
DEls
52efc53e55 refactor(batch): drop unused status field, expand test edges
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 19:29:35 +02:00
DEls
3f15c6ace7 refactor(batch): import BatchStatus from pb-types
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 19:24:57 +02:00
DEls
fce0719b62 feat(batch): add state-machine helpers (TDD)
TDD: tests first, then the pure helpers nextBatchState and nextRetryAt.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 19:22:45 +02:00
DEls
32dd1b2842 feat(types): add batch async-queue fields to pb-types
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 19:20:32 +02:00
DEls
c534572590 feat(migration): add async-queue fields to data_import_batches
Extends the data_import_batches collection for the PO/DN async-import
queue redesign (see spec docs/superpowers/specs/2026-08-19-po-import-queue-redesign.md
and plan docs/superpowers/plans/2026-08-19-po-import-queue-implementation.md).

Adds 8 fields:
  content_type, status (select: pending|processing|processed|failed|dead),
  attempts, last_error, last_attempt_at, next_retry_at, processed_at,
  processed_record_id

Extends the batch_type enum to include purchase_orders and delivery_notes.

Adds index idx_batches_pending on (status, batch_type, next_retry_at) for
the cron query.

Idempotent: safe to re-run.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 19:19:17 +02:00
DEls
109a6938da Enhance PO upload error handling and validation
- Implement improved error handling for the purchase order upload process, ensuring that failures from n8n are properly surfaced to the user.
- Introduce a timeout mechanism for the n8n fetch request to handle potential delays more effectively.
- Refine the validation logic for the purchase order payload to ensure stricter adherence to expected formats and values.
2026-08-19 15:03:48 +02:00
DEls
8f37f3467f Spec: clarify PO upload dialog does not auto-close
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 15:03:48 +02:00
DEls
a6aa3bdb5f Drop dead try/catch around Buffer.from(base64) in /ingest route
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 15:03:48 +02:00
DEls
5abc96116f Import PO MIME/size constants from shared schema module
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 15:03:48 +02:00
DEls
e7f8799c45 Mount UploadPurchaseOrderButton on /dashboard/purchasing 2026-08-19 15:03:47 +02:00
DEls
0b873ae308 Add UploadPurchaseOrderButton component 2026-08-19 15:03:47 +02:00
DEls
38844b29d4 Escape backslashes in PO duplicate-check filter for consistency with matchSite
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 15:03:47 +02:00
DEls
a295d9ea18 Add /api/purchase-orders/ingest route (n8n callback, writes to PB) 2026-08-19 15:03:47 +02:00
DEls
2be0c8de8b Surface n8n failures + add upload timeout + dedupe MIME/size constants
- Purchase order upload route now returns 502 n8n_rejected when n8n
  responds with a non-2xx status, instead of silently claiming success.
- Add a 10s AbortController timeout on the n8n fetch, returning 504
  n8n_timeout on expiry (502 n8n_unreachable on other network errors).
- Extract ALLOWED_MIME and MAX_FILE_BYTES from upload/route.ts into
  src/lib/purchase-order-schema.ts so the upcoming ingest route can
  share the same validation constants.
2026-08-19 15:03:47 +02:00
DEls
508e78f72b Add /api/purchase-orders/upload route (forwards file to n8n) 2026-08-19 15:03:47 +02:00
DEls
75770073a6 Tighten PO payload validator (disc/vat range, raw_ocr shape, PB filter escape)
- Validate disc_percent and vat_percent are finite numbers in [0, 100]
- Reject raw_ocr arrays (was passing typeof === 'object' check)
- Escape backslashes before quotes in matchSite's PocketBase filter

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 15:03:47 +02:00
DEls
108b17b556 Refactor Purchase Order upload flow documentation
- Updated the documentation to reflect the latest changes in the Purchase Order upload and n8n ingestion process.
- Clarified the data flow from browser to Next.js API endpoints and through to PocketBase.
- Ensured that the documentation aligns with the recent implementation and design specifications.
2026-08-19 02:25:11 +02:00
DEls
716fa45bf6 Add Vitest + n8n env var documentation 2026-08-19 02:21:36 +02:00
DEls
24fc85d911 Plan: Purchase Order upload + n8n ingestion implementation
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 02:16:22 +02:00
DEls
14b63e50ab Design: Purchase Order upload + n8n ingestion
Adds design spec and n8n handoff doc for the new PO upload flow:
browser -> Next.js /api/purchase-orders/upload -> n8n OCR ->
Next.js /api/purchase-orders/ingest -> PocketBase via pbAdmin().

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-19 02:10:58 +02:00
DEls
fb1c922fbf Enhance Site Detail Page with Financial Roll-ups and Plant Utilization
- Introduced new data fetching for plant and income claims, enhancing the financial overview.
- Added calculations for daily costs and integrated weekly and monthly income vs expenses roll-ups.
- Implemented plant utilization metrics to track equipment efficiency.
- Updated the SiteDetailData interface and SiteDetailView component to accommodate new data structures and display improvements.
2026-08-19 02:09:13 +02:00
DEls
20a16a27f8 Enhance Dashboard and Sidebar Navigation
- Updated the Dashboard page to improve card styling with a warm-amber glow strip and adjusted icon presentation for better visual appeal.
- Expanded the Sidebar navigation by adding new entries for "Sites," "Workforce," "Plant & Equipment," "Fleet & Fuel," and "Purchasing," each with corresponding icons and descriptions to enhance user navigation and access to key features.
2026-08-19 01:54:16 +02:00
DEls
c5b1acfc7f Update package dependencies and remove unused API routes
- Added `pocketbase` as a dependency in `package.json` and `package-lock.json`.
- Updated the `dev` script in `package.json` to run on port 6004 instead of 3000.
- Removed unused API routes: `src/app/api/health/route.ts`, `src/app/api/mcp/route.ts`, `src/app/api/upload/route.ts`, and their associated logic to streamline the codebase.
- Cleaned up `package-lock.json` by removing unnecessary peer dependencies and ensuring consistency in the dependency tree.
2026-08-19 00:14:47 +02:00