Andrey Epifancev 1cce1575b8 openspec: архив counter-pairs, Purpose consolidations-ui (#195, AURORA-1746, M-7)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 23:10:42 +04:00

296 lines
30 KiB
Markdown
Raw Permalink 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.

# consolidations-ui Specification
## Purpose
Аналитик работает со списком консолидаций ГК [34] (флажок «Только в работе», «Версия», меню «⋯»), берёт версию в работу или перехватывает её [35], создаёт консолидацию [42] и меняет статус версии из шапки страницы отчёта [30], [33-01]; при уходе со «Сводной ОСВ», если есть пары участников без встречных записей, — предупреждение «Проверьте встречные записи участников» (AURORA-1746, вместо [31-00]).
## Requirements
### Requirement: Список консолидаций ГК
Экран консолидаций ГК [34] SHALL показывать версии из `GET /company-groups/{id}/consolidations` таблицей с колонками
Год, Квартал (К1–К4), Версия, Статус (бейдж, подписи — «Подписи статусов версии»), ГСК, Дата создания, Дата верификации,
Организаций (`members_count`) и колонкой действий: «Взять в работу» (или «В работе»), «К отчётности», меню «⋯». Версии
одного периода SHALL показываться деревом: верхняя строка — первая версия периода из ответа API (по убыванию
`version_no`), остальные — раскрываемыми строками под ней. Колонка «ГСК» SHALL иметь фильтр в заголовке, «Дата создания» —
без фильтра. Пока API не отдаёт ГСК консолидации и дату верификации, колонки «ГСК» и «Дата верификации» SHALL показывать
«—». Рядом с заголовком «Консолидации» — число строк («N шт.»). Клик по строке SHALL выбирать версию для панели
«Организации» и подсвечивать строку.
#### Scenario: Колонки списка
- **WHEN** открыт список консолидаций ГК «Русский бисквит»
- **THEN** у таблицы колонки Год, Квартал, Версия, Статус, ГСК, Дата создания, Дата верификации, Организаций и колонка
действий; у «ГСК» есть фильтр, у «Дата создания» — нет; в «ГСК» и «Дата верификации» — «—»
#### Scenario: Выбор версии
- **WHEN** аналитик кликает по строке версии 2024 К3
- **THEN** строка подсвечена, панель «Организации» показывает состав этой версии
### Requirement: Колонка «Версия»
Колонка «Версия» SHALL быть пустой у версии с `version_no = 1` и показывать номер у версий с `version_no ≥ 2`.
#### Scenario: Единственная версия периода
- **WHEN** у консолидации 2025 К1 одна версия (`version_no = 1`)
- **THEN** ячейка «Версия» пустая
#### Scenario: Вторая версия периода
- **WHEN** у консолидации 2024 К1 версии 2 и 1
- **THEN** у строки версии 2 в колонке «Версия» — «2», у строки версии 1 — пусто
### Requirement: Фильтр «Только в работе»
Над таблицей SHALL быть флажок «Только в работе», по умолчанию **выключенный**. Включённый флажок SHALL перечитывать список
запросом `GET /company-groups/{id}/consolidations?in_work_only=true` (отбор делает сервер: остаются версии со статусом не
из `VERIFIED`, `CANCELLED`, `ARCHIVED`, `REJECTED`); выключенный — запросом с `in_work_only=false` (или без параметра).
Фронт MUST NOT фильтровать версии по статусу сам.
#### Scenario: Открытие экрана
- **WHEN** аналитик впервые открывает список консолидаций ГК
- **THEN** флажок «Только в работе» снят, запрос — с `in_work_only=false`, в таблице есть верифицированные и отменённые
версии
#### Scenario: Включение фильтра
- **WHEN** аналитик отмечает «Только в работе»
- **THEN** список перечитан с `in_work_only=true`, отменённой версии в таблице нет
### Requirement: Взять в работу
Кнопка «Взять в работу» в строке SHALL открывать модалку «Взять консолидацию в работу?» [35]. Состояние блокировки
модалка SHALL брать из свежей карточки `GET /consolidations/versions/{id}` (`lock`):
- блокировки нет — текст «Консолидация <год> К<n> · <ГК> будет закреплена за вами для редактирования.», кнопки
«Отмена» / «Взять в работу»; «Взять в работу» отправляет `POST /consolidations/versions/{id}/lock` без
`takeover_reason`;
- версию держит другой пользователь — текст «Консолидация взята в работу <ФИО>. Укажите причину замены.» и поле причины
(обязательно, ≤ 1000 символов, строка из пробелов считается пустой); «Взять в работу» неактивна, пока причина пуста, и
отправляет `POST …/lock {takeover_reason}` (перехват).
После 200/201 модалка SHALL закрыться с сообщением «Консолидация взята в работу.» и перечитать список. Ответ 409
`LOCKED_BY_OTHER` на запрос без причины (версию успели взять) SHALL переводить модалку в режим перехвата с предупреждением
«<ФИО> только что взял(-а) консолидацию в работу.» и пустым полем причины. Ответ 409 `VERSION_NOT_EDITABLE` SHALL
закрывать модалку с сообщением «Нельзя взять в работу отменённую версию.». Прочие ошибки (403 без права
`CONSOLIDATION_EDIT`, иные 409/422) SHALL показываться текстом сервера (`ApiError.message`), модалка остаётся открытой. ФИО
в тексте модалки не получает вторую точку («Соколов А. В. Укажите причину замены.»). У версии со статусом `CANCELLED` кнопка
«Взять в работу» SHALL быть неактивной (условие — по коду статуса, а не по подписи); у версии, которую держит текущий
пользователь, вместо неё SHALL показываться неактивная кнопка «В работе».
#### Scenario: Свободная версия
- **WHEN** аналитик нажимает «Взять в работу» у версии без блокировки и подтверждает
- **THEN** отправлен `POST …/lock` без тела причины, модалка закрыта, в строке — неактивная «В работе»
#### Scenario: Перехват с причиной
- **WHEN** версию держит «Соколов А. В.», аналитик нажимает «Взять в работу»
- **THEN** модалка показывает «Консолидация взята в работу Соколов А. В. Укажите причину замены.» и поле причины;
«Взять в работу» неактивна, пока причина пуста; после ввода «Отпуск» отправлен `POST …/lock {takeover_reason: "Отпуск"}`
#### Scenario: Версию взяли, пока модалка открыта
- **WHEN** в модалке свободной версии аналитик нажимает «Взять в работу», а сервер отвечает 409 `LOCKED_BY_OTHER`
- **THEN** модалка остаётся открытой в режиме перехвата с предупреждением «<ФИО> только что взял(-а) консолидацию в
работу.» и пустым полем причины
#### Scenario: Отменённая версия
- **WHEN** версия в статусе «Отменена»
- **THEN** «Взять в работу» в её строке неактивна
#### Scenario: Нет права
- **WHEN** пользователь без `CONSOLIDATION_EDIT` подтверждает перехват, сервер отвечает 403
- **THEN** показан текст ответа сервера («недостаточно прав: требуется CONSOLIDATION_EDIT»), модалка открыта
#### Scenario: Панель «Организации» после взятия в работу
- **WHEN** в списке выбрана версия, аналитик берёт её в работу
- **THEN** список и карточка выбранной версии перечитаны, «Ответственный» в панели «Организации» — текущий владелец
блокировки
### Requirement: Меню «⋯» строки консолидации
Меню «⋯» строки SHALL содержать пункты «Добавить версию», «Отключить подтверждение КФО», «История изменений» с иконками
[43]. Пока функции нет, пункты SHALL быть неактивными (`disabled`) с подсказкой «Будет позже»; нажатие на неактивный пункт
MUST NOT выполнять действий (ни перехода, ни всплывающего сообщения).
#### Scenario: Неактивный пункт
- **WHEN** аналитик открывает «⋯» и нажимает «История изменений»
- **THEN** пункт неактивен, видна подсказка «Будет позже», переход в «Журнал действий» не происходит
### Requirement: Панель «Организации»
Пока в списке не выбрана версия, панель «Организации» SHALL показывать заголовок «Организации» с подписью «Выберите
консолидацию» и текст «Выберите консолидацию выше» вместо таблицы. В колонках «ОФР» и «Бух. баланс» статус «Отсутствует»
SHALL быть нейтральным серым бейджем (как в легенде чек-листа [37]); красный — только у «Ошибка».
#### Scenario: Версия не выбрана
- **WHEN** открыт список консолидаций, строка не выбрана
- **THEN** панель «Организации» без таблицы и без «0 шт.», в ней текст «Выберите консолидацию выше»
#### Scenario: Документ отсутствует
- **WHEN** у ЮЛ выбранной версии нет ББ
- **THEN** в колонке «Бух. баланс» — серый бейдж «Отсутствует»
### Requirement: Подписи статусов версии
Фронт SHALL показывать статусы версии одними подписями в списке, фильтре колонки «Статус», шапке карточки и модалке
«Изменить статус»: `INITIATED` — «Инициация», `DOCUMENTS_PROCESSING` — «Обработка документов», `DATA_CORRECTION` —
«Корректировка данных», `CONSOLIDATION_RF` — «Консолидация РФ», `VERIFICATION_RF` — «Верификация РФ», `CONSOLIDATION_GO` —
«Консолидация ГО», `VERIFICATION_GO` — «Верификация ГО», `VERIFIED` — «Верифицирован», `CANCELLED` — «Отменена»,
`ADJUSTMENT` — «Корректировка», `ARCHIVED` — «В архиве», `REJECTED` — «Отклонён».
#### Scenario: Корректировка данных
- **WHEN** версия в статусе `DATA_CORRECTION`
- **THEN** в списке и в карточке — бейдж «Корректировка данных»
#### Scenario: Отменённая версия
- **WHEN** версия в статусе `CANCELLED`
- **THEN** в списке и в карточке — бейдж «Отменена»
### Requirement: Вид модалки «Новая консолидация»
Модалка [42] (состав и создание — спека `consolidation-setup`) SHALL показывать поля без звёздочек обязательности и кнопку
«+ Создать» (иконка «плюс»), неактивную, пока не выбраны ГК, ГСК, год, квартал и не отмечено не меньше двух ЮЛ. В блоке
«ЮЛ консолидации» строка ЮЛ SHALL содержать флажок, наименование (длинное — с многоточием) и ИНН отдельной колонкой,
выровненной по правому краю независимо от длины наименования; над списком — поиск по наименованию, под списком —
«Выбрано: N / M».
#### Scenario: Выравнивание ИНН
- **WHEN** в блоке «ЮЛ консолидации» есть ЮЛ «ВМГ, ООО» и «ПЕКАРНЯ СЧАСТЛИВОЧНАЯ, ООО»
- **THEN** их ИНН стоят в одной колонке на одной вертикали
#### Scenario: Одно ЮЛ
- **WHEN** выбраны ГК, ГСК, год, квартал и отмечено одно ЮЛ
- **THEN** «+ Создать» неактивна
### Requirement: Кнопка «Изменить статус» в карточке версии
Шапка карточки версии [30] SHALL показывать справа «Статус отчёта» (бейдж) и кнопку «Изменить статус». Кнопка SHALL быть
активной, только если версия взята в работу текущим пользователем (`lock.locked_by` карточки = `id` из `/me`) и для
текущего статуса есть хотя бы один ручной переход (требование «Модалка «Изменить статус»»). Без своей блокировки кнопка
SHALL быть неактивной с подсказкой «Возьмите консолидацию в работу»; при своей блокировке и отсутствии ручных переходов —
неактивной.
#### Scenario: Версия не взята в работу
- **WHEN** аналитик открыл карточку версии `DATA_CORRECTION`, блокировки нет
- **THEN** «Изменить статус» неактивна, подсказка «Возьмите консолидацию в работу»
#### Scenario: Версия у текущего пользователя
- **WHEN** аналитик взял версию `DATA_CORRECTION` в работу и открыл карточку
- **THEN** «Изменить статус» активна
#### Scenario: Нет ручных переходов
- **WHEN** аналитик держит блокировку версии `VERIFIED`
- **THEN** «Изменить статус» неактивна
### Requirement: Модалка «Изменить статус»
Модалка [33-01] SHALL показывать «Текущий этап» (бейдж статуса), «Новый этап» (выпадающий список) и «Комментарий»
(≤ 1000 символов), кнопки «Отмена» / «Изменить статус». Варианты «Нового этапа» SHALL совпадать с ручными переходами бэка
(`MANUAL_TRANSITIONS` в `api/src/services/consolidation_status.py`; API их не отдаёт, фронт держит зеркало в одном месте):
- `INITIATED` → `CANCELLED`;
- `DOCUMENTS_PROCESSING` → `CANCELLED`, `INITIATED`;
- `DATA_CORRECTION` → `CANCELLED`, `INITIATED`;
- из остальных статусов ручных переходов нет.
По умолчанию в «Новом этапе» ничего не выбрано. «Изменить статус» SHALL быть активной, только когда выбран новый этап и
комментарий непуст после обрезки пробелов (комментарий обязателен для обоих переходов). Нажатие SHALL отправлять
`POST /consolidations/versions/{id}/status {to_status, comment}` (комментарий обрезан); при 200 модалка закрывается, а
карточка версии на экране заменяется карточкой из ответа: бейдж, пояснение под заголовком и состояние кнопки «Изменить
статус» берутся из неё (после «Отменена» блокировка снята — кнопка неактивна). Ответы 409 (`VERSION_NOT_EDITABLE`,
`LOCK_REQUIRED`, `LOCKED_BY_OTHER`, `TRANSITION_NOT_ALLOWED`) и 422 (`COMMENT_REQUIRED`) SHALL показываться сообщением об
ошибке, модалка остаётся открытой, статус на экране не меняется.
#### Scenario: Варианты из «Корректировки данных»
- **WHEN** аналитик держит блокировку версии `DATA_CORRECTION` и открывает «Изменить статус»
- **THEN** «Текущий этап» — «Корректировка данных», в «Новом этапе» ничего не выбрано, варианты — «Отменена» и
«Инициация»; «Изменить статус» неактивна
#### Scenario: Отмена версии
- **WHEN** аналитик выбирает «Отменена», пишет комментарий «Дубль» и нажимает «Изменить статус»
- **THEN** отправлен `POST /consolidations/versions/{id}/status {to_status: "CANCELLED", comment: "Дубль"}`; модалка
закрыта; бейдж — «Отменена», «Изменить статус» неактивна; в списке консолидаций у версии статус «Отменена»
#### Scenario: Возврат в «Инициацию»
- **WHEN** аналитик держит блокировку версии `DOCUMENTS_PROCESSING`, выбирает «Инициация» и пишет комментарий
- **THEN** после ответа 200 бейдж — «Инициация», кнопка «Изменить статус» остаётся активной (переход в «Отменена» доступен)
#### Scenario: Комментарий из пробелов
- **WHEN** выбран новый этап, комментарий состоит из пробелов
- **THEN** «Изменить статус» неактивна
#### Scenario: Переход отклонён сервером
- **WHEN** пока модалка открыта, статус версии изменился, и сервер отвечает 409 `TRANSITION_NOT_ALLOWED`
- **THEN** показано сообщение об ошибке, модалка открыта, бейдж на экране прежний
### Requirement: Пояснение статуса под заголовком карточки
Под заголовком карточки версии «<ГК> — <год> <квартал>» SHALL показываться пояснение текущего статуса, а не его
название: `CONSOLIDATION_RF` — «Автоматическая консолидация, ручные проверки и сравнение КФО клиента и банка», `VERIFIED` —
«Отчет верифицирован ГО, КА ГО внес в систему номер и дату решения УОБ» (макет [30], [33-01]); `INITIATED`,
`DOCUMENTS_PROCESSING`, `DATA_CORRECTION`, `CANCELLED` — тексты аналитика, до их получения — временные из ТЗ «Статусная
модель» (`design.md`, решение без обсуждения (б)): `INITIATED` — «Система зафиксировала состав ГСК на отчетную дату и
инициатора (КА РФ или КА ГО)», `DOCUMENTS_PROCESSING` — «КА загружает документы; система отправляет pdf файлы в ИС
Сканирование, производит парсинг ОСВ, бухгалтерского баланса и ОФР», `DATA_CORRECTION` — «Выявлены некорректные данные в
ИС Сканирование, проводится ручная корректировка. Пользователь валидирует распарсенные данные и вносит правки»,
`CANCELLED` — «Заявка на формирование/подтверждение КФО аннулирована по инициативе пользователя». Тексты держатся в одном
месте рядом с подписями статусов.
#### Scenario: Пояснение «Корректировки данных»
- **WHEN** открыта карточка версии `DATA_CORRECTION`
- **THEN** под заголовком — пояснение статуса, а не слова «Корректировка данных»
#### Scenario: Пояснение после смены статуса
- **WHEN** версия переведена в «Отменена» из модалки «Изменить статус»
- **THEN** пояснение под заголовком сменилось на пояснение статуса `CANCELLED` без перезагрузки страницы
### Requirement: Предупреждение о несохранённых правках в модалках консолидаций
Пока открыта модалка с начатым вводом, браузер SHALL спрашивать подтверждение при закрытии или перезагрузке вкладки
(стандартный диалог `beforeunload`, текст задаёт браузер): «Взять консолидацию в работу?» — введена причина перехвата
(непустая после обрезки пробелов); «Изменить статус» — выбран новый этап или введён комментарий; «Новая консолидация» —
выбрано хотя бы одно из полей ГК, ГСК, год, квартал. После закрытия модалки (отправка или «Отмена») предупреждения нет.
Переход по меню приложения при открытой модалке закрыт её маской.
#### Scenario: Причина перехвата введена
- **WHEN** в модалке перехвата введена причина «Отпуск» и аналитик перезагружает вкладку
- **THEN** браузер показывает диалог подтверждения ухода
#### Scenario: Ничего не введено
- **WHEN** модалка «Изменить статус» открыта, этап не выбран, комментарий пуст, аналитик закрывает вкладку
- **THEN** вкладка закрывается без диалога
### Requirement: Текущий пользователь и его роли в боковом меню
Клик по логотипу «АРМ КА» в раскрытом боковом меню SHALL раскрывать блок с данными из `GET /me`: ФИО, логин (email),
роли (названия из `role_details[].name` через запятую; если названия нет — код роли; без ролей — «не назначена») и филиал.
Повторный клик или клик вне блока SHALL закрывать его. Названия — только для показа, логика фронта по ним не решается.
#### Scenario: Пользователь с несколькими ролями
- **WHEN** пользователь с ролями KA_RF и RKA кликает по логотипу «АРМ КА»
- **THEN** раскрывается блок с его ФИО, email, подписью «Роли» и названиями обеих ролей (не кодами) и филиалом
#### Scenario: Название роли недоступно
- **WHEN** в `role_details` нет названия для роли пользователя
- **THEN** в блоке показан код этой роли
### Requirement: Предупреждение «Нет встречных записей»
При уходе со вкладки «ОСВ» на другую вкладку отчёта версии фронт SHALL показывать модалку «Проверьте встречные записи
участников», если в сводке есть пары `counter_pairs` и этот список пар ещё не показывался в модалке для этой версии в
текущей сессии браузера. Текст: «Нет встречных записей: N пар у M участников.» (N — число пар, M — число разных `missing_id`;
слова согласуются с числом: «1 пара у 1 участника», «2 пары у 2 участников», «5 пар у 3 участников»)
и «Возможно, контрагент среди строк без ИНН, его ИНН не найден в справочнике или это пропуск в учёте. Ещё K строк без
ИНН учтены как внешние.» (K —
`unmapped_total`; при K = 0 вторая фраза без последнего предложения). Кнопки:
- «Показать пары» — закрыть модалку без перехода и открыть на вкладке «ОСВ» режим «Встречные записи» с первой парой;
- «Остаться» — закрыть модалку без перехода;
- «Перейти» — выполнить переход.
Показ запоминается в `sessionStorage` браузера по версии вместе со списком пар (упорядоченные пары `holder_id → missing_id`);
повторный уход с «ОСВ» в той же сессии модалку не показывает, пока список пар не изменился (например, сопоставление
закрыло одну из пар — модалка покажет оставшиеся). Недоступное хранилище — модалка показывается при каждом уходе, ошибкой это
не считается. Без пар (`counter_pairs` пуст) переход SHALL выполняться без модалки при любом `unmapped_total`. В БД ничего
не пишется (решение владельца 27.10.2026). Для версии в завершённом статусе (`VERIFIED`, `CANCELLED`, `ARCHIVED`,
`REJECTED`) модалка не показывается. Пока шагов после ОСВ нет, их вкладки неактивны и модалка не вызывается.
#### Scenario: Пара без встречных записей
- **WHEN** в сводке одна пара (Русский бисквит ← Пекарня Счастливочная), `unmapped_total = 1260`, и аналитик выбирает
вкладку «Шахматка»
- **THEN** показана модалка «Нет встречных записей: 1 пара у 1 участника…»; «Перейти» открывает шахматку
#### Scenario: Повтор в той же сессии
- **WHEN** аналитик вернулся на «ОСВ» и снова выбирает «Шахматку», список пар не изменился
- **THEN** шахматка открывается без модалки
#### Scenario: Список пар изменился
- **WHEN** после показа модалки аналитик сопоставил контрагента, закрыв одну из двух пар, и выбирает «Шахматку»
- **THEN** модалка показана снова с «Нет встречных записей: 1 пара у 1 участника…»
#### Scenario: Только строки без ИНН
- **WHEN** `counter_pairs` пуст, `unmapped_total = 214`, и аналитик выбирает «Шахматку»
- **THEN** шахматка открывается без модалки
#### Scenario: Показать пары
- **WHEN** в модалке нажато «Показать пары»
- **THEN** вкладка «ОСВ» остаётся, открыт режим «Встречные записи» с первой парой