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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
- 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>
- 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>
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>
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>
- 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>
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>
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>
- Orders now match on customer invoice numbers and delivery-note
numbers carried on their lines (tagged "invoice" / "delivery note").
- New Delivery Notes source: DN number, customer PO, customer and
company names; preview pulls lines and invoices for visible hits only.
- Quick-overview dialog moved to search-preview.tsx and redesigned:
type-tinted header, status pills, stat tiles, invoice chips with
QuickBooks links, line list, icon timeline with dates and gaps.
Fixes the dialog being pinned at 24rem by the primitive's sm:max-w-sm.
- Result list: per-type colour (icon tile, accent border, badge, row
hover), status pills per row, and a jump bar; mobile-tightened.
- Stock-order preview now fetches ordered_value/created (Value row and
age chip were always empty); supplier names keep their original case.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Third and last place that read `quotes.company` directly. The list page
showed "Mre" next to Pierre Visser, who works for Harmony — the same
drift already reported on the detail page.
This one was a deliberate perf shortcut rather than an oversight.
`QUOTE_LIST_EXPAND` dropped `customer.company` to "save one IN query per
page load" and the page read the quote's own `company` relation
instead. That relation is a denormalised copy that PocketBase never
re-cascades, so the shortcut read a field nothing keeps correct — and
the column it fed is what a rep reads to decide whether to approve, so
a stale company there is worse than a slightly larger query.
The 3-level expand is back, on both the list and the search path (the
search results feed the same columns, so they needed it too). Search
now also RANKS on the customer's company rather than the stale copy —
otherwise a query for "Mre" would surface quotes whose results then
render "Harmony", contradicting themselves on screen. It falls back to
the quote's own copy for the ~4% of customers with no company relation,
where the two are equal by construction.
Applies to both the desktop table column and the mobile card.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Audit of the POS catalogue against the supplier price lists: broken
supplier names, cost drift over 15%, negative stock, items not
sellable from stock, and SKUs split across two codes for the same
product. Includes the supplier-name backups taken before any mutation.
Follows the existing convention of keeping dated audit output under
reports/.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>