Откатены к 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>
131 lines
14 KiB
Markdown
131 lines
14 KiB
Markdown
# Гудвилл: доводка фронта по ревью (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».
|