- Backend: actual events from T-Bank operations, types filter, split forecast/actual summary - Frontend: draft/applied filters with multi-select types, status column (Факт/Прогноз), green actual amounts - Docs: update spec, plan, tasks
13 KiB
Broker Events And Payouts Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Добавить в брокерский счёт T-Bank раздел предстоящих и прошедших событий с прогнозом будущих выплат, фактическими прошедшими поступлениями и управляемыми фильтрами.
Architecture: Backend предоставляет единый read-only endpoint событий поверх существующих данных
T-Bank и MOEX. Слой агрегации объединяет прогнозные события по текущим позициям и фактические
прошедшие выплаты из операций счёта, затем строит раздельный summary факта и прогноза. Frontend
держит фильтры в черновике, применяет их только по кнопке Показать и визуально разделяет
Факт/Прогноз.
Tech Stack: NestJS 10, Prisma, CacheService, MOEX client, T-Bank module, React 18, React Router 6, TanStack Query 5, TypeScript, Vitest, Testing Library.
Связанные SDD-артефакты
- Epic:
docs/epics/BrokerPortfolio.md - Spec:
docs/features/broker-events-and-payouts/spec.md - Tasks:
docs/features/broker-events-and-payouts/tasks.md
Gate перед реализацией
До начала кода пользователь должен отдельно подтвердить, что:
- текущая spec остаётся источником истины;
plan.mdиtasks.mdсогласованы;- первая версия действительно ограничивается T-Bank-сценарием и оценкой по текущим позициям;
- follow-up по entitlement, налогам и полной купонной сетке остаются вне реализации.
Архитектурные решения
Backend endpoint
Новый endpoint:
GET /api/v1/broker/accounts/:accountId/events?from=YYYY-MM-DD&to=YYYY-MM-DD&types=dividend,coupon,maturity,offer
Endpoint возвращает:
items— плоский список событий по текущим позициям счёта;summary— агрегаты по фактическим и прогнозным денежным событиям за период;asOf— момент построения read model.
Backend read model
Новый сервис BrokerEventsService живёт внутри TBankModule и использует:
BrokerAccountsServiceдля проверки счёта;BrokerPortfolioServiceили внутренний shared-path для получения текущих позиций;BrokerInstrumentsServiceдля сопоставления T-Bank instrument metadata;MoexClientServiceдля дивидендов и bond enrichment;BrokerOperationsServiceдля получения фактических прошедших выплат;CacheServiceдля кэширования результата поaccountId + from + to + types.
Сервис не записывает события в Prisma и не вводит отдельные таблицы в первой версии.
Формирование событий
По акциям:
- для каждой позиции, сопоставимой с MOEX share instrument, читаются dividend events;
- в выборку попадают только события, чья
registryCloseDateлежит в диапазоне; estimatedAmountрассчитывается как текущийquantity * payoutPerUnit, если payout известен.
По облигациям:
- используется уже доступный enrichment-слой MOEX;
nextCouponDateдаёт событиеcoupon;matDateдаёт событиеmaturity;offerDateдаёт событиеoffer;couponValueиfaceValueиспользуются для оценкиestimatedAmount, когда они доступны.
Семантика summary
summary агрегирует денежные события раздельно по источнику:
- дивиденды;
- купоны;
- погашения.
Поля прогноза считаются только по source: 'forecast', поля факта — только по source: 'actual'.
Оферты остаются в общем списке событий, но не входят в денежные итоги.
Фактические прошедшие события
Для прошедшей части диапазона backend читает исполненные операции T-Bank:
OPERATION_TYPE_DIVIDENDиOPERATION_TYPE_DIV_EXT→dividend;OPERATION_TYPE_COUPON→coupon;OPERATION_TYPE_BOND_REPAYMENTиOPERATION_TYPE_BOND_REPAYMENT_FULL→maturity.
Фактическая строка получает source: 'actual', actualAmount из operation.payment, дату операции
как eventDate, estimateMode: null. Прогнозная строка получает source: 'forecast',
estimatedAmount, estimateMode: 'current_position'.
Частичная деградация
Сервис работает по принципу best-effort:
- ошибка по одной бумаге не роняет весь ответ;
- unsupported или несопоставимые позиции пропускаются;
- если сумма события не может быть рассчитана, событие всё равно возвращается без суммы.
API и типы
Query contract
Новый query DTO:
type BrokerEventsQuery = {
from: string;
to: string;
types?: string;
};
Обе даты обязательны. types опционален: если параметр отсутствует, backend использует все типы.
Response contract
type BrokerPortfolioEvent = {
id: string;
type: 'dividend' | 'coupon' | 'maturity' | 'offer';
source: 'forecast' | 'actual';
category: 'cashflow' | 'corporate';
eventDate: string;
paymentDate: string | null;
ticker: string | null;
name: string | null;
instrumentUid: string | null;
instrumentType: 'share' | 'bond' | 'other';
quantitySnapshot: number | null;
payoutPerUnit: number | null;
estimatedAmount: number | null;
actualAmount: number | null;
currency: string | null;
estimateMode: 'current_position' | null;
};
type BrokerEventsSummary = {
eventCount: number;
nearestEventDate: string | null;
totalEstimatedCashflow: number;
actualCashflow: number;
forecastEstimatedCashflow: number;
dividendsTotal: number;
couponsTotal: number;
principalRepaymentTotal: number;
actualDividendsTotal: number;
actualCouponsTotal: number;
actualPrincipalRepaymentTotal: number;
};
Карта файлов
Backend
- Create
apps/backend/src/modules/tbank/dto/broker-events-query.dto.ts - Create
apps/backend/src/modules/tbank/dto/broker-events-response.dto.ts - Modify
apps/backend/src/modules/tbank/dto/broker-envelope.dto.ts - Modify
apps/backend/src/modules/tbank/types/broker.types.ts - Create
apps/backend/src/modules/tbank/services/broker-events.service.ts - Create
apps/backend/src/modules/tbank/services/broker-events.service.spec.ts - Modify
apps/backend/src/modules/tbank/tbank.controller.ts - Modify
apps/backend/src/modules/tbank/tbank.controller.spec.ts - Modify
apps/backend/src/modules/tbank/tbank.module.ts
Frontend
- Modify
apps/frontend/src/shared/api/responses.ts - Modify
apps/frontend/src/shared/api/index.ts - Modify
apps/frontend/src/shared/api/types.tsthrough codegen if Swagger changes are published - Create
apps/frontend/src/entities/broker-event/api/brokerEventApi.ts - Create
apps/frontend/src/entities/broker-event/model/useBrokerEvents.ts - Create
apps/frontend/src/entities/broker-event/index.ts - Create
apps/frontend/src/pages/broker-events/ui/BrokerEventsPage.tsx - Create
apps/frontend/src/pages/broker-events/index.ts - Create
apps/frontend/src/widgets/broker-events-overview/ui/BrokerEventsOverview.tsx - Create
apps/frontend/src/widgets/broker-events-overview/index.ts - Modify
apps/frontend/src/widgets/broker-account-layout/ui/BrokerAccountLayout.tsx - Modify
apps/frontend/src/pages/broker-account/ui/BrokerAccountOverviewPage.tsx - Modify
apps/frontend/src/app/routing/AppRoutes.tsx
Documentation
- Modify
apps/docs/docs/backend/tbank-invest.md - Modify
apps/docs/docs/backend/modules.mdif the public module description changes materially
Rollout shape
Overview
Overview получает компактный блок ближайших событий:
- не более 5 событий;
- summary по ближайшим выплатам не обязателен, если он перегружает карточку;
- ссылка ведёт в полный раздел
События.
Dedicated page
Страница События включает:
- выбор диапазона дат;
- summary cards;
- список событий;
- loading, empty и error states.
Verification strategy
- Backend unit tests для event mapping, summary и partial failures.
- Backend controller tests для query validation и response shape.
- Frontend hook tests для query lifecycle.
- Frontend page/component tests для фильтра периода, summary и overview-виджета.
- Final verification:
npm test -w apps/backendnpm test -w apps/frontendnpm run build -w apps/backendnpm run build -w apps/frontendnpm run build -w apps/docsесли публичные docs менялисьnpm run codegen -w apps/frontendесли Swagger-контракт обновлён
Frontend date range UI
Даты и типы хранятся в URL-параметрах from / to / types для возможности поделиться ссылкой.
На странице — два date input из дизайн-системы и multi-select типов событий. dayjs используется для
форматирования, валидации и дефолтов.
Поток данных:
useSearchParams → applied filters → useBrokerEvents(query) → TanStack Query
local draft state → button "Показать" → setSearchParams(applied filters)
Дефолт при первом визите без параметров: today-7d – today+7d и все типы событий. Валидация: to >=
from, выбран хотя бы один тип. При невалидных фильтрах кнопка Показать disabled или показывает
ошибку, запрос не выполняется. Изменение полей не запускает запрос до применения.
Follow-up implementation tasks
Backend
- Расширить
BrokerEventsQueryDtoпараметромtypesс валидацией comma-separated значений. - Расширить
BrokerPortfolioEventи Swagger DTO полямиsource,actualAmount, nullableestimateMode. - Расширить
BrokerEventsSummaryраздельными полями факта и прогноза. - Добавить в
BrokerEventsServiceфильтрацию типов и cache key сtypes. - Инжектировать
BrokerOperationsServiceвBrokerEventsServiceи строить actual events по исполненным операциям для прошедшей части диапазона. - Исключить дубли forecast/actual для прошедших cashflow-событий по ключу
type + ticker + date. - Обновить backend unit/controller tests на query
types, actual events и summary.
Frontend
- Расширить handwritten response types и
BrokerEventsQueryпараметромtypes. - Обновить
useBrokerEventsquery key с учётомtypes. - Переделать
BrokerEventsPageна applied filters + draft filters + кнопкуПоказать. - Добавить multi-select типов событий по дизайн-системе/текущим UI-паттернам проекта.
- Отобразить
Факт/Прогноз,Поступило, зелёное выделение actual-сумм и раздельный summary. - Обновить frontend tests на отсутствие запроса при черновом изменении фильтров, применение по кнопке, multi-select типов и actual event styling.