PocketBase's created is empty on every dn_batches record, making the
board's -created sort a no-op and the tick's oldest-first meaningless.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Operators can now drop a folder of slips at once instead of uploading
them one at a time. Each file becomes its own inbox batch; the route
processes all of them in one request and returns per-file outcomes
(created / duplicate / error).
Backward compatible: single-file requests still get the legacy
response shape. New multi-file response shape:
201 {
results: Array<{ ok: true, batch_id, ... } |
{ ok: true, duplicate: true, ... } |
{ ok: false, error, filename }>,
summary: { total, created, duplicates, errors }
}
The UI input accepts multiple files; the result panel shows one row
per file with a colour-coded icon.
8 new tests cover: per-batch outcomes, >50 files rejected,
mixed MIME types, byte-identical duplicates within one request,
site lookup short-circuits before per-file work.
project_code was a text copy of the parent site's code, denormalised
onto 8 child collections that each already have a real site relation.
Written once at insert, never updated -- docs/data-model.md documents
the drift risk. On purchase_orders it never even held a real project
code: production had requisition numbers, site names, and the PO's own
number in that field.
Every read site now resolves the code through site/expand.site/siteById
instead, reusing each page's existing site map where one already
existed. Every create() call stops writing the column. The chat agent's
list_imports tool no longer reads data_import_batches -- it now reads
timesheets, fuel_sheets, plant_hours_sheets, purchase_orders and
delivery_notes filtered by processing_status, which was necessary before
the pool can be dropped (Task 20 would otherwise 404 the agent).
Also closed a Task 17 gap found while doing this: the agent tools' vehicle
and fuel-log handlers still resolved project code from the now-removed
vehicles.project_code column.
Wrote scripts/migrations/2026-09-15-drop-legacy-columns.py, the one
irreversible step in this whole restructure. Its guard against orphaning
fuel_logs rows checked the wrong condition -- "fuel_sheet is empty" --
which produced a false abort: 73 fuel_logs rows have no fuel_sheet
because they predate batch tracking entirely and never had a
batch_reference to begin with, so dropping data_import_batches costs
them nothing. Fixed the guard to check what it actually claims to check:
a row that HAD a batch_reference and was never relinked. Verified live
that no such row exists on any of the three affected collections.
Script run in dry-run only; not yet applied.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
delivery_notes already creates its own row at upload time with the slip
attached, and n8n's OCR poller already reads delivery_notes directly by
processing_status=pending — it never looked at data_import_batches. The
batch row created alongside every upload was therefore a pure duplicate
write: same file, uploaded twice, read by nothing downstream. Move dedup
onto the DN row itself (it has file_hash from Task 1) and delete the
batch-tracking code paths in both the upload route and the OCR-result
webhook.
The plan's suggested dedup filter/sort used `created`, but this PB
instance has no usable created/updated autodate fields on delivery_notes
(sort=-created 400s here, as already noted for purchase_orders and
flagged by Task 6). Used date_received instead, matching the field the
rest of the app already sorts this collection by (src/lib/agent/load.ts).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mirrors the existing /api/purchase-orders/upload pattern onto the
delivery-note path so byte-identical re-uploads short-circuit with
`200 + { duplicate: true, existing_dn_id, ... }` instead of
creating a second delivery_notes row.
Concretely:
- Hash the bytes with SHA-256 before any PB round-trip.
- Query `data_import_batches` for any processed batch with the same
hash inside a 30-day window (the same window the PO route uses; the
underlying `idx_batches_file_hash` index already covers it).
- On hit, return the existing DN + batch ids and skip creation. The
n8n OCR pipeline is never invoked for a duplicate slip, so a
double-clicking operator costs us nothing.
- On miss, stamp `file_hash` on the new batch row so subsequent
re-uploads can dedup against it.
- Wire the response through `UploadDeliveryNoteButton` as a new
'duplicate' panel showing the existing DN id plus a quick link to
/dashboard/purchasing where the awaiting-PO inbox lives.
pb-types: adds file_hash?: string to DataImportBatchRecord so the
column the PB schema already has is reflected in the TS shape.
- 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.
- 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.
- 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.