Commit Graph
3 Commits
Author SHA1 Message Date
1dc85736d8 feat(import): add Wise (TransferWise) CSV import format (#1018)
* feat(import): add Wise (TransferWise) CSV import format

Wise exports a single multi-currency transaction history (one row per balance
movement). Add it as a bank-file format plugin so it flows through the existing
upload -> preview -> confirm -> execute wizard.

- lib/import/bank-file/formats/wise.ts: quote-aware parse (dates contain a
  space), Direction IN/OUT drives the sign, booked on the moved side (target
  for IN, source for OUT). Native currency preserved; SEK conversion is left to
  the downstream FX/booking pipeline (Riksbanken).
- Non-zero Wise fees become their own negative "Wise avgift" row (source and
  target), so the fee books separately and the balance ties out.
- Only COMPLETED rows import. external_id keys on the stable Wise ID
  (TRANSFER-/PLAN_ORDER-, -fee suffix for fee rows) via a new 'wise' branch in
  generateExternalId, so re-imports dedup exactly.
- Register the format (types, parser list), add it to the manual-format picker
  and the v1 /imports/bank format enum.

Tests cover detection, IN/OUT signing + currency, fee splitting, stable
external_id, and COMPLETED-only filtering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexander Reinthal <email@reinthal.me>

* fix(import): harden Wise parser against malformed rows (CodeRabbit #1018)

- Strict amount parsing: reject "12abc"/"1,234" instead of parseFloat coercing
  them to 12/1 and silently corrupting the imported amount.
- Require Status to be exactly COMPLETED: a blank/missing status no longer
  slips through the completed-only filter.
- Fail hard on an unsupported Direction: a blank or non-IN/OUT value (e.g.
  NEUTRAL for a balance conversion) throws instead of being guessed as income;
  the parse route surfaces it as BANK_FILE_PARSE_FAILED. Proper conversion
  support is tracked in #1019.
- Never invent currencies: a missing movement currency skips the row with a
  warning (no SEK default), and a fee with no currency of its own is dropped
  with a warning rather than inheriting the movement currency.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexander Reinthal <email@reinthal.me>

---------

Signed-off-by: Alexander Reinthal <email@reinthal.me>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jakob Wennberg <jakob.wennberg@gmail.com>
2026-07-16 16:22:32 +02:00
f8033cb32d fix(transactions): bind manually-fed transactions to a cash account (#1016) (#1017)
* fix(transactions): bind manually-fed transactions to a cash account (#1016)

create_transactions inserted rows with cash_account_id = null, so ledger
accounts fed via MCP/CSV without a PSD2 feed (e.g. 1935 Wise SEK) had no
kassakonto: get_reconciliation_status 404'd with "Okänt kassakonto" and the
"Matcha mot befintlig verifikation" dialog fell back to 1930.

No schema change: cash_accounts.bank_connection_id is already nullable and
source='manual' already exists (every company is seeded a manual 1930). This
is the creation-side leg of the #985-#987 root cause: the resolution chain was
fixed, but manually-fed accounts never got the cash_account_id link.

- Add ensureManualCashAccount (lib/cash-accounts/service.ts): find-or-create a
  manual (source='manual', bank_connection_id=null) cash_accounts row for a
  ledger slot, tolerating the (company_id, ledger_account) UNIQUE race.
- Add an optional ledger_account hint (^19xx) to gnubok_create_transactions;
  commitCreateTransaction resolves/creates the manual account and sets
  cash_account_id on the inserted row.

Reconciliation and voucher matching then resolve the real account unchanged.
Forward-looking; historical cash_account_id=null remediation stays in #1001.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexander Reinthal <email@reinthal.me>

* fix(cash-accounts): guard ensureManualCashAccount against currency mismatch (CodeRabbit #1017)

The existing-row lookup matched only on (company_id, ledger_account) and
returned the row id ignoring currency, so a SEK transaction hinting at a ledger
already claimed for USD would bind to the wrong-currency cash account. Since
that pair is UNIQUE (one currency per ledger), a mismatch is a real conflict:
throw instead of silently mis-binding.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Alexander Reinthal <email@reinthal.me>

---------

Signed-off-by: Alexander Reinthal <email@reinthal.me>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Jakob Wennberg <jakob.wennberg@gmail.com>
2026-07-16 16:15:06 +02:00
Alexander ReinthalandJakob Wennberg edef48471c feat: add currency for articles (#834)
Co-authored-by: Jakob Wennberg <jakob.wennberg@gmail.com>
2026-07-16 16:00:05 +02:00