diff --git a/openspec/changes/archive/2026-08-27-ledger-entry-type-label/.openspec.yaml b/openspec/changes/archive/2026-08-27-ledger-entry-type-label/.openspec.yaml new file mode 100644 index 0000000..f845845 --- /dev/null +++ b/openspec/changes/archive/2026-08-27-ledger-entry-type-label/.openspec.yaml @@ -0,0 +1,2 @@ +schema: wealth-ledger-sdd +created: 2026-08-27 diff --git a/openspec/changes/archive/2026-08-27-ledger-entry-type-label/design.md b/openspec/changes/archive/2026-08-27-ledger-entry-type-label/design.md new file mode 100644 index 0000000..c079492 --- /dev/null +++ b/openspec/changes/archive/2026-08-27-ledger-entry-type-label/design.md @@ -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.")`. +- Без денежного движения → прежний нейтральный «Без движения денег». + +**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 строки. | diff --git a/openspec/changes/archive/2026-08-27-ledger-entry-type-label/proposal.md b/openspec/changes/archive/2026-08-27-ledger-entry-type-label/proposal.md new file mode 100644 index 0000000..b936443 --- /dev/null +++ b/openspec/changes/archive/2026-08-27-ledger-entry-type-label/proposal.md @@ -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 не меняются. diff --git a/openspec/changes/archive/2026-08-27-ledger-entry-type-label/specs/brokerage-account-ledger/spec.md b/openspec/changes/archive/2026-08-27-ledger-entry-type-label/specs/brokerage-account-ledger/spec.md new file mode 100644 index 0000000..9c7bcd6 --- /dev/null +++ b/openspec/changes/archive/2026-08-27-ledger-entry-type-label/specs/brokerage-account-ledger/spec.md @@ -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 без подмены или сокрытия доступных данных инструмента diff --git a/openspec/changes/archive/2026-08-27-ledger-entry-type-label/tasks.md b/openspec/changes/archive/2026-08-27-ledger-entry-type-label/tasks.md new file mode 100644 index 0000000..2c321d8 --- /dev/null +++ b/openspec/changes/archive/2026-08-27-ledger-entry-type-label/tasks.md @@ -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. diff --git a/openspec/specs/brokerage-account-ledger/spec.md b/openspec/specs/brokerage-account-ledger/spec.md index e8a610c..c837256 100644 --- a/openspec/specs/brokerage-account-ledger/spec.md +++ b/openspec/specs/brokerage-account-ledger/spec.md @@ -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 инструмента