diff --git a/apps/backend/src/modules/tbank/mappers/operation.mapper.ts b/apps/backend/src/modules/tbank/mappers/operation.mapper.ts index b7f3e0e..3d9fc4b 100644 --- a/apps/backend/src/modules/tbank/mappers/operation.mapper.ts +++ b/apps/backend/src/modules/tbank/mappers/operation.mapper.ts @@ -108,6 +108,7 @@ export function mapOperation(item: TBankOperationItem, accountId: string): Broke type, category: categorizeOperationType(type), description: item.description || item.name || null, + name: item.name ?? null, state: item.state ?? null, instrumentUid: item.instrumentUid ?? null, figi: item.figi ?? null, diff --git a/apps/backend/src/modules/tbank/mappers/portfolio.mapper.ts b/apps/backend/src/modules/tbank/mappers/portfolio.mapper.ts index cce8974..3b60b4b 100644 --- a/apps/backend/src/modules/tbank/mappers/portfolio.mapper.ts +++ b/apps/backend/src/modules/tbank/mappers/portfolio.mapper.ts @@ -3,6 +3,7 @@ import type { BrokerMoney, BrokerPortfolio, BrokerPosition, + BrokerPositionsPage, } from '../types/broker.types'; import type { TBankInstrument, @@ -22,42 +23,58 @@ function isBrokerMoney(value: BrokerMoney | null): value is BrokerMoney { return value !== null; } +export function mapBrokerPosition(input: { + position: { + figi?: string; + instrumentUid?: string; + positionUid?: string; + ticker?: string; + classCode?: string; + instrumentType?: string; + quantity?: { units?: string | number; nano?: number }; + blockedLots?: { units?: string | number; nano?: number }; + currentPrice?: { currency?: string; units?: string | number; nano?: number }; + averagePositionPrice?: { currency?: string; units?: string | number; nano?: number }; + expectedYield?: { units?: string | number; nano?: number }; + dailyYield?: { currency?: string; units?: string | number; nano?: number }; + }; + instruments: Map>; +}): BrokerPosition { + const quantity = mapQuotationToNumber(input.position.quantity); + const currentPrice = mapMoneyValue(input.position.currentPrice); + const currentValue = + currentPrice && quantity !== null + ? { + ...currentPrice, + units: String(Math.trunc(currentPrice.value * quantity)), + nano: 0, + value: Number((currentPrice.value * quantity).toFixed(9)), + } + : null; + const instrument = + (input.position.instrumentUid && input.instruments.get(input.position.instrumentUid)) || + (input.position.positionUid && input.instruments.get(input.position.positionUid)) || + undefined; + + return { + figi: input.position.figi ?? null, + instrumentUid: input.position.instrumentUid ?? null, + positionUid: input.position.positionUid ?? null, + ticker: input.position.ticker || instrument?.ticker || null, + classCode: input.position.classCode || instrument?.classCode || null, + instrumentType: input.position.instrumentType || instrument?.instrumentType || null, + name: instrument?.name ?? null, + quantity, + blockedLots: mapQuotationToNumber(input.position.blockedLots), + currentPrice, + currentValue, + averagePositionPrice: mapMoneyValue(input.position.averagePositionPrice), + expectedYieldPercent: mapQuotationToNumber(input.position.expectedYield), + dailyYield: mapMoneyValue(input.position.dailyYield), + }; +} + export function mapBrokerPortfolio(input: MapBrokerPortfolioInput): BrokerPortfolio { - const mappedPositions = (input.portfolio.positions ?? []).map((position) => { - const quantity = mapQuotationToNumber(position.quantity); - const currentPrice = mapMoneyValue(position.currentPrice); - const currentValue = - currentPrice && quantity !== null - ? { - ...currentPrice, - units: String(Math.trunc(currentPrice.value * quantity)), - nano: 0, - value: Number((currentPrice.value * quantity).toFixed(9)), - } - : null; - const instrument = - (position.instrumentUid && input.instruments.get(position.instrumentUid)) || - (position.positionUid && input.instruments.get(position.positionUid)) || - undefined; - - return { - figi: position.figi ?? null, - instrumentUid: position.instrumentUid ?? null, - positionUid: position.positionUid ?? null, - ticker: position.ticker || instrument?.ticker || null, - classCode: position.classCode || instrument?.classCode || null, - instrumentType: position.instrumentType || instrument?.instrumentType || null, - name: instrument?.name ?? null, - quantity, - blockedLots: mapQuotationToNumber(position.blockedLots), - currentPrice, - currentValue, - averagePositionPrice: mapMoneyValue(position.averagePositionPrice), - expectedYieldPercent: mapQuotationToNumber(position.expectedYield), - dailyYield: mapMoneyValue(position.dailyYield), - }; - }); - return { account: input.account, totals: { @@ -78,7 +95,36 @@ export function mapBrokerPortfolio(input: MapBrokerPortfolioInput): BrokerPortfo }, cash: (input.positions.money ?? []).map(mapMoneyValue).filter(isBrokerMoney), blockedCash: (input.positions.blocked ?? []).map(mapMoneyValue).filter(isBrokerMoney), - positions: mappedPositions, + asOf: new Date().toISOString(), + }; +} + +export function mapBrokerPositionsPage(input: { + accountId: string; + portfolio: TBankPortfolioResponse; + instruments: Map>; + cursor?: string; + limit: number; +}): BrokerPositionsPage { + const allPositions = (input.portfolio.positions ?? []).map((position) => + mapBrokerPosition({ position, instruments: input.instruments }), + ); + + let startIndex = 0; + if (input.cursor) { + const found = allPositions.findIndex((p) => p.positionUid === input.cursor); + startIndex = found >= 0 ? found + 1 : allPositions.length; + } + + const pageItems = allPositions.slice(startIndex, startIndex + input.limit); + const hasNext = startIndex + input.limit < allPositions.length; + const nextCursor = hasNext ? (pageItems[pageItems.length - 1]?.positionUid ?? null) : null; + + return { + accountId: input.accountId, + items: pageItems, + nextCursor, + hasNext, asOf: new Date().toISOString(), }; }