Merge pull request '«Назад» из документа возвращает панель «Первичные документы» организации (#197, AURORA-1682)' (#198) from feat/197-back-to-primary-docs into main

This commit is contained in:
aep 2026-10-27 22:12:08 +03:00
commit ec783c8ec0
10 changed files with 164 additions and 16 deletions

View File

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-27

View File

@ -0,0 +1,20 @@
## Context
AURORA-1682, RSHB/arm-core#197. Панель «Первичные документы» открывалась локальным состоянием `ConsolidationPage`
(`drawerOpen`), ЮЛ панели — в сторе (`current_legal_entity_id`). «Назад» из документа (`navigate(-1)`) монтирует страницу
консолидации заново, и панель закрыта.
## Decisions made without discussion
- **2026-10-27, R-1.** Открытая панель хранится в адресе (`?entity=<legal_entity_id>`), а не в сторе или sessionStorage.
Обоснование: выбранная версия уже живёт в `?version=` (ConsoleTable), история браузера сама возвращает адрес с панелью,
а ссылку с панелью можно переслать. Цена ошибки: если параметр не нужен в адресе — заменить на состояние в сторе
(правка в `ConsolidationsList.tsx` и двух функциях `utils/consolidations.ts`).
- **2026-10-27, R-2.** Открытие и закрытие панели заменяют запись истории (`replace: true`), как выбор версии. Обоснование:
иначе «Назад» браузера после закрытия панели снова открывал бы её. Цена ошибки: низкая — одна опция `setSearchParams`.
- **2026-10-27, R-3.** Режим «Все документы организации» в адрес не входит: после «Назад» панель открыта столом
консолидации. Обоснование: в задаче речь о возврате к панели организации; переключатель и без того сбрасывается при каждом
открытии панели. Цена ошибки: низкая — добавить второй параметр адреса.
- **2026-10-27, R-4.** Панель открывается, только когда ЮЛ из адреса уже в сторе (`current_legal_entity_id === entity`).
Обоснование: стор обновляется эффектом после первого рендера — без условия панель на мгновение показала бы прежнее ЮЛ и
запросила бы его файлы. Цена ошибки: нет (поведение для пользователя то же).

View File

@ -0,0 +1,38 @@
## Why
AURORA-1682 (замечание тестирования 16.10.2026, п. 1; чек-лист Циликова 23.10.2026, строка 9): аналитик открывает документ
из панели «Первичные документы» организации, проверяет его и нажимает «Назад» — открывается карточка консолидации без
панели, и для следующего документа панель приходится открывать заново. Панель открывалась локальным состоянием страницы
консолидации (`drawerOpen`), а «Назад» (`navigate(-1)`) монтирует страницу заново.
## What Changes
Репозиторий — только arm-core (`web/`), эндпоинты без изменений.
- Открытая панель «Первичные документы» хранится в адресе экрана консолидации параметром `?entity=<legal_entity_id>`
рядом с `?version=`; открытие и закрытие панели заменяют текущую запись истории (как выбор версии). Расчёт параметров —
`panelEntity` / `withPanelEntity` (`web/src/utils/consolidations.ts`), закреплён тестом.
- «Назад» из документа, открытого из панели, возвращает на адрес с `?entity=` — панель той же организации открыта.
Переключатель документа (AURORA-1692) и прямая ссылка ведут себя как прежде.
- Дельты: `document-review-ui` — MODIFIED «Переключатель документа» (сценарий «Назад после перебора документов»);
`primary-files-ui` — ADDED «Открытая панель в адресе экрана консолидации».
## Non-goals
- Режим «Все документы организации» в адрес не попадает: после «Назад» панель открывается столом консолидации, как при
обычном открытии.
- Изменения бэкенда и DBML.
## Capabilities
### New Capabilities
### Modified Capabilities
- `document-review-ui`: MODIFIED «Переключатель документа».
- `primary-files-ui`: ADDED «Открытая панель в адресе экрана консолидации».
## Impact
`web/src/layouts/ConsolidationsList.tsx`, `web/src/widgets/ConsoleLegalEntities/ConsoleLegalEntities.tsx`,
`web/src/utils/consolidations.ts` (+ тест). Ссылки на экран консолидации с `?entity=` открывают панель этой организации.

View File

@ -0,0 +1,20 @@
## MODIFIED Requirements
### Requirement: Переключатель документа
В шапке экрана документа SHALL быть выпадающий список «Документ» с документами файлов того же ЮЛ в той же консолидации,
что и файл открытого документа (файл вне консолидации — все файлы ЮЛ): у пункта имя файла, вторая строка (тип · формат ·
дата) и статус; пункты, которые нельзя открыть, неактивны. Выбор пункта SHALL открывать `/documents/:document_id`
выбранного документа заменой текущей записи истории браузера, без новой записи: кнопка «Назад» экрана и браузера
возвращает на экран, с которого открыт первый документ, а не к предыдущему выбранному. Если первый документ открыт из
панели «Первичные документы» консолидации, «Назад» SHALL открывать экран консолидации с этой панелью той же организации.
#### Scenario: Назад после перебора документов
- **WHEN** аналитик открыл документ из панели «Первичные документы» организации в карточке консолидации, выбрал в списке
«Документ» подряд ещё два документа и нажал «Назад»
- **THEN** открыт экран консолидации с той же выбранной версией и открытой панелью «Первичные документы» той же
организации
#### Scenario: Документ открыт прямой ссылкой
- **WHEN** документ открыт прямой ссылкой в новой вкладке, аналитик выбрал в списке «Документ» другой документ и нажал
«Назад» экрана
- **THEN** открыт список групп компаний `/groups`

View File

@ -0,0 +1,16 @@
## ADDED Requirements
### Requirement: Открытая панель в адресе экрана консолидации
Открытая панель «Первичные документы» SHALL храниться в адресе экрана консолидации `/groups/:group_id` параметром
`?entity=<legal_entity_id>` рядом с `?version=`. Клик по организации в панели «Организации» SHALL добавлять параметр,
закрытие панели — убирать его; оба перехода SHALL заменять текущую запись истории браузера, без новой записи. Адрес с
`?entity=` SHALL открывать панель этой организации. Режим «Все документы организации» в адрес не входит: панель,
открытая по адресу, показывает стол консолидации.
#### Scenario: Возврат из документа
- **WHEN** аналитик открыл панель организации, перешёл по ссылке документа и вернулся «Назад»
- **THEN** открыт экран консолидации с той же версией и панелью «Первичные документы» той же организации
#### Scenario: Закрытая панель не возвращается
- **WHEN** аналитик открыл панель организации, закрыл её и нажал «Назад» браузера
- **THEN** открыт экран, с которого пришли на экран консолидации, панель не открывается снова

View File

@ -0,0 +1,11 @@
## 1. «Назад» на панель «Первичные документы» (AURORA-1682, RSHB/arm-core#197)
- [x] 1.1 `web/src/utils/consolidations.ts`: `panelEntity`, `withPanelEntity` — ЮЛ панели в `?entity=`; тест в
`consolidations.test.ts`.
- [x] 1.2 `web/src/layouts/ConsolidationsList.tsx`: панель открыта по `?entity=` (когда ЮЛ из адреса уже в сторе),
открытие и закрытие — `setSearchParams(…, { replace: true })`; `ConsoleLegalEntities` передаёт id ЮЛ строки.
- [x] 1.3 Дельты `document-review-ui` (MODIFIED «Переключатель документа») и `primary-files-ui` (ADDED «Открытая панель
в адресе экрана консолидации»).
- [x] 1.4 Проверка в браузере: панель организации → документ → переключить 2–3 документа → «Назад» — карточка
консолидации с панелью той же организации; закрыть панель → «Назад» браузера не открывает её снова; сверка с макетом
[39], [41].

View File

@ -4,8 +4,9 @@ import { useParams, useSearchParams } from 'react-router-dom';
import { ApiError } from '../api/client';
import PageTitle from '../components/PageTitle';
import { fetchOneCompanyGroup } from '../services/api/directories/company_groups';
import { setSelectedVersion } from '../store/slices/consolidations/consolidationsSlice';
import { useAppDispatch } from '../store/store';
import { setSelectedLegalEntity, setSelectedVersion } from '../store/slices/consolidations/consolidationsSlice';
import { useAppDispatch, useAppSelector } from '../store/store';
import { panelEntity, withPanelEntity } from '../utils/consolidations';
import { ConsoleLegalEntitiesTable } from '../widgets/ConsoleLegalEntities/ConsoleLegalEntities';
import { ConsoleTableWidget } from '../widgets/ConsoleTable/ConsoleTable';
import { DocumentsDrawer } from '../widgets/FilesDrawer/FilesDrawer';
@ -17,12 +18,16 @@ function errorText(e: unknown): string {
}
export function ConsolidationPage() {
const [drawerOpen, setDrawerOpen] = useState(false);
const [reloadKey, setReloadKey] = useState(0);
const { group_id } = useParams();
const [searchParams] = useSearchParams();
const [searchParams, setSearchParams] = useSearchParams();
const versionParam = searchParams.get('version');
// открытая панель «Первичные документы» — в адресе (?entity=): «Назад» из документа возвращает с той же панелью
const entityParam = panelEntity(searchParams);
// панель открывается, когда ЮЛ из адреса уже в сторе: иначе на первом рендере она показала бы прежнее ЮЛ
const panelOpen = useAppSelector((s) => entityParam !== null && s.consolidations.current_legal_entity_id === entityParam);
const dispatch = useAppDispatch();
const setPanelEntity = (entityId: string | null) => setSearchParams((params) => withPanelEntity(params, entityId), { replace: true });
const [comGroupName, setComGroupName] = useState('');
const [locationPath, setLocationPath] = useState('Отчётность>');
@ -48,6 +53,11 @@ export function ConsolidationPage() {
dispatch(setSelectedVersion(versionParam));
}, [dispatch, versionParam]);
// ЮЛ панели следует за ?entity=; закрытие панели выбор не сбрасывает (панель скрыта, ЮЛ без запроса не перечитывается)
useEffect(() => {
if (entityParam) dispatch(setSelectedLegalEntity(entityParam));
}, [dispatch, entityParam]);
// уход со страницы сбрасывает выбор (при смене группы ?version= пропадает из URL — выбор сбросит эффект выше)
useEffect(() => {
return () => {
@ -97,18 +107,13 @@ export function ConsolidationPage() {
flexDirection: 'column',
minHeight: 0,
}}>
<ConsoleLegalEntitiesTable
reloadKey={reloadKey}
onDrawerOpen={() => {
setDrawerOpen(true);
}}
/>
<ConsoleLegalEntitiesTable reloadKey={reloadKey} onDrawerOpen={setPanelEntity} />
</div>
</main>
<DocumentsDrawer
open={drawerOpen}
open={panelOpen}
onClose={() => {
setDrawerOpen(false);
setPanelEntity(null);
setReloadKey((k) => k + 1);
}}
/>

View File

@ -16,12 +16,14 @@ import {
isLockedBy,
isValidReason,
manualTransitions,
panelEntity,
parseInWorkOnly,
rememberCounterPairsShown,
reportTabNavigation,
resolveLeaveAfterFetch,
shouldWarnCounterPairs,
versionLabel,
withPanelEntity,
} from './consolidations';
const lock = (userId: string): LockOut => ({
@ -143,6 +145,24 @@ describe('вкладки отчёта (AURORA-1851)', () => {
});
});
describe('панель «Первичные документы» в адресе (AURORA-1682)', () => {
it('открытие добавляет ?entity= к выбранной версии, исходные параметры не меняются', () => {
const params = new URLSearchParams('version=v1&in_work_only=true');
const next = withPanelEntity(params, 'le1');
expect(next.toString()).toBe('version=v1&in_work_only=true&entity=le1');
expect(params.toString()).toBe('version=v1&in_work_only=true');
});
it('закрытие убирает ?entity=, версия остаётся', () => {
expect(withPanelEntity(new URLSearchParams('version=v1&entity=le1'), null).toString()).toBe('version=v1');
});
it('организация панели читается из адреса', () => {
expect(panelEntity(new URLSearchParams('version=v1&entity=le1'))).toBe('le1');
expect(panelEntity(new URLSearchParams('version=v1'))).toBeNull();
});
});
const pair = (holder: string, missing: string): CounterPairOut => ({
holder_id: holder,
holder_name: `H${holder}`,

View File

@ -100,6 +100,23 @@ export const reportTabNavigation = (pathname: string, activeKey: string, newKey:
options: { replace: true },
});
/** Параметр адреса экрана консолидации с ЮЛ открытой панели «Первичные документы» (AURORA-1682). */
const PANEL_ENTITY_PARAM = 'entity';
/** ЮЛ открытой панели «Первичные документы» из адреса; панель закрыта — `null`. */
export const panelEntity = (params: URLSearchParams): string | null => params.get(PANEL_ENTITY_PARAM);
/**
* Параметры адреса с открытой (`entityId`) или закрытой (`null`) панелью «Первичные документы»; исходные не меняются.
* Панель живёт в адресе, как выбранная версия: «Назад» из документа возвращает на консолидацию с той же панелью.
*/
export function withPanelEntity(params: URLSearchParams, entityId: string | null): URLSearchParams {
const next = new URLSearchParams(params);
if (entityId === null) next.delete(PANEL_ENTITY_PARAM);
else next.set(PANEL_ENTITY_PARAM, entityId);
return next;
}
/** Статусы, в которых версия закрыта для правок: модалку о парах без встречных записей не показываем. */
export const FINISHED_STATUSES: readonly ConsStatus[] = ['VERIFIED', 'CANCELLED', 'ARCHIVED', 'REJECTED'];

View File

@ -6,7 +6,7 @@ import { ColumnSettings, DataTable, useFilteredRows, useVisibleColumns } from '.
import { useTableFilters } from '../../components/GenericTable/useTableFilters';
import { downloadChecklistCsv } from '../../services/api/consolidations';
import { getVersionCard } from '../../services/api/consolidations';
import { setSelectedLegalEntity, setSelectedVersion } from '../../store/slices/consolidations/consolidationsSlice';
import { setSelectedVersion } from '../../store/slices/consolidations/consolidationsSlice';
import { useAppDispatch, useAppSelector } from '../../store/store';
import type { IVersionCard, MemberRow } from '../../types/consolidations/version_members';
import { saveBlob } from '../../utils/download';
@ -15,7 +15,7 @@ import { membersConfig } from './config';
import { normalizeData } from './normalize';
import { checklistFilename, responsibleName } from './panelInfo';
export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawerOpen: () => void; reloadKey: number }) {
export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawerOpen: (entityId: string) => void; reloadKey: number }) {
const dispatch = useAppDispatch();
const [loading, setLoading] = useState(false);
const consolidation_id = useAppSelector((s) => s.consolidations.current_version_id);
@ -60,8 +60,7 @@ export function ConsoleLegalEntitiesTable({ onDrawerOpen, reloadKey }: { onDrawe
const onRow = (row: MemberRow) => ({
onClick: () => {
dispatch(setSelectedVersion(row.version_id));
dispatch(setSelectedLegalEntity(row.id));
onDrawerOpen();
onDrawerOpen(row.id);
},
});
if (consolidationData === null) {