feat(tbank): extract mapBrokerPosition, add mapBrokerPositionsPage, add name to operation

This commit is contained in:
Sergey Krylov 2026-06-17 14:34:02 +03:00
parent 953c7c29a6
commit 05f1e792c5
2 changed files with 83 additions and 36 deletions

View File

@ -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,

View File

@ -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<string, Partial<TBankInstrument>>;
}): 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<BrokerPosition>((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<string, Partial<TBankInstrument>>;
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(),
};
}