import { Injectable } from '@nestjs/common'; import { CacheService } from '../../cache/cache.service'; import { ApiEnvelopePayload } from '../../../common/dto/api-response.dto'; import type { BrokerOperationQueryDto } from '../dto/broker-operation-query.dto'; import { mapOperationsPage } from '../mappers/operation.mapper'; import { TBANK_CACHE_KEYS } from '../tbank.config'; import { EntityNotFoundException } from '../../../common/exceptions/entity-not-found.exception'; import type { BrokerOperationsPage } from '../types/broker.types'; import type { TBankOperationsByCursorResponse } from '../types/tbank-proto.types'; import { BrokerAccountsService } from './broker-accounts.service'; import { TBankClientService } from './tbank-client.service'; @Injectable() export class BrokerOperationsService { constructor( private readonly accountsService: BrokerAccountsService, private readonly tbankClient: TBankClientService, private readonly cacheService: CacheService, ) {} async getOperations( accountId: string, query: BrokerOperationQueryDto, ): Promise> { const account = await this.accountsService.findById(accountId); if (!account) throw new EntityNotFoundException('BrokerAccount', accountId); const request = this.buildRequest(accountId, query); const result = await this.cacheService.getOrFetch( TBANK_CACHE_KEYS.operations, [accountId, JSON.stringify(request)], () => this.fetchOperations(accountId, request), 'tbankOperationsTtl', ); return new ApiEnvelopePayload(result.data, result.fromCache, result.cachedAt); } private buildRequest(accountId: string, query: BrokerOperationQueryDto): Record { const now = new Date(); const startOfYear = new Date(Date.UTC(now.getUTCFullYear(), 0, 1)); const operationTypes = query.operationTypes ? query.operationTypes .split(',') .map((value) => value.trim()) .filter(Boolean) : undefined; return { accountId, instrumentId: query.instrumentId, from: { seconds: Math.floor(new Date(query.from ?? startOfYear.toISOString()).getTime() / 1000), }, to: { seconds: Math.floor(new Date(query.to ?? now.toISOString()).getTime() / 1000), }, cursor: query.cursor, limit: query.limit ?? 100, operationTypes, state: query.state ?? 'OPERATION_STATE_EXECUTED', withoutCommissions: false, withoutTrades: false, withoutOvernights: false, }; } private async fetchOperations( accountId: string, request: Record, ): Promise { const operationsClient = this.tbankClient.getServiceClient('OperationsService') as any; const response = await this.tbankClient.callUnary< Record, TBankOperationsByCursorResponse >( 'OperationsService/GetOperationsByCursor', operationsClient.getOperationsByCursor.bind(operationsClient), request, ); return mapOperationsPage(accountId, response); } }