refactor!: support RUB only (WealthLedger/main#55) (#204)
Backend verify / commits (push) Skipped
Backend verify / security (push) Successful in 1m32s
Backend verify / verify (push) Failing after 9m25s

BREAKING CHANGE: per-currency API fields replaced with single RUB Money.
This commit was merged in pull request #204.
This commit is contained in:
2026-11-11 10:07:11 +03:00
parent 85d65f8997
commit 809936e62d
61 changed files with 617 additions and 735 deletions
@@ -18,11 +18,11 @@ import {
import {
IncomeAnalyticsEntryDto,
IncomeAnalyticsResourceDto,
IncomeCurrencySummaryDto,
IncomeDependencyDto,
IncomeInstrumentDto,
IncomeSummaryDto,
IncomeSummaryResourceDto,
IncomeTotalDto,
} from './income-analytics.dto';
import {
IncomeBucketGranularity,
@@ -105,7 +105,7 @@ export class IncomeAnalyticsService {
assert(current.meta.lastSuccessfulAt);
const selected = qualifying(current.items).map((entry) => ({
...entry,
instrumentKey: `${instrumentKey(entry)}:${entry.payment?.currency ?? ''}`,
instrumentKey: instrumentKey(entry),
}));
return {
data: {
@@ -139,23 +139,19 @@ function summaryResponse(
entries: BrokerageOperationDto[],
calculatedAt: string,
): IncomeSummaryDto {
return { accountId, period, totalsByCurrency: totals(entries), calculatedAt };
return { accountId, period, total: totals(entries), calculatedAt };
}
function totals(entries: BrokerageOperationDto[]): IncomeCurrencySummaryDto[] {
const values = new Map<string, Total>();
function totals(entries: BrokerageOperationDto[]): IncomeTotalDto {
const total = zeroTotal();
for (const entry of entries) {
if (!entry.payment) continue;
const total = values.get(entry.payment.currency) ?? zeroTotal();
const value = decimalFromString(entry.payment.value);
if (isTbankProtocolValue(entry.type, TbankOperationType.Coupon)) {
total.coupon = add(total.coupon, value);
} else total.dividend = add(total.dividend, value);
values.set(entry.payment.currency, total);
}
return [...values]
.sort(([left], [right]) => left.localeCompare(right))
.map(([currency, total]) => presentTotal(currency, total));
return presentTotal(total);
}
function buckets(
@@ -169,7 +165,7 @@ function buckets(
}));
return rangeBuckets(range, granularity).map((period) => ({
period,
totalsByCurrency: totals(
total: totals(
datedEntries
.filter(({ date }) => date >= period.from && date <= period.to)
.map(({ entry }) => entry),
@@ -189,7 +185,7 @@ function instruments(
return [...groups]
.map(([key, group]) => {
const first = group[0];
const [total] = totals(group);
const total = totals(group);
return {
key,
figi: first.figi,
@@ -225,15 +221,10 @@ function zeroTotal(): Total {
return { coupon: { value: 0n, scale: 0 }, dividend: { value: 0n, scale: 0 } };
}
function presentTotal(
currency: string,
value: Total,
): IncomeCurrencySummaryDto {
const total = add(value.coupon, value.dividend);
function presentTotal(value: Total): IncomeTotalDto {
return {
currency,
coupon: { currency, value: formatDecimal(value.coupon) },
dividend: { currency, value: formatDecimal(value.dividend) },
total: { currency, value: formatDecimal(total) },
coupon: { currency: 'RUB', value: formatDecimal(value.coupon) },
dividend: { currency: 'RUB', value: formatDecimal(value.dividend) },
total: { currency: 'RUB', value: formatDecimal(add(value.coupon, value.dividend)) },
};
}