Commit graph

566 commits

Author SHA1 Message Date
13d887eae8 fix(repairs-app): stop the book-in button covering the last card, and cover the session header 2026-11-06 17:32:33 +02:00
e2764506c7 feat(repairs-app): the status board with reconciling counts 2026-11-06 16:52:17 +02:00
43aa1a96bb fix(repairs-app): make /offline a real route so the service worker can install 2026-11-06 16:37:22 +02:00
10810142b0 feat(repairs-app): the phone app shell, manifest and service worker 2026-11-06 15:59:39 +02:00
745593ee35 fix(repairs): send the session on the detail photo upload, and cover every handler 2026-11-06 15:43:12 +02:00
20ef99264d fix(repairs): require a browser session on the repairs API and its files 2026-11-06 15:12:02 +02:00
6e96d823b0 fix(stock-counts): generate POS count PDFs on demand without storage 2026-11-06 12:50:28 +02:00
95fd09b1a2 feat(giellie): conversational stock counts with split PDFs and photo review 2026-11-06 11:40:13 +02:00
9358b379ff Merge feature/repairs-phase-1 into main 2026-11-06 09:30:44 +02:00
0ff6a2a3a6 fix(repairs): pin the transition guard's wiring, test the office pages, and clear the review findings 2026-11-06 08:46:21 +02:00
7031499bf0 fix(giellie): attribute the stock_items.create opening balance
Creating a stock item with `current_qty > 0` wrote the opening-balance
`stock_ledger` row with `created_by: ""`, so the row landed in the audit
trail unattributed — and it was written after the quantity, so a failure
in between left the two out of step.

- The create now requires `actor.id` (the same 15-char check and error
  style as `stock_items.set_quantities`) and stamps the opening-balance
  row with it.
- The opening balance is booked through the audited write path: the item
  is created with no quantity, the ledger row is written first, and the
  quantity second. If the quantity write fails, the service reverses
  the ledger row, so `SUM(change_qty)` still rebuilds `current_qty` and a
  retry is safe.
- A zero or negative opening balance is written on the create itself,
  exactly as before.
2026-11-06 08:07:20 +02:00
ab16404003 fix(giellie): route stock_items.adjust through the audited write path
`stock_items.adjust` wrote its `stock_ledger` row with no acting user
(`created_by: ""`), and it updated `current_qty` with no compensation —
the same orphan-ledger shape the new quantity tool removed. The docs
advertise this tool, so it is a load-bearing write path.

- `updateStockItemsQuantities` takes optional `reason`, `referenceType`,
  `referenceId` and `notes` overrides. The defaults are unchanged
  ("adjustment", "manual", the service's own note), so the /stock page
  and the agent write exactly what they wrote before.
- The adjust handler now requires `actor.id` (the same 15-char check and
  error style as `stock_items.set_quantities`), reads the row's current
  quantity, turns the signed delta into an absolute target and calls the
  service. Attribution, ledger-first ordering and the compensating
  reversal row come from the service instead of being duplicated here.
- The caller's `reason` and `notes` survive the conversion, and a
  negative resulting quantity is allowed — the till oversells on purpose.
- The tool description marks the tool as superseded for quantity work
  and points at `stock_items.set_quantities`.
2026-11-06 08:01:10 +02:00
283d970cb0 refactor(repairs): one status vocabulary, and the supplier scope input 2026-11-06 08:00:14 +02:00
c4936613e1 fix(mcp): tell the agent what repairs.upload_file does, and test the rest of the repair tools 2026-11-06 07:39:58 +02:00
9146fd46f0 feat(mcp): eight repair tools for the agent 2026-11-06 07:17:01 +02:00
adb019061a feat(repairs): expose supplier scope, signed copies and status counts 2026-11-06 06:52:23 +02:00
a3b5996d15 fix(giellie): correct the stock tool actor, audit and quantity pointers 2026-11-06 05:35:26 +02:00
c943118300 fix(giellie): a bad stock line must not take the approval card down
The card is fed the model's raw tool input, so a malformed line (a
null, or a qty that is not a number) reached the renderer unchecked
and null.sku threw during render — taking the card, and its
Approve/Reject buttons, with it.

Lines are now read the way the MCP route reads them: an unreadable
line is reported in place, the good lines beside it still render, and
the operator is told that approving rejects the whole batch.

The headings stay put as well. The table now rides inside the card's
own scroller instead of a second scroll region, the header row is
sticky on an opaque background, and the set/add note rides with it —
so every row of a 40-line stocktake is read with its labels and its
mode in view.
2026-11-06 01:26:55 +02:00
9813732e5f fix(giellie): say what "applied" means on a dry run
On a dry run nothing is written, so a row the engine reports as
applied only means it would be applied. The tool description said
none of that, and a reader of the description alone still got the
old ambiguity.
2026-11-06 00:52:35 +02:00
38cd015e28 fix(mcp): a stock quantity write needs an acting user
The batch ran with an empty actor.id when the caller sent none, so
the ledger row went out unattributed — against the plan's own rule
that the acting user is stamped on every ledger row. The tool now
requires a real 15-char user id, exactly as stock_items.set_pricing
does, and declares the actor in its schema so a schema-following
client can supply one.

The other fixes follow from the same review:

- A numeric SKU is a SKU, so it is coerced to its string form; a
  boolean or an object is malformed input and is refused rather
  than dropped silently.
- The duplicate-SKU error names both spellings and the lines they
  are on, so the operator can see which two rows collided.
- dry_run also accepts the string "true". It is the one flag that
  decides whether data is written at all.
- The description now says the batch is rejected as a whole on a
  repeated SKU, and that quantities are truncated to whole units.
2026-11-05 23:53:54 +02:00
2c415d82f1 feat(mcp): stock_items.set_quantities
One call sets the stock quantity on up to 100 pasted lines. Each line
is resolved by exact SKU, then by description, and reports what it
could not resolve instead of guessing.

The batch is validated up front — a duplicate SKU is a malformed
paste, not a per-row failure — while a line that simply does not match
is reported and the rest of the batch still applies.
2026-11-05 23:24:50 +02:00
4eb6ebf838 Merge codex/order-item-payments into main
The item supplier payments and approvals queue: a payment hold on order
items, the Payments and Approvals page, supporting documents and proof
of payment, and the PB hook that stops an unpaid item becoming ready for
collection.
2026-11-05 00:43:06 +02:00
9ce6c97772 feat(theme): the picker reads the real palette, not a hand-kept copy
The theme registry carried a hand-maintained set of hex swatches for
every palette, and they had drifted away from the CSS the app actually
paints. The picker previewed colours the product never showed.

The registry now holds only what cannot be read from CSS — id, label,
tagline, fonts, radius — and the picker, the Settings swatches and the
preview cards resolve --background, --primary and --foreground off the
theme itself. A preview can no longer disagree with the screen it
previews.

themes.css is rewritten so every palette carries a real dark surface,
and the palettes that are dark by nature stay dark whichever way the
mode switch is set. The pre-hydration script validates the stored theme
id against the registry, so a retired id falls back to WFDASH instead of
painting an unstyled page, and the old "warm" mode migrates to Light.

The legacy text-{warn,success,info}-foreground classes now resolve to
the semantic text colour and switch to the filled ink only on an exact
solid fill, so one class reads correctly on both a tinted badge and a
filled button. A solid focus outline replaces the faint ring, and the
charts and quote status badges take their colours from tokens rather
than hard-coded dark strokes.

Tests: theme-bootstrap.test.tsx covers saved-theme recovery and the
storage-blocked path; e2e/themes.spec.ts walks every palette in both
modes checking surface contrast, tinted and solid semantic pairs, focus
colour, chart tokens and the destructive hover, and asserts that the
picker preview matches the applied theme.
2026-11-05 00:40:40 +02:00
97603dce71 refactor: simplify supplier payments to document checklist
Remove payment amount, reference, notes and manual combining forms. Link supplier POs automatically and record paid from supplier evidence plus proof, keeping readiness and attachment safeguards.

Keep active supplier/company views current and retire stale supplier evidence on routing changes. Verified 5167 tests with only the two existing script-import errors.
2026-11-03 07:18:56 +02:00
cb0113f9f4 feat: add item supplier payments and approvals queue
Keep payment holds independent of workflow and margin approval. Require proof before collection readiness; deduplicate uploads transactionally in PocketBase and preserve payment history.

Includes schema migration, deployment runbook, browser checks and isolated PocketBase regression coverage. Full suite has 5156 passing tests and retains two pre-existing script-import errors.
2026-11-03 01:27:07 +02:00
65036a8a6a feat(delivery-pool): company sections start collapsed
Like the Collection Pool, the page opens as a list of companies; click
one to see its invoice cards. A search opens the matching companies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 10:25:20 +02:00
bae4f9e795 Lock quote letterheads to customer companies
Add a per-company quote letterhead lock backed by `companies.confirmed_letterhead`. The quote process flow prompts for a default when unset, permits only the confirmed letterhead with a triple-click per-quote override, and enforces the rule during PDF generation and email sending. Also add company settings controls and API, email-ingest defaults, migration and backfill tooling, and unit coverage.
2026-10-31 10:20:23 +02:00
afdfbce71d fix(pos): never sell or list an item at R0; customer lines go to the price list
Owner's rule: the POS counter only carries priced stock.

- POS catalogue lists only active stock items with selling_price > 0
  (POS_CATALOG_FILTER); checkout refuses any line priced at R0 or less.
- New stock order now gives a new stock item the PO line's cost and the
  default 80% markup. It used to drop the cost, which is how 157 stock
  items reached the till at R0.
- Collection Confirm "Add to POS" becomes "Add to price list": customer
  lines go to price_list at cost + 80% (needs SKU, cost and supplier),
  never into stock_items. Route renamed to add-to-price-list.
- scripts/2026-10-30-pos-zero-price-cleanup.ts: priced 79 items from their
  PO line cost, switched off 48 with no cost and no stock, left 30 stocked
  ones active for costing (already applied on live PB).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:56:03 +02:00
45529731a7 feat(delivery-pool): assign invoiced deliveries to drivers, close with the signed copy
New /delivery-pool page, a clone of the Collection Pool for deliveries
with no pending stage. Invoiced lines are grouped per company, one card
per customer invoice; a card expands to its lines. Assign a driver and
day per invoice (or bulk per company); uploading the signed copy adds it
to every line, marks the lines completed + delivered_at, and closes a
delivery note once none of its lines is still open.

- Driver lives in new order_items.delivery_driver / delivery_assigned_at
  (npm run pb:migrate:delivery-pool), never drv_id (collection driver).
- Pool starts at DELIVERY_POOL_START (2026-10-30); lines whose DN is
  already completed/cancelled (POS counter sales) are left out.
- Service + /api/v1/delivery-pool routes (browser session); the service
  503s until the migration has run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 09:55:35 +02:00
6883971bb7 feat(collection-pool): pending stops become ready on their expected date
A scheduled check (n8n, every 2 hours, POST /api/v1/collection-pool/ready-dates/tick)
marks pending customer stops ready once their expected-ready date is today or
earlier (South African time). The stop stays unassigned, keeps its pool age,
and gets an audit note. Stops with a driver problem flag are left alone.
Stock lines are not covered: they have no date field.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 22:08:19 +02:00
91173edbcd feat(collection-pool): stock lines can go back to pending and be marked ready
Back to pending on a stock line used to clear only the driver, so it stayed
in the pool as ready. Stock lines now carry a pool_pending flag (the inverse
of ready, so every existing line stays ready) and behave like customer stops:
pending badge, Mark Ready (new stock-ready route), no driver assignment while
pending. The back-to-pending route refuses to claim success when PocketBase
has no pool_pending field yet.

Needs the manual live step npm run pb:migrate:pool-pending (already run).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 22:08:19 +02:00
25466d4e64 fix(collection-ai): the AI never changes a price
On PO202610005TEL the AI read a 15% discount that was not on the invoice
and cut a line's cost from R146.47 to R124.50. It no longer writes a cost
to order or stock lines. A cost that differs from the invoice's discounted
price shows as a red Price tick, which also keeps the card out of
auto-push, so a person settles it. Earlier cost changes stay visible on
the line. Tests now assert no cost is ever written; runbook and AGENTS.md
updated.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 13:56:16 +02:00
18cdc2976e feat: show the build commit as a faint corner mark
Declares ARG NEXT_PUBLIC_APP_COMMIT, supplied by Ship, and renders it as a
nearly transparent, click-through stamp. Renders nothing when unset.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-30 01:27:22 +02:00
3eea87ec03 feat(collection-confirm): auto-push purple cards on to invoicing, with a 2-day log
After each AI tick, every Collection Confirm card the page shows purple
(cardAiState verified) is confirmed without a person. Customer lines go
to ready for invoicing with the invoice number and date the AI read; stock
orders go to stock booking once every line is collected. Cards with a
problem flag, a photo for another PO, or no readable invoice number/date
stay for review.

Each push is recorded in ai_settings.collection_auto_push_log and shown at
the top of the page for today and yesterday (PO, supplier, items, value).
Off by default behind a Settings > AI switch; needs the manual live step
npm run pb:migrate:collection-auto-push before it will run.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 11:36:23 +02:00
6c83282888 feat(settings): link to AI settings from the Settings page
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 11:36:23 +02:00
b212d0fcd8 feat(manager): a mobile orders board for the managers at /manager
A hidden, installable PWA: one link, opened on a phone, showing where
every order is. New / Processing / Completed, with a tap-to-preview
popup and the invoice/line detail Gary needs. Read-only, and NOT in
the sidebar — the route lives outside `(app)`, so `<Sidebar />` never
renders there.

The preview popup is reused, not rebuilt: the service emits
`UniversalHit`s and the page hands them to the existing
`SearchPreviewDialog`, so the manager gets the desk's quick-overview
verbatim.

Also fixes the /orders Completed tab, which was matching ZERO orders.
`delivered_at` is stamped on only 2 of 12,957 delivered lines and the
business stopped writing `delivered` on 2026-08-14 (the live pipeline
now ends at `invoiced` / `checked_in`), so the old filter never
matched. "Settled" is now defined once — `SETTLED_STATUSES` +
`SETTLED_DATE_FIELDS` — and dated per status: `invoiced_date` for
invoiced (the only properly stamped field, 2,199 lines), `delivered_at`
for delivered, `updated` only where no dedicated column exists.

`delivery_note` and `checked_in` move into `completed` as instructed,
and the standalone `post_processing` tab is retired (a DN is not a
stage of its own). `invoiced` stays in `collected` as well, so
invoiced orders don't vanish from the workflow tabs.

Completed tab: 0 -> 341 orders over 30 days, 112 over 7.
2026-10-26 23:09:01 +02:00
a6d53e3b2b feat(price-check): say whether the POS item is exact or only closest
The blue badge read the same whether the AI had matched the item
exactly or only found the nearest one, so a line priced from an M22
bolt looked like any other line.

Now an exact pick reads as before, a closest pick leads with
"Closest: {name} ({differs})" and raises a closest_only flag, and a
fuzzy match keeps its "Similar items:" wording. The popover shows the
pick and the differences at the top.

A refresh is still a no-AI recompute, but it keeps the stored pick
instead of throwing it away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 23:05:09 +02:00
9b3f30a01f feat(process): show the blue price check on both Process pages
The blue price-check badge now appears under the purple AI marker on
every line the AI filled (reviewed or not):

  - quote Process page, mobile card: its own row under the header, so
    the one-line summary is readable on a phone
  - quote Process page, desktop table: inside the Description cell
    rather than the cramped 32-px "#" column, which has no room for a
    summary
  - order Process page: its own full-width row under the markers, so
    the summary is readable on a phone

The summary truncates rather than wrapping — the popover always shows
it in full.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 21:45:46 +02:00
3248e3aa03 feat(ui): blue price-check badge with its popover
The blue badge that sits under the purple AI marker: a tag icon plus the
one-line summary, with an orange dot on the icon when the price check
found something worth a look.

The popover shows POS stock (on hand, status, cost, sell, supplier), the
top three price-list matches, what we last paid each supplier, what we
quoted and ordered before, and how the match was made ("POS item the AI
picked" / "Matched by SKU" / "Similar items"). Refresh re-computes the
intel through the existing /api/v1/items/{collection}/{id}/intel/refresh
route and reads the line back afterwards.

Also closes the colour-token gap: globals.css mapped --color-info to
var(--info), but --info / --info-foreground were never defined, so
text-info rendered nothing. They now have a light and a dark fallback
next to the --ai tokens; themes that define their own --info still win.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 21:33:50 +02:00
793c9ea215 feat(orders): tint New-tab cards by the AI quote match
Purple when the order is linked to the quote the AI chose and every
line was paired "sure"; yellow when the AI matched a quote but not
every line for sure, or it is only a suggestion. New tab only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:52:59 +02:00
37933ccdc0 feat(orders): quote matching is AI-only
The rule-based matcher no longer writes or shows suggestions: on
2026-10-24 it had left 2,888 on unlinked orders, one quote suggested
to 309 of them.

- createOrder runs the AI order assistant (matchOrder) straight after
  intake when it is switched on, instead of the rule-based suggestion
- order page: Re-match / Try again and the rule-based suggestion card
  are gone; Match with AI is the action, and "Match by reference" saves
  the reference then runs the AI (the run route takes { reference })
- the unauthenticated /orders/[id]/quote-match route is removed
- the sweep route and MCP orders.quote_match_sweep run the AI batch
  (matchUnlinkedOrders, shared with the tick)
- scripts/2026-10-24-clear-rule-quote-suggestions.ts clears the old
  suggestions (dry run unless --apply); replaces the rematch script,
  which would have written rule-based ones again
- also fixes the setState-in-effect lint error on the order page

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:49:26 +02:00
d3a52d97f2 feat(orders): purple review for AI-filled order lines
A line the AI filled now shows up purple until a person checks it, so
nothing the assistant priced is ever taken on trust.

- order-assistant-review.ts: the four client helpers — a line is
  pending only while it is filled, unreviewed and still priced from
  the quote; a human save keeps "ai_quote" / "quote" / "catalogue" and
  turns anything else into "manual"; the review patch records who
  checked the line and whether the prices were accepted unchanged.
- order-assistant-save.ts: the save-time merge, the order-side twin of
  quote-assistant-save.ts. The value the operator committed wins over
  the one the page was loaded with, so the one-click "use this cost"
  copy really does end the AI's provenance.
- The Process page applies the purple row class, shows the bot marker
  with the quote it was filled from, the reason and the doubts, and
  routes every line save through the merge.
- The Orders board shows an "AI x/y" badge: how many lines the AI
  filled, and how many still need a review. One query per page, and
  never fatal — the board has to render whether or not the assistant's
  columns exist yet.
- AiFillMarker grows an "order" variant instead of assuming a POS item.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 14:25:11 +02:00
89e52bbd9d feat(orders): AI fills order lines from the matched quote
The assistant now prices the order's lines from the quote they are
paired with, as purple drafts a person then reviews.

- order-assistant-core.ts: isOrderLineWritable + buildOrderLineFillPatch.
  Only a pending line with no cost of its own, no person-set pricing
  source and no reviewed fill may be written. Supplier / SKU /
  matched_price_list_id only when the line has none; selling_price only
  when the line has none; markup + markup_percent written together.
  pricing_source becomes "ai_quote". A customer price that differs
  from the quoted price by more than 5% becomes a doubt.
- order-assistant-lines.ts (new): the shared, cycle-free helpers —
  idFilter, aiQuotePairs, writeAiPairs, clearAiPairs and
  fillOrderFromQuote. It lives in its own module so orders-service.ts
  can use it without an import cycle, since order-assistant-service.ts
  already imports setOrderQuote.
- The fill re-reads every line right before writing it, so a cost a
  person typed in the meantime wins. It stays quiet until the migration
  has added order_items.ai_fill — PB rejects a projection that names a
  column the collection does not have.
- setOrderQuote writes the AI's line pairs before pairOrderLines runs,
  so the AI's pairing wins over word matching, then fills the lines.
  A failure there is logged and never fails the link.
- The tick gains a second pass that fills the lines of orders already
  linked to a quote. Only orders the fill actually wrote to count
  towards the budget, so a skipped order cannot block the run.
- The run route fills after the match and reports the counts.

PocketBase filters have no IN operator, so every id list is an
OR-chain. fetchQuoteItemsForQuotesAdmin no longer tries an IN filter
first — that was a guaranteed 400 on every call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 14:24:56 +02:00
6fe09f7752 feat(orders): Settings → AI order assistant section
Task 9 of the AI order assistant plan.

- new "Order assistant" card in Settings → AI: enabled switch, run
  every (minutes, 5-1440), auto-link when sure, fill lines from the
  quote, the prompt with show/reset default, and the last-run summary
- parseSettingsForm reads oa_* and validates the interval; the last-run
  fields are never written by the form
- the page passes the new settings through as the initial state

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 13:16:09 +02:00
00d09b3b12 feat(orders): show AI quote match on the order page
Task 8 of the AI order assistant plan.

- new AiQuoteMatchPanel: "Linked by AI" / "AI suggests Q…", the
  reason, the doubts and the line-pair table (order line -> quote line
  with same_item)
- the quote match card gains a "Match with AI" button in every state,
  posts to /api/v1/orders/{id}/assistant/run with the PB token, toasts
  the 409 message verbatim, then reloads the order
- new card state: the AI suggested a quote -> the AI panel + Accept
  (links the AI's quote) / Match with AI / Dismiss
- the AI suggestion marks the order as quote_matching_attempted so the
  deterministic sweep cannot overwrite it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 13:13:28 +02:00
85533245bf feat(orders): order assistant routes, MCP tool and runbook
Task 7 of the AI order assistant plan.

- POST /api/v1/orders/assistant/tick (n8n, assertAgentAuth)
- POST /api/v1/orders/{id}/assistant/run (browser session; 409 for
  "AI is disabled" and "not set up yet")
- MCP orders.assistant_match { orderId } -> matchOrder(force) with a
  TOOL_HINTS entry; deliberately NOT in READ_TOOLS
- docs/order-assistant.md runbook and the AGENTS.md section

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 13:06:51 +02:00
e4fbcf3748 feat(orders): label lines split off a short collection
Shows "Short delivered · on <PO>" next to the description on /orders and
the order page so the buyer doesn't re-order units already on back order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 07:59:57 +02:00
2e55d21937 feat(collection): split short pickups on confirm, stop re-billing
Collection Confirm cuts a short line to what was collected (that part
invoices and closes) and creates the rest as a new pending line on the
same order, with Back order (same PO / straight to pool) on the process
page. Invoicing subtracts qty_invoiced so a part-invoiced line can't bill
twice. Supplier-side lines close at confirm when their order is billed,
and stragglers get a close button on Invoicing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 04:04:50 +02:00
33aa56cbf8 feat(driver): match an invoice photo to the driver's assigned lines
POST /api/v1/driver/invoice-match returns exact matches (same product,
invoice qty = ordered qty) and lines to review. Read-only for the lines.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 20:58:33 +02:00
5c46152df7 feat(collection-confirm): never hold collected stock lines back for outstanding ones
Confirm stock now sends the collected lines to stock booking and moves
lines not yet collected to a follow-on order (<PO>-2, -3, ...). The
invoice matcher follows a closed PO to its open follow-on.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 19:53:21 +02:00