180 lines
20 KiB
Markdown
180 lines
20 KiB
Markdown
# consolidated-osv-ui Specification
|
||
|
||
## Purpose
|
||
Аналитик видит сводную ОСВ версии [30]: сводку с несопоставленными строками, пробелы по кварталам, таблицу с фильтрами в заголовках колонок и экспорт в xlsx; сопоставляет контрагента строки с ЮЛ справочника [32].
|
||
|
||
## Requirements
|
||
|
||
### Requirement: Сводка версии
|
||
Экран «Сводная ОСВ» [30] SHALL показывать шапку версии (ГК, период, номер, статус) и сводку
|
||
`GET /consolidations/versions/{id}/osv/summary`: строк всего, документов, несопоставленных (`unmapped_total` — счётчик
|
||
«Осталось N» — строки без сопоставленного ЮЛ и без ИНН из файла, arm-core #74; сопоставление — «Указать» в строке),
|
||
автоматически сопоставленных.
|
||
|
||
#### Scenario: Контрольные цифры РБ 2024 Q3
|
||
- **WHEN** открыт свод версии РБ 2024 Q3 после подтверждения 22 ОСВ из `fixtures/osv/{rb,schastl}/2024-Q{1,2,3}`
|
||
- **THEN** «строк» = 1385, «документов» = 22, «несопоставленных» = 1260
|
||
|
||
### Requirement: Таблица свода и фильтры
|
||
Экран SHALL показывать строки `GET /consolidations/versions/{id}/osv` постранично (размер страницы ≤ 2000): ЮЛ, счёт,
|
||
контрагент, ИНН контрагента, сальдо на начало Дт/Кт, обороты Дт/Кт, сальдо на конец Дт/Кт (суммы — строками с
|
||
разделителями разрядов) и последней колонкой «Тип контрагента» — бейдж ГСК (синий) / ГК (зелёный) / Внешний (серый), как
|
||
в макете [30]. Фильтры — в заголовках колонок: ЮЛ (`legal_entity_id`), корень счёта (`account_root`), поиск по
|
||
контрагенту (`q`, ≤ 500 символов), тип контрагента (`counterparty_type`, одно значение — на колонке «Тип контрагента»);
|
||
только несопоставленные (`unmapped`) — кнопкой «Показать ЮЛ без ИНН» в баннере и воронкой колонки «ИНН» (подсвечена,
|
||
пока фильтр включён, макет [32-osv-no-inn]). «Применить» и «Сбросить» SHALL закрывать окно фильтра.
|
||
Смена фильтра SHALL перечитывать первую страницу.
|
||
|
||
#### Scenario: Строка из контрольных цифр
|
||
- **WHEN** фильтр ЮЛ «Пекарня Счастливочная» и поиск «бисквит»
|
||
- **THEN** строка `60.01` показывает сальдо нач. Кт 4 454 151.93, обороты Дт 128 058 143.65, Кт 125 950 450.77,
|
||
сальдо кон. Кт 2 346 459.05, признак «ГСК»
|
||
|
||
### Requirement: Ручное сопоставление контрагента
|
||
Ячейка ИНН (моноширинный шрифт, по центру) SHALL показывать, как в макете [32-osv-no-inn]:
|
||
- ИНН из файла (`counterparty_inn`) — просто текстом, сопоставлена строка или нет (строка с ИНН сопоставления не требует,
|
||
решение владельца 08.10.2026, отмена П-1);
|
||
- строка сопоставлена, ИНН в файле нет — ИНН ЮЛ справочника (`counterparty_entity_inn`) кнопкой-«таблеткой»
|
||
(`osv-inn-mapped-button`); нажатие открывает ту же модалку для пересопоставления, у текущего ЮЛ вместо «Связать» —
|
||
неактивное «Связано»;
|
||
- нет ни ЮЛ, ни ИНН — подсвеченная ячейка со ссылкой «Указать» (`osv-inn-link-button`).
|
||
|
||
«Указать» и «таблетка» SHALL открывать модалку
|
||
«Выбор организации для связи с <имя контрагента из файла>» [32-osv-map-counterparty]: поиск, таблица ИНН / Организация /
|
||
ГК / ГСК / ГСЗ / «Связать», рекомендуемые (`recommended`) — первыми под заголовком «Рекомендуемые» с зелёной подсветкой.
|
||
Кандидаты — `GET …/counterparty-candidates?scope=&entry_id=&q=` с паузой 300 мс после ввода: по умолчанию `scope=group`
|
||
(периметр и ГК); галочка «Искать во всём справочнике» — `scope=all`, при поиске короче 3 символов запрос не отправляется
|
||
и показывается подсказка.
|
||
«Связать» SHALL отправлять `POST …/osv/entries/{entry_ids.at(-1)}/counterparty {legal_entity_id}`, после успеха —
|
||
сообщение (с кем связано, сколько записей ОСВ сопоставлено автоприменением, создан или перенесён алиас), закрывать
|
||
модалку и перечитывать страницу свода и сводку («Осталось N» на клиенте не вычисляется). Ответы 409:
|
||
- `ALIAS_TAKEN` — подтверждение «Имя уже закреплено за <владельцы>. Перенести?» → повтор с `move_alias = true`;
|
||
- `LOCK_REQUIRED` — подтверждение «Взять версию в работу?» → `POST …/lock` → повтор; отказ — ничего не меняется;
|
||
- `LOCKED_BY_OTHER` (от правки или от `POST …/lock`) — «Версию держит <имя>» (имя — из ответа `POST …/lock` или из
|
||
`lock` карточки версии), без перехвата;
|
||
- `VERSION_NOT_EDITABLE` и прочие ошибки — текстом сервера.
|
||
|
||
#### Scenario: Сопоставление «Пекарня Счастливочная ООО» в ОСВ РБ
|
||
- **WHEN** в своде РБ 2024 Q3 у строки контрагента «Пекарня Счастливочная ООО» без сопоставления нажато «Указать» и у
|
||
рекомендуемой «Пекарни Счастливочной» — «Связать» (версия не взята в работу → «Взять в работу»)
|
||
- **THEN** строка показывает ЮЛ и тип «ГСК», «Осталось N» уменьшается на число сопоставленных строк, у ЮЛ появляется алиас
|
||
|
||
#### Scenario: Алиас занят
|
||
- **WHEN** имя строки уже алиас другого ЮЛ и аналитик выбирает третье ЮЛ
|
||
- **THEN** показан вопрос о переносе с именами владельцев; «Перенести» повторяет запрос с `move_alias = true`
|
||
|
||
### Requirement: Пробелы
|
||
Экран SHALL показывать `gaps[]` сводки списком «ЮЛ — счёт — недостающие кварталы» с пояснением, что строки этих пар в
|
||
своде отсутствуют.
|
||
|
||
#### Scenario: Пробелы РБ 2024 Q3
|
||
- **WHEN** открыт свод версии РБ 2024 Q3
|
||
- **THEN** показано 7 пробелов, у каждого недостающие кварталы «Q1, Q2»
|
||
|
||
### Requirement: Экспорт
|
||
Кнопка «Скачать xlsx» SHALL получать `GET /consolidations/versions/{id}/osv/export` с текущими фильтрами через клиент API
|
||
с токеном и сохранять файл; имя — из `Content-Disposition`, а если заголовок недоступен —
|
||
`Сводная_ОСВ_<ГК>_<год>_<квартал>.xlsx`.
|
||
|
||
#### Scenario: Экспорт с фильтром
|
||
- **WHEN** аналитик скачивает свод с фильтром по ЮЛ
|
||
- **THEN** сохраняется xlsx, а ошибка сервера показывается текстом, а не скачивается
|
||
|
||
### Requirement: Единицы и знаки после запятой
|
||
Над таблицей сводной ОСВ [30] SHALL быть два выпадающих списка: «Единицы» — «руб.», «тыс. руб.», «млн руб.», «млрд руб.»
|
||
(по умолчанию «руб.»: суммы API — в рублях) и «Знаков после запятой» — 0, 1, 2, 3 (по умолчанию 2). Выбор SHALL менять
|
||
только показ сумм шести денежных колонок (сальдо на начало, обороты, сальдо на конец, Дт/Кт); запрос, фильтры и экспорт в
|
||
xlsx не меняются. Пересчёт SHALL выполняться над строкой суммы из API без арифметики с плавающей точкой: запятая
|
||
сдвигается на 3/6/9 разрядов, округление до выбранного числа знаков — половина от нуля, разряды разделяются пробелом,
|
||
дробная часть — через запятую. Нулевая сумма SHALL показываться «—» при любых единицах; ненулевая сумма, округлившаяся до
|
||
нуля, — нулём с выбранным числом знаков, без знака «минус».
|
||
|
||
#### Scenario: Тысячи рублей
|
||
- **WHEN** выбраны «тыс. руб.» и 3 знака, сумма строки — `24000.00`
|
||
- **THEN** в ячейке «24,000»
|
||
|
||
#### Scenario: Округление без потери разрядов
|
||
- **WHEN** выбраны «тыс. руб.» и 2 знака, сумма — `4454151.93`
|
||
- **THEN** в ячейке «4 454,15»
|
||
|
||
#### Scenario: Ноль
|
||
- **WHEN** сумма ячейки — `0.00`, выбраны любые единицы и число знаков
|
||
- **THEN** в ячейке «—»
|
||
|
||
#### Scenario: Малая сумма в крупных единицах
|
||
- **WHEN** выбраны «млн руб.» и 2 знака, сумма — `-0.40`
|
||
- **THEN** в ячейке «0,00»
|
||
|
||
### Requirement: Баннер строк без ИНН
|
||
Пока в сводке `unmapped_total > 0`, над таблицей свода SHALL показываться баннер (стиль `osv-mapping-alert` макета [30]):
|
||
заголовок «Есть организации без ИНН и без сопоставления», текст «Осталось: N. Несопоставленные учитываются как внешние;
|
||
при необходимости укажите организацию кнопкой «Указать».» и кнопка «Показать ЮЛ без ИНН» / «Показать все строки»
|
||
(фильтр `unmapped`, как прежде). Баннер MUST NOT требовать сопоставить все строки для перехода к следующему шагу:
|
||
переход не блокируется (решение заказчика 16.10.2026, AURORA-1388). При `unmapped_total = 0` баннера нет.
|
||
|
||
#### Scenario: Есть строки без ИНН
|
||
- **WHEN** открыт свод версии РБ 2024 Q3 до сопоставления (`unmapped_total` = 1260)
|
||
- **THEN** баннер «Есть организации без ИНН и без сопоставления», «Осталось: 1260. Несопоставленные учитываются как
|
||
внешние…»; слов «Для перехода к следующему шагу сопоставьте все» на экране нет
|
||
|
||
### Requirement: Режим «Встречные записи»
|
||
Над сводом на вкладке «ОСВ» SHALL быть переключатель режимов «Свод | Встречные записи (N)», N — число пар
|
||
`summary.counter_pairs`. Режим — параметр адреса `?view=pairs` (без параметра — «Свод»); переключение режима MUST NOT
|
||
добавлять запись в историю браузера. Шапка свода (единицы, знаки после запятой) действует в обоих режимах.
|
||
|
||
В режиме «Встречные записи» экран SHALL показывать сверху список пар, под ним на всю ширину две таблицы одна под другой:
|
||
- **сверху** — список пар одной строкой «таблеток» с переносом, в порядке ответа, строка пары «Б ← А» (имена `missing_name` и `holder_name`) с числами
|
||
`holder_rows` и `missing_unmapped_rows`; выбранная пара подсвечена; при входе выбрана первая пара либо пара из адреса
|
||
(`?view=pairs&holder=<id>&missing=<id>`);
|
||
- **первая таблица** — «У <А> есть записи с <Б>»: строки `GET …/osv?legal_entity_id=А&counterparty_id=Б` — счёт, контрагент,
|
||
обороты Дт/Кт, сальдо на конец Дт/Кт;
|
||
- **вторая таблица** — «У <Б> — строки без ИНН и без сопоставления»: строки `GET …/osv?legal_entity_id=Б&unmapped=true` — счёт,
|
||
контрагент, обороты Дт/Кт, сальдо на конец Дт/Кт и «Указать» в каждой строке; над таблицей — поиск по контрагенту
|
||
(`q`, ≤ 500 символов, как фильтр свода); постранично, как таблица свода. Все четыре колонки
|
||
сумм MUST быть видны без горизонтальной прокрутки при ширине окна 1440 px (на узком окне допустима прокрутка таблицы). Если
|
||
`missing_rows_total = 0` — вместо таблицы текст «У <Б> в своде нет строк: ОСВ не привязаны или по ним пробелы»; если
|
||
строки у Б есть, а без ИНН и без сопоставления нет (`missing_unmapped_rows = 0`) — текст «Строк без ИНН нет: возможно,
|
||
ИНН контрагента из файла не найден в справочнике или это пропуск в учёте».
|
||
|
||
Обе таблицы и список пар MUST оставаться внутри белой карточки экрана: тело режима прокручивается по вертикали внутри
|
||
карточки, а не выходит за её границу.
|
||
|
||
Пока сводки нет, вместо тела режима показывается индикатор загрузки, а подпись переключателя — «Встречные записи» без «(N)».
|
||
Если запрос сводки не удался, показывается ошибка с кнопкой «Повторить», повторяющей запрос.
|
||
|
||
«Указать» SHALL открывать модалку сопоставления из требования «Ручное сопоставление контрагента» с теми же ответами и
|
||
ошибками, но список кандидатов сразу сужен до участника пары А: одна строка под заголовком «Участник пары» с «Связать»
|
||
(кандидаты запрашиваются с `scope=group` и `q` = имя А), поле поиска и «Искать во всём справочнике» скрыты; флажок
|
||
«Показать все организации» возвращает обычный список модалки (решение владельца 27.10.2026). Если А среди кандидатов не
|
||
нашёлся — текст «Участник пары не найден среди кандидатов — включите «Показать все организации»». В обычном своде
|
||
«Указать» не меняется. После «Связать» экран SHALL перечитывать сводку и обе таблицы. Если выбранная пара исчезла из списка, выбирается
|
||
первая оставшаяся; пустой список — текст «Все участники периметра видят друг друга» (переключатель — «Встречные записи (0)»).
|
||
|
||
#### Scenario: Пара Русского бисквита
|
||
- **WHEN** открыт свод версии РБ 2024 Q3 до сопоставления и выбран режим «Встречные записи»
|
||
- **THEN** переключатель «Встречные записи (1)»; сверху пара «Русский бисквит ← Пекарня Счастливочная»; в первой таблице строки
|
||
Счастливочной с контрагентом «Русский бисквит»; во второй таблице строки РБ без ИНН, среди них «Пекарня Счастливочная ООО» с
|
||
«Указать»
|
||
|
||
#### Scenario: Сопоставление из режима закрывает пару
|
||
- **WHEN** во второй таблице у «Пекарня Счастливочная ООО» нажато «Указать» и выбрана ООО «Пекарня Счастливочная»
|
||
- **THEN** после «Связать» список пар пуст, показан текст «Все участники периметра видят друг друга», переключатель —
|
||
«Встречные записи (0)»
|
||
|
||
#### Scenario: Модалка сопоставления сужена до участника пары
|
||
- **WHEN** в паре «Русский бисквит ← Пекарня Счастливочная» во второй таблице нажато «Указать»
|
||
- **THEN** в модалке одна строка — ООО «Пекарня Счастливочная» под заголовком «Участник пары» с «Связать»; после
|
||
«Показать все организации» — поиск и обычный список кандидатов
|
||
|
||
#### Scenario: Сводка ещё не получена
|
||
- **WHEN** открыт режим «Встречные записи», запрос сводки в пути
|
||
- **THEN** показан индикатор загрузки, подпись переключателя — «Встречные записи» без числа
|
||
|
||
#### Scenario: Ошибка запроса сводки
|
||
- **WHEN** запрос сводки в режиме «Встречные записи» завершился ошибкой
|
||
- **THEN** показан Alert с текстом ошибки и кнопкой «Повторить»; нажатие повторяет запрос
|
||
|
||
#### Scenario: Режим из адреса
|
||
- **WHEN** открыт адрес `…/versions/{id}/osv?view=pairs`
|
||
- **THEN** показан режим «Встречные записи» с первой парой; кнопка «Назад» браузера не проходит по переключениям режимов
|