refactor: simplify investment data flow #39

Merged
codex-bot merged 1 commits from codex/code-quality-refactor into develop 2026-10-02 08:12:03 +03:00
3 changed files with 71 additions and 65 deletions
Showing only changes of commit 162add08d8 - Show all commits
@@ -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<typeof decimal>,
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<typeof decimal>,
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<typeof decimal>,
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))),
};
}
@@ -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 {},
}));
@@ -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<typeof decimal>,
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<typeof decimal>,
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<typeof decimal>,
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<string, ReturnType<typeof decimal>>();
for (const event of events) {