From 64284a29179c43b29b52d52bd72a15d4f242dd5e Mon Sep 17 00:00:00 2001 From: Veronika Tsygankova Date: Thu, 12 Nov 2026 09:07:20 +0300 Subject: [PATCH 1/2] =?UTF-8?q?[AURORA-2006]=20-=20=D0=93=D1=83=D0=B4?= =?UTF-8?q?=D0=B2=D0=B8=D0=BB=D0=BB=20(=D0=B2=D0=BA=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=BA=D0=B0):=20=D0=BC=D0=B5=D0=BB=D0=BA=D0=B8=D0=B5=20=D0=B4?= =?UTF-8?q?=D0=B5=D1=84=D0=B5=D0=BA=D1=82=D1=8B=20=D0=BF=D0=BE=D1=81=D0=BB?= =?UTF-8?q?=D0=B5=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ответы 404/409 при «Сохранить» перечитывают таблицу - Исправлено поведение Стрелки раскрытия блока - Вывод сообщения об ошибке - Убран лишние лишний CSS из шахматки - hooks/useFullScreen.tsx => hooks/useFullScreen.ts, убрано удблирование типов --- .../{useFullScreen.tsx => useFullscreen.ts} | 0 web/src/types/goodwill/goodwill.ts | 9 +- .../ConsolidationGoodwillContent.tsx | 206 ++++++++++-------- .../ConsolidationGoodwillTable.css | 10 - .../ConsolidationGoodwillTable.tsx | 6 +- 5 files changed, 126 insertions(+), 105 deletions(-) rename web/src/hooks/{useFullScreen.tsx => useFullscreen.ts} (100%) diff --git a/web/src/hooks/useFullScreen.tsx b/web/src/hooks/useFullscreen.ts similarity index 100% rename from web/src/hooks/useFullScreen.tsx rename to web/src/hooks/useFullscreen.ts diff --git a/web/src/types/goodwill/goodwill.ts b/web/src/types/goodwill/goodwill.ts index 96439be..21f33fc 100644 --- a/web/src/types/goodwill/goodwill.ts +++ b/web/src/types/goodwill/goodwill.ts @@ -1,8 +1,5 @@ /** DTO таблицы гудвилла — по `api/src/schemas/goodwill.py` и `../docs/dev/api-contract-goodwill.md`. Деньги и доля — строки. */ -export interface Ref { - id: string; - name: string; -} +import type { ReadOnlyReason, Ref } from '../corrections'; export interface GoodwillWarning { code: 'CAPITAL_SOURCE_MISSING' | 'CAPITAL_SOURCE_AMBIGUOUS' | 'OWNERSHIP_TOTAL_EXCEEDED' | string; @@ -42,13 +39,11 @@ export interface GoodwillGroup { totals: GoodwillTotals; } -export type GoodwillReadOnlyReason = 'VERSION_NOT_EDITABLE' | 'LOCK_REQUIRED' | 'LOCKED_BY_OTHER'; - export interface GoodwillTableResponse { version_id: string; goodwill_enabled: boolean; editable: boolean; - read_only_reason: GoodwillReadOnlyReason | null; + read_only_reason: ReadOnlyReason | null; rows: GoodwillRow[]; groups: GoodwillGroup[]; totals: GoodwillTotals; diff --git a/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillContent/ConsolidationGoodwillContent.tsx b/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillContent/ConsolidationGoodwillContent.tsx index 21d0fc3..37e40c1 100644 --- a/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillContent/ConsolidationGoodwillContent.tsx +++ b/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillContent/ConsolidationGoodwillContent.tsx @@ -1,18 +1,18 @@ import { DownloadOutlined, EditOutlined, FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons'; -import { Alert, Button, ConfigProvider, Select, Space, Switch, Tooltip, Typography } from 'antd'; +import { Alert, Button, ConfigProvider, Flex, Select, Space, Switch, Tooltip, Typography } from 'antd'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; import { ApiError } from '../../../api/client'; import { ConsolidationFormatTableFilter } from '../../../components/ConsolidationFormatTableFilter/ConsolidationFormatTableFilter'; import { type ColumnDef, ColumnSettings, useVisibleColumns } from '../../../components/GenericTable'; -import { useFullscreen } from '../../../hooks/useFullScreen'; +import { useFullscreen } from '../../../hooks/useFullscreen'; import { exportGoodwillTable, fetchGoodwillTable, saveGoodwillRows, saveGoodwillSettings } from '../../../services/api/goodwill'; import type { VersionCard } from '../../../types/consolidations/version_members'; import type { GoodwillEdit, GoodwillSign, GoodwillTableResponse } from '../../../types/goodwill/goodwill'; import { saveBlob, xlsxFilename } from '../../../utils/download'; import { changedRows, effectiveRow, matchesSign, parseShare, rowKey } from '../../../utils/goodwill'; import { useNotificationApi } from '../../../utils/useNotification'; -import { readOnlyHint } from '../../CorrectionTable/readOnly'; +import { errorOutcome, readOnlyHint } from '../../CorrectionTable/readOnly'; import { ConsolidationGoodwillTable } from '../ConsolidationGoodwillTable/ConsolidationGoodwillTable'; import { type GoodwillTreeRow, type RowIssues, formatMoney, getGoodwillColumns } from '../ConsolidationGoodwillTable/config'; @@ -45,6 +45,7 @@ export const GoodwillContent = ({ card }: { card: VersionCard | null }) => { const [data, setData] = useState(null); const [loading, setLoading] = useState(false); + const [loadError, setLoadError] = useState(null); const [saving, setSaving] = useState(false); const [settingsSaving, setSettingsSaving] = useState(false); const [decimals, setDecimals] = useState(2); @@ -60,10 +61,14 @@ export const GoodwillContent = ({ card }: { card: VersionCard | null }) => { const loadData = useCallback( async (vId: string) => { setLoading(true); + setLoadError(null); try { setData(await fetchGoodwillTable(vId)); } catch (e) { - if (e instanceof ApiError) notification.error({ description: e.message }); + const message = e instanceof ApiError ? e.message : 'Не удалось загрузить таблицу гудвилла'; + setData(null); + setLoadError(message); + notification.error({ description: message }); } finally { setLoading(false); } @@ -158,6 +163,16 @@ export const GoodwillContent = ({ card }: { card: VersionCard | null }) => { setIsEditMode(false); }; + // сервер закрыл правки (перечитали после 409, версию забрали или закрыли) — выходим из редактирования: + // иначе экран остаётся редактируемым и каждое «Сохранить» снова получает 409 + useEffect(() => { + if (data && !data.editable) { + setEdits({}); + setServerIssues({}); + setIsEditMode(false); + } + }, [data]); + const handleSave = async () => { if (!version_id || !data) return; if (pending.length === 0) { @@ -170,13 +185,21 @@ export const GoodwillContent = ({ card }: { card: VersionCard | null }) => { closeEditing(); notification.success({ description: 'Изменения сохранены' }); } catch (e) { + // 409 (версию закрыли, блокировку забрали) и 404 (пары уже нет) — перечитываем + // таблицу, строку не подсвечиваем; правки в несуществующих парах при пересчёте cбрасываем + const out = errorOutcome(e, 'Не удалось сохранить изменения'); + notification.error({ description: out.message }); + if (out.reload) { + setServerIssues({}); + loadData(version_id); + return; + } const detail = rowErrorDetail(e); if (detail) { const key = `${detail.head_legal_entity_id}:${detail.subsidiary_legal_entity_id}`; const field = ERROR_FIELD[detail.code ?? ''] ?? 'ownership_share'; setServerIssues((prev) => ({ ...prev, [key]: { ...prev[key], [field]: detail.message } })); } - if (e instanceof ApiError) notification.error({ description: e.message }); } finally { setSaving(false); } @@ -188,7 +211,9 @@ export const GoodwillContent = ({ card }: { card: VersionCard | null }) => { try { setData(await saveGoodwillSettings(version_id, { goodwill_enabled: enabled })); } catch (e) { - if (e instanceof ApiError) notification.error({ description: e.message }); + const out = errorOutcome(e, 'Не удалось изменить настройку гудвилла'); + notification.error({ description: out.message }); + if (out.reload) loadData(version_id); } finally { setSettingsSaving(false); } @@ -229,91 +254,98 @@ export const GoodwillContent = ({ card }: { card: VersionCard | null }) => { /> )} -
- -
- - - Гудвилл - + + {sign !== 'all' ? : null} - - {data?.goodwill_enabled - ? `В КФО: ${formatMoney(data.totals.goodwill_in_kfo_amount, units, decimals)}` - : 'Гудвилл не учитывается в КФО'} - - -
-
- - - + + - - - + +
- - } - /> - + } + /> + + )} ); diff --git a/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/ConsolidationGoodwillTable.css b/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/ConsolidationGoodwillTable.css index 322ce45..1da9d38 100644 --- a/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/ConsolidationGoodwillTable.css +++ b/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/ConsolidationGoodwillTable.css @@ -1,18 +1,8 @@ -.goodwillTable .empty-cell { - background-color: #f2f4f7; -} -.goodwillTable .ant-table-tbody > tr:hover > td.empty-cell { - background-color: #f2f4f7 !important; -} - .goodwillTable .ant-table-summary { border-end-end-radius: 8px !important; border-end-start-radius: 8px !important; } -.goodwillTable .ant-table-cell-fix { - background-color: #f8fafc !important; -} .goodwillTable .resRow { background-color: #e6f4ea !important ; } diff --git a/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/ConsolidationGoodwillTable.tsx b/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/ConsolidationGoodwillTable.tsx index 4ac6abf..cad7c23 100644 --- a/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/ConsolidationGoodwillTable.tsx +++ b/web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable/ConsolidationGoodwillTable.tsx @@ -57,6 +57,10 @@ export const ConsolidationGoodwillTable: React.FC = isFullscreen = false, }) => { const treeData = useMemo(() => buildTree(data, rows), [data, rows]); + const expandIconColumnIndex = Math.max( + 0, + columns.findIndex((col) => col.key === 'name'), + ); return ( @@ -72,7 +76,7 @@ export const ConsolidationGoodwillTable: React.FC = bordered title={() => toolbar} key={treeData.map((g) => g.key).join('|')} - expandable={{ defaultExpandAllRows: true, expandIconColumnIndex: 1 }} + expandable={{ defaultExpandAllRows: true, expandIconColumnIndex }} summary={() => } rowClassName={rowClassName} /> -- 2.47.2 From 39e942b548c198e54ccd64b97d8bc42e7fa060f8 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Thu, 12 Nov 2026 11:18:47 +0400 Subject: [PATCH 2/2] =?UTF-8?q?openspec:=20goodwill-ui-errors=20=E2=80=94?= =?UTF-8?q?=20409/404,=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B0=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=BA=D0=B8=20=D0=B8=20=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=B5=D0=BB=D0=BA=D0=B0=20=D0=B1=D0=BB=D0=BE=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B3=D1=83=D0=B4=D0=B2=D0=B8=D0=BB=D0=BB=D0=B0=20(AU?= =?UTF-8?q?RORA-2006)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../changes/goodwill-ui-errors/proposal.md | 37 ++++++++ .../specs/goodwill-ui/spec.md | 93 +++++++++++++++++++ openspec/changes/goodwill-ui-errors/tasks.md | 9 ++ 3 files changed, 139 insertions(+) create mode 100644 openspec/changes/goodwill-ui-errors/proposal.md create mode 100644 openspec/changes/goodwill-ui-errors/specs/goodwill-ui/spec.md create mode 100644 openspec/changes/goodwill-ui-errors/tasks.md diff --git a/openspec/changes/goodwill-ui-errors/proposal.md b/openspec/changes/goodwill-ui-errors/proposal.md new file mode 100644 index 0000000..e68d1b3 --- /dev/null +++ b/openspec/changes/goodwill-ui-errors/proposal.md @@ -0,0 +1,37 @@ +# Proposal + +## Why + +AURORA-2006 (PR arm-core #244): дефекты вкладки «Гудвилл», отложенные финальным ревью PR #220 (AURORA-1544). +- Ответы 409 и 404 на «Сохранить» не перечитывали таблицу: если версию забрал другой пользователь, экран оставался + редактируемым и каждое «Сохранить» снова получало 409; при 404 по исчезнувшей паре подсвечивалась доля строки. +- Стрелка раскрытия блока была привязана к номеру колонки: если скрыть «Учет в отчете» в «Настройке таблицы», стрелка + уезжала в другую колонку. +- После ошибки загрузки таблица писала «Вложений по 58.01 между компаниями периметра нет» вместо ошибки. + +## What Changes + +- 409 и 404 на «Сохранить» и на переключатель «Учитывать гудвилл в КФО» — уведомление с текстом сервера и перечитывание + таблицы, без подсветки строки; если после перечитывания `editable = false`, режим редактирования закрывается и + введённое сбрасывается. Так же, как в таблице корректировок (`errorOutcome`). +- Стрелка раскрытия блока — в колонке «Наименование дочерней организации», где бы она ни стояла; колонка скрыта — в первой + видимой. +- Ошибка загрузки — текст ошибки и кнопка «Повторить» вместо таблицы. + +## Capabilities + +### Modified Capabilities + +- `goodwill-ui` — «Таблица гудвилла»: стрелка блока и ошибка загрузки; «Редактирование таблицы гудвилла»: 409/404 при + сохранении; «Переключатель, фильтр и экспорт гудвилла»: 409/404 при переключении. + +## Impact + +Только фронт arm-core: `web/src/widgets/ConsolidationGoodwill/*`, `web/src/types/goodwill/goodwill.ts` (типы `Ref` и +причина «только чтение» — из `types/corrections.ts`), `web/src/hooks/useFullscreen.ts` (переименован из +`useFullScreen.tsx`). API, DBML, правила `05-domain-rules.md` не меняются. + +## Non-goals + +- Сверка предпросмотра P/G с ответом сервера на реальной паре 58.01 — проверка после выкладки (AURORA-2006, раздел + «Проверить после выкладки»), не код. diff --git a/openspec/changes/goodwill-ui-errors/specs/goodwill-ui/spec.md b/openspec/changes/goodwill-ui-errors/specs/goodwill-ui/spec.md new file mode 100644 index 0000000..c937d37 --- /dev/null +++ b/openspec/changes/goodwill-ui-errors/specs/goodwill-ui/spec.md @@ -0,0 +1,93 @@ +## MODIFIED Requirements + +### Requirement: Таблица гудвилла +Вкладка `goodwill` страницы отчёта версии SHALL загружать таблицу одним запросом `GET /consolidations/versions/{id}/goodwill` +(контракт `../docs/dev/api-contract-goodwill.md`) и показывать блоки по ГО в порядке ответа: строка ГО, строки ДО, строка +«Всего» блока из `groups[].totals`, внизу «Итого по листу» из `totals`. Колонки — по макету `[31-03]`: «Учет в отчете», +«Наименование дочерней организации», «Доля владения», «Фондовые вложения головной организации», «Доля участия головной +организации в уставном капитале», «Уставный капитал», «Гудвилл (+/-)», «Комментарий». Сумма `null` SHALL показываться +прочерком, ноль — нулём в выбранных единицах; итоги фронт MUST NOT считать сам. Стрелка раскрытия блока SHALL стоять в +колонке «Наименование дочерней организации» при любом наборе видимых колонок; если эта колонка скрыта — в первой видимой. +Если загрузка не удалась, вместо таблицы SHALL показываться текст ошибки (сообщение сервера или «Не удалось загрузить +таблицу гудвилла») и кнопка «Повторить»; сообщение «Вложений по 58.01 между компаниями периметра нет» MUST NOT +показываться при ошибке. + +#### Scenario: Нулевой и нерассчитанный гудвилл +- **WHEN** у одной строки `goodwill_amount = "0.00"`, у другой `null` +- **THEN** первая показывает «0,00», вторая — «—» + +#### Scenario: Строка «Нет вложений» +- **WHEN** у строки `has_investments = false` +- **THEN** строка серая, с меткой «Нет вложений», чекбокс недоступен, суммы — прочерки, сохранённые доля и комментарий видны + +#### Scenario: Предупреждения +- **WHEN** у строки есть `warnings` (например, `CAPITAL_SOURCE_MISSING`) +- **THEN** рядом с названием ДО иконка предупреждения, по наведению — тексты `message` + +#### Scenario: Стрелка блока при скрытой колонке +- **WHEN** в «Настройке таблицы» снята колонка «Учет в отчете» +- **THEN** стрелка раскрытия блока остаётся в колонке «Наименование дочерней организации» + +#### Scenario: Ошибка загрузки +- **WHEN** `GET …/goodwill` отвечает ошибкой +- **THEN** вместо таблицы — текст ошибки и «Повторить»; «Повторить» запрашивает таблицу заново и при успехе показывает её + +### Requirement: Редактирование таблицы гудвилла +Кнопка «Редактировать» SHALL быть доступна при `editable = true`; иначе — недоступна с подсказкой причины +(`read_only_reason`). В режиме редактирования (баннер «Доступны для редактирования столбцы «Доля владения» и +«Комментарий».») у действующих строк доступна галочка; у отмеченной действующей строки — поля доли и комментария, P и G +строки SHALL пересчитываться предварительно по введённой доле. Доля — 0–100, не больше двух знаков, запятая или точка; +ошибочная доля MUST NOT обрезаться или подменяться, а показывается ошибкой у поля. «Сохранить» SHALL быть недоступно, +пока у отмеченной действующей строки нет корректной доли или комментария. Групповой чекбокс блока SHALL отмечать и снимать +все действующие строки блока, включая скрытые фильтром, и показывать промежуточное состояние при частичном выборе. +«Отменить изменения» SHALL возвращать последнее сохранённое состояние. На ответ 409 (версия закрыта, блокировку забрали) +или 404 (пары уже нет) при «Сохранить» экран SHALL показать уведомление с текстом сервера и перечитать таблицу, строку +MUST NOT подсвечивать; если после перечитывания `editable = false`, режим редактирования SHALL закрыться, введённое — +сброситься. + +#### Scenario: Сохранение набора +- **WHEN** аналитик отметил две строки, ввёл доли «80,5» и «100» и комментарии и нажал «Сохранить» +- **THEN** уходит один `PUT …/goodwill` только с изменёнными строками; ответ заменяет таблицу, режим редактирования закрывается + +#### Scenario: Снятие галочки у строки без доли +- **WHEN** аналитик снимает галочку у строки, где доля не вводилась, и сохраняет +- **THEN** строка уходит с `ownership_share: null` и сохраняется без ошибки + +#### Scenario: Ошибка строки от сервера +- **WHEN** сервер отвечает 422 `COMMENT_REQUIRED` с ключом пары +- **THEN** поле комментария этой строки подсвечено ошибкой, режим редактирования не закрыт, введённое не потеряно + +#### Scenario: Лишние знаки доли +- **WHEN** аналитик вводит долю «80,123» +- **THEN** у поля ошибка «Число от 0 до 100, не больше двух знаков после запятой», значение в поле не меняется, «Сохранить» недоступно + +#### Scenario: Версию забрал другой пользователь +- **WHEN** аналитик в режиме редактирования нажимает «Сохранить», сервер отвечает 409 `LOCKED_BY_OTHER`, перечитанная + таблица приходит с `editable = false` +- **THEN** уведомление с текстом сервера, режим редактирования закрыт, показаны сохранённые значения, «Редактировать» + недоступна с подсказкой «Консолидацию редактирует другой пользователь» + +#### Scenario: Пары больше нет +- **WHEN** сервер отвечает на «Сохранить» 404 по паре, которой больше нет в версии +- **THEN** уведомление с текстом сервера, таблица перечитана, ни одна строка не подсвечена; правки этой пары в запрос + больше не попадают + +### Requirement: Переключатель, фильтр и экспорт гудвилла +Панель вкладки SHALL содержать переключатель «Учитывать гудвилл в КФО» (`PUT …/goodwill-settings`, доступен при +`editable` вне режима редактирования) с подписью суммы `goodwill_in_kfo_amount` или «Гудвилл не учитывается в КФО»; +фильтр «Гудвилл»: «Все значения», «Положительные», «Отрицательные», «Нулевые» (нерассчитанный гудвилл — только во «Все +значения»); единицы и знаки после запятой; «Настройка таблицы»; «Экспорт» — `GET …/goodwill/export` с текущим фильтром +(`sign`); «На весь экран». На ответ 409 или 404 при переключении экран SHALL показать уведомление с текстом сервера и +перечитать таблицу. + +#### Scenario: Экспорт с фильтром +- **WHEN** выбран фильтр «Положительные» и нажат «Экспорт» +- **THEN** запрос уходит с `sign=positive` + +#### Scenario: Выключение гудвилла +- **WHEN** аналитик выключает переключатель +- **THEN** таблица перечитывается из ответа, под переключателем подпись «Гудвилл не учитывается в КФО», ручной ввод строк не меняется + +#### Scenario: Переключатель после закрытия версии +- **WHEN** аналитик переключает «Учитывать гудвилл в КФО», а сервер отвечает 409 `VERSION_NOT_EDITABLE` +- **THEN** уведомление с текстом сервера, таблица перечитана, переключатель недоступен diff --git a/openspec/changes/goodwill-ui-errors/tasks.md b/openspec/changes/goodwill-ui-errors/tasks.md new file mode 100644 index 0000000..165af17 --- /dev/null +++ b/openspec/changes/goodwill-ui-errors/tasks.md @@ -0,0 +1,9 @@ +# Tasks + +- [x] 1.1 arm-core `web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillContent` — 409/404 при «Сохранить» и при + переключателе: `errorOutcome`, перечитывание, выход из редактирования при `editable = false` (PR #244, автор) +- [x] 1.2 arm-core `web/src/widgets/ConsolidationGoodwill/ConsolidationGoodwillTable` — стрелка блока по ключу колонки + `name`; лишний CSS шахматки удалён (PR #244, автор) +- [x] 1.3 arm-core `web/` — ошибка загрузки с «Повторить»; `useFullscreen.ts`; типы из `types/corrections.ts` (PR #244, автор) +- [x] 1.4 Ревью: прогон в браузере на ГК «Пример» 2025 К3 — скрытие «Учет в отчете», 409 на `PUT …/goodwill` с + перечитыванием `editable = false`, 500 на `GET …/goodwill` и «Повторить» (ответы сервера подменены в Playwright) -- 2.47.2