222 lines
9.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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. Слой агрегации строит best-effort список событий по текущим позициям счёта и
summary по будущим денежным потокам. Frontend расширяет shell брокерского счёта новой вкладкой
`События`, отдельной страницей и компактным overview-виджетом ближайших событий.
**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
```
Endpoint возвращает:
- `items` — плоский список событий по текущим позициям счёта;
- `summary` — агрегаты по денежным событиям за период;
- `asOf` — момент построения read model.
### Backend read model
Новый сервис `BrokerEventsService` живёт внутри `TBankModule` и использует:
- `BrokerAccountsService` для проверки счёта;
- `BrokerPortfolioService` или внутренний shared-path для получения текущих позиций;
- `BrokerInstrumentsService` для сопоставления T-Bank instrument metadata;
- `MoexClientService` для дивидендов и bond enrichment;
- `CacheService` для кэширования результата по `accountId + from + to`.
Сервис не записывает события в Prisma и не вводит отдельные таблицы в первой версии.
### Формирование событий
По акциям:
- для каждой позиции, сопоставимой с MOEX share instrument, читаются dividend events;
- в выборку попадают только события, чья `registryCloseDate` лежит в диапазоне;
- `estimatedAmount` рассчитывается как текущий `quantity * payoutPerUnit`, если payout известен.
По облигациям:
- используется уже доступный enrichment-слой MOEX;
- `nextCouponDate` даёт событие `coupon`;
- `matDate` даёт событие `maturity`;
- `offerDate` даёт событие `offer`;
- `couponValue` и `faceValue` используются для оценки `estimatedAmount`, когда они доступны.
### Семантика summary
`summary` агрегирует только денежные события:
- дивиденды;
- купоны;
- погашения.
Оферты остаются в общем списке событий, но не обязаны входить в сумму денежных потоков первой
версии.
### Частичная деградация
Сервис работает по принципу best-effort:
- ошибка по одной бумаге не роняет весь ответ;
- unsupported или несопоставимые позиции пропускаются;
- если сумма события не может быть рассчитана, событие всё равно возвращается без суммы.
## API и типы
### Query contract
Новый query DTO:
```ts
type BrokerEventsQuery = {
from: string;
to: string;
};
```
Обе даты обязательны в первой версии, чтобы не вводить неочевидные дефолты по периоду.
### Response contract
```ts
type BrokerPortfolioEvent = {
id: string;
type: 'dividend' | 'coupon' | 'maturity' | 'offer';
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;
currency: string | null;
estimateMode: 'current_position';
};
type BrokerEventsSummary = {
eventCount: number;
nearestEventDate: string | null;
totalEstimatedCashflow: number;
dividendsTotal: number;
couponsTotal: number;
principalRepaymentTotal: 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` для возможности поделиться ссылкой. На странице — два `TextField[type=date]` из дизайн-системы. dayjs для форматирования, валидации и дефолтов.
Поток данных:
```
useSearchParams → from/to → useBrokerEvents(query) → TanStack Query (автоrefetch)
```
Дефолт при первом визите без параметров: today today+7d. Валидация: to >= from. При невалидных датах — показ ошибки под полем, запрос не выполняется.