import { Injectable, ServiceUnavailableException } from '@nestjs/common'; import { isZero } from '../../../common/decimal'; import { TbankAccessService, TbankClient, } from '../../../integrations/tbank/tbank.api'; import { decimal } from '../../../integrations/tbank/decimal'; import { totalsByCurrency } from '../portfolio/portfolio.api'; import { BrokerageAccountDto } from './brokerage-account.dto'; @Injectable() export class BrokerageAccountsService { constructor( private readonly access: TbankAccessService, private readonly tbank: TbankClient, ) {} async list(userId: string): Promise { return this.access.withSupportedAccounts( userId, async ({ token, accounts }) => Promise.all( accounts.map(async (account) => { const [portfolioResult, positionsResult] = await Promise.allSettled( [ this.tbank.getPortfolio(token, account.id), this.tbank.getPositions(token, account.id), ], ); if ( portfolioResult.status === 'rejected' && !isTbankUnavailable(portfolioResult.reason) ) throw portfolioResult.reason; if ( positionsResult.status === 'rejected' && !isTbankUnavailable(positionsResult.reason) ) throw positionsResult.reason; if ( portfolioResult.status === 'rejected' || positionsResult.status === 'rejected' ) return { ...account, portfolioSummary: null }; const positions = portfolioResult.value.positions ?? []; let stockPositionCount = 0; let bondPositionCount = 0; for (const position of positions) { if (isZero(decimal(position.quantity ?? {}))) continue; const type = position.instrumentType?.toLowerCase(); if (type === 'share') stockPositionCount += 1; if (type === 'bond') bondPositionCount += 1; } return { ...account, portfolioSummary: { totalsByCurrency: totalsByCurrency( positions, positionsResult.value.money ?? [], ), stockPositionCount, bondPositionCount, }, }; }), ), ); } } function isTbankUnavailable(error: unknown): boolean { if (!(error instanceof ServiceUnavailableException)) return false; const response = error.getResponse(); return ( typeof response === 'object' && response !== null && 'code' in response && response.code === 'TBANK_UNAVAILABLE' ); }