* chore: repo-wide bloat sweep, remove dead code and fold duplicate helpers
Remove 33 dead files, ~270 unreferenced exports/types, 13 dead i18n
namespaces and 4 unused dependencies; fold byte-identical helper copies
into one canonical home each (lib/utils chunk/sleep/utcDateStamp,
lib/dates/iso, lib/invariants/uuid, lib/xml/escape, lib/reports/sru/format,
lib/pdf/number-text, lib/browser/panel-request, lib/api/v1/body +
v1ValidationError rolled out to ~55 v1 routes, booking-template schemas).
No behaviour change: v1 bodies and status codes, MCP tool schemas, DB
writes and money math are untouched. Naive ore rounding was deliberately
not swapped for roundOre; see DECISIONS.md 2026-09-02 for the full list
of things left alone on purpose.
tsc, lint, 19588 unit tests and check:guards green; antipattern baseline
ratcheted (naive-ore-round 622 -> 620, hand-rolled-invariant 115 -> 113).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* test(transactions): import RawTransaction from @/types after the ingest re-export removal
CI's type ratchet (check:types, full tsconfig) caught the one test file
that still imported the type through lib/transactions/ingest.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* feat(reconciliation): match migrated bank history against imported SIE verifikat
A first-class Fortnox/SIE migrator path: after SIE import plus bank connect
or bank CSV upload, historical bank rows are auto-matched (>= 0.9) or
suggestion-matched (0.75-0.89, persisted for review) against the imported
verifikat, with a guided review surface, instead of landing as anonymous
"Att bokfora" rows.
Phase 0: per-cash-account unattended sweep (fixes#1298 cross-account
pooling); widen payment_match_log action CHECK with
linked_to_existing_voucher (silently unlogged since March).
Phase 1: potential_journal_entry_id/method/confidence on transactions with
CHECK + invalidation triggers; persistSuggestions in runReconciliation;
sweep after bank CSV import with SIE overlap (suppressing
auto-categorization); sweep summaries stamped on bank_connections and
bank_file_imports; POST /api/reconciliation/bank/confirm-suggestions with
per-pair server-side revalidation (voucher consumption + bank-leg amount
and direction).
Phase 2: "Granska forslag" review tab on Transactions with chunked bulk
confirm, per-row fallbacks, "Kor matchning igen" (all_accounts sweep mode,
mutually exclusive with dry_run), attn line, pre-migration row marker.
Phase 3: ImportResultStep dual CTA (bank connect + CSV), migrator variant
of the account-picker #917 nudge, sweep outcome on the onboarding
checklist bank step.
Non-selection apply runs on /api/reconciliation/bank/run now floor at 0.9
and persist the review band instead of auto-committing fuzzy matches.
Migrations already applied to staging under the same versions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(reconciliation): resolve PR review findings in one pass
Swedish accounting review (both previously-deferred holes closed):
- runReconciliation's >= 0.9 auto-apply now writes 'matched' to
payment_match_log (behandlingshistorik, BFNAR 2013:2 kap 8); the bus
event alone lands in the 30-day event_log and is not an audit record.
- The three match-route storno-conflict branches detach reconciliation
links via unlinkReconciliation instead of storno-reversing the linked
verifikat: a reconciliation link points at an independent verifikat
that may evidence other affarshandelser, and a wholesale reversal is
an over-broad rattelse (BFL 5 kap 5 §).
- Historical gap quantified on prod (read-only, recorded in DECISIONS):
762 unlogged manual links across 52 companies since 2026-03-23.
CodeRabbit:
- confirm-suggestions route: maxDuration 300 for full 500-item batches.
- AccountPickerDialog: migrator-nudge buttons set lookbackTouched so the
async gap-fill probe cannot override an explicit choice.
- enable-banking post-backfill sweep: persistSuggestions so the review
band is not dropped.
- bank-file execute: sie_sweep stamp errors are logged, not swallowed.
- ImportResultStep: sandbox keeps the CSV CTA (file import works there).
- payment_match_log CHECK swap: NOT VALID + VALIDATE, no table scan
under ACCESS EXCLUSIVE.
- logMatchEvent calls awaited (serverless can freeze unawaited work).
- DECISIONS.md stale version reference annotated.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(reconciliation): defer reconciliation-link detach until the match commits
Round-2 review findings:
- CodeRabbit: the eager unlinkReconciliation call could orphan a
transaction if the match flow failed after it. All three match routes
now persist NOTHING up front: the final transaction update overwrites
journal_entry_id and clears reconciliation_method in the same write,
so any failure in between leaves the existing link intact. The release
is logged as 'unmatched' after the commit.
- Swedish review: the auto_suggested logMatchEvent in runReconciliation
is now awaited like every other audit write.
- DECISIONS entry split into compliance/CodeRabbit lines and updated to
describe the deferred detach.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(reconciliation): literal reconciliation_method payloads for the phantom-column scanner
The conditional spreads introduced with the deferred detach pushed the
scanner's unresolvable-expression count past its ceiling (380 > 378).
reconciliation_method: null is correct unconditionally on a confirmed
invoice/supplier match (null is already the value on every row that was
not reconciliation-linked), so the payloads become plain literals the
guard can verify. No behavior change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(invoice-inbox): store HTML mails as underlag, expandable field editor
Body-only mails and .html attachments (including forwarded .eml bodies) no
longer dead-end as "Fel vid bearbetning": the mail body is wrapped into a
self-contained text/html document, stored through the normal upload/extract
pipeline, and extracted via a new HTML-to-text Bedrock path, so the mail
itself can serve as bookable underlag. Empty mails keep the error row,
unsupported types are still rejected, and webhook retries dedupe on
resend_email_id.
Mail HTML is attacker-controlled, so rendering is fully sandboxed: iframe
sandbox in the workspace preview and a CSP sandbox header on
/api/documents/:id/inline for text/html. The type is accepted only from the
email pipeline (EMAIL_ALLOWED_MIME_TYPES), never from manual upload.
The "Extraherade falt" rail gains an expand button opening a centered
dialog with the same autosaving field editor at a readable size (two
columns), which also gives every failed or skipped extraction a manual
fallback.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(bookkeeping): sortable verifikat list headers with amount sort
- clickable sort toggles on the verifikat list headers (asc -> desc -> default)
- total_amount computed column + sort_by total/description on the list route
- failed list loads render an error card with retry, never the empty-ledger state
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(import): decode bank CSV as Windows-1252 fallback in column mapping
The client read the uploaded file with file.text(), which is UTF-8-only,
so Windows-1252 exports (e.g. Handelsbanken) rendered and re-parsed with
U+FFFD in place of Swedish characters. Decode from bytes with the shared
decodeFileContent() helper, matching what the server parse route does.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(bookkeeping): stackable sort keys on verifikat list headers
- shift-click adds a column as secondary/tertiary sort key (max 3), plain
click keeps the single-key tri-state cycle
- sort_by accepts a comma-separated priority list; single tokens stay valid
- voucher tiebreak follows the last key's direction (#972 parity)
- priority numbers on stacked headers; hint text in the filter dialog
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(invoices): keep ROT/RUT deduction fields positive on credit notes
Crediting an invoice with a ROT/RUT deduction failed 100% of the time:
the credit-note path negated deduction_total (and per-item
deduction_amount) like the other amounts, but both columns carry
CHECK (>= 0), so Postgres rejected the insert and the user only saw
'Kunde inte skapa kreditfaktura'.
Store the deduction fields as positive magnitudes, matching the
convention everywhere else. The stored sign is inert on credit notes:
the reversing verifikat recomputes the ROT/RUT split from the items,
and the PDF and amount-to-pay logic skip deductions on credit notes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* refactor(transactions): share the source chip across inbox and history modes
Move SourceFilter to transaction-types.ts (widened with 'bank:other' and
'acct:<id>'), render the one toolbar ContextPicker in both view modes,
and drop the narrower duplicate chip inside TransactionHistoryList. The
history list now applies the acct:/bank:other narrowing itself and hides
skattekonto rows under any bank-side selection.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(deps): bump js-yaml to 4.3.1
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(schema): recognize PostgREST computed columns in the migration parser
The verifikat amount sort orders by total_amount, a PostgREST computed
column (a function on the journal_entries row type, migration
20260811100000). The schema guard only modeled real columns, so
no-phantom-columns flagged the order as a phantom.
Teach the parser that a function whose only argument is a table's row
type joins that table's column set, with DROP FUNCTION retraction when
the signature names the row type.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: resolve PR #1523 review findings
- journal-entries route: append the globally unique id tiebreak to every
direct-query sort; voucher series+number repeat across fiscal years, so
the all-years scope could duplicate or drop rows at page boundaries.
Existing order assertions updated, new all-years tiebreak test.
- documents inline route: CSP source policy on HTML previews; sandbox
alone still loads remote resources, letting a tracking pixel notify the
sender on open. New route test asserts the full header.
- JournalEntryList: catch rejected list requests so loading cannot stick
forever, and gate every post-await state write behind a request
generation so a slow earlier request cannot overwrite the current sort.
- TransactionHistoryList: pagination follows the selected source scope
(reachable with zero matches on the current page, hidden for the
skattekonto scope it cannot affect).
- transactions page: bank:other picker availability derives from history
rows too, not only the pending inbox dataset.
- DECISIONS.md: mark the superseded single-sort decision; record the
credit-note deduction positive-magnitude invariant and its verified
reader inventory (Swedish review flag).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: guard metadata refetches behind the list request generation
fetchAttachmentCounts and fetchRattelseFlags write state after their own
awaits; a stale list request's late completion could overwrite attachment
counts and rattelse flags for rows a newer request just rendered, showing
false missing-underlag warnings. Both helpers now take the caller's
generation guard and discard stale completions, including the
attachment-counts loaded flag.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(categorize): dimension bags end-to-end + runtime template learning
The categorize path could not tag: categorize-core accepted a dimensions
bag but no route or UI ever passed one, and runtime template learning
dropped the bag entirely (only SIE import produced dimension-carrying
patterns).
- CategorizeTransactionSchema gains dimensions; the dashboard route, v1
single and v1 batch-categorize apply it to the mapping result's
business lines (explicit bag wins over a learned counterparty bag).
- categorization_templates.default_dimensions (migration 20260728091000)
records the bag of the latest tagged booking; latest-explicit-wins, an
untagged booking never erases it. Applied on the legacy single-line
template path and the mirrored-refund path; multi-line SIE patterns
keep their authoritative per-entry bags.
- QuickReviewDialog gets a LineDimensionFields picker (dimensions_enabled
gate, same as BulkBookDialog), prefilled from the counterparty
suggestion's learned bag; hidden for multi-line patterns whose per-line
bags would ignore an edit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Renumber migration above 20260728120000 (out-of-order vs prod after #1271)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat: add user locale preference to user_preferences table
- Introduced a new column 'locale' in the user_preferences table to store per-user UI language preferences.
- Added a CHECK constraint to ensure only supported locales ('sv', 'en') are allowed.
- Triggered a schema reload notification for the changes.
chore: declare CSS module support in TypeScript
- Added a declaration for CSS modules in globals.d.ts to enable TypeScript support for importing CSS files.
* feat: add Swish as an invoice payment method in company settings
* feat(accounting): update accounting method validation and messaging for aktiebolag and enskild firma
* Remove AI subsystem and related code
- Deleted AI proposals and requests persistence logic from `lib/ai/proposals/persist.ts`.
- Removed re-validation logic for proposals in `lib/ai/proposals/re-validate.ts`.
- Cleaned up schemas related to AI flows in `lib/api/schemas.ts`.
- Removed AI-related fields from bookkeeping engine in `lib/bookkeeping/engine.ts`.
- Eliminated AI event types from `lib/events/types.ts`.
- Updated tests to reflect the removal of AI-related functionality in `lib/extensions/__tests__/sectors.test.ts`.
- Adjusted initialization logic in `lib/init.ts` to exclude AI proposal handler registration.
- Cleaned up transaction ingestion logic in `lib/transactions/ingest.ts` to remove AI flow checks.
- Updated helper functions in `tests/helpers.ts` to remove AI-related settings.
- Removed AI-related types and interfaces from `types/index.ts`.
- Added migration script to drop AI-related tables and settings from the database.
* fix(migrations): ensure foreign key constraint is dropped before removing AI tables
* feat(invoice-inbox): implement deterministic invoice field extraction and inbox provisioning
- Added `extract-invoice-fields.ts` for extracting fields from PDF invoices using regex and pdfjs-dist, replacing the previous AI classifier.
- Introduced `inbox-provisioning.ts` to manage company inbox addresses and rotation of inboxes using Supabase RPCs.
- Created `resend-inbound.ts` for handling inbound email events and attachments via the Resend API.
- Defined the extension manifest for the invoice inbox, specifying required environment variables and descriptions.
- Migrated database schema to remove AI-related columns and tighten the status enum in `invoice_inbox_items`.
* feat(invoice-inbox): remove AI-specific columns and tighten status enum
* fix(skattekonto): remove manual entry creation reference from transaction input
* fix(schemas): remove accounting method validation for aktiebolag in UpdateSettingsSchema
Expand supplier invoice module with overdue cron job, credit note journal
entries, and event emissions on approve/mark-paid/create flows. Add entity
type (EF/AB) awareness to transaction categorization UI and category
mapping logic. Add comprehensive tests for supplier-invoice-entries,
transaction-entries, and expanded API route coverage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Expand BAS reference from ~180 to ~1,276 accounts (full BAS Kontoplan 2026)
with K2 exclusion flags, per-class data files, and computed SRU codes
- Evolve invoice inbox into unified document inbox handling invoices, receipts,
and government letters with AI-powered classification (Claude Haiku Vision)
- Add multi-pass document-to-transaction matching engine with greedy assignment
for both supplier invoices (reference/amount/date/name) and receipts
(weighted amount/merchant/date scoring)
- Add supplier invoice matching in transaction ingest pipeline
- Inject booking template suggestions into AI extraction prompts
- Surface matched documents in swipe categorization UI with one-tap booking
- Auto-activate missing BAS accounts during SIE import against full reference
- Add K2 filter toggle in Chart of Accounts manager
- Add receipt confirmation route with BFNAR representation fields
- Add database migrations for K2 support and document matching columns
- Remove obsolete extension migration scripts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add journal entry preview, human-readable account names, auto-apply VAT,
fallback template suggestions, example prompts, invoice match comparison,
and batch result feedback. Also includes user-description-match extension,
describe/batch-describe API routes, improved AI categorization with multi-
suggestion support, and template embedding search.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add auto-detecting CSV parsers for Länsförsäkringar, ICA Banken, Skandia,
and Lunar. Refine SEB detection to avoid false matches. Update bank file
upload UI with new bank options and export instructions. Include booking
templates, improved AI categorization, and transaction review enhancements.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>