Versie 0.2.0
This commit is contained in:
commit
e77d8fd373
495 files changed
+81026
No files matched your search
@@ -0,0 +1,148 @@
|
||||
// Generated, do not edit. sha256:605d840e94e42af14d150c5b1095887b6d46b752b6e0dc0407b3f4a209ee7b66
|
||||
import { describe, it, beforeEach, mock } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
|
||||
import { createDbHarness } from '../helpers/db-node.mjs';
|
||||
import { toSearchName } from '../../shared/search-name.js';
|
||||
import { isOwnWealth } from '../../shared/wealth-scope.js';
|
||||
|
||||
const harness = createDbHarness();
|
||||
mock.module('../../server/db.js', { namedExports: harness.exports });
|
||||
|
||||
const { assetPositions } = await import('../../server/data/events.js');
|
||||
const { positionsByAsset } = await import('../../server/data/positions.js');
|
||||
const { latestAnchors } = await import('../../server/data/valuations.js');
|
||||
const { metalHoldings } = await import('../../server/data/metals.js');
|
||||
|
||||
const { run, lastId } = harness.exports;
|
||||
const PEIL = '2026-12-31T00:00:00.000Z';
|
||||
const rijVan = (rijen, id) => rijen.find(r => r.id === id);
|
||||
const eigen = (assetId) => rijVan(positionsByAsset(PEIL, { ownOnly: true }), assetId);
|
||||
const alles = (assetId) => rijVan(positionsByAsset(PEIL), assetId);
|
||||
|
||||
let btc, xau, munt, kluis, spaarpot, pensioen;
|
||||
beforeEach(() => {
|
||||
harness.recreate();
|
||||
btc = asset('BTC', 'Bitcoin', 'crypto', 8);
|
||||
xau = asset('XAU', 'Goud', 'metal', 4);
|
||||
run(`INSERT INTO MetalProduct (assetId, name, searchName, form, fineGrams) VALUES (?, 'Krugerrand', 'krugerrand', 'coin', 311035)`, [xau]);
|
||||
munt = lastId();
|
||||
kluis = account('Kluis', null);
|
||||
spaarpot = account('Spaarpot kind', 'managed');
|
||||
pensioen = account('Pensioen', 'none');
|
||||
});
|
||||
|
||||
function asset(symbol, naam, kind, decimals) {
|
||||
run(`INSERT INTO Asset (symbol, name, searchName, kind, unit, subunit, decimals, displayUnit)
|
||||
VALUES (?,?,?,?,?,NULL,?,'unit')`, [symbol, naam, toSearchName(naam), kind, symbol, decimals]);
|
||||
return lastId();
|
||||
}
|
||||
function account(naam, wealthScope) {
|
||||
run(`INSERT INTO Account (name, searchName, kind, isOwnCustody, scanEnabled, isActive, closedAt, wealthScope)
|
||||
VALUES (?,?,'bank',1,0,1,NULL,?)`, [naam, toSearchName(naam), wealthScope]);
|
||||
return lastId();
|
||||
}
|
||||
function event({ assetId = null, type = 'buy', to = null, from = null, amount = 1000, unitFee = null,
|
||||
priceOrigin = null, productId = null, productCount = null, at = '2026-03-01T12:00:00.000Z' } = {}) {
|
||||
const prijs = type === 'buy' || type === 'sell' ? 50000 : null;
|
||||
run(`INSERT INTO Event (assetId, type, occurredAt, amount, fiatPrice, fiatFee, unitFee, productId, productCount,
|
||||
fromAccountId, toAccountId, source, priceOrigin, createdAt)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,'manual',?,?)`,
|
||||
[assetId ?? btc, type, at, amount, prijs, type === 'buy' ? 10 : null, unitFee, productId, productCount,
|
||||
from, to, priceOrigin, at]);
|
||||
return lastId();
|
||||
}
|
||||
function balans({ assetId = null, acc, balance, at = '2026-06-30T00:00:00.000Z' }) {
|
||||
run(`INSERT INTO Valuation (assetId, valuedOn, accountId, balance, fiatRate, rateAt, source)
|
||||
VALUES (?,?,?,?,1.0,?, 'manual')`, [assetId ?? btc, at.slice(0, 10), acc, balance, at]);
|
||||
return lastId();
|
||||
}
|
||||
|
||||
describe('isOwnWealth', () => {
|
||||
it('is waar zonder account en zonder keuze, onwaar bij beheerd en bij telt niet mee', () => {
|
||||
assert.equal(isOwnWealth(null), true, 'wat aan geen account hangt is van niemand anders');
|
||||
assert.equal(isOwnWealth({ wealthScope: null }), true);
|
||||
assert.equal(isOwnWealth({ wealthScope: 'managed' }), false);
|
||||
assert.equal(isOwnWealth({ wealthScope: 'none' }), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ownOnly zonder afwijking', () => {
|
||||
it('geeft dezelfde rijen als zonder de optie, ook met een gift, een fee en een zelf-transfer', () => {
|
||||
event({ to: kluis, amount: 100_000 });
|
||||
event({ to: null, amount: 20_000, at: '2026-03-02T00:00:00.000Z' });
|
||||
event({ type: 'transfer', from: kluis, to: null, amount: 5_000, unitFee: 300, at: '2026-04-01T00:00:00.000Z' });
|
||||
event({ type: 'transfer', from: null, to: kluis, amount: 7_000, priceOrigin: 'none', at: '2026-04-02T00:00:00.000Z' });
|
||||
event({ type: 'transfer', from: kluis, to: kluis, amount: 1_000, unitFee: 50, at: '2026-04-03T00:00:00.000Z' });
|
||||
event({ type: 'sell', from: kluis, amount: 10_000, at: '2026-05-01T00:00:00.000Z' });
|
||||
event({ assetId: xau, to: kluis, amount: 622_070, productId: munt, productCount: 2 });
|
||||
balans({ acc: kluis, balance: 90_000 });
|
||||
assert.deepEqual(assetPositions(PEIL, { ownOnly: true }), assetPositions(PEIL));
|
||||
assert.deepEqual(positionsByAsset(PEIL, { ownOnly: true }), positionsByAsset(PEIL));
|
||||
assert.deepEqual(latestAnchors(PEIL, { ownOnly: true }), latestAnchors(PEIL));
|
||||
assert.deepEqual(metalHoldings({ ownOnly: true }), metalHoldings());
|
||||
});
|
||||
});
|
||||
|
||||
describe('ownOnly over de grens van het eigen vermogen', () => {
|
||||
it('telt een aankoop op een beheerd account niet, ook niet in de aankoopcijfers', () => {
|
||||
event({ to: kluis, amount: 100_000 });
|
||||
event({ to: spaarpot, amount: 40_000 });
|
||||
assert.equal(alles(btc).net, 140_000);
|
||||
assert.equal(alles(btc).buy_events, 2);
|
||||
const r = eigen(btc);
|
||||
assert.equal(r.net, 100_000);
|
||||
assert.equal(r.buy_events, 1);
|
||||
assert.equal(r.buy_amount, 100_000);
|
||||
assert.equal(r.buy_fee_total, 10, 'alleen de fee van de eigen aankoop');
|
||||
});
|
||||
|
||||
it('telt een overboeking naar een spaarpot als onttrekking, met de fee', () => {
|
||||
event({ to: kluis, amount: 100_000 });
|
||||
event({ type: 'transfer', from: kluis, to: spaarpot, amount: 30_000, unitFee: 200, at: '2026-04-01T00:00:00.000Z' });
|
||||
assert.equal(alles(btc).net, 99_800, 'in het geheel valt alleen de fee weg');
|
||||
assert.equal(eigen(btc).net, 69_800, 'het eigen vermogen ziet het geld vertrekken');
|
||||
assert.equal(eigen(btc).gift_amount, 0, 'een overboeking met een prijsherkomst is geen gift');
|
||||
});
|
||||
|
||||
it('telt een overboeking terug als storting', () => {
|
||||
event({ to: spaarpot, amount: 50_000 });
|
||||
event({ type: 'transfer', from: spaarpot, to: kluis, amount: 20_000, at: '2026-04-01T00:00:00.000Z' });
|
||||
assert.equal(eigen(btc).net, 20_000);
|
||||
assert.equal(alles(btc).net, 50_000);
|
||||
});
|
||||
|
||||
it('negeert een balans op een beheerd account, en houdt die op een eigen account', () => {
|
||||
event({ to: kluis, amount: 100_000 });
|
||||
event({ to: spaarpot, amount: 40_000 });
|
||||
balans({ acc: spaarpot, balance: 45_000 });
|
||||
balans({ acc: kluis, balance: 101_000 });
|
||||
assert.equal(alles(btc).net, 146_000);
|
||||
assert.equal(eigen(btc).net, 101_000, 'de spaarpot verschuift het eigen vermogen niet');
|
||||
assert.deepEqual(latestAnchors(PEIL, { ownOnly: true }).map(a => a.accountId), [kluis]);
|
||||
});
|
||||
|
||||
it('houdt een boeking zonder account bij het eigen vermogen', () => {
|
||||
event({ to: null, amount: 25_000 });
|
||||
assert.equal(eigen(btc).net, 25_000);
|
||||
});
|
||||
|
||||
it('behandelt telt niet mee net als beheerd', () => {
|
||||
event({ to: kluis, amount: 100_000 });
|
||||
event({ to: pensioen, amount: 60_000 });
|
||||
event({ type: 'transfer', from: kluis, to: pensioen, amount: 10_000, at: '2026-04-01T00:00:00.000Z' });
|
||||
assert.equal(eigen(btc).net, 90_000);
|
||||
assert.equal(alles(btc).net, 160_000);
|
||||
});
|
||||
|
||||
it('telt munten die naar een spaarpot gaan niet meer bij het eigen vermogen', () => {
|
||||
event({ assetId: xau, to: kluis, amount: 933_105, productId: munt, productCount: 3 });
|
||||
event({ assetId: xau, to: spaarpot, amount: 311_035, productId: munt, productCount: 1 });
|
||||
event({ assetId: xau, type: 'transfer', from: kluis, to: spaarpot, amount: 311_035, productId: munt,
|
||||
productCount: 1, at: '2026-04-01T00:00:00.000Z' });
|
||||
assert.equal(metalHoldings()[0].net_count, 4, 'het geheel: vier gekocht, een verplaatsing telt niet');
|
||||
const eigenMunt = metalHoldings({ ownOnly: true })[0];
|
||||
assert.equal(eigenMunt.net_count, 2, 'drie gekocht, één naar de spaarpot');
|
||||
assert.equal(eigenMunt.buy_events, 1, 'de aankoop op de spaarpot telt niet mee');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user