spec: unify income payment cards and drop period comparisons (WealthLedger/main#33)
209 lines
16 KiB
Markdown
209 lines
16 KiB
Markdown
# Engineering Design
|
||
|
||
## Context and Constraints
|
||
|
||
Аналитика дохода использует выбранный период, отдельные previous/year диапазоны
|
||
и публикует их через DTO и metadata; план синхронизации также запрашивает эти
|
||
диапазоны. Frontend строит блоки сравнений из ответа и показывает исполненные
|
||
операции через `LedgerEntryRow`. Полный журнал, недавние записи и личная история
|
||
бумаги используют этот же ряд; прогноз использует `BrokerageEventCard`;
|
||
календарные карточки акции и облигации реализованы внутри соответствующих
|
||
страниц. Все уже используют `EntryCard` либо существующие Mantine primitives,
|
||
но это не один предметный компонент выплаты.
|
||
|
||
Граница реализации — утверждённые Delta Specs и
|
||
[System Analysis](system-analysis/analysis.md), включая
|
||
[целевой контракт](system-analysis/contracts/income-analytics-response.schema.json).
|
||
Доступ, деньги, полнота периода, порядок записей и формы остальных API
|
||
сохраняются. Клиент получает публичный OpenAPI artifact явно.
|
||
|
||
## Technical Goals / Non-Goals
|
||
|
||
- Исключить сравнения из чтения, синхронизации, API и UI без изменения итогов
|
||
выбранного периода.
|
||
- Один компонент для купона/дивиденда в исполненной операции, прогнозе и
|
||
графике бумаги; режим определяется источником, а не знаком или датой.
|
||
- Не преобразовывать график в личный доход и не объединять чужие деньги,
|
||
прогноз и сумму на бумагу. Не удалять `buckets` или `instruments` из API.
|
||
|
||
## Implementation Impact Map
|
||
|
||
### main
|
||
|
||
N/A: нет изменений workspace, Compose, release orchestration или конфигурации.
|
||
|
||
### frontend
|
||
|
||
Affected: страница дохода и её display model, компонент записи операций,
|
||
компонент будущего события, календарные карточки акций/облигаций, локальная
|
||
копия публичного OpenAPI и генерируемые типы. Владельцы экранов сохраняют
|
||
свои списки, фильтры и локальные состояния.
|
||
|
||
### backend
|
||
|
||
Affected: расчёт и планирование income периода, чтение операций для
|
||
income-analytics, DTO ответа и план фонового получения выбранного интервала.
|
||
Источник исполненных операций и агрегация selected остаются владельцами денег.
|
||
|
||
### qa
|
||
|
||
N/A: самостоятельного репозитория QA нет; системные проверки описывает
|
||
`qa-analysis.md` без введения несуществующей инфраструктуры.
|
||
|
||
## Decisions
|
||
|
||
### main
|
||
|
||
Изменения не требуются: координация ссылками на Issue/Change, без workspace
|
||
автоматизации ради одного API перехода.
|
||
|
||
### frontend
|
||
|
||
Один предметный `PayoutCard` разместить на уровне `entities`, доступном
|
||
страницам и компонентам других slices через public export. Он использует
|
||
существующий `EntryCard` как визуальную основу; `EntryCard` не становится
|
||
владельцем финансовой семантики. Презентационные данные передаются явным
|
||
discriminated union по происхождению `executed | forecast | schedule`:
|
||
исполненная запись содержит signed payment и точный timestamp; прогноз —
|
||
nullable projected amount, dateKind и дату; график — nullable per-security
|
||
сумму, отдельную nullable оценку и доступные даты/параметры выпуска. Ветка
|
||
графика показывает дополнительные условия купона и дивиденда без размножения
|
||
карточек. У отсутствующих значений текстовый fallback, ноль форматируется
|
||
отдельно от неизвестного. Не вводить один универсальный произвольный набор
|
||
optional props, допускающий ложную комбинацию «исполнено + оценка».
|
||
|
||
Владельцы исходных данных адаптируют типизированный ответ непосредственно на
|
||
границе использования либо через узкий mapper сущности для повторных callers:
|
||
`LedgerEntryRow` передаёт точные купоны/дивиденды в `PayoutCard`, прочие
|
||
операции оставляет на прежнем пути; `BrokerageEventCard` делает то же для
|
||
coupon/dividend, сохраняя maturity/offer; страницы графиков заменяют только
|
||
содержимое календарной карточки. Доход продолжает вызывать `LedgerEntryRow`,
|
||
сохраняя `wrap`, HTML-список и occurrence key. Личная история инструмента
|
||
сохраняет quantity/price/account details там, где они доступны. Ключи списков
|
||
и порядок определяются caller, не самим компонентом.
|
||
|
||
Удалить comparison-only display model, компонент блока, подписи и стили,
|
||
относящиеся только к сравнениям. Не удалять предупреждение неполного
|
||
selected или сохранённого snapshot. Обновить текущие fixtures и регрессии
|
||
после передачи OpenAPI; генерация типов потребителя берёт только его
|
||
локальную копию artifact.
|
||
|
||
Альтернатива с одинаковым CSS для трёх существующих компонентов отвергнута:
|
||
не обеспечивает фактическое переиспользование. Альтернатива с общим DTO
|
||
backend отвергнута: источники и денежные смыслы различны, wire-контракт
|
||
остальных API не меняется.
|
||
|
||
### backend
|
||
|
||
Сократить план income-analytics до selected: сервис запрашивает один диапазон,
|
||
не конструирует comparison totals/availability; план экранной синхронизации
|
||
не включает предыдущий год и месяц. Сохранить независимый план current-month
|
||
summary и серверный выбор точных типов купона/дивиденда. В DTO удалить
|
||
`previous`, `yearComparison`, comparison DTO при отсутствии других callers;
|
||
`dependencies` сохранить в исходной форме с единственной ролью `selected`.
|
||
Текущий shared operations range reader использует индексированные имена
|
||
resource keys для трёх периодов; после одного диапазона оставить корректную
|
||
идентичность selected и убрать устаревший индексированный список. Сохраняем
|
||
status/coverage/freshness и публикацию подтверждённого периода. Дневные/
|
||
недельные/месячные buckets остаются, так как их удаление не согласовано.
|
||
|
||
Не вводить миграцию БД: убираются чтения и поля ответа, а не сохранённые
|
||
операции или подтверждённое покрытие. Никакой пересборки прошлого дохода.
|
||
|
||
## Cross-cutting Implementation
|
||
|
||
Существующая проверка пользователя/счёта и очистка личного query cache при
|
||
смене подключения остаются на местах. Изменение количества планируемых
|
||
диапазонов не добавляет work и не меняет поведение hourly attempt для других
|
||
экранов. Существующие request ID, metadata попытки и статусы дают достаточную
|
||
диагностику; новых событий или полей логирования не требуется. Пересчёта
|
||
денег, валютной конвертации и нового кэша нет.
|
||
|
||
## Data / Storage Changes
|
||
|
||
N/A: Prisma/SQLite schema и исторические записи не меняются; выбранное
|
||
покрытие продолжает подтверждаться до публикации окончательного результата.
|
||
|
||
## Contract Implementation
|
||
|
||
Backend обновляет DTO и исполняемый generated OpenAPI. Для обоих
|
||
income-analytics маршрутов проверить отсутствие `previous`/`yearComparison`
|
||
и наличие только `selected` в dependency role при сохранении nullable `data`,
|
||
остальных полей и Problem Details. Сверить обе схемы 200 с
|
||
[target JSON Schema](system-analysis/contracts/income-analytics-response.schema.json)
|
||
семантически, а не только с TypeScript shape; существующую проверку parity
|
||
runtime OpenAPI также сохранить. Передать созданный backend artifact frontend
|
||
явно; frontend обновляет snapshot и generated types штатной локальной
|
||
процедурой, без чтения соседнего checkout в сборке. Прежний клиент,
|
||
требующий удалённых полей, несовместим.
|
||
|
||
## Developer Verification Strategy
|
||
|
||
Backend: адресные проверки планирования только выбранного интервала, чтения
|
||
income summary/analytics, точных signed сумм и недоступного selected;
|
||
проверка HTTP контракта и OpenAPI parity. Frontend: наблюдаемое отсутствие
|
||
блоков сравнений в loaded/loading/error/empty; сохранение period URL,
|
||
порядка/повторов выплат и корректные режимы общего компонента на всех
|
||
потребителях. После локальных узких проверок выполнить обязательный verify
|
||
каждого изменённого implementation repo. Это developer-owned проверки;
|
||
системная матрица отдельно в `qa-analysis.md`.
|
||
|
||
## UI/UX Package
|
||
|
||
**Applicability and scope:** changed UI; пакет полностью inline, новых
|
||
изображений и assets не требуется. Один approval Design относится к этой
|
||
ревизии и приведённому ниже точному источнику переиспользования.
|
||
|
||
**Package revision:** `1`, change `unify-income-payment-cards`.
|
||
|
||
**Screens, states and interactions:** доход — фильтр, итоги, плоский список
|
||
исполненных выплат, без сравнений; прогноз на странице событий/превью —
|
||
пометка «Прогноз», неизвестная сумма отлична от нуля, дата реестра помечена;
|
||
графики акции/облигации — «Событие по графику», разные подписи per-share/
|
||
per-bond и личной оценки. В истории операций карточка сохраняет описание,
|
||
дату и доступную identity инструмента, signed Money и детали там, где были.
|
||
Loaded, empty, loading, error и обновление старого snapshot сохраняют текущие
|
||
локальные границы экранов. Оферта, погашение, сделка и налог не получают
|
||
режим выплаты. Компонент сам не переключает состояние и не инициирует запрос.
|
||
|
||
**Responsive behavior:** однотактный проход по карточкам без горизонтального
|
||
overflow при узкой ширине и длинных значениях; labels, значения и статусы
|
||
читаются текстом и клавиатурой, цвет остаётся дополнительным признаком.
|
||
Сохраняются доступные имена региона и фокус элементов фильтра.
|
||
|
||
**Reused source and differences:** immutable locator
|
||
`wealth-ledger-specs:openspec/changes/archive/2026-09-09-simplify-income-page/design.md`
|
||
(секция «Одна карточка соответствует одной выплате») — плоский income список,
|
||
одна карточка на запись, signed Money и сохранение порядка. Дополнить
|
||
только единым предметным presentation для прогнозов/графиков с явными
|
||
подписями происхождения; не переносить прогноз внутрь income страницы.
|
||
Визуальная основа реализации — существующий `EntryCard`; его геометрия
|
||
остаётся общим стилем, а не заменой `PayoutCard`. Пакет целиком inline.
|
||
|
||
## Risks / Trade-offs
|
||
|
||
- Разные даты и null-поля трёх источников провоцируют неверные labels при
|
||
слишком широком mapper: явные варианты данных и локальные проверки экранов
|
||
важнее экономии нескольких props.
|
||
- Для календаря бумаги нет логотипа/тикера в каждой записи: владелец страницы
|
||
передаёт известную identity, не создавая новый запрос.
|
||
- Меньше загружаемых диапазонов снижает объём работы, но отсутствие сравнения
|
||
может изменить порядок фонового покрытия старых периодов; цель сохранения
|
||
другой независимой истории остаётся прежней.
|
||
|
||
## Migration / Rollout / Rollback
|
||
|
||
Сначала обновить backend DTO/логику и получить опубликованный OpenAPI, затем
|
||
передать artifact frontend и обновить UI/типизацию; согласованно поставить
|
||
две стороны breaking ответа. Не выпускать старый frontend против нового
|
||
backend как поддерживаемую комбинацию. При rollback вернуть согласованную
|
||
предыдущую пару backend/frontend и прежний OpenAPI snapshot; сохранённые
|
||
операции и покрытие не требуют data rollback. Синхронизация canonical specs
|
||
и AS-IS docs выполняется по отдельным approved tasks после реализации.
|
||
|
||
## Solution Assessment
|
||
|
||
N/A: не создаётся новая инфраструктура, интеграция, storage, кэш или система
|
||
аутентификации. Существующие `EntryCard`, owner-slices и DTO сохраняются;
|
||
сопоставление альтернатив общего UI дано в решении frontend.
|