Booking an order from the Orders page could fail outright on a fresh
company. seed_chart_of_accounts() seeds a deliberately small chart:
3001/3002/3003 and 2611/2621/2631 are in it, but 3004, 3740 and the
clearing account are not. All three are reachable from an entirely
ordinary order (a 0%-rate line, an ore residual, or simply no
payment-method mapping yet), and the engine treats a missing or
inactive account as AccountsNotInChartError, so the user's first click
on Bokfor returned an error naming accounts they had no reason to know
about, with no way forward but to hand-add them.
The book route now ensures the closed set of accounts our own prefill
can emit exists before drafting. Deliberately narrow: only accounts in
WEBSHOP_PREFILL_ACCOUNTS are ever created, and only when a submitted
line uses one, so an account the user typed still surfaces as a real
error instead of quietly growing the chart. A deactivated row is
reactivated rather than duplicated, and every failure is swallowed so
the engine's typed error still wins over a chart tidy-up.
The unmapped default also moves from 1680 to 1686. 1680 is the generic
"Andra kortfristiga fordringar" parent; 1686 "Fordringar for kontokort
och kuponger" is what BAS defines for a claim on a payment provider,
which is what money sitting at Klarna or Stripe actually is. The Stripe
extension already settles against 1686, so a store running both
surfaces now shares one clearing account instead of splitting the same
receivable across two.
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(woocommerce): store order/refund feed extension
Connect a WooCommerce store via the wc-auth key handshake (manual key
fallback) with per-store consumer key/secret AES-256-GCM encrypted at rest,
and import paid orders and refunds into the transactions inbox as a
bank-style feed on the 1680 cash account. Feed-only: nothing auto-books,
gateway fees/payouts are out of scope (core wc/v3 does not expose them).
Sync is cursor-paginated on modified_after (offset pages only inside
same-second date_modified ties), terminates on an empty page, holds the
cursor below failed refund fetches / ingest errors / deadline-skipped work,
checks the time budget between refund fetches, and drops rows dated on or
before bookkeeping_locked_through on every run. Nightly cron gated on the
extension registry + new paid capability woocommerce_sync (backfilled to
existing bank_sync grant holders).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(migrations): move woocommerce migrations past main's 20260806090000
origin/main gained 20260806090000_recurring_schedule_interval_months while
this branch was in flight; identical version timestamps abort the Supabase
apply, so the two new migrations move to 20260806170000/20260806170100.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(woocommerce): resolve CodeRabbit review findings
- callback 503s early when WOOCOMMERCE_CREDENTIALS_ENCRYPTION_KEY is
unset: encryptCredential would otherwise throw after the probe and
strand the pending row without error_message
- disconnect and upstream-revoke clear the encrypted consumer key/secret:
nothing reads them after revoke and keeping decryptable dead
credentials is unnecessary retention
- manual sync gets a 240s time budget and the panel reports a truncated
run as 'partial, sync again' instead of a normal completion
- listOrderRefunds terminates on an empty batch (hosts may cap per_page),
dedupes by id against hosts that ignore page, and caps total pages
- unparseable money strings count as errors and log instead of being
silently identical to a zero total
- pg test uses per-run unique store URLs so committed rows cannot hit
the store_url partial unique index across pg-real runs
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(woocommerce): resolve CodeRabbit cycle-2 findings
- listOrderRefunds throws when the page cap is exhausted with data still
flowing, instead of returning a silently partial list the sync cursor
would advance past; the error routes into the existing held-cursor
refund-retry path
- partial sync results keep the row-error count, and the partial toast
string surfaces it (ICU plural, hidden at zero) in both locales
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore: retrigger CI after dropped push event
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>