Commit graph

995 commits

Author SHA1 Message Date
d73fb6f0ab feat(collection-ai): read invoice discounts and set the discounted cost
HYCO (I009753) prints full prices on the lines and "Discount 25.00%" in
the totals; Swagelok (622831) has a "Disc %" column on each line. The AI
compared our cost (already the discounted price) with the printed price,
so the Price tick went red on both.

- The read now captures a line's discount_percent and a whole-document
  doc_discount_percent (prompt rule 5b) and stores net_unit_price on each
  line (both discounts stacked).
- The Price tick compares the cost with the price after discount; the
  tooltip and the document-lines panel spell it out
  ("R15.10 less 25% invoice discount = R11.33").
- Owner's call: when a "sure" tax-invoice line has a discount and our
  cost isn't the discounted price, the AI sets the cost to it (customer
  lines through updateOrderItemCost, which adds a cost note; stock lines
  through updateStockPurchaseItem) and records why in ai_check.cost_changed,
  shown in purple under the line. No discount, a delivery note or a
  "likely" match: the cost is never touched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 13:18:21 +02:00
9ddb811afd feat(collection-confirm): AI checks on/off switch in the page header
The n8n tick was calling in every 5 minutes but WFDASH answered
"disabled" because Collection checks was off in Settings → AI, and
nothing on Collection Confirm said so.

- New "AI checks" switch next to Refresh, the same setting as Settings →
  AI → Collection checks. Purple and "On" when running, with the last
  run in the tooltip; a gear links to /settings/ai.
- GET/PUT /api/v1/collection-confirm/ai/enabled (browser session). Only
  reads/writes collection_ai_enabled and the last run — no key, model or
  prompt goes through it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:18:13 +02:00
86020ed48d feat(collection-confirm): purple for cards and lines the AI checked
A collapsed PO card turned green as soon as the driver uploaded a photo,
whether or not the AI had read it, so "everything is green" said nothing
about the agent. Now:

- A collapsed card with at least one AI-checked line is purple (the AI
  colour) instead of green; green still means "photo, not checked yet".
- The header says "AI checked N of M lines" with the robot icon, open
  or closed.
- Each line the AI checked gets a light purple wash in the items table.

Lines whose documents couldn't be paired (pair_failed, grey ticks) don't
count as checked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 12:13:45 +02:00
4eb4a24b84 fix(collection-confirm): show AI ticks under the description; always fill the SKU
- The AI and POS columns sat at the far right of the items table, hidden
  behind the document preview in the split view. Ticks (now labelled SKU /
  Qty / Desc / Price) and the POS status render under each description,
  and the table is back to its original six columns and width.
- Owner's call: fill an empty SKU on a sure pairing even if the code may
  be misread; the rep edits it and a near-miss still helps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:45:34 +02:00
a4345c1f85 fix(collection-ai): day-first dates, only fill SKUs read with certainty, tolerant supplier names
- Dates: the model also returns the date as printed; code parses SA
  day-first formats (16-10-26 -> 2026-10-16) and drops dates more than a
  year old or a month ahead (the Bestobell read gave 2016-10-26).
- SKU: a new code_certain flag per line; the AI only fills an empty SKU
  when it read every character clearly (it filled 0515315 for 051315).
- Supplier: names sharing a distinctive word (Bestobell, Belting) no
  longer raise a 'supplier differs' finding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:36:58 +02:00
79d54ce3fd fix(collection-ai): re-read sideways invoice photos turned upright
A sideways Bestobell invoice was read as doc 10045064 / a bank-details
line; turned 270 deg the same photo reads correctly (10245084,
PO202610010MRE, rubber insertion @ R365). When no printed PO number
resolves, retry the photo at 270 and 90 deg and keep the first read whose
PO resolves (within the tick's time budget).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 16:07:26 +02:00
f8294b4e1d feat(pocketbase): switch on the audit-log hooks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:41:20 +02:00
8c99381aa5 docs(pocketbase): note how to publish hooks in DEPLOY
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:37:07 +02:00
0890835e91 chore(pocketbase): publish hooks to WFDASH-PBHOOKS for the VPS sync
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:35:54 +02:00
6b99f52dac chore(pocketbase): track live quotes hook and add git-based hook sync
quotes.pb.js is copied byte-for-byte from the live WFDASH PocketBase.
DEPLOY lists the hooks the VPS cron syncs into the hooks volume; the
audit hooks stay undeployed for now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 15:34:41 +02:00
810f0d7350 Merge branch 'feat/collection-ai-checks': Collection Confirm AI checks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 12:07:42 +02:00
cba2d84549 docs(collection-ai): runbook tick table, audit scope; spec content_hash index is non-unique
F2: tick table (SKU green via the code match incl. AI fills, grey per tick,
pairing failure -> all grey, different product -> red), stock lines use the
line SKU, Add to POS needs a SKU, SKU fills audited for order lines only.
F3: content_hash is a non-unique index (identical uploads share a hash,
skipped PDFs have an empty hash) - matches the migration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:21:25 +02:00
8efee9db42 fix(collection-ai): pre-fill only from the chip's tax invoice, AI column only with ai_check
M2: the confirm dialog pre-fill uses the card's newest document (as the
chip) and only when it is a tax invoice.
M3: the AI ticks column (and its min-width) shows only when a row has
ai_check; the POS column stays whenever in_pos is known.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:20:46 +02:00
f60b1d893c fix(collection-ai): round quantities before comparing in the findings
F1: outstanding = round(ordered - collected, 3) > 0 and the order-line
mismatch compares round(order_qty, 3) with round(quantity, 3), so float
noise never produces "0 are still outstanding" or a false mismatch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:19:34 +02:00
515c9d4cd2 fix(collection-ai): one AI call per failed photo, card PO always paired, findings write is soft, guard both collections
I7: a hash whose read failed this run is recorded as a failed copy with no
second AI call.
M4: the card's own PO lines are always offered for pairing alongside the
POs the document names; the fallback finding still applies when none resolve.
M5: a failed findings write no longer leaves the lines unchecked.
M6: the schema guard requires ai_check on order_items and stock_purchase_items.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:18:47 +02:00
d4cdde7384 fix(collection-ai): Add to POS never duplicates SKU-less rows, failures are per row
I5: a customer row without a SKU returns needs_sku and creates nothing; the
Add to POS button is disabled for it ("Set a SKU first").
I6: a createStockItem failure is caught per row (status failed + error), the
route still 200s, and the page toasts "N added, M failed, K need a SKU".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:16:21 +02:00
457ad30f12 fix(collection-ai): stock rows tick and show their own line SKU
I4: ConfirmRow.line_sku carries stock_purchase_items.sku; the SKU tick,
the AI robot and (for stock rows) the SKU column use line_sku before the
catalogue sku via effectiveSku. The SKU pencil keeps line_sku in step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:14:17 +02:00
b49911601d fix(collection-ai): grey ticks when pairing failed, show line findings on the card
I3: ai_check.pair_failed marks lines whose covering document couldn't be
paired; computeLineTicks returns all grey for them. The card findings box
now also lists each row's ai_check.findings after the document findings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:12:42 +02:00
d1956bc1cf fix(collection-ai): only sure/likely pairings count, renumber doc lines, drop filled-SKU tick shortcut
I1: a 'different'/unrated answer never claims a PO line in parsePairs, is
stored as doc_line null (verdict + reason kept), loses to a real pairing,
is not a pairing for the findings, and gives all-red ticks.
I2: document lines are always numbered 1..n.
M1: the SKU tick goes green only through the normalised code match.
Also adds effectiveSku (line_sku before sku) for I4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 10:10:59 +02:00
8c3cfd45fc feat(collection-ai): settings section, runbook
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:55:44 +02:00
137b458a06 fix(collection-ai): page handler tests, full document lines, snapshot dialog defaults, tick aria detail
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:45:12 +02:00
c1656f5ccd feat(collection-ai): ticks, findings and POS buttons on Collection Confirm
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:41:52 +02:00
cc3e1d040b feat(collection-ai): add to POS, AI routes and MCP tool
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:33:56 +02:00
b60f7fd3ce fix(collection-ai): keep reads on errors, record pairing failures, budget reads
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 09:24:04 +02:00
16cf5e2746 feat(collection-ai): card check, tick and list passthrough
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 02:14:53 +02:00
2af5d089f4 feat(collection-ai): pair document lines with PO lines
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 01:54:42 +02:00
8fc4930de7 feat(collection-ai): vision read of driver documents
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 01:40:33 +02:00
7bcd92f999 fix(collection-ai): findings fallback tests, rounding
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 01:37:29 +02:00
122eb9862e feat(collection-ai): document findings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 01:35:45 +02:00
084e0951c7 test(collection-ai): sharpen tick qty/price cases
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 01:26:18 +02:00
5262de97fd feat(collection-ai): line tick rules
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 01:24:51 +02:00
e6cac6e447 feat(collection-ai): data model, settings, migration
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-17 01:18:10 +02:00
3e37ae482f docs(collection-confirm): AI checks implementation plan
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 01:04:42 +02:00
3624d87801 docs(collection-confirm): AI checks design spec
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 01:00:49 +02:00
78ba560096 fix(quote-assistant): refuse to fill until the ai_fill field exists
PocketBase drops unknown fields on update, so before the migration runs
the AI's prices would land with no purple marker and no notes. The
manual button / MCP now return 409 'not set up yet' instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 00:26:13 +02:00
dca9c01319 Merge branch 'feat/ai-quote-assistant': AI quote assistant
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 00:12:22 +02:00
3091ac94de docs(quote-assistant): runbook accuracy, MCP tool listed in API docs
- Runbook: pre-migration settings don't persist (not "saves fail"),
  eligible statuses, 15 lines per quote, audit attribution, clone/template
  behaviour, supplier kept, doubts dot, confidence guidance (Medium is the
  default; raise to High if reps often change AI prices).
- quotes.assistant_fill added to docs/api-mcp-layer.md,
  docs/audit/api-mcp-coverage.md and the Giellie skill.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:42:44 +02:00
6604e8b837 fix(quote-assistant): review minors (templates, forced order, bounds, UI)
- M1: fillQuote and the tick refuse templates ("quote is not open
  (template)"); both quote pages gate the fill button with
  canAiFillQuote (new/follow_up/claimed, not a template).
- M4: run summary keeps <= 20 errors of <= 300 chars (boundRunErrors).
- M5: forced runs offer never-tried lines before tried ones.
- M6: counts route 400s on ids that aren't PB record ids.
- M7: a fill never replaces a supplier already on the line.
- M8: warn dot on the AI marker when a pending fill has doubts.
- M9: MCP hint states the real eligibility rule.
- M10: detail-page price dialog re-reads the line before recording an
  AI review.
- M11: exported QUOTE_NOT_OPEN_ERROR_PREFIX / AI_DISABLED_ERROR used by
  the run route.
- M14: prompt textarea maxLength 50000.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:41:28 +02:00
ee5c3bcbb3 fix(quote-assistant): exact-SKU rows always shortlisted; per-line shortlist errors
- With a SKU on the line, shortlistStockItems runs a second narrow query
  (sku = raw || sku ~ normalised) and merges by id, so the capped broad
  pull can't crowd out the exact SKU row. Quotes and backslashes are
  stripped from the raw SKU before any filter is built.
- fillQuote catches a shortlist failure per line and marks only that line
  error instead of abandoning the quote.
- Supplier names load once per fillQuote and are passed to each
  shortlist call (new optional parameter; default unchanged).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:32:26 +02:00
dd3ab2ad9a fix(quote-assistant): clone/save-as-template don't copy unreviewed AI prices
A source line whose AI fill is still pending review (isAiFillPending) is
copied without cost, selling price, markup or supplier, so the purple
draft price can't resurface as an ordinary price on the copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:30:22 +02:00
4e0f7a5c5c fix(quote-assistant): AI fill on Process page keeps unsaved typing
After "Let AI fill this quote" the page re-pulls the quote's lines and
merges them with mergeItemsAfterAiRun: rows unchanged since the last load
take the server copy (so fills show), rows with unsaved changes stay as
typed, with a toast saying how many were kept. Previously onDone ran
fetchData, which replaced every row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:29:04 +02:00
a9160de1d6 fix(quote-assistant): retry unanswered lines, bound and serialise ticks
- decideWithAi throws on stop_reason max_tokens instead of returning a
  partial answer; fillQuote marks a requested line the AI did not answer
  as error (retried after 1 h), never a permanent no_match.
- QUOTE_MAX_LINES 30 -> 15; the tick also caps lines per quote so the
  60-line budget reaches other quotes.
- runAssistantTick stops starting quotes after a 90 s time budget and
  returns reason running while a newer start than the last summary is
  under 10 minutes old (lease).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:26:28 +02:00
51bd4ec32c fix(quote-assistant): credit AI writes to the assistant, not the rep who clicked
AuditActor gains ignoreRequestUser: buildAuditHeaders then omits the
forwarded PB token (X-Audit-Auth) and request-derived email hint, so the
PB audit hook falls back to X-Audit-Actor-Name. fillQuote sets it for
all its writes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 23:22:59 +02:00
144971779f feat(quote-assistant): settings section, n8n tick, runbook
Quote assistant card in Settings, AI (on/off, interval, min confidence,
editable prompt with reset, last-run line); parseSettingsForm handles the
qa_* fields. docs/quote-assistant.md has the runbook and the n8n tick
workflow spec; AGENTS.md gets a short section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:10:02 +02:00
fa854060c4 feat(quote-assistant): AI x/y badge on quotes board
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 20:01:43 +02:00
40094be3f3 feat(quote-assistant): purple AI lines on process and quote pages
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:56:02 +02:00
c8e977c61e fix(quote-assistant): warn-coloured doubts, real button for no-match badge, more button tests
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:48:19 +02:00
ef6193367d feat(quote-assistant): ai colour token, marker, fill button
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:45:44 +02:00
d34be52a66 feat(quote-assistant): tick/run/counts routes, MCP tool, re-price guard
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:38:38 +02:00
bb06165318 feat(quote-assistant): fill service, tick, counts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 19:29:16 +02:00