chore: strengthen backend eslint profile
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import type { Request } from 'express';
|
||||
|
||||
import { Controller, Get, Param, Query, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||
import type { Request } from 'express';
|
||||
|
||||
import { ApiProblemResponses } from '../../../common/decorators/api-safe-error-response.decorator';
|
||||
import { AccessPayload, JwtAuthGuard } from '../../auth/auth.api';
|
||||
import {
|
||||
UpcomingEventsResourceDto,
|
||||
UpcomingEventsQueryDto,
|
||||
UpcomingEventsResourceDto,
|
||||
} from './upcoming-events.dto';
|
||||
import { UpcomingEventsService } from './upcoming-events.service';
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsDateString, IsOptional, Matches } from 'class-validator';
|
||||
|
||||
import { MoneyDto } from '../../../common/money.dto';
|
||||
import { AccountReferenceDto } from '../account-reference.dto';
|
||||
import {
|
||||
InvestmentReadQueryDto,
|
||||
InvestmentResourceMetaDto,
|
||||
} from '../investment-resource.dto';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { IsDateString, IsOptional, Matches } from 'class-validator';
|
||||
import { MoneyDto } from '../../../common/money.dto';
|
||||
import { AccountReferenceDto } from '../account-reference.dto';
|
||||
|
||||
export class UpcomingEventsQueryDto extends InvestmentReadQueryDto {
|
||||
@ApiPropertyOptional({
|
||||
@@ -43,10 +44,10 @@ export class UpcomingEventDto {
|
||||
declare dateKind: 'event' | 'registry';
|
||||
|
||||
@ApiProperty({ type: String, nullable: true })
|
||||
declare figi: string | null;
|
||||
declare figi: null | string;
|
||||
|
||||
@ApiProperty({ type: String, nullable: true })
|
||||
declare instrumentUid: string | null;
|
||||
declare instrumentUid: null | string;
|
||||
|
||||
@ApiProperty()
|
||||
declare ticker: string;
|
||||
@@ -55,10 +56,10 @@ export class UpcomingEventDto {
|
||||
declare displayName: string;
|
||||
|
||||
@ApiProperty({ type: String, nullable: true })
|
||||
declare isin: string | null;
|
||||
declare isin: null | string;
|
||||
|
||||
@ApiProperty({ type: String, nullable: true })
|
||||
declare logoName: string | null;
|
||||
declare logoName: null | string;
|
||||
|
||||
@ApiProperty({ type: MoneyDto, nullable: true })
|
||||
declare projectedAmount: MoneyDto | null;
|
||||
@@ -80,7 +81,8 @@ export class UpcomingEventsDto {
|
||||
|
||||
export class UpcomingEventsResourceDto {
|
||||
@ApiProperty({ type: UpcomingEventsDto, nullable: true })
|
||||
declare data: UpcomingEventsDto | null;
|
||||
declare data: null | UpcomingEventsDto;
|
||||
|
||||
@ApiProperty({ type: InvestmentResourceMetaDto })
|
||||
declare meta: InvestmentResourceMetaDto;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { InstrumentSourcesModule } from '../instruments/instrument-sources.module';
|
||||
import { PortfolioModule } from '../portfolio/portfolio.module';
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { TbankModule } from '../../../integrations/tbank/tbank.module';
|
||||
import { AuthModule } from '../../auth/auth.module';
|
||||
import { InstrumentSourcesModule } from '../instruments/instrument-sources.module';
|
||||
import { PortfolioModule } from '../portfolio/portfolio.module';
|
||||
import { UpcomingEventsController } from './upcoming-events.controller';
|
||||
import { UpcomingEventsService } from './upcoming-events.service';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { dividendEvent, bondEvent } from './upcoming-events.service';
|
||||
import { decimalFromString } from '../../../common/decimal';
|
||||
import { bondEvent, dividendEvent } from './upcoming-events.service';
|
||||
jest.mock('../../../prisma/prisma.service', () => ({
|
||||
PrismaService: class {},
|
||||
}));
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { MILLISECONDS_PER_SECOND } from '../../../common/time';
|
||||
|
||||
import type { MoexBondEvent } from '../../../integrations/moex/moex.api';
|
||||
|
||||
import {
|
||||
add,
|
||||
decimalFromString,
|
||||
@@ -8,21 +10,21 @@ import {
|
||||
multiply,
|
||||
} from '../../../common/decimal';
|
||||
import { MoneyDto } from '../../../common/money.dto';
|
||||
import type { MoexBondEvent } from '../../../integrations/moex/moex.api';
|
||||
import { MILLISECONDS_PER_SECOND } from '../../../common/time';
|
||||
import { decimal } from '../../../integrations/tbank/decimal';
|
||||
import {
|
||||
TbankAccessService,
|
||||
InstrumentMetadataService,
|
||||
metadataKey,
|
||||
TbankAccessService,
|
||||
type TbankDividend,
|
||||
} from '../../../integrations/tbank/tbank.api';
|
||||
import {
|
||||
PortfolioService,
|
||||
ForecastService,
|
||||
type PortfolioPositionDto,
|
||||
} from '../portfolio/portfolio.api';
|
||||
import { CatalogEnrichmentService } from '../instruments/instruments.api';
|
||||
import { investmentResourceMeta } from '../investment-resource.mapper';
|
||||
import {
|
||||
ForecastService,
|
||||
type PortfolioPositionDto,
|
||||
PortfolioService,
|
||||
} from '../portfolio/portfolio.api';
|
||||
import {
|
||||
UpcomingEventDto,
|
||||
UpcomingEventsDto,
|
||||
@@ -74,7 +76,7 @@ export class UpcomingEventsService {
|
||||
metadata.get(metadataKey(position))?.data?.ticker ||
|
||||
'',
|
||||
}))
|
||||
.filter((entry): entry is { uid: string; ticker: string } =>
|
||||
.filter((entry): entry is { ticker: string; uid: string } =>
|
||||
Boolean(entry.uid),
|
||||
);
|
||||
const uids = [...new Set(identities.map((entry) => entry.uid))];
|
||||
@@ -82,7 +84,7 @@ export class UpcomingEventsService {
|
||||
let offset = 0;
|
||||
offset < uids.length;
|
||||
offset += calendarReadBatchSize
|
||||
)
|
||||
) {
|
||||
sources.push(
|
||||
...(await this.sources.readMany(
|
||||
userId,
|
||||
@@ -98,6 +100,7 @@ export class UpcomingEventsService {
|
||||
},
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
for (const position of positions) {
|
||||
const generic = metadata.get(metadataKey(position))?.data;
|
||||
@@ -126,7 +129,7 @@ export class UpcomingEventsService {
|
||||
(source) =>
|
||||
source.input.uid === uid &&
|
||||
source.input.kind ===
|
||||
(type === 'share' ? 'dividends' : 'bond-events'),
|
||||
(type === 'share' ? 'dividends' : 'bond-events'),
|
||||
);
|
||||
const instrument =
|
||||
sources.find(
|
||||
@@ -148,15 +151,15 @@ export class UpcomingEventsService {
|
||||
};
|
||||
const quantity = decimalFromString(position.quantity);
|
||||
const projected =
|
||||
type === 'share'
|
||||
? (calendar.payload?.dividends ?? [])
|
||||
type === 'share' ?
|
||||
(calendar.payload?.dividends ?? [])
|
||||
.filter(
|
||||
(event) =>
|
||||
event.dividendType?.toLowerCase() !== 'cancelled' &&
|
||||
Boolean(event.recordDate),
|
||||
)
|
||||
.map((event) => dividendEvent(identity, quantity, event))
|
||||
: (calendar.payload?.bondEvents ?? []).map((event) =>
|
||||
.map((event) => dividendEvent(identity, quantity, event)) :
|
||||
(calendar.payload?.bondEvents ?? []).map((event) =>
|
||||
bondEvent(identity, quantity, event),
|
||||
);
|
||||
events.push(
|
||||
@@ -166,9 +169,9 @@ export class UpcomingEventsService {
|
||||
)
|
||||
.map((event) => ({
|
||||
...event,
|
||||
...(accountId === 'all' && position.account
|
||||
? { account: position.account }
|
||||
: {}),
|
||||
...(accountId === 'all' && position.account ?
|
||||
{ account: position.account } :
|
||||
{}),
|
||||
})),
|
||||
);
|
||||
}
|
||||
@@ -179,14 +182,14 @@ export class UpcomingEventsService {
|
||||
a.type.localeCompare(b.type) ||
|
||||
a.ticker.localeCompare(b.ticker),
|
||||
);
|
||||
const candidate: UpcomingEventsDto | undefined = ready
|
||||
? {
|
||||
const candidate: undefined | UpcomingEventsDto = ready ?
|
||||
{
|
||||
accountId,
|
||||
calculatedAt: new Date().toISOString(),
|
||||
events,
|
||||
incomeTotalsByCurrency: incomeTotals(events),
|
||||
}
|
||||
: undefined;
|
||||
} :
|
||||
undefined;
|
||||
return this.forecasts.result({
|
||||
userId,
|
||||
revision: connection.revision,
|
||||
@@ -197,6 +200,7 @@ export class UpcomingEventsService {
|
||||
candidate,
|
||||
});
|
||||
}
|
||||
|
||||
getAll(userId: string, query: UpcomingEventsQueryDto = {}) {
|
||||
return this.get(userId, 'all', query);
|
||||
}
|
||||
@@ -205,7 +209,7 @@ export class UpcomingEventsService {
|
||||
export function dividendEvent(
|
||||
metadata: Pick<
|
||||
UpcomingEventDto,
|
||||
'figi' | 'instrumentUid' | 'ticker' | 'displayName' | 'isin' | 'logoName'
|
||||
'displayName' | 'figi' | 'instrumentUid' | 'isin' | 'logoName' | 'ticker'
|
||||
>,
|
||||
quantity: ReturnType<typeof decimal>,
|
||||
event: TbankDividend,
|
||||
@@ -217,20 +221,20 @@ export function dividendEvent(
|
||||
.toISOString()
|
||||
.slice(0, 10),
|
||||
dateKind: 'registry',
|
||||
projectedAmount: event.dividendNet
|
||||
? projectedMoney(
|
||||
projectedAmount: event.dividendNet ?
|
||||
projectedMoney(
|
||||
quantity,
|
||||
formatDecimal(decimal(event.dividendNet)),
|
||||
event.dividendNet.currency,
|
||||
)
|
||||
: null,
|
||||
) :
|
||||
null,
|
||||
};
|
||||
}
|
||||
|
||||
export function bondEvent(
|
||||
metadata: Pick<
|
||||
UpcomingEventDto,
|
||||
'figi' | 'instrumentUid' | 'ticker' | 'displayName' | 'isin' | 'logoName'
|
||||
'displayName' | 'figi' | 'instrumentUid' | 'isin' | 'logoName' | 'ticker'
|
||||
>,
|
||||
quantity: ReturnType<typeof decimal>,
|
||||
event: MoexBondEvent,
|
||||
@@ -241,16 +245,16 @@ export function bondEvent(
|
||||
date: event.date,
|
||||
dateKind: 'event',
|
||||
projectedAmount:
|
||||
'value' in event
|
||||
? projectedMoney(quantity, event.value, event.currency)
|
||||
: null,
|
||||
'value' in event ?
|
||||
projectedMoney(quantity, event.value, event.currency) :
|
||||
null,
|
||||
};
|
||||
}
|
||||
|
||||
function supported(position: Position): boolean {
|
||||
return (
|
||||
!isZero(decimalFromString(position.quantity)) &&
|
||||
['STOCK', 'BOND'].includes(position.type)
|
||||
['BOND', 'STOCK'].includes(position.type)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -259,8 +263,9 @@ function dateWindow(query: UpcomingEventsQueryDto) {
|
||||
const end = new Date(`${from}T00:00:00.000Z`);
|
||||
end.setUTCDate(end.getUTCDate() + DEFAULT_EVENT_WINDOW_DAYS);
|
||||
const to = query.to ?? end.toISOString().slice(0, 10);
|
||||
if (from > to)
|
||||
if (from > to) {
|
||||
throw new BadRequestException({ code: 'INVALID_EVENTS_PERIOD' });
|
||||
}
|
||||
return { from, to };
|
||||
}
|
||||
|
||||
@@ -280,8 +285,9 @@ function projectedMoney(
|
||||
function incomeTotals(events: UpcomingEventDto[]): MoneyDto[] {
|
||||
const totals = new Map<string, ReturnType<typeof decimal>>();
|
||||
for (const event of events) {
|
||||
if (!['coupon', 'dividend'].includes(event.type) || !event.projectedAmount)
|
||||
if (!['coupon', 'dividend'].includes(event.type) || !event.projectedAmount) {
|
||||
continue;
|
||||
}
|
||||
const current = totals.get(event.projectedAmount.currency) ?? {
|
||||
value: 0n,
|
||||
scale: 0,
|
||||
|
||||
Reference in New Issue
Block a user