# Гудвилл: доводка фронта по ревью (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 `. ## 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` (`PUT …/goodwill`, тело `{rows}`); `saveGoodwillSettings(versionId, {goodwill_enabled})`; `exportGoodwillTable(versionId, sign: GoodwillSign)` (`?sign=`). `saveGoodwillRow` удалить. - Produces (`utils/goodwill.ts`): `rowKey(row: Pick): 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): 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`, стартует пустым; 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».