# 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: ```text 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: ```ts type BrokerEventsQuery = { from: string; to: string; types?: string; }; ``` Обе даты обязательны. `types` опционален: если параметр отсутствует, backend использует все типы. ### Response contract ```ts 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.ts` through 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.md` if 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/backend` - `npm test -w apps/frontend` - `npm run build -w apps/backend` - `npm run build -w apps/frontend` - `npm 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 1. Расширить `BrokerEventsQueryDto` параметром `types` с валидацией comma-separated значений. 2. Расширить `BrokerPortfolioEvent` и Swagger DTO полями `source`, `actualAmount`, nullable `estimateMode`. 3. Расширить `BrokerEventsSummary` раздельными полями факта и прогноза. 4. Добавить в `BrokerEventsService` фильтрацию типов и cache key с `types`. 5. Инжектировать `BrokerOperationsService` в `BrokerEventsService` и строить actual events по исполненным операциям для прошедшей части диапазона. 6. Исключить дубли forecast/actual для прошедших cashflow-событий по ключу `type + ticker + date`. 7. Обновить backend unit/controller tests на query `types`, actual events и summary. ### Frontend 1. Расширить handwritten response types и `BrokerEventsQuery` параметром `types`. 2. Обновить `useBrokerEvents` query key с учётом `types`. 3. Переделать `BrokerEventsPage` на applied filters + draft filters + кнопку `Показать`. 4. Добавить multi-select типов событий по дизайн-системе/текущим UI-паттернам проекта. 5. Отобразить `Факт`/`Прогноз`, `Поступило`, зелёное выделение actual-сумм и раздельный summary. 6. Обновить frontend tests на отсутствие запроса при черновом изменении фильтров, применение по кнопке, multi-select типов и actual event styling.