Commit graph

304 commits

Author SHA1 Message Date
DEls
dcde915323 feat(delivery-notes): resolve an ambiguous product line and remember it
Co-Authored-By: MiniMax <noreply@minimax.io>
2026-09-17 21:42:18 +02:00
DEls
525fd38392 feat(dn-pipeline): use the three-outcome matcher and aliases everywhere
One resolution per line, shared by product_matched, units_convertible and
promote, so a note cannot pass its check and then be linked elsewhere.

Co-Authored-By: MiniMax <noreply@minimax.io>
2026-09-17 21:40:43 +02:00
DEls
15b8bbdba4 feat(dn-pipeline): add the remembered product alias table
Co-Authored-By: MiniMax <noreply@minimax.io>
2026-09-17 21:34:37 +02:00
DEls
13a6122fa7 feat(dn-pipeline): three-outcome PO item matcher with a dimension gate
matched / ambiguous / none. The dimension set must match exactly, so
U PIPES-600 can no longer resolve to S&S Pipes-1050 on shared tokens.
Includes a regression test asserting no known input returns a wrong match.

Co-Authored-By: MiniMax <noreply@minimax.io>
2026-09-17 21:33:47 +02:00
DEls
305b82bfeb fix(dn-pipeline): a failed note write no longer aborts the batch
Found while reviewing the tasks 2-5 work.

promoteNote was unguarded, so a write failure on one note threw out of
runBatch. The notes promoted before it stayed in delivery_notes while the
final update -- the one that writes notes_promoted -- never ran, so the
board reported "0 of 5" for a ledger that already held two. The notes
after it were never attempted at all.

Now the failure is caught, attributed to that note as a failing check,
and the remaining notes still process. Verified by mutation: the new test
fails when the catch re-throws.

Also replaces two run.test.ts tests that injected a notes_reconciled
check the real runBatchChecks can no longer emit -- they passed while
describing a world that no longer exists. The allNotesPromoted gate is
documented as belt-and-braces: it cannot currently be the deciding factor,
because a skipped note always coincides with a failing check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 21:04:16 +02:00
DEls
c7b01ab850 refactor(dn-pipeline): enforce note reconciliation without an echo check
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 20:58:51 +02:00
DEls
703cdedc42 fix(dn-pipeline): a PO line with no unit no longer blocks delivery
61 of 148 purchase_order_items have an empty unit. Blocking every
delivery against them rejects 41% of lines over a gap on the order.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 20:57:44 +02:00
DEls
e66f29b124 fix(dn-pipeline): convert units before comparing nett to quantity
Delivery notes weigh in kg while the same supplier's invoices weigh in
tons, so the check was comparing 29980 kg against 30.275 tons. Also stops
failing documents that simply have no weigh section.

Also updates promote.test.ts, run.test.ts and segment.test.ts fixtures
for the new required mass_unit field on PageRead/NoteSegment (not named
in the plan's file list for this task, but required for typecheck).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 20:56:24 +02:00
DEls
06311c6de2 fix(dn-batches): add uploaded_at so ordering works
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>
2026-09-17 20:53:23 +02:00
DEls
a7c116c036 feat(delivery-notes): accept multi-file uploads (up to 50 per request)
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.
2026-09-17 18:46:51 +02:00
DEls
31fbc52c4d chore: 410 the old ocr-result route and archive its workflow export 2026-09-17 15:31:29 +02:00
DEls
6317f5da09 fix(delivery-notes): surface every processing_status in the filter 2026-09-17 15:23:13 +02:00
DEls
826050c48a feat(dn-batches): add re-run, dismiss and note correction actions 2026-09-17 14:54:59 +02:00
DEls
85c1a456d7 feat(delivery-notes): add the batch detail view with note correction 2026-09-17 14:45:47 +02:00
DEls
ddc17b29c1 feat(delivery-notes): add the inbox kanban board 2026-09-17 14:43:18 +02:00
DEls
575352c553 feat(dn-batches): serve rendered document pages 2026-09-17 14:39:43 +02:00
DEls
5e88ebe410 feat(delivery-notes): upload creates an inbox batch, not a delivery note 2026-09-17 14:31:22 +02:00
DEls
ebede76c40 feat(dn-batches): add the tick route with compare-and-set claiming 2026-09-17 14:25:25 +02:00
DEls
540bea8b5d feat(dn-pipeline): orchestrate a batch through extraction, checks and promotion 2026-09-17 14:16:04 +02:00
DEls
3eee3e17e3 feat(dn-pipeline): promote a validated note into delivery_notes 2026-09-17 14:11:33 +02:00
DEls
710146e27c feat(dn-pipeline): extract one page at a time with strict JSON validation 2026-09-17 14:05:12 +02:00
DEls
d4b1425540 feat(dn-pipeline): render document pages and extract per-note page ranges 2026-09-17 14:00:26 +02:00
DEls
bd31f4c634 feat(dn-pipeline): add deterministic per-note and per-batch checks 2026-09-17 13:57:12 +02:00
DEls
5d77ac9047 feat(dn-pipeline): group pages into notes with total page coverage
Every page ends in exactly one note, unassigned, or needs-subsplit, and
the function throws rather than return a segmentation that loses one.
2026-09-17 13:54:30 +02:00
DEls
889f45a6f0 feat(dn-pipeline): add shared pipeline types 2026-09-17 13:52:16 +02:00
DEls
72cdb2fb1b feat(delivery-notes): add dn_batches and dn_batch_notes collections 2026-09-17 13:49:47 +02:00
DEls
6cee861c3e chore: final review polish (trailing newlines, JSDoc, changelog tense) 2026-09-17 12:49:37 +02:00
DEls
e7882609b1 feat(purchasing): show received quantity in both PO and slip units 2026-09-17 12:19:42 +02:00
DEls
6a9c7403c4 fix(delivery-notes): convert received quantity into the PO line's unit
Fetches the PO item unit and delegates row construction to
buildDeliveryNoteItemRows, which also removes the matched-id off-by-one
that linked lines to the wrong PO item after a zero-quantity line.
2026-09-17 12:16:03 +02:00
DEls
401fa51ff9 feat(delivery-notes): build line item rows with unit conversion
Filters once so matched PO item ids cannot drift when a zero-quantity
line is dropped, and converts the slip quantity into the PO line's unit.
An unconvertible line contributes zero rather than entering the maths at
the wrong magnitude.
2026-09-17 12:11:40 +02:00
DEls
1a30f6a621 feat(delivery-notes): add qty_raw/unit_raw/unit_converted to line items 2026-09-17 12:07:59 +02:00
DEls
2881db22d9 feat(units): add closed-table quantity conversion 2026-09-17 12:04:19 +02:00
DEls
11635d9f8f feat(units): add unit alias normalisation 2026-09-17 11:57:47 +02:00
DEls
eba3f8c89a feat(clock): in-app PWA install prompt with Chrome + iOS branches
The kiosk phone needs the app installed for the full-screen
standalone experience, but no browser auto-prompts on day one —
Chrome's mini-infobar waits for an engagement heuristic that's
too slow for the kiosk use case, and iOS Safari never fires
beforeinstallprompt at all.

New InstallPrompt component mounted in the clock layout (so it
only appears on /clock, not the dashboard). Three branches:

  * Chrome / Edge / Android: defer beforeinstallprompt, surface
    our own Install button that calls prompt() on tap.
  * iOS Safari: detect iOS user agent and show 'Tap Share then
    Add to Home Screen' (Apple doesn't fire the install event).
  * Already installed: detect display-mode:standalone +
    navigator.standalone and render nothing — silent on the
    kiosk itself.

Dismissal is per-session (sessionStorage), so a fresh visit on
a new device re-shows the prompt. The banner sits above the
clock footer (bottom: 6rem) so it doesn't fight the Person /
Fleet tabs for taps.
2026-09-14 11:17:54 +02:00
DEls
059e32cabb feat(clock): add orange viewfinder + scanning state to the QR screen
The kiosk phone is mounted on a wall with no physical UI to confirm
'this is the scanner' — without the overlay the camera feed alone
is a confusing blank screen. Now the scanner paints:

  * a centred orange-cornered viewfinder (the QR alignment frame)
  * a black vignette dimming everything outside that frame
    (achieved via a 9999px box-shadow on the viewfinder, the
    cleanest way to punch a rounded hole in an overlay without SVG
    mask math)
  * an orange scan line sweeping top-to-bottom inside the frame
    (transform-based keyframes, pauses under prefers-reduced-motion)
  * a pulsing 'Scanning for badge' status pill at the top
  * a one-line 'Hold the QR badge steady inside the frame' hint
    at the bottom

Everything is pointer-events-none so touches still reach the
camera. The overlay hides during the green/blue/red result banners
so it doesn't compete with the success message.
2026-09-14 00:28:47 +02:00
DEls
5f44d6a926 feat(clock): replace icon with clock-on-cube design + OG share preview
The new PWA icon matches the supplied reference: dark background, a
clock face with an orange upper-right arc and hands at 12 and 4
o'clock, sitting on an isometric cube whose two visible faces each
have the MEGA 'M' carved into the background colour, with two small
orange base wedges under the cube.

When the /clock URL is shared (Slack, iMessage, etc.) the static
1200x630 opengraph-image.png is picked up automatically and renders
the icon alongside the 'MEGA Clock' wordmark and tagline, via
openGraph + twitter metadata added to the route.
2026-09-13 21:55:40 +02:00
DEls
978ddf03cc feat(workforce): add "On site now" page derived from MEGA Clock scans
A supervisor-facing view of who's currently clocked in, per site — a
live muster list built entirely from the existing clock_events data
(no schema change). Lives in the dashboard next to Workers/Timesheets,
not on the kiosk phone, since it's a manager tool rather than
something a worker scanning their own badge needs to see.

The per-employee "latest event" reduction is shared via
src/lib/clock-events.ts so the on-site page and the Workforce
landing page's new "On site now" KPI tile stay in sync by
construction rather than by copy-pasted logic.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 00:59:43 +02:00
DEls
3ec2db9b2a feat(clock): bigger footer tabs, session scan counter, wake lock + haptics
Footer buttons gain icons and more padding for easier tapping on a
wall-mounted kiosk phone. The header now shows a running count of
badges scanned since the app was opened. PersonScanner also requests a
screen wake lock while its camera is active (re-acquired on
visibilitychange, since the OS revokes it when backgrounded) and adds
vibration feedback per scan outcome, so a kiosk phone doesn't need to
be watched closely to confirm a scan landed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 00:51:53 +02:00
DEls
a7ac99d934 feat(clock): add vertical divider between Person and Fleet tabs 2026-09-12 00:34:50 +02:00
DEls
b212cd49f6 feat(clock): add visible divider above the Person/Fleet footer tabs 2026-09-12 00:28:14 +02:00
DEls
d8ae2ba094 feat(clock): add MEGA Clock icon set 2026-09-12 00:17:25 +02:00
DEls
3d211e9121 feat(clock): add /clock page with its own installable manifest 2026-09-12 00:17:04 +02:00
DEls
3236ba32ac feat(clock): add ClockView with Person/Fleet footer tabs 2026-09-12 00:16:30 +02:00
DEls
af6f7153fb feat(clock): add PersonScanner camera + QR decode component 2026-09-12 00:16:22 +02:00
DEls
13c29eb93d feat(clock): add /clock layout (PIN gate, no sidebar) 2026-09-12 00:16:07 +02:00
DEls
42766739fd feat(clock): add safe-area padding utility for the footer nav 2026-09-12 00:15:53 +02:00
DEls
221c30e1fe chore(clock): add ambient BarcodeDetector type 2026-09-12 00:15:46 +02:00
DEls
a6c273e70a feat(clock): add POST /api/clock/scan clock-in/out endpoint 2026-09-12 00:15:32 +02:00
DEls
59136a0935 fix(auth): return to the originating page after PIN unlock 2026-09-12 00:14:55 +02:00
DEls
2d3426b756 feat(auth): add safe-redirect helper for post-PIN navigation 2026-09-12 00:14:35 +02:00