Sergey Krylov f7dc338719
Some checks failed
CI / ci (pull_request) Failing after 3m9s
CI / ci (push) Failing after 3m9s
feat: add actual payouts and filter-as-draft UX to broker events calendar
- 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
2026-06-22 21:48:03 +03:00

13 KiB
Raw Permalink Blame History

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_EXTdividend;
  • OPERATION_TYPE_COUPONcoupon;
  • OPERATION_TYPE_BOND_REPAYMENT и OPERATION_TYPE_BOND_REPAYMENT_FULLmaturity.

Фактическая строка получает 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.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.