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.
This commit is contained in:
2026-11-11 08:22:16 +03:00
parent fee61537b6
commit 3b55b611b5
60 changed files with 624 additions and 736 deletions
+18 -33
View File
@@ -113,9 +113,9 @@ describe('saved payout calendars and complete forecasts (SQLite HTTP)', () => {
lastSuccessfulAt: new Date().toISOString(),
isStale: false,
positions,
totalsByCurrency: [],
cashBalances: [],
structure: { currencies: [], unvaluedCategories: [] },
totals: { currency: 'RUB', value: '0' },
cash: { currency: 'RUB', value: '0' },
structure: { totalValue: '0', hasNegativeValues: false, categories: [], unvaluedCategories: [] },
};
await prisma.tbankPortfolio.upsert({
where: {
@@ -273,17 +273,14 @@ describe('saved payout calendars and complete forecasts (SQLite HTTP)', () => {
single.data?.events.find((event) => event.type === 'offer')
?.projectedAmount,
).toBeNull();
expect(single.data?.incomeTotalsByCurrency).toEqual([
{ currency: 'RUB', value: '6' },
{ currency: 'USD', value: '20.000000002' },
]);
expect(single.data?.incomeTotal).toEqual({ currency: 'RUB', value: '6' });
const all = (await events('all').expect(200))
.body as UpcomingEventsResourceDto;
expect(
all.data?.events.every((event) => event.account?.id === 'account'),
).toBe(true);
expect(all.data?.incomeTotalsByCurrency).toEqual(
single.data?.incomeTotalsByCurrency,
expect(all.data?.incomeTotal).toEqual(
single.data?.incomeTotal,
);
expect(moex.bondEvents).toHaveBeenCalledWith('BOND');
expect(upstream.getBondCoupons).not.toHaveBeenCalled();
@@ -334,7 +331,7 @@ describe('saved payout calendars and complete forecasts (SQLite HTTP)', () => {
expect(newRange.data).toEqual({
...saved.data,
events: [],
incomeTotalsByCurrency: [],
incomeTotal: { currency: 'RUB', value: '0' },
});
expect(newRange.data?.window).toEqual(window);
await prisma.tbankConnection.update({
@@ -372,8 +369,8 @@ describe('saved payout calendars and complete forecasts (SQLite HTTP)', () => {
}
expect(
((await events().expect(200)).body as UpcomingEventsResourceDto).data
?.incomeTotalsByCurrency,
).toEqual([{ currency: 'USD', value: '90.000000009' }]);
?.incomeTotal,
).toEqual({ currency: 'RUB', value: '0' });
});
it('serves instrument payouts from shared calendars with private exact holdings and retains successful source timestamps on failure', async () => {
@@ -539,9 +536,7 @@ describe('saved payout calendars and complete forecasts (SQLite HTTP)', () => {
await settled();
const result = (await events().expect(200))
.body as UpcomingEventsResourceDto;
expect(result.data?.incomeTotalsByCurrency).toEqual([
{ currency: 'RUB', value: '6' },
]);
expect(result.data?.incomeTotal).toEqual({ currency: 'RUB', value: '6' });
expect(moex.bondEvents).toHaveBeenCalledWith('BOND');
});
@@ -574,9 +569,7 @@ describe('saved payout calendars and complete forecasts (SQLite HTTP)', () => {
await settled();
const before = (await events().expect(200))
.body as UpcomingEventsResourceDto;
expect(before.data?.incomeTotalsByCurrency).toEqual([
{ currency: 'RUB', value: '6' },
]);
expect(before.data?.incomeTotal).toEqual({ currency: 'RUB', value: '6' });
const calendar = await prisma.tbankInstrumentSource.findFirstOrThrow({
where: { kind: 'bond-events' },
});
@@ -675,7 +668,7 @@ describe('saved payout calendars and complete forecasts (SQLite HTTP)', () => {
const w1 = windowOf(day1, '2099-03-04');
const dividend = (date: string) => ({
recordDate: time(date),
dividendNet: { units: '10', nano: 1, currency: 'usd' },
dividendNet: { units: '10', nano: 1, currency: 'rub' },
});
const defaultEvents = async () =>
(await get('brokerage-accounts/account/events').expect(200))
@@ -705,9 +698,7 @@ describe('saved payout calendars and complete forecasts (SQLite HTTP)', () => {
await refresh({});
const first = await defaultEvents();
expect(first.data?.window).toEqual(w0);
expect(first.data?.incomeTotalsByCurrency).toEqual([
{ currency: 'USD', value: '20.000000002' },
]);
expect(first.data?.incomeTotal).toEqual({ currency: 'RUB', value: '20.000000002' });
jest.setSystemTime(new Date(`${day1}T12:00:00Z`));
await portfolio([position(shareUid, '5')]);
@@ -720,9 +711,7 @@ describe('saved payout calendars and complete forecasts (SQLite HTTP)', () => {
'2099-02-05',
'2099-03-04',
]);
expect(second.data?.incomeTotalsByCurrency).toEqual([
{ currency: 'USD', value: '100.00000001' },
]);
expect(second.data?.incomeTotal).toEqual({ currency: 'RUB', value: '100.00000001' });
const stale = (await events('account', 'owner', w0).expect(200))
.body as UpcomingEventsResourceDto;
expect(stale.data?.calculatedAt).toBe(second.data?.calculatedAt);
@@ -736,7 +725,7 @@ describe('saved payout calendars and complete forecasts (SQLite HTTP)', () => {
expect(disjoint.data).toEqual({
...second.data,
events: [],
incomeTotalsByCurrency: [],
incomeTotal: { currency: 'RUB', value: '0' },
});
expect(await prisma.tbankForecast.count()).toBe(2);
});
@@ -779,7 +768,7 @@ describe('saved payout calendars and complete forecasts (SQLite HTTP)', () => {
const dividend = (units: string, dividendType = 'Regular') => ({
recordDate: time(date),
dividendType,
dividendNet: { units, currency: 'usd' },
dividendNet: { units, currency: 'rub' },
});
// One hourly walk: the scheduler stamps it once and passes it to every account.
const startWalk = async () => {
@@ -803,16 +792,12 @@ describe('saved payout calendars and complete forecasts (SQLite HTTP)', () => {
it('picks up a changed and then a cancelled payout on the next pass of the same window', async () => {
upstream.getDividends.mockResolvedValue([dividend('10')]);
await pass(await startWalk());
expect((await latest()).data?.incomeTotalsByCurrency).toEqual([
{ currency: 'USD', value: '20' },
]);
expect((await latest()).data?.incomeTotal).toEqual({ currency: 'RUB', value: '20' });
const first = await dividendRow();
upstream.getDividends.mockResolvedValue([dividend('30')]);
await pass(await startWalk());
expect((await latest()).data?.incomeTotalsByCurrency).toEqual([
{ currency: 'USD', value: '60' },
]);
expect((await latest()).data?.incomeTotal).toEqual({ currency: 'RUB', value: '60' });
expect((await dividendRow()).dataRevision).toBe(first.dataRevision + 1);
upstream.getDividends.mockResolvedValue([dividend('30', 'Cancelled')]);