arm-core/docs/superpowers/plans/2026-11-06-goodwill-review-front.md
Andrey Epifancev fe4c5db0e6 Гудвилл: ветка без правок вне задачи, влит main
Откатены к main правки справочников, шахматки, корректировок и types/api.ts
(обработка ошибок, Alert, удалённые комментарии) — не относятся к AURORA-1544 и
конфликтовали с #219/#221; шахматка переведена на общий фильтр единиц
components/ConsolidationFormatTableFilter; удалены тестовые MOCK_DATA; формат.
План доводки — docs/superpowers/plans/2026-11-06-goodwill-review-front.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 18:31:47 +04:00

131 lines
14 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.

# Гудвилл: доводка фронта по ревью (arm-core #220) — план
> **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:** довести PR arm-core #220 (ветка `feat/goodWill`, AURORA-1544) до мержа: убрать правки вне задачи, влить `main`,
перевести вкладку «Гудвилл» на фактический API бэка (#222 после доводки по плану `2026-11-06-goodwill-review-back.md`),
закрыть расхождения с DOC-259025 и макетом `31-03`.
**Architecture:** чистые функции вкладки — `web/src/utils/goodwill.ts` (+ vitest); типы — `web/src/types/goodwill/goodwill.ts`
по схемам `api/src/schemas/goodwill.py`; запросы — `web/src/services/api/goodwill.ts`; экран —
`web/src/widgets/ConsolidationGoodwill/*`. Агрегаты (итоги «Всего» по ГО и по листу) фронт не считает — берёт `groups[].totals`
и `totals` из ответа.
**Tech Stack:** React 18 + TS strict, antd 6, vitest, biome + ESLint/Prettier.
**Spec:** контракт `../docs/dev/api-contract-goodwill.md` (обновлён в docs #204), DOC-259025 «Гудвилл» §4–§6, макет
`../docs/mockup/screens/31-03-goodwill.png`, `31-03-01-goodwill-edit.png`.
Рабочий каталог: `/Users/andrey.epifancev/projects/awp-ca/.wt/review-220/arm-core` (ветка `feat/goodWill`). Бэк для ручной
проверки — ветка `feat/goodwill-back` (worktree `.wt/review-222/arm-core`), пока #222 не в `main`.
## Global Constraints
- Деньги — строки; арифметика только через `toCents`/`fromCents` (`utils/money.ts`), никаких `Number` над суммами.
- `null` — «—», `0.00` — ноль по формату единиц; нерассчитанный гудвилл не попадает ни в один фильтр знака.
- Доля: ввод с запятой или точкой, 0–100, не больше двух знаков; ошибку показать у поля и заблокировать «Сохранить», значение не обрезать и не подменять.
- Все запросы — через `api/client.ts`; ошибки показываются только для `ApiError`, ошибки валидации форм antd тостом не показываются.
- Код вне `widgets/ConsolidationGoodwill`, `types/goodwill`, `services/api/goodwill.ts`, `utils/goodwill*`, `hooks/useFullScreen.tsx`,
`components/ConsolidationFormatTableFilter` и вкладок отчёта (`ConsolidationReportPage.tsx`, `OsvTabs.tsx`) совпадает с `main`
(кроме строки импорта фильтра в `ConsolidatedChessContent.tsx`).
- Коммиты — по-русски, `Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>`.
## Review Focus
- Снять галочку у строки, где доля ни разу не вводилась: пакет уходит с `ownership_share: null`, сервер отвечает 200.
- Сервер вернул 422 по одной строке пакета: поле этой строки подсвечено, режим редактирования не закрыт, введённое не потеряно.
- «Выделить все» в блоке, где есть «Нет вложений» и скрытые фильтром строки: отмечаются все действующие строки блока, «Нет вложений» — нет.
- Версия без своей блокировки (`read_only_reason = LOCK_REQUIRED`): «Редактировать» и переключатель недоступны, видна причина.
- Фильтр «Положительные» + Экспорт: в файл уходит тот же фильтр (`sign=positive`).
---
### Task 1: Ветка — только гудвилл
**Files:**
- Restore from `origin/main`: `web/src/layouts/CatalogueGroups.tsx`, `web/src/types/api.ts`,
`web/src/widgets/CatalogueCompanyGroup/CompanyGroupForm/CompanyGroupForm.tsx`, `web/src/widgets/CatalogueJur/**`,
`web/src/widgets/RelatedGroup/**`, `web/src/widgets/CorrectionTable/CorrectionContent.tsx`,
`web/src/widgets/ConsolidationChess/**` (кроме удаления `ConsolidationChessFormatTableFilter` — см. Step 2)
- Modify: `web/src/widgets/ConsolidationChess/ConsolidationChessContent/ConsolidatedChessContent.tsx` (только импорт фильтра),
`web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillContent/ConsolidationGoodwillContent.tsx` (удалить `MOCK_DATA`)
- [ ] **Step 1:** `git merge origin/main` (конфликты в `CatalogueGroups.tsx`, `ConsolidatedChessContent.tsx`, `ConsolidationChessTable.tsx` —
брать сторону `main`), затем `git checkout origin/main -- <файлы из списка>`.
- [ ] **Step 2:** общий фильтр единиц — оставить `components/ConsolidationFormatTableFilter`, удалить
`widgets/ConsolidationChess/ConsolidationChessFormatTableFilter/`, в шахматке поменять только импорт и имя компонента.
- [ ] **Step 3:** `git diff --stat origin/main...HEAD` — изменены только файлы из Global Constraints; `useFullscreen` использует только вкладка гудвилла.
- [ ] **Step 4:** из `web/`: `npm run format`, затем `make -C .. web-check`, `npm test`, `npm run lint` — зелёные.
- [ ] **Step 5:** коммит «Гудвилл: ветка без правок вне задачи, влит main».
### Task 2: Типы, запросы и чистые функции
**Files:**
- Modify: `web/src/types/goodwill/goodwill.ts`, `web/src/services/api/goodwill.ts`
- Create: `web/src/utils/goodwill.ts`, `web/src/utils/goodwill.test.ts`
**Interfaces:**
- Produces (типы, по `api/src/schemas/goodwill.py` ветки `feat/goodwill-back`): `GoodwillRow` — суммы `string | null`,
`has_investments: boolean`, `warnings: {code: string; message: string}[]`; `GoodwillTotals` + `charter_capital_amount`;
`GoodwillGroup {head_legal_entity: Ref; totals: GoodwillTotals}`; `GoodwillTableResponse.groups: GoodwillGroup[]`;
`GoodwillRowKeyIn {head_legal_entity_id; subsidiary_legal_entity_id; ownership_share: string | null; include_in_kfo: boolean; comment: string | null}`;
`GoodwillSign = 'all' | 'positive' | 'negative' | 'zero'`.
- Produces (запросы): `saveGoodwillRows(versionId, rows: GoodwillRowKeyIn[]): Promise<GoodwillTableResponse>` (`PUT …/goodwill`, тело `{rows}`);
`saveGoodwillSettings(versionId, {goodwill_enabled})`; `exportGoodwillTable(versionId, sign: GoodwillSign)` (`?sign=`). `saveGoodwillRow` удалить.
- Produces (`utils/goodwill.ts`):
`rowKey(row: Pick<GoodwillRow,'head_legal_entity'|'subsidiary_legal_entity'>): string` (`${head}:${sub}`);
`parseShare(input: string): {ok: true; value: string | null} | {ok: false; error: string}` (пусто → `null`; запятая → точка);
`previewAmounts(row: GoodwillRow, share: string | null, include: boolean): {participation: string | null; goodwill: string | null}`;
`matchesSign(goodwill: string | null, sign: GoodwillSign): boolean`;
`changedRows(rows: GoodwillRow[], edits: Record<string, GoodwillEdit>): GoodwillRowKeyIn[]`, где
`GoodwillEdit = {include_in_kfo?: boolean; ownership_share?: string | null; comment?: string | null}`.
- [ ] **Step 1: тесты** `goodwill.test.ts`:
`parseShare`: `"80,12"` → `{ok, "80.12"}`, `"100"` ok, `""` → `{ok, null}`, `"80,123"`, `"100,01"`, `"-1"`, `"abc"` → `{ok:false}`;
`previewAmounts` (DOC-259025 §11.1): ФВ `"180000.00"`, УК `"100000.00"`, доля `"80"`, отмечена → `{participation:"80000.00", goodwill:"100000.00"}`;
ФВ `"50000.00"` → goodwill `"-30000.00"`; УК `"371.17"`, доля `"99.99"` → participation `"371.13"` (HALF_UP: 371.13288…);
не отмечена или доля `null` или УК `null` → `{null, null}`;
`matchesSign`: `null` → false для всех, кроме `all`; `"0.00"` — только `zero`/`all`;
`changedRows`: правка, совпадающая с исходной строкой, не попадает; снятая галочка у строки без доли даёт `ownership_share: null`.
- [ ] **Step 2:** `npm test -- goodwill` — FAIL; реализовать (`previewAmounts` — в копейках через `toCents`/`fromCents`, доля в сотых долях
процента `BigInt`, `P = round(УК_коп × доля_сотых / 10000)` HALF_UP); PASS.
- [ ] **Step 3:** `npx tsc --noEmit` — ошибки только в компонентах вкладки (чинятся в Task 3); коммит «Гудвилл: типы и запросы по API бэка, чистые функции вкладки».
### Task 3: Вкладка по API, DOC-259025 и макету
**Files:**
- Modify: `web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillContent/ConsolidationGoodwillContent.tsx`,
`.../ConsolidationGoodwillTable/{ConsolidationGoodwillTable.tsx,config.tsx,ConsolidationGoodwillTable.css}`,
`.../ConsolidationGoodwillSummary/ConsolidationGoodwillSummary.tsx`
**Interfaces:**
- Consumes: Task 2.
- [ ] **Step 1: отображение.** Суммы — `null` → «—», иначе формат единиц (ноль — `0,00`); строки «Нет вложений» (`!has_investments`) —
серым, подпись «Нет вложений» в колонке сумм, чекбокс и поля недоступны; `warnings` — иконка `WarningOutlined` с `Tooltip` из `message`
у названия ДО; «Всего» блока — из `groups[].totals`, «Итого по листу» — из `totals` (без `sumAmounts`/`Number`).
- [ ] **Step 2: режим «Редактировать».** Правки — `Record<rowKey, GoodwillEdit>`, стартует пустым; P и G в режиме — `previewAmounts`;
доля — `parseShare` с ошибкой под полем; групповой чекбокс отмечает/снимает все действующие строки блока, включая скрытые фильтром,
`indeterminate` при частичном выборе; «Сохранить» недоступно, если у отмеченной действующей строки нет доли, доля с ошибкой или пустой комментарий;
сохранение — один `saveGoodwillRows(changedRows(...))`, ответ заменяет таблицу и закрывает режим; `ApiError` с `body.detail.subsidiary_legal_entity_id`
подсвечивает поле этой строки (`COMMENT_REQUIRED` → комментарий, `INVALID_SHARE` → доля), режим не закрывается; «Отменить изменения» — сброс правок.
- [ ] **Step 3: переключатель.** `Switch` «Учитывать гудвилл в КФО» в панели инструментов, значение `goodwill_enabled`, доступен при `editable`;
смена → `saveGoodwillSettings`, ответ заменяет таблицу; при выключенном — подпись «Гудвилл не учитывается в КФО».
Если `!editable` — у «Редактировать» `Tooltip` с причиной (`LOCK_REQUIRED` — «Возьмите версию в работу», `LOCKED_BY_OTHER` — «Версия в работе у другого пользователя», `VERSION_NOT_EDITABLE` — «Версия недоступна для изменения»).
- [ ] **Step 4: фильтр и экспорт.** Варианты «Все значения», «Положительные», «Отрицательные», «Нулевые» через `matchesSign`; экспорт передаёт текущий `sign`.
- [ ] **Step 5:** `npm run format`, `make -C .. web-check`, `npm test`, `npm run lint` — зелёные; коммит «Гудвилл: вкладка по API и DOC-259025».
### Task 4: UI-спека и проверка на стенде
**Files:**
- Create: `openspec/changes/goodwill-ui/{proposal.md,design.md,tasks.md,specs/goodwill-ui/spec.md}`
- [ ] **Step 1:** OpenSpec change `goodwill-ui` — требования Steps 1–4 Task 3 со сценариями WHEN/THEN, ссылки на контракт и DOC-259025;
`design.md` — раздел `## Decisions made without discussion` (R-1: правки вне задачи откатены; R-2: итоги с бэка; R-3: предпросмотр P/G только по строкам,
итоги — после сохранения); `openspec validate goodwill-ui --strict`.
- [ ] **Step 2: стенд.** Поднять бэк из `.wt/review-222/arm-core` (ветка `feat/goodwill-back`, порт 8000, БД стенда), фронт отсюда
(`REACT_APP_ROOT_PATH=/ npm run start`), версия «РБ 2024 Q3» или любая с ОСВ 58.01; в Playwright пройти: открыть вкладку, сверить
с `31-03-goodwill.png`, «Редактировать» — сверить с `31-03-01-goodwill-edit.png`, ввести долю `80,5` и комментарий, сохранить, снять галочку
у строки без доли и сохранить, переключить «Учитывать гудвилл», фильтр «Нулевые», экспорт. Скриншоты — в scratchpad, найденное — чинить в Task 3.
- [ ] **Step 3:** коммит «Гудвилл: UI-спека goodwill-ui».