Commit graph

536 commits

Author SHA1 Message Date
DEls
b8dcf20146 fix(dn): settle one product line at a time, and keep the link
Answering the second product question returned batch_locked. Resolving a
line fired a full re-run, which holds the batch lease for some twenty
seconds, so the next answer arrived while the lease was still taken. The
re-run achieved nothing either: resolve-item already links the line,
re-evaluates the document and writes its stage.

It was also quietly destructive. A run rebuilds `items` from the page
reads, and only a choice saved as an alias can be re-derived from the
descriptions — so a link made without ticking "remember this alias" was
discarded by the next run, including the tick a minute later. Manual
links now persist in manual_overrides alongside the order number, and are
honoured only while the linked item still belongs to the resolved order,
so correcting the order cannot leave lines pointing at the previous one.

Each line now has its own Save rather than committing the moment the
dropdown changes, and an answered line leaves the list instead of
reloading onto the same screen with the same questions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 00:15:13 +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
7a02ab9bb7 feat(dn): work a delivery note on its own page, not its whole batch
A batch is the unit only while its files are being prepared, read and
grouped. Past that point holding its documents together means one
unresolved order keeps every settled sibling waiting, and clicking a
document to fix one order presented a page strip and five unrelated
notes.

The inbox board now changes unit where the work does: Queued, Reading
photos and Check page grouping hold batches; Needs matching, Ready to
post and Complete hold individual documents. Both stage ladders already
existed in PocketBase, so nothing needed migrating. Failed batches move
to their own strip rather than spending a column.

/document/[id] is the new home for one note — its pages, values, lines,
failures, order picker and, at last, a button that posts it. The post
endpoint has been there and gated since it was written with nothing in
the app calling it. The batch screen keeps page grouping and lists its
documents as links.

The re-run button ran runBatch regardless of DN_STAGED_INGESTION_ENABLED.
On a staged batch that was destructive rather than merely inconsistent:
runBatch reads the single dn_batches.file, so a five-photograph batch
would have been rebuilt from its first photograph alone. It now selects
the runner exactly as the tick does.

Save grouping only set a message. A save bumps the assembly revision and
deliberately blanks each candidate's commercial data for recomputation,
so the screen appeared to do nothing while quietly emptying six
documents, and the next save was rejected as stale against a revision the
operator never saw. It reloads now, as its neighbouring buttons already
did. Choosing an order re-runs the checks in the same action.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 23:50:24 +02:00
DEls
74a2a9de11 feat(dn): record quantity findings instead of refusing the delivery
A delivery note is a record of material that physically arrived. Refusing
to record it because its quantity cannot be reconciled with the order
leaves the yard's paperwork outside the system, so quantity findings are
now reported on the purchase order rather than gating ingestion.

qty_plausible and delivered_totals_available become informational: still
computed, still stored, never blocking. Only assembly and identity —
po_resolved, product_matched, units_convertible, not_duplicate — hold a
document back.

qty_plausible also stops guessing. Where no common unit exists it fell
back to the raw slip figure, comparing 30,260 kg against an order of 30
and reporting a 1,009x overage with "check the slip for a misread
decimal". The slip was right; the order had no unit. 61 of one site's 148
PO lines carry no unit, and units_convertible deliberately tolerates
that, so the two checks disagreed about the same gap. It now declines to
compare rather than produce a number that blames the wrong document.

The delivered-totals query OR-ed every relevant PO item id into one
filter sent as a GET parameter. Measured against production, 80 clauses
(3,436 chars) succeed and 85 (3,651) return a bare 400; that site's 148
lines put it permanently over, so no candidate could ever reach
ready_to_post. Chunked at 50.

Batch stage no longer counts candidates needing review. Grouping is a
batch question and matching is a document question, and folding them
together parked batches under "Check page grouping" with no grouping left
to check — holding settled notes behind one unresolved order.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 23:50:10 +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
da756f5224 feat(worker-qr): real PDF + CSV batch export
Replace the off-screen CSS grid + window.print() in the worker QR
batch dialog with two on-click exports:

  - Download PDF — A4 portrait, 2x5 grid of CR80 (85x54mm) cards per
    page, brand orange header, generated with pdf-lib (dynamically
    imported, same pattern as src/lib/photo-pdf.ts).
  - Download CSV — UTF-8 BOM, columns employee_no, name, position,
    worker_type, project_code, qr_payload via Papa.unparse.

The single-card print on the worker detail page still uses
window.print() — one card has no pagination problem, only the batch
view did.

Spec §4.4 updated to match the new output mechanism and explain the
mobile failure mode that prompted the change.

New tests (8) cover the CSV builder (header order, qr_payload ==
employee_no per spec §4.1, RFC 4180 quoting, empty input) and the PDF
builder (empty input throws, valid %PDF output, 11 workers paginate
onto 2 pages, fake PNG injection so layout is testable without qrcode).
2026-09-20 20:43:10 +02:00
DEls
3a0aedec0d Merge branch 'codex/hybrid-dn-ingestion'
Staged, photo-first delivery note ingestion: bulk JPEG/PDF intake, one
persisted vision read per page, deterministic assembly into candidate
documents, site-scoped supplier/PO resolution, split review, a
supplier-grouped work queue with manual line linking, and fenced
idempotent posting.

The new pipeline stays dormant behind DN_STAGED_INGESTION_ENABLED; the
tick route keeps running the legacy runner until the flag is set to
exactly "true". The PocketBase schema is already applied in production
(verified by a read-only dry run: every collection, field and index
present, no financial rows touched).

The local draft of the multi-file upload route is superseded by the
branch version, which parameterises its PocketBase filters, checks
duplicates before creating a batch, and rolls back a partially written
batch. That draft is kept in stash@{0}.

Verified on the merged tree: tsc --noEmit clean, 1200 tests across 106
files passed, next build passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 21:56:23 +02:00
DEls
df47e6d2ba docs: note queue server boundary runtime fix 2026-09-19 20:56:48 +02:00
DEls
03ac13d951 fix: keep queue model server safe 2026-09-19 20:56:25 +02:00
DEls
d00b5f1204 docs: record quoted index normalization verification 2026-09-19 19:37:18 +02:00
DEls
1a97d7db44 fix(migration): preserve quoted index semantics 2026-09-19 19:36:50 +02:00
DEls
485443a1e4 docs: record migration idempotency verification 2026-09-19 19:33:56 +02:00
DEls
bee2b3dd17 fix(migration): normalize PocketBase index definitions 2026-09-19 19:33:39 +02:00
DEls
d12ca92468 docs: record scoped ledger totals verification 2026-09-19 19:26:37 +02:00
DEls
bb70f3339a fix(dn): scope staged ledger totals to PO items 2026-09-19 19:26:20 +02:00
DEls
9fafec64b1 docs: record staged readiness follow-up 2026-09-19 19:23:40 +02:00
DEls
199dfba195 fix(dn): align staged readiness with ledger totals 2026-09-19 19:23:22 +02:00
DEls
0f910c1bf7 docs: record final delivery note fix verification 2026-09-19 19:17:46 +02:00
DEls
bbd7a7bb23 fix(dn): harden staged assembly and page identity 2026-09-19 19:17:46 +02:00
DEls
9ace0fbd3a docs: record audit identity fix 2026-09-19 19:00:06 +02:00
DEls
eed128ebb3 fix: bind posting audit to delivery note 2026-09-19 18:59:52 +02:00
DEls
4b0da73dcf docs: record audit and PO verification fixes 2026-09-19 18:58:25 +02:00
DEls
11bc1a4b6b fix: verify audit collisions and purchase orders 2026-09-19 18:58:07 +02:00
DEls
d2dc3a4262 docs: record posting fix verification 2026-09-19 18:54:38 +02:00
DEls
310219d09b fix: keep staged migration dry run additive 2026-09-19 18:54:24 +02:00
DEls
4b4c9b2590 docs: record delivery note posting fix round 2026-09-19 18:53:49 +02:00
DEls
2914eefacf fix: harden delivery note posting review gates 2026-09-19 18:53:25 +02:00
DEls
25d3dd6af0 docs: record delivery note ingestion verification 2026-09-19 18:47:58 +02:00
DEls
5d094183e6 feat: add delivery note posting and pipeline comparison 2026-09-19 18:45:30 +02:00
DEls
45b3c66480 docs: update task 11 round 3 report 2026-09-19 18:38:13 +02:00
DEls
81a4427fea test: cover correction transaction fencing 2026-09-19 18:38:02 +02:00
DEls
0846ce5790 docs: update task 11 round 2 report 2026-09-19 18:36:05 +02:00
DEls
fe59c618e0 fix: fail closed on persisted correction checks 2026-09-19 18:35:55 +02:00
DEls
9ce425063d docs: update task 11 fix report 2026-09-19 18:32:03 +02:00
DEls
8f2bb1fc13 fix: harden delivery note correction gates 2026-09-19 18:31:48 +02:00
DEls
70117628a6 docs: add task 11 report 2026-09-19 18:22:37 +02:00
DEls
25f8112f6d feat: add delivery note correction linking 2026-09-19 18:21:45 +02:00
DEls
bf00cc0bcd docs: report queue date fallback fix 2026-09-19 18:12:40 +02:00
DEls
e2bb799c52 fix: preserve staged queue upload dates 2026-09-19 18:12:24 +02:00
DEls
b2582d3225 docs: report delivery note queue fixes 2026-09-19 18:11:02 +02:00
DEls
c094aa0140 fix: harden delivery note queue mappings and counts 2026-09-19 18:10:40 +02:00
DEls
2e5e504054 docs: place task 10 report with delivery note sdd 2026-09-19 18:04:07 +02:00
DEls
94426e3bc0 feat: add supplier purchase order delivery note queue 2026-09-19 18:03:14 +02:00
DEls
26143e5ac3 docs(dn): report split review model fix 2026-09-19 17:57:24 +02:00
DEls
1d74905a91 fix(dn): isolate split review model and orphan pages 2026-09-19 17:57:07 +02:00
DEls
d22d7b1867 docs(dn): report split review fix round 2026-09-19 17:53:03 +02:00
DEls
5c57dcbf5c fix(dn): harden split review and page proxy 2026-09-19 17:52:44 +02:00
DEls
61c0e602fe docs(dn): report task 9 split review 2026-09-19 17:44:06 +02:00
DEls
da45513ead feat(dn): add staged inbox and split review 2026-09-19 17:43:36 +02:00
DEls
919124facb docs(dn): record task 8 fix round 4 2026-09-19 17:34:05 +02:00