import type { Performance } from "@/entities/performance"; import { formatDate, formatMoney, formatPercentagePoints, formatSignedPercent } from "@/shared/lib"; type TextValue = { tone: Tone; value: string }; type Tone = | "negative" | "neutral" | "positive"; const reasonLabels: Record = { ACCOUNT_VALUATION_DATES_DIFFER: "Оценки счетов сделаны в разные дни", AMBIGUOUS_RETURN: "У расчёта доходности несколько возможных решений", HISTORY_START_UNKNOWN: "Начало истории счёта неизвестно", INDEX_HISTORY_UNAVAILABLE: "История индекса временно недоступна", NO_SOLUTION: "Расчёт доходности не сходится", OPERATIONS_HISTORY_INCOMPLETE: "История операций ещё не получена полностью", PORTFOLIO_VALUATION_INCOMPLETE: "Нет полной оценки портфеля", UNCONFIRMED_TRANSFER: "Перевод между счетами не удалось подтвердить", UNVALUED_SECURITIES_TRANSFER: "Бумаги переведены без денежной оценки", }; const unknownReason = "Причину недоступности не удалось определить"; function performanceVm(data: Performance) { return { basis: data.return.basis, comparison: data.outperformance ? { money: signedMoney(data.outperformance.money), points: valueText( data.outperformance.percentagePoints, formatPercentagePoints, ), } : null, current: formatMoney(data.currentValue), currentPoint: data.series.length > 0 ? { date: formatDate(`${data.series.at(-1)?.date ?? data.period.to}T00:00:00Z`) } : null, days: data.period.days, excluded: data.excludedAccounts.map(item => ({ id: item.accountId, name: item.name, reason: reasonText(item.reason), })), from: data.period.from, indexReason: data.benchmark.unavailableReason ? reasonText(data.benchmark.unavailableReason) : null, indexReturn: valueText(data.benchmark.return.value, formatSignedPercent), indexValue: data.benchmark.value ? formatMoney(data.benchmark.value) : "—", invested: formatMoney(data.netInvested), profit: signedMoney(data.profit), returnReason: data.return.unavailableReason ? reasonText(data.return.unavailableReason) : null, returnValue: valueText(data.return.value, formatSignedPercent), series: data.series.map(item => ({ benchmarkValue: Number(item.benchmarkValue), date: item.date, label: formatDate(`${item.date}T00:00:00Z`), netInvested: Number(item.netInvested), referenceValue: item.referenceValue === null ? null : Number(item.referenceValue), })), to: data.period.to, }; } function reasonText(reason: string): string { // eslint-disable-next-line security/detect-object-injection -- Lookup accepts only an API reason, with a safe fallback. return reasonLabels[reason] ?? unknownReason; } function signedMoney(value: Performance["profit"]): TextValue { return { tone: tone(value.value), value: formatMoney(value, { withSign: true }), }; } function tone(value: string): Tone { const number = Number(value); if (number > 0) return "positive"; return number < 0 ? "negative" : "neutral"; } // Tone follows the displayed text, so "0,0 %" is never coloured. function valueText( value: null | string, format: (value: string) => string, ): TextValue { if (value === null) return { tone: "neutral", value: "—" }; const text = format(value); if (text.startsWith("+")) return { tone: "positive", value: text }; return { tone: text.startsWith("−") ? "negative" : "neutral", value: text }; } export { performanceVm, reasonText };