Commit graph

95 commits

Author SHA1 Message Date
DEls
a8e67faf3d docs(plans): purchasing UI tweaks — 4 small tasks with TDD-friendly bite-sized steps 2026-08-26 23:58:30 +02:00
DEls
7020449853 docs(specs): purchasing UI tweaks — shorter upload labels, unlinked-DN styling, view PO PDF, fulfillment bug 2026-08-26 23:56:34 +02:00
DEls
6558b3b24e docs(specs): update URL examples and clarify wording in specifications 2026-08-26 23:34:57 +02:00
DEls
417723628f docs(specs): fix stale URL example, 'above' wording, and add recordId notes 2026-08-26 23:34:10 +02:00
DEls
0c95243ecf docs(specs): screenshot of compact needs-review modal with working PDF 2026-08-26 23:33:33 +02:00
DEls
d2a4efa6e8 feat(needs-review): shrink modal to max-w-md for glance-and-pick flow 2026-08-26 23:26:26 +02:00
DEls
aa76470446 fix: pass recordId to SlipPreview from both callsites 2026-08-26 23:24:39 +02:00
DEls
ce3de2f9fb fix(slip-preview): build PB URL from recordId (was parsing filename) 2026-08-26 23:22:33 +02:00
DEls
aded006b9f docs(plans): PDF URL fix + modal compact 2026-08-26 23:21:26 +02:00
DEls
5e86fadc38 docs(specs): PDF URL fix + compact needs-review modal 2026-08-26 23:20:08 +02:00
DEls
0c2e092a3a feat(needs-review): compact modal with sticky header above slip 2026-08-26 23:10:05 +02:00
DEls
9ed7e55139 feat(purchasing): enhance purchasing page with received items tracking
Updated the PurchasingPage component to include received items data, allowing for accurate fulfillment percentage calculations based on authoritative data. The implementation now sums received quantities per purchase order item, replacing the previous denormalized field. Additionally, the needs-review delivery notes component has been improved with a modal for reviewing slips and assigning purchase orders, enhancing user interaction. The SlipPreview component has been integrated for better visual representation of delivery slips.
2026-08-26 23:05:05 +02:00
DEls
20d12f46d9 feat(slip-preview): render PDFs in an iframe instead of download-only 2026-08-26 23:03:41 +02:00
DEls
6df78d8ccd docs(plans): needs-review modal resize + inline PDF viewer 2026-08-26 23:00:10 +02:00
DEls
988ffc6377 docs(specs): needs-review modal resize + inline PDF viewer 2026-08-26 22:58:11 +02:00
DEls
d645a39d9d feat(ocr-result): enhance PO validation and tier resolution logic
Updated the OCR result processing to include validation of matched PO items before determining the verification tier. This change ensures that if the local PO items fetch is empty but n8n supplied matched IDs, the tier will downgrade to draft, requiring user review. Additionally, the `resolveOcrTier` function has been modified to account for item verifiability, improving the accuracy of tier assignments based on confidence levels and PO resolution status. Unit tests have been added to cover these new scenarios.
2026-08-26 22:07:42 +02:00
DEls
d03df384ea feat(ocr-result, po-items): enhance PO matching logic with fuzzy search
Updated the OCR result processing to include a more robust PO matching mechanism. The new implementation prioritizes exact matches, followed by normalized comparisons, and finally employs a Levenshtein-based fuzzy match for improved accuracy. This change ensures that delivery notes are correctly linked to purchase orders, even when OCR introduces minor errors. Additionally, the behavior for handling confidence levels and PO resolution has been clarified in the documentation, enhancing overall processing reliability.
2026-08-26 21:39:59 +02:00
DEls
2fcd7550de feat(po-items): server-side Levenshtein fuzzy match against all POs
OCR is noisy. Instead of exact-then-fuzzy-fallback, always fetch
all ~15 POs from PocketBase, normalise both sides (strip non-
alphanumeric, lowercase), score each with Levenshtein similarity,
and return the best match above 0.75 threshold. Single-char
insert/delete (MEGCP09239 vs MEGCPO9239) scores ~0.9 — matches.
Completely different POs (similarity <0.5) don't match.

Plus the existing 'return 200 with empty items' fallback if no
match — keeps the workflow loop alive even when OCR is bad.
2026-08-26 18:32:54 +02:00
DEls
901d7f2841 fix(po-items): return 200 with empty items on PO not found
OCR sometimes mis-reads PO numbers (e.g. 'MEGCP09239' instead of
'MEGCPO9239') — when that happens the PO lookup 404'd, which
terminated the n8n workflow loop even with onError set on the
HTTP node.

Return 200 with {po: null, items: [], note: 'po_not_found'} instead.
The downstream 'Match items to PO' Code node already treats an
empty items array as 'no PO to match against' and leaves every
OCR item unmatched. The OCR result route then marks the DN as
needs-review and the user can fix the PO link manually.
2026-08-26 17:54:51 +02:00
DEls
32049a67cc fix(po-items): sanitise poNumber param (strip leading # from OCR)
OCR sometimes prefixes PO numbers with '#' from the slip header
(e.g. "#MEGCPO9165"). The workflow URL would otherwise break on
the # being a URL fragment separator, and the exact-match PB
filter would also fail (the fuzzy fallback worked, but a clean
sanitise is cheaper).
2026-08-26 17:23:21 +02:00
DEls
a129affe38 fix(po-items): use double cast for RecordModel -> typed po
TS strict-mode rejects direct cast from RecordModel because the
two types don't sufficiently overlap. The fix is the standard
'as unknown as' double-cast. Was previously caught only by the
Docker build's stricter typecheck, not by the local dev
typecheck (which was polluted by .next/dev/types stale cache).
2026-08-26 16:53:04 +02:00
DEls
187d2a44ee feat(po-items): add route for n8n to fetch PO line items by number
GET /api/purchase-orders/by-number/[poNumber]/items
- Auth via shared x-ingest-secret
- Returns { po: {id, purchase_order_number, supplier}, items: [{id, description, qty, qty_outstanding, unit}] }
- Exact lookup first, then fuzzy fallback (strip non-alphanumeric + lowercase)
- Used by the Delivery Note OCR workflow to enrich the LLM extraction
  with PO context so each line item can be linked back to a
  purchase_order_items row.
2026-08-26 16:41:55 +02:00
DEls
a100f742ac fix(ocr-result): use && not AND in batch filter
PB's filter parser only accepts && as the boolean AND operator; the
literal word AND returns HTTP 400 'Something went wrong while processing
your request'. The rest of the codebase already uses && (see
purchase-orders/upload/route.ts:81).

Caused updateBatchRow to silently no-op after every successful OCR
run — the batch row never transitioned to 'processed'.
2026-08-26 16:34:54 +02:00
DEls
1606bd2cc7 feat(delivery-notes): own batch lifecycle in OCR pipeline
- upload: create data_import_batches row when DN is uploaded
  (batch_type=delivery_notes, processed_record_id=<dnId>)
- ocr-result: mark the batch processed on success, failed/dead on
  failure (uses nextBatchState from batch-state.ts for retries)
- new /api/delivery-notes/[id]/slip proxy route: lets n8n fetch the
  slip file via shared secret instead of embedding the PocketBase
  admin JWT in workflow headers (which was leaking in the workflow JSON)

Backfill: existing pending DN ekko6xkhynoekas needs a manually
created data_import_batches row until it's reprocessed.
2026-08-26 15:50:44 +02:00
DEls
8dbf393446 feat(delivery-notes): enhance delivery note upload and processing
- Updated the delivery note upload API to include site ID for better project association.
- Modified the delivery note schema to support additional fields such as processing status, OCR confidence, and received by name.
- Enhanced the UI components for uploading delivery notes to allow file drop functionality and improved user feedback during file selection.
- Refactored purchasing page to integrate delivery notes that require review, providing better visibility and management of pending items.
- Added new KPIs to track active purchase orders and fulfillment percentages, improving overall dashboard insights.
2026-08-26 15:33:30 +02:00
DEls
2ace4365e6 feat(timesheets): enhance import functionality with site linking and employee creation
- Updated the timesheet import route to support optional metadata fields for site linking and auto-creation of missing employees.
- Enhanced parsing to detect multiple file formats, including Hills Casual Matrix and long-format CSV/XLSX.
- Modified response structure to include detected format and created employees in the JSON response.
- Improved UI components to allow project selection and options for creating new workers during import.
- Refactored import logic to ensure a seamless user experience when handling timesheet uploads.
2026-08-26 12:44:02 +02:00
DEls
f5c063765d feat(fuel-view): enhance inline editing and UI components
- Integrated inline editing functionality for vehicle descriptions, allowing users to edit directly within the FuelView.
- Added a Topbar with EditModeToggle and ImportFuelLogsButton for improved user interaction.
- Implemented EditModeBanner to provide feedback during editing sessions.
- Updated FleetMobileCard and FleetRow components to support inline editing, enhancing the overall user experience.
- Refactored FuelView to accept title and subtitle props for better contextual information.
2026-08-26 08:54:57 +02:00
DEls
2158538782 fix(employee-details): enhance detail drawer scrolling and layout
- Improved the layout of the detail drawer using flexbox for better responsiveness.
- Implemented a scrollable container for the timesheet table to prevent overflow.
- Ensured a clean and organized appearance for displaying employee details.
2026-08-25 23:38:35 +02:00
DEls
ac21f8a214 fix(employees-view): improve detail drawer layout and scrolling behavior
- Adjusted the DialogContent to use flex layout and set a maximum height for better responsiveness.
- Wrapped the timesheet table in a scrollable div to prevent overflow and enhance user experience.
- Ensured the layout maintains a clean and organized appearance while displaying employee details.
2026-08-25 22:47:26 +02:00
DEls
472be99fd8 feat(dashboard): add job start date to site records and enhance site management
- Introduced a new optional field `job_start_date` in the site schema to track project start dates.
- Updated the SitesPage component to utilize `job_start_date` for determining site status and fallback logic.
- Enhanced the SitesView component to display the new status "upcoming" for sites with future start dates.
- Modified the SiteDetailPage to include `job_start_date` in the site details and added an EditSiteButton for site management.
- Updated the site templates to include `job_start_date` for better pre-filling in the site creation form.
2026-08-25 19:01:56 +02:00
DEls
1b9779f47e feat(timesheets): enhance import functionality to support XLSX and CSV formats
- Updated the import route to accept both long-format CSV and Hills-style wide-format XLSX files.
- Implemented a new parsing mechanism that detects file type and processes accordingly.
- Enhanced error handling to provide more informative messages during file parsing failures.
- Modified the response structure to include total rows processed and detailed error reporting.
- Updated the UI components to reflect changes in file selection and user instructions for importing timesheets.
2026-08-25 18:38:02 +02:00
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