Перейти к содержанию

Карта отображения баланса

Слово «Баланс» встречается на семи экранах, и за ним стоят разные величины. Документ фиксирует, какое поле читает каждый экран и какими равенствами эти величины связаны.

Повод для карты: 29.07.2026 портфель показал $3 500.48, а страница вывода в тот же момент $1 496.06, обе подписи гласили «Баланс». Причина была не в расчёте, а в том, что связь между этими числами нигде не была ни описана, ни проверена (SI-461).

Три величины

Все три считает canonical/balance_service.go, других источников быть не должно.

Величина Что это Где считается
totalValue Живой баланс на сейчас BalanceService.GetUserBalance
availableBalance Доступно к выводу Равно totalValue по бизнес-правилу от 2025-12-18 (изъятие и вывод объединены)
displayedBalance Снапшот на конец вчера, если он положителен, иначе живой итог canonical.PickDisplayedBalance

Снапшот нужен, чтобы заголовок не дрожал от внутридневных реконсиляций. Плата за это — расхождение с живым остатком в течение дня, и закрывать его обязана плашка «Сегодня».

Инварианты

  1. displayedBalance + todayNetChange == totalValue с точностью до доходности, начисленной за текущие сутки. Плашка «Сегодня» намеренно показывает только подтверждённые движения денег, без начислений. Проверяется: TestDashboardHeadlineReconcilesWithLiveBalance.
  2. availableBalance == totalValue. Проверяется там же.
  3. Заявка на вывод меняет баланс в день выплаты, а не в день подачи. Единственный источник правила — canonical.WithdrawalBalanceEffect. Проверяется: TestWithdrawalBalanceEffect, TestSplitTodayActivity_WithdrawalCountsOnTheDayMoneyLeft, TestAnalyticsWithdrawalPeriod_MatchesCanonicalRule.
  4. Контракт /api/user/dashboard существует в одном экземпляре: Go-структура types.UserDashboardResponse, из неё генерируется TypeScript. Рукописных копий быть не должно. Проверяется: frontend/user-app/src/__tests__/contract/api-contracts.test.ts.

Экраны

user-app

Экран Подпись Поле Комментарий
Портфель, карточка «Баланс» displayedBalance Плюс расшифровка «Сегодня» из todayUserDeposits / todayGiftDeposits / todayWithdrawals
Вывод средств «Баланс» availableBalance Живой остаток: выводить можно только то, что есть сейчас
Депозит баланс в шапке totalValue Живой остаток
Реестр операций «Итого за период → Баланс» displayedBalance + todayNetChange Тот же заголовок, что в портфеле
График баланса ось «Баланс» /api/user/balance/daily-export Ежедневная таблица; последняя точка — вчера

Портфель и страница вывода показывают разные числа в течение дня по устройству, а не по ошибке. Разницу объясняет плашка «Сегодня»; если она пуста, а числа расходятся — это баг.

admin-app

Экран Подпись Поле Комментарий
Карточка клиента «Баланс» financialSummary.displayedBalance Тот же снапшот, что видит клиент
Список клиентов колонка «Баланс» bal.Total (живой) Расходится с карточкой в течение дня

Последняя строка — известное расхождение, требующее продуктового решения: оператор видит в списке живой остаток, а в карточке того же клиента — снапшот на вчера. Оба числа корректны, но подписаны одинаково. Варианты: показывать в списке снапшот (оператор видит ровно то же, что клиент) либо в карточке живой остаток (оператор всегда видит фактические деньги). До решения расхождение остаётся, но теперь оно записано, а не спрятано.

Что ломалось раньше

  • Дашборд относил вывод к дню подачи заявки, а баланс — к дню выплаты. Ручная выплата идёт несколько суток (на PROD 6 из 7 завершённых выводов закрыты не в день заявки), поэтому плашка «Сегодня» пустовала, а заголовок показывал уже потраченные деньги.
  • Структура ответа дашборда объявлялась внутри функции-обработчика, генератор типов её не видел, и фронт держал три рукописные копии контракта с разными наборами полей.
  • Регресс-тест на согласованность сравнивал displayedBalance сам с собой и в комментарии описывал контракт, которого в коде не было.