docs(specs): archive ledger-entry-type-label change and sync main spec

This commit is contained in:
2026-08-27 11:35:01 +03:00
parent 329f0a86a4
commit 37c0133e8b
6 changed files with 181 additions and 3 deletions
@@ -0,0 +1,2 @@
schema: wealth-ledger-sdd
created: 2026-08-27
@@ -0,0 +1,73 @@
## Context
Account Ledger уже возвращает по каждой записи нормализованный `category` (trade, income, tax, fee, transfer, other) и upstream `type` операции (`OPERATION_TYPE_COUPON`, `OPERATION_TYPE_DIVIDEND`, `OPERATION_TYPE_DIVIDEND_TRANSFER` и др.). Frontend-компонент `LedgerEntryRow` сейчас в цветной денежной метке выводит обобщённую подпись направления «Поступление» / «Списание» из i18n-ключа `ledger.direction.*`, а категорию дополнительно повторяет во вторичной метадате строки. Изменение затрагивает только presentation frontend; backend-контракт уже содержит все нужные поля и не меняется.
## Goals / Non-Goals
**Goals:**
- Денежная метка записи показывает конкретный тип события: «Купон» / «Дивиденд» для income, иначе подпись категории («Сделка», «Налог», «Комиссия», «Перевод», «Прочее»).
- Подпись типа выводится один раз; дублирование типа во вторичной метадате убирается.
- Знак «+» / «−» и семантический цвет остаются единственными носителями направления.
- Обзор счёта (пять последних записей) использует ту же семантику через общий компонент строки.
- Синхронизация as-built документации.
**Non-Goals:**
- Без изменений backend API, OpenAPI контракта, маппинга категорий, пагинации, фильтров, кэша или денежных вычислений.
- Без новых категорий upstream/нормализованных категорий; классификация купона/дивиденда определяется исключительно по уже существующему `category` + `type`.
- Без изменений Future Cash Events и Portfolio.
## Decisions
### Определять подпись типа на frontend по `category` и `type`
`LedgerEntryRow` выводит подпись через хелпер по `operation.category` и `operation.type`:
- `category === "income"` → `type === "OPERATION_TYPE_COUPON"` ? «Купон» : «Дивиденд» (все остальные upstream-типы отображаются как «Дивиденд», включая `OPERATION_TYPE_DIVIDEND` и `OPERATION_TYPE_DIVIDEND_TRANSFER`).
- любой другой `category` → `t("ledger.category.<category>")`.
- Без денежного движения → прежний нейтральный «Без движения денег».
**Rationale:** контракт уже несёт `category` и upstream `type`, поэтому классификация не требует backend-изменений и новой интеграции; нормализация купона/дивиденда известна (backend `brokerage-operation.mapper.ts`). Подпись принадлежит presentation слою сущности, а не VM страницы. Альтернатива — расширить contract отдельным полем — отвергнута как избыточная: upstream `type` уже достаточен и не вносит новой observable behavior.
### Один источник подписи в цветной метке, без дублирования
Вторичная метадата строки (дата · инструмент) теряет категорию; единственное место подписи типа — цветная денежная метка рядом со знаком. **Rationale:** вариант согласован с Change Owner (option 1a) и исключает повтор типа в одной строке.
### i18n
В `ru.ts` в `ledger` добавляются ключи подписи «Купон» / «Дивиденд» (`ledger.entryLabel.income.coupon`, `ledger.entryLabel.income.dividend`); для остальных категорий переиспользуются существующие `ledger.category.*`. Ключи `ledger.direction.*` («Поступление» / «Списание») перестают использоваться и удаляются. **Rationale:** `ledger.category.income` («Доход») остаётся нужен только чекбоксу фильтра категорий, поэтому подпись дохода выносится в отдельные ключи.
## Delivery Map
| Area | Status | Architectural outcome or N/A reason |
| --- | --- | --- |
| frontend | `affected` | Компактная строка `LedgerEntryRow` показывает подпись типа вместо направления; убрано дублирование категории; i18n-ключи; тесты. |
| backend | `N/A` | Никаких изменений API или интеграции; `category` и `type` уже в контракте. |
| specs | `affected` | Принятые delta requirements закрепляют новую поведенческую семантику подписи строки. |
| docs | `affected` | As-built документация Account Ledger описывает подпись типа и отсутствие дублирования. |
### Contract Gate Decision
| Gate | Status | Reason |
| --- | --- | --- |
| runtime-openapi | `N/A` | Публичный API contract не меняется ни на одной стороне; frontend использует уже существующие поля. |
## Risks / Trade-offs
- Небольшой хрупкость связки с upstream `type`: если T-Bank добавит новый upstream-тип дохода, то по умолчанию он отобразится как «Дивиденд». Это допустимо (не блокирует) и не изменяет денежную семантику; при необходимости upstream-типы можно уточнить в маппинге позже без контрактного изменения.
- Удаление «Поступление»/«Списание» убирает явное словесное направление; компенсируется знаком и цветом (закреплено в specs и ранее утверждённой семантике «направление независимо от цвета»).
- Изменение тестов и i18n затрагивает только frontend и проверяется его canonical gate.
## Migration Plan
1. Frontend: добавить i18n-ключи подписи дохода, убрать неиспользуемые `ledger.direction.*`.
2. Frontend: заменить в `LedgerEntryRow` знак+направление на знак+подпись типа; убрать категорию из вторичной метадаты.
3. Обновить тесты (направление → подпись типа; добавить сценарии Купон/Дивиденд/Комиссия).
4. Обновить as-built документацию `brokerage-account-ledger.md`.
### Rollback Contract
| Field | Decision |
| --- | --- |
| Status | `N/A` |
| Reason | Чисто reversible presentation-изменение frontend: нет публичного контракта, миграции, данных, auth/security или дорого необратимого решения; откат — восстановление предшествующей разметки и i18n строки. |
@@ -0,0 +1,28 @@
## Why
В ряду Account Ledger (и в пяти последних записях обзора счёта) сейчас выводится цветная текстовая метка «Поступление» / «Списание». Она избыточна: направление движения денег и без неё понятно по знаку «+» / «−» и семантическому цвету. Вместо неё пользователю полезнее видеть конкретный тип события («Сделка», «Налог», «Комиссия», «Перевод», «Прочее»), а для доходных операций — различать «Купон» и «Дивиденд» вместо общего «Доход». Это делает список читаемее и убирает дублирование типа во вторичной метадате строки.
## What Changes
Намерение — изменить только текстовую семантику денежной метки записи журнала: вместо «Поступление» / «Списание» строка показывает конкретный тип события. Для типа операции купона показывается «Купон», для дивидендных операций — «Дивиденд»; для остальных записей — метка их категории («Сделка», «Налог», «Комиссия», «Перевод», «Прочее»). Записи без движения денег продолжают показываться нейтрально. Тип события выводится один раз; избыточное повторение категории во вторичной строке записи убирается. Знак и цвет денежного изменения сохраняются как единственные носители направления.
Non-goals:
- Никаких изменений backend API, контракта, маппинга категорий, пагинации, фильтров, кэша или денежных вычислений.
- Никаких новых категорий операций; upstream типы купона и дивиденда уже нормализованы в категорию income и не меняются.
- Никаких изменений списка будущих событий (Future Cash Events) и Portfolio.
- Никаких изменений правил доступа, безопасности или хранения.
## Capabilities
### New Capabilities
- Нет новых capability.
### Modified Capabilities
- `brokerage-account-ledger`: изменяется требование «Full ledger presents compact rows» — текстовая семантика денежной метки записи: вместо явного обозначения поступления/списания строкой запись показывает конкретный тип события (купон/дивиденд для income, иначе метку категории) и не повторяет категорию во вторичной метадате; знак и цвет остаются носителями направления. Пяти последним записям обзора применяется та же семантика денежного движения и идентичности, что и полному журналу.
## Impact
Затронут frontend (компактная строка записи журнала в обзоре счёта и в полном журнале) и связанная as-built документация. На пользователя: читаемость списка операций без изменения денежной семантики. Backend и контракт API не затрагиваются; списки будущих событий и Portfolio не меняются.
@@ -0,0 +1,33 @@
## MODIFIED Requirements
### Requirement: Full ledger presents compact rows
Страница полного журнала MUST показывать для каждой записи дату, тип операции, инструмент и денежное изменение в исходной валюте в компактной структурированной строке. При доступной presentation metadata инструмента строка MUST показывать его визуальную идентичность; при её отсутствии MUST использовать понятный текстовый fallback. Для каждой записи денежное изменение в исходной валюте MUST сопровождаться подписью конкретного типа события и знаком: для купонной операции — «Купон», для дивидендной — «Дивиденд», для остальных записей — подпись их категории («Сделка», «Налог», «Комиссия», «Перевод», «Прочее»). Строка MUST NOT повторять подпись типа события во вторичной метадате записи. Знак «+» / «−» и семантический цвет MUST оставаться носителями направления и MUST NOT заменяться только текстом. Страница MUST обозначать записи без движения денег нейтрально и MUST NOT показывать цену или количество.
#### Scenario: Отображение записи только с инструментом
- **WHEN** полный журнал отображает запись без денежной выплаты
- **THEN** страница определяет её как запись без движения денег
#### Scenario: Отображение поступления
- **WHEN** полный журнал отображает запись с положительной денежной выплатой
- **THEN** строка явно показывает поступление знаком и подписью конкретного типа события («Купон», «Дивиденд» или категории) и его денежное изменение в исходной валюте, не полагаясь только на цвет
#### Scenario: Отображение списания
- **WHEN** полный журнал отображает запись с отрицательной денежной выплатой
- **THEN** строка явно показывает списание знаком и подписью конкретного типа события («Купон», «Дивиденд» или категории) и его денежное изменение в исходной валюте, не полагаясь только на цвет
#### Scenario: Отображение купона
- **WHEN** полный журнал отображает запись с upstream-типом купона
- **THEN** строка показывает подпись «Купон»
#### Scenario: Отображение дивиденда
- **WHEN** полный журнал отображает запись с upstream-типом дивиденда
- **THEN** строка показывает подпись «Дивиденд»
#### Scenario: Подпись типа не дублируется
- **WHEN** полный журнал отображает запись с типом события и вторичной метадатой
- **THEN** подпись типа события присутствует в строке один раз и не повторяется во вторичной метадате записи
#### Scenario: Отсутствует visual metadata инструмента
- **WHEN** полный журнал отображает запись без доступной presentation metadata инструмента
- **THEN** строка показывает понятный текстовый fallback без подмены или сокрытия доступных данных инструмента
@@ -0,0 +1,29 @@
## Frontend
- [x] 1.1 [frontend] i18n: добавить ключи подписи дохода `ledger.entryLabel.income.coupon` = «Купон» и `ledger.entryLabel.income.dividend` = «Дивиденд» (и параллельные ключи в других локалях, если они есть); удалить ставшие неиспользуемыми ключи `ledger.direction.incoming` / `ledger.direction.outgoing` / `ledger.direction.neutral`; сохранить `ledger.category.income` для фильтра.
- **Covers:** `brokerage-account-ledger` — `Full ledger presents compact rows`
- **Verification:** в `frontend` в `src/shared/i18n/locales/ru.ts` присутствуют новые ключи и отсутствует использование `ledger.direction.*`; typecheck и lint проходят.
- [x] 1.2 [frontend] `LedgerEntryRow`: заменить цветную текстовую подпись направления на подпись типа события по `category` и upstream `type` (income: `OPERATION_TYPE_COUPON` → «Купон», остальные типы → «Дивиденд»; иначе — метка категории; без денежного движения — «Без движения денег») и убрать категорию из вторичной метадаты строки, оставив знак и семантический цвет носителями направления.
- **Covers:** `brokerage-account-ledger` — `Full ledger presents compact rows`
- **Verification:** в `frontend` обновлённый `src/entities/broker-operation/ui/LedgerEntryRow.tsx` отдаёт подпись типа один раз, метадата содержит дату и инструмент; код проходит typecheck, lint и build.
- [x] 1.3 [frontend] Tests: обновить ожидания направления на подпись типа в `src/pages/account-operations/ui/AccountOperationsPage.test.tsx` и `src/pages/account/ui/AccountPage.test.tsx`; добавить сценарии: купон → «Купон», дивиденд → «Дивиденд», комиссия → «Комиссия».
- **Covers:** `brokerage-account-ledger` — `Full ledger presents compact rows`
- **Verification:** в `frontend` обновлённые и новые asserts соответствуют подписи типа; `npm test` (или эквивалент по package.json) зелёный для затронутых файлов.
- [x] 1.4 [frontend] Canonical gate `npm ci && npm run verify` в `frontend`.
- **Covers:** `brokerage-account-ledger` — `Full ledger presents compact rows`
- **Verification:** gate завершается успешно (strict TypeScript, Biome, risk-based tests/coverage ratchet, Knip, dependency boundaries, build).
## Docs
- [x] 2.1 [docs] Обновить as-built `docs/features/brokerage-account-ledger.md`: описать, что денежная метка строки показывает подпись типа события («Купон» / «Дивиденд» для income, иначе метка категории), направление передаётся знаком и цветом, категория не дублируется во вторичной метадате.
- **Covers:** `N/A` — документация фиксирует фактическое as-built состояние; самостоятельного observable behavior нет, изменение описывает результат frontend-задачи 1.1–1.3.
- **Verification:** изменённый файл отражает новую семантику строки; markdown lint и `git diff --check` проходят.
## Workspace
- [x] 3.1 [workspace] Семантическая верификация реализации против Proposal, Delta Specs, Design и Tasks.
- **Covers:** `brokerage-account-ledger` — `Full ledger presents compact rows`
- **Verification:** описание завершённых реализаций подтверждает каждый delta Requirement и не вносит неучтённого behavior.
@@ -63,7 +63,8 @@
- **THEN** обзор показывает для неё понятный текстовый fallback и сохраняет дату, тип, инструмент и денежное изменение записи доступными для чтения
### Requirement: Full ledger presents compact rows
Страница полного журнала MUST показывать для каждой записи дату, тип операции, инструмент и денежное изменение в исходной валюте в компактной структурированной строке. При доступной presentation metadata инструмента строка MUST показывать его визуальную идентичность; при её отсутствии MUST использовать понятный текстовый fallback. Для положительной денежной выплаты строка MUST явно обозначать поступление знаком, текстовой семантикой и положительным цветом; для отрицательной — списание знаком, текстовой семантикой и отрицательным цветом. Страница MUST обозначать записи без движения денег нейтрально и MUST NOT показывать цену или количество.
Страница полного журнала MUST показывать для каждой записи дату, тип операции, инструмент и денежное изменение в исходной валюте в компактной структурированной строке. При доступной presentation metadata инструмента строка MUST показывать его визуальную идентичность; при её отсутствии MUST использовать понятный текстовый fallback. Для каждой записи денежное изменение в исходной валюте MUST сопровождаться подписью конкретного типа события и знаком: для купонной операции — «Купон», для дивидендной — «Дивиденд», для остальных записей — подпись их категории («Сделка», «Налог», «Комиссия», «Перевод», «Прочее»). Строка MUST NOT повторять подпись типа события во вторичной метадате записи. Знак «+» / «−» и семантический цвет MUST оставаться носителями направления и MUST NOT заменяться только текстом. Страница MUST обозначать записи без движения денег нейтрально и MUST NOT показывать цену или количество.
#### Scenario: Отображение записи только с инструментом
- **WHEN** полный журнал отображает запись без денежной выплаты
@@ -71,11 +72,23 @@
#### Scenario: Отображение поступления
- **WHEN** полный журнал отображает запись с положительной денежной выплатой
- **THEN** строка явно показывает поступление и его денежное изменение в исходной валюте, не полагаясь только на цвет
- **THEN** строка явно показывает поступление знаком и подписью конкретного типа события («Купон», «Дивиденд» или категории) и его денежное изменение в исходной валюте, не полагаясь только на цвет
#### Scenario: Отображение списания
- **WHEN** полный журнал отображает запись с отрицательной денежной выплатой
- **THEN** строка явно показывает списание и его денежное изменение в исходной валюте, не полагаясь только на цвет
- **THEN** строка явно показывает списание знаком и подписью конкретного типа события («Купон», «Дивиденд» или категории) и его денежное изменение в исходной валюте, не полагаясь только на цвет
#### Scenario: Отображение купона
- **WHEN** полный журнал отображает запись с upstream-типом купона
- **THEN** строка показывает подпись «Купон»
#### Scenario: Отображение дивиденда
- **WHEN** полный журнал отображает запись с upstream-типом дивиденда
- **THEN** строка показывает подпись «Дивиденд»
#### Scenario: Подпись типа не дублируется
- **WHEN** полный журнал отображает запись с типом события и вторичной метадатой
- **THEN** подпись типа события присутствует в строке один раз и не повторяется во вторичной метадате записи
#### Scenario: Отсутствует visual metadata инструмента
- **WHEN** полный журнал отображает запись без доступной presentation metadata инструмента