Files
backend/src/modules/investments/performance/domain/performance-flows.spec.ts
T
ksv741 3b55b611b5 refactor!: support RUB only (WealthLedger/main#55)
Filter non-RUB data at ingest, collapse per-currency collections into single RUB Money, drop currency from TbankPortfolioValuePoint, remove NON_RUB_ACTIVITY and screener currency filter.

BREAKING CHANGE: per-currency API fields replaced with single RUB Money; stock screener currency param removed.
2026-11-11 08:22:16 +03:00

50 lines
2.2 KiB
TypeScript

import { formatDecimal } from '#src/common/decimal';
import { candidateTransfers, readPerformanceOperations, sumFlows } from './performance-flows';
const at = (date: string) => new Date(date);
const op = (accountId: string, type: string, date: string, units: string, currency = 'rub') => ({
accountId, occurredAt: at(date), instrumentUid: null,
payload: { type, payment: { units, nano: 0, currency } },
});
describe('performance cash flows', () => {
it('includes only external cash and preserves dividends within the portfolio', () => {
const operations = [
op('A', 'OPERATION_TYPE_INPUT', '2025-01-01T15:00:00Z', '100000'),
op('A', 'OPERATION_TYPE_INPUT_ACQUIRING', '2025-02-01T15:00:00Z', '50000'),
op('A', 'OPERATION_TYPE_OUTPUT', '2025-03-01T15:00:00Z', '-20000'),
op('A', 'OPERATION_TYPE_DIVIDEND', '2025-04-01T15:00:00Z', '3000'),
];
const result = readPerformanceOperations(operations);
expect(formatDecimal(sumFlows(result.flows))).toBe('130000');
expect(result.flows).toHaveLength(3);
});
it('treats equal opposite cash movements as unconfirmed transfer candidates, not a proven pair', () => {
const result = readPerformanceOperations([
op('A', 'OPERATION_TYPE_OUTPUT', '2025-03-01T15:00:00Z', '-50000'),
op('B', 'OPERATION_TYPE_INPUT', '2025-03-01T15:01:00Z', '50000'),
]);
expect(candidateTransfers(result)).toEqual([{ accountIds: ['A', 'B'], type: 'cash' }]);
expect(result.flows).toHaveLength(2);
});
it('detects unvalued securities transfers', () => {
const result = readPerformanceOperations([
op('B', 'OPERATION_TYPE_INPUT_SECURITIES', '2025-01-01T15:00:00Z', '0'),
]);
expect([...result.unvaluedAccounts]).toEqual(['B']);
});
it('skips non-RUB cash movements read back from persisted payloads', () => {
const result = readPerformanceOperations([
op('A', 'OPERATION_TYPE_INPUT', '2025-01-01T15:00:00Z', '100'),
op('A', 'OPERATION_TYPE_INPUT', '2025-02-01T15:00:00Z', '21', 'usd'),
]);
expect(result.flows).toHaveLength(1);
expect(formatDecimal(sumFlows(result.flows))).toBe('100');
expect([...result.missingAmountAccounts]).toEqual(['A']);
});
});