From 1be6db65d70f6a96629988289500098b2728b2eb Mon Sep 17 00:00:00 2001 From: codex-bot Date: Fri, 2 Oct 2026 08:12:02 +0300 Subject: [PATCH] refactor: simplify investment data flow --- .../upcoming-events/upcoming-event.mapper.ts | 68 +++++++++++++++++++ .../upcoming-events.service.spec.ts | 2 +- .../upcoming-events.service.ts | 66 +----------------- 3 files changed, 71 insertions(+), 65 deletions(-) create mode 100644 src/modules/investments/upcoming-events/upcoming-event.mapper.ts diff --git a/src/modules/investments/upcoming-events/upcoming-event.mapper.ts b/src/modules/investments/upcoming-events/upcoming-event.mapper.ts new file mode 100644 index 0000000..5226bbe --- /dev/null +++ b/src/modules/investments/upcoming-events/upcoming-event.mapper.ts @@ -0,0 +1,68 @@ +import { + decimalFromString, + formatDecimal, + multiply, +} from '../../../common/decimal'; +import { ISO_DATE_LENGTH, ISO_DATE_START, MILLISECONDS_PER_SECOND } from '../../../common/time'; +import type { MoexBondEvent } from '../../../integrations/moex/moex.api'; +import { decimal } from '../../../integrations/tbank/decimal'; +import type { TbankDividend } from '../../../integrations/tbank/tbank.api'; +import { UpcomingEventDto } from './upcoming-events.dto'; + +export function dividendEvent( + metadata: Pick< + UpcomingEventDto, + 'displayName' | 'figi' | 'instrumentUid' | 'isin' | 'logoName' | 'ticker' + >, + quantity: ReturnType, + event: TbankDividend, +): UpcomingEventDto { + return { + ...metadata, + type: 'dividend', + date: new Date(Number(event.recordDate?.seconds) * MILLISECONDS_PER_SECOND) + .toISOString() + .slice(ISO_DATE_START, ISO_DATE_LENGTH), + dateKind: 'registry', + projectedAmount: event.dividendNet + ? projectedMoney( + quantity, + formatDecimal(decimal(event.dividendNet)), + event.dividendNet.currency, + ) + : null, + }; +} + +export function bondEvent( + metadata: Pick< + UpcomingEventDto, + 'displayName' | 'figi' | 'instrumentUid' | 'isin' | 'logoName' | 'ticker' + >, + quantity: ReturnType, + event: MoexBondEvent, +): UpcomingEventDto { + return { + ...metadata, + type: event.type, + date: event.date, + dateKind: 'event', + projectedAmount: + 'value' in event + ? projectedMoney(quantity, event.value, event.currency) + : null, + }; +} + +function projectedMoney( + quantity: ReturnType, + value: string, + currency: string | undefined, +): UpcomingEventDto['projectedAmount'] { + const normalizedCurrency = currency?.trim().toUpperCase(); + if (!normalizedCurrency) return null; + return { + currency: normalizedCurrency, + value: formatDecimal(multiply(quantity, decimalFromString(value))), + }; +} diff --git a/src/modules/investments/upcoming-events/upcoming-events.service.spec.ts b/src/modules/investments/upcoming-events/upcoming-events.service.spec.ts index 207e233..f26c890 100644 --- a/src/modules/investments/upcoming-events/upcoming-events.service.spec.ts +++ b/src/modules/investments/upcoming-events/upcoming-events.service.spec.ts @@ -1,5 +1,5 @@ import { decimalFromString } from '../../../common/decimal'; -import { bondEvent, dividendEvent } from './upcoming-events.service'; +import { bondEvent, dividendEvent } from './upcoming-event.mapper'; jest.mock('../../../prisma/prisma.service', () => ({ PrismaService: class {}, })); diff --git a/src/modules/investments/upcoming-events/upcoming-events.service.ts b/src/modules/investments/upcoming-events/upcoming-events.service.ts index 1f61e3f..f03fb38 100644 --- a/src/modules/investments/upcoming-events/upcoming-events.service.ts +++ b/src/modules/investments/upcoming-events/upcoming-events.service.ts @@ -1,22 +1,17 @@ import { BadRequestException, Injectable } from '@nestjs/common'; -import type { MoexBondEvent } from '../../../integrations/moex/moex.api'; - import { add, decimalFromString, formatDecimal, isZero, - multiply, } from '../../../common/decimal'; import { MoneyDto } from '../../../common/money.dto'; -import { ISO_DATE_LENGTH, ISO_DATE_START, MILLISECONDS_PER_SECOND } from '../../../common/time'; -import { decimal } from '../../../integrations/tbank/decimal'; +import { ISO_DATE_LENGTH, ISO_DATE_START } from '../../../common/time'; import { InstrumentMetadataService, metadataKey, TbankAccessService, - type TbankDividend, } from '../../../integrations/tbank/tbank.api'; import { CatalogEnrichmentService } from '../instruments/instruments.api'; import { investmentResourceMeta } from '../investment-resource.mapper'; @@ -30,6 +25,7 @@ import { UpcomingEventsDto, UpcomingEventsQueryDto, } from './upcoming-events.dto'; +import { bondEvent, dividendEvent } from './upcoming-event.mapper'; enum EventsErrorCode { InvalidPeriod = 'INVALID_EVENTS_PERIOD' } @@ -211,51 +207,6 @@ export class UpcomingEventsService { } } -export function dividendEvent( - metadata: Pick< - UpcomingEventDto, - 'displayName' | 'figi' | 'instrumentUid' | 'isin' | 'logoName' | 'ticker' - >, - quantity: ReturnType, - event: TbankDividend, -): UpcomingEventDto { - return { - ...metadata, - type: 'dividend', - date: new Date(Number(event.recordDate?.seconds) * MILLISECONDS_PER_SECOND) - .toISOString() - .slice(ISO_DATE_START, ISO_DATE_LENGTH), - dateKind: 'registry', - projectedAmount: event.dividendNet - ? projectedMoney( - quantity, - formatDecimal(decimal(event.dividendNet)), - event.dividendNet.currency, - ) - : null, - }; -} - -export function bondEvent( - metadata: Pick< - UpcomingEventDto, - 'displayName' | 'figi' | 'instrumentUid' | 'isin' | 'logoName' | 'ticker' - >, - quantity: ReturnType, - event: MoexBondEvent, -): UpcomingEventDto { - return { - ...metadata, - type: event.type, - date: event.date, - dateKind: 'event', - projectedAmount: - 'value' in event - ? projectedMoney(quantity, event.value, event.currency) - : null, - }; -} - function supported(position: Position): boolean { return ( !isZero(decimalFromString(position.quantity)) && @@ -274,19 +225,6 @@ function dateWindow(query: UpcomingEventsQueryDto) { return { from, to }; } -function projectedMoney( - quantity: ReturnType, - value: string, - currency: string | undefined, -): MoneyDto | null { - const normalizedCurrency = currency?.trim().toUpperCase(); - if (!normalizedCurrency) return null; - return { - currency: normalizedCurrency, - value: formatDecimal(multiply(quantity, decimalFromString(value))), - }; -} - function incomeTotals(events: UpcomingEventDto[]): MoneyDto[] { const totals = new Map>(); for (const event of events) {