Money tracker: Knab CSV import, multi-bank linking, sum reconcile, history range
Some checks failed
Deploy production / deploy (push) Failing after 23s

- Knab statement exports: per-account ids (knab:<account>:<Referentie>), routed by IBAN,
  transfers between own accounts merged into one tracker account are dropped (interest kept)
- Import: multiple files, hand-entered twins get the import id, hand entries equal to the
  sum of 2-3 payments within a day are replaced by them
- bank-link --bank revolut|abn|knab with per-bank default account; ASPSP notes printed
- bank-sync: longest history on an account first sync (--full to force), sum reconcile,
  per-session status so one bank failing does not mark the others
- Timeline: 1 year / 3 years / All history range
This commit is contained in:
2026-10-24 01:37:35 +00:00
parent 2cada618bf
commit 763d7881fb
13 changed files with 729 additions and 67 deletions

View File

@@ -0,0 +1,30 @@
import { describe, expect, it } from 'vitest';
import { findSumMatch } from './reconcile';
describe('sum matching', () => {
const pool = [
{ date: '2026-03-01', amountCents: -20000 },
{ date: '2026-03-01', amountCents: -300 },
{ date: '2026-03-02', amountCents: -600 },
{ date: '2026-03-05', amountCents: -900 },
{ date: '2026-03-01', amountCents: 900 }
];
it('finds the payments a hand entry stands for', () => {
expect(
findSumMatch({ date: '2026-03-01', amountCents: -20900 }, pool, new Set())?.sort()
).toEqual([0, 1, 2]);
expect(findSumMatch({ date: '2026-03-01', amountCents: -20300 }, pool, new Set())).toEqual([
0, 1
]);
});
it('ignores used rows, other days and opposite signs', () => {
expect(
findSumMatch({ date: '2026-03-01', amountCents: -20900 }, pool, new Set([2]))
).toBeNull();
expect(findSumMatch({ date: '2026-03-05', amountCents: -1200 }, pool, new Set())).toBeNull();
expect(findSumMatch({ date: '2026-03-01', amountCents: -19100 }, pool, new Set())).toBeNull();
});
it('needs at least two parts', () => {
expect(findSumMatch({ date: '2026-03-01', amountCents: -300 }, pool, new Set())).toBeNull();
});
});