* fix(reports): convert FX to SEK in supplier/AR ledger reconciliation
The supplier and AR ledger reports were summing remaining_amount
directly without converting foreign-currency invoices, so a
EUR/USD invoice would land in the aging total at face value while
the corresponding 2440 / 1510 GL line was already posted in SEK.
This produced false reconciliation discrepancies (e.g. 496,25 kr
ledger vs 952,50 kr GL with four EUR/USD invoices).
Apply resolveSekAmount(remaining, null, currency, exchange_rate)
in supplier-ledger, supplier-reconciliation, ar-ledger, and
ar-reconciliation. Per-invoice detail rows on the AR ledger keep
the original currency for display; only aging buckets and totals
become SEK. Adds mixed-currency test cases to all four files.
Also bundles unrelated WIP from the working tree:
- bank-reconciliation: log the swallowed catch error and drop the
fallback path for the deleted get_unlinked_bank_lines RPC, using
get_unlinked_1930_lines directly.
- new GET /api/transactions list endpoint with unmatched/reconciled/
currency/date filters and full route tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(reports): address PR #396 review findings
Compliance and Greptile review identified four issues; all four are
addressed here.
- Reconciliation: surface unconverted_fx_count on ReconciliationResult
and ARReconciliationResult. When > 0 the difference field may be a
data gap (FX invoice with no exchange_rate) rather than a true
reconciliation break. UI now renders a Swedish caveat below the
Avstämd / Ej avstämd badge so users understand the cause. New tests
assert the count is set on legacy FX rows.
- Reconciliation: document the invoice-date-rate assumption explicitly
in the JSDoc of both reconciliation generators. Per ML 8 kap 21–23 §,
the report uses each invoice's stored exchange_rate; partial payments
settled at a different rate produce a delta correctly booked to
3960/7960 as valutakursvinst/-förlust, but the GL will diverge from
the report by that amount until a subledger-derived total is wired up
(deferred follow-up).
- fetchUnlinkedGLLines: drop the misleading bankAccount parameter.
It was advertised as configurable but the function silently returned
[] for any value other than '1930'. Now the signature is honest:
1930-only until proper multi-account support is built.
- /api/transactions: query MAX_ROWS+1 rows so the response can include
has_more and limit fields. Callers can now detect truncation, which
matters once a company crosses 500 unmatched transactions in the
selected range. New test asserts has_more=true when 501 rows are
returned by the DB and the response is sliced to MAX_ROWS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(reports): address PR #396 round-2 compliance review
Compliance bot v2 review surfaced four findings on the previous commit;
three are addressed here. The fourth (an fx_rate_diff_amount indicator
distinguishing real reconciliation breaks from correctly-booked
valutakursvinst/-förlust) requires a subledger-derived total against
3960/7960 — already documented as a deferred follow-up in the JSDoc.
Changes:
- Exclude unconvertible FX rows from SEK sums. resolveSekAmount's
null-rate fallback returned the raw foreign amount, so a 100 EUR
invoice with no rate was being added to a SEK total as if it were
100 SEK. All four generators (supplier-ledger,
supplier-reconciliation, ar-ledger, ar-reconciliation) now skip
rows where currency != SEK and exchange_rate is missing/zero, and
count them in unconverted_fx_count.
- Surface unconverted_fx_count on SupplierLedgerReport /
ARLedgerReport (not just the reconciliation result). UI shows a
Swedish caption beneath the "Totalt utestående" card whenever the
count is positive, so users see the warning even if they don't
enable the reconciliation panel.
- Add outstanding_sek: number | null to ARInvoiceDetail. The
per-invoice detail row keeps `outstanding` in invoice currency for
display, but now also exposes the converted SEK value (or null when
unconvertible). Defensive against future callers that sum across
customers — they should use outstanding_sek to avoid mixing
currencies (a real momsdeklaration foot-gun otherwise).
- Add a 1930-only notice to BankReconciliationView. The reconciliation
is scoped to account 1930; users with Plusgiro 1920, kreditkort 1940,
or valutakonton now see a Swedish caption explaining those are
reconciled separately.
Tests:
- supplier-ledger: previous "falls back to original amount" case
flipped to assert the row is excluded and counted; assertion that
the legacy supplier disappears from the list when their only row is
unconvertible.
- supplier-reconciliation: previous "1 100 ledger vs 1 000 GL" case
flipped to assert ledger=1 000 and is_reconciled=true with
unconverted_fx_count=1.
- ar-reconciliation: same pattern.
- ar-ledger: existing FX-mix test extended to assert outstanding_sek
on each detail row; new test covering the null-rate exclusion path
asserts the detail row is still pushed (with outstanding_sek=null)
but excluded from buckets and the grand total.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(reports): address PR #396 round-3 compliance review
Compliance bot v3 surfaced four findings on the previous commit; two
are addressed here, two are deliberately skipped (rationale in JSDoc /
this message).
Addressed:
- is_reconciled now returns false whenever unconverted_fx_count > 0,
even if the numeric difference is zero. Per BFL 5 kap, the
reconciliation must cover all affärshändelser; if a row was
excluded for a missing exchange rate, the calculation is
incomplete by construction and the period cannot honestly be
stamped Avstämd. The fix is for the user to fill in the missing
rate, not for the system to claim balance on partial data.
- AR reconciliation now sums account 1510 + 1513 in the GL balance
comparison. Forward-looking defense for ROT/RUT fakturamodellen
invoices that split AR receivables across the customer portion
(1510) and the Skatteverket claim (1513). Today no production code
posts to 1513 so the value is unchanged in practice; once
fakturamodellen invoicing is added, the reconciliation will
continue to balance without requiring another fix. UI label
updated to "Kundfordringar (1510 + 1513) saldo" so the inclusion
is visible. Field name and shape are unchanged for back-compat
with the supplier-side parallel.
Skipped:
- "Block is_reconciled=true when any FX invoice exists in an open
period" — overly aggressive; would block reconciliation for any
FX-using company even when their books are correctly matched. The
proper solution is the deferred subledger-derived total against
3960/7960 (already documented in JSDoc on both reconciliation
generators), which can compute the *expected* FX rate difference
and either subtract it from `difference` or expose it as
`fx_rate_difference`. Not in scope for this PR.
- Plusgiro 1920 wording in BankReconciliationView — bot itself
marked this "not a hard finding". The 1920 reference is accurate
per BAS 2026.
Tests:
- supplier-reconciliation: existing v3 exclusion test updated —
asserts is_reconciled=false despite numbers matching, with
comment explaining BFL rationale.
- ar-reconciliation: same update + new test covering the 1510 + 1513
sum (1 200 on 1510, 300 on 1513, ledger total 1 500 → reconciled).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>