money: quick-add bar, server-filtered transactions page, CSV import dedupe + Revolut statements
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { cents, csvDate, importCents, parseCsv, parseTransactionImport } from './money-tracker';
|
||||
import {
|
||||
cents,
|
||||
csvDate,
|
||||
importCents,
|
||||
parseCsv,
|
||||
parseTransactionImport,
|
||||
planImport
|
||||
} from './money-tracker';
|
||||
|
||||
describe('transaction import parsing', () => {
|
||||
it('handles quoted commas and semicolon-separated European amounts', () => {
|
||||
@@ -63,3 +70,50 @@ describe('transaction import parsing', () => {
|
||||
).toThrow('Quote values containing commas');
|
||||
});
|
||||
});
|
||||
|
||||
describe('revolut statements and import dedupe', () => {
|
||||
const statement =
|
||||
'Type,Product,Started Date,Completed Date,Description,Amount,Fee,Currency,State,Balance\n' +
|
||||
'CARD_PAYMENT,Current,2026-10-01 10:00:00,2026-10-02 09:00:00,Albert Heijn,-12.34,0.00,EUR,COMPLETED,100.00\n' +
|
||||
'TOPUP,Current,2026-10-03 10:00:00,,Pending top-up,50.00,0.00,EUR,PENDING,\n' +
|
||||
'TRANSFER,Current,2026-10-04 10:00:00,2026-10-04 10:00:01,To Joint,-100.00,0.50,EUR,COMPLETED,0.00\n' +
|
||||
'CARD_PAYMENT,Current,2026-10-05 10:00:00,2026-10-05 11:00:00,Coffee,-3.00,0.00,USD,COMPLETED,0.00\n';
|
||||
|
||||
it('keeps completed EUR rows, uses the completed date and deducts fees', () => {
|
||||
const rows = parseTransactionImport(statement, 'Revolut');
|
||||
expect(rows.map((row) => [row.date, row.account, row.description, row.amountCents])).toEqual([
|
||||
['2026-10-02', 'Revolut', 'Albert Heijn', -1234],
|
||||
['2026-10-04', 'Revolut', 'To Joint', -10050]
|
||||
]);
|
||||
expect(rows[0].externalId).toMatch(/^csv:[0-9a-f]{32}$/);
|
||||
expect(parseTransactionImport(statement, 'Revolut')[0].externalId).toBe(rows[0].externalId);
|
||||
expect(() => parseTransactionImport(statement)).toThrow('Choose which account');
|
||||
});
|
||||
|
||||
it('gives identical rows in one file distinct, stable ids', () => {
|
||||
const file =
|
||||
'date,account,description,amount\n2026-10-01,Cash,Kiosk,-2\n2026-10-01,Cash,Kiosk,-2\n';
|
||||
const [a, b] = parseTransactionImport(file);
|
||||
expect(a.externalId).not.toBe(b.externalId);
|
||||
expect(parseTransactionImport(file).map((row) => row.externalId)).toEqual([
|
||||
a.externalId,
|
||||
b.externalId
|
||||
]);
|
||||
});
|
||||
|
||||
it('skips rows already imported or already entered by hand (±1 day)', () => {
|
||||
const rows = [
|
||||
{ accountId: 1, date: '2026-10-02', amountCents: -1234, externalId: 'csv:a' },
|
||||
{ accountId: 1, date: '2026-10-02', amountCents: -1234, externalId: 'csv:b' },
|
||||
{ accountId: 1, date: '2026-10-04', amountCents: -500, externalId: 'csv:c' },
|
||||
{ accountId: 2, date: '2026-10-04', amountCents: -500, externalId: 'csv:d' }
|
||||
];
|
||||
const existing = [
|
||||
{ id: 10, accountId: 1, date: '2026-10-01', amountCents: -1234, externalId: null },
|
||||
{ id: 11, accountId: 1, date: '2026-10-04', amountCents: -500, externalId: 'csv:c' }
|
||||
];
|
||||
const { insert, duplicates } = planImport(rows, existing);
|
||||
expect(insert.map((row) => row.externalId)).toEqual(['csv:b', 'csv:d']);
|
||||
expect(duplicates).toBe(2);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user