diff --git a/openspec/changes/back-to-primary-docs/.openspec.yaml b/openspec/changes/back-to-primary-docs/.openspec.yaml new file mode 100644 index 0000000..97e3642 --- /dev/null +++ b/openspec/changes/back-to-primary-docs/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-10-27 diff --git a/openspec/changes/back-to-primary-docs/design.md b/openspec/changes/back-to-primary-docs/design.md new file mode 100644 index 0000000..899cc10 --- /dev/null +++ b/openspec/changes/back-to-primary-docs/design.md @@ -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=`), а не в сторе или 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`). + Обоснование: стор обновляется эффектом после первого рендера — без условия панель на мгновение показала бы прежнее ЮЛ и + запросила бы его файлы. Цена ошибки: нет (поведение для пользователя то же). diff --git a/openspec/changes/back-to-primary-docs/proposal.md b/openspec/changes/back-to-primary-docs/proposal.md new file mode 100644 index 0000000..d885a58 --- /dev/null +++ b/openspec/changes/back-to-primary-docs/proposal.md @@ -0,0 +1,38 @@ +## Why + +AURORA-1682 (замечание тестирования 16.10.2026, п. 1; чек-лист Циликова 23.10.2026, строка 9): аналитик открывает документ +из панели «Первичные документы» организации, проверяет его и нажимает «Назад» — открывается карточка консолидации без +панели, и для следующего документа панель приходится открывать заново. Панель открывалась локальным состоянием страницы +консолидации (`drawerOpen`), а «Назад» (`navigate(-1)`) монтирует страницу заново. + +## What Changes + +Репозиторий — только arm-core (`web/`), эндпоинты без изменений. + +- Открытая панель «Первичные документы» хранится в адресе экрана консолидации параметром `?entity=` + рядом с `?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=` открывают панель этой организации. diff --git a/openspec/changes/back-to-primary-docs/specs/document-review-ui/spec.md b/openspec/changes/back-to-primary-docs/specs/document-review-ui/spec.md new file mode 100644 index 0000000..8db3690 --- /dev/null +++ b/openspec/changes/back-to-primary-docs/specs/document-review-ui/spec.md @@ -0,0 +1,20 @@ +## MODIFIED Requirements + +### Requirement: Переключатель документа +В шапке экрана документа SHALL быть выпадающий список «Документ» с документами файлов того же ЮЛ в той же консолидации, +что и файл открытого документа (файл вне консолидации — все файлы ЮЛ): у пункта имя файла, вторая строка (тип · формат · +дата) и статус; пункты, которые нельзя открыть, неактивны. Выбор пункта SHALL открывать `/documents/:document_id` +выбранного документа заменой текущей записи истории браузера, без новой записи: кнопка «Назад» экрана и браузера +возвращает на экран, с которого открыт первый документ, а не к предыдущему выбранному. Если первый документ открыт из +панели «Первичные документы» консолидации, «Назад» SHALL открывать экран консолидации с этой панелью той же организации. + +#### Scenario: Назад после перебора документов +- **WHEN** аналитик открыл документ из панели «Первичные документы» организации в карточке консолидации, выбрал в списке + «Документ» подряд ещё два документа и нажал «Назад» +- **THEN** открыт экран консолидации с той же выбранной версией и открытой панелью «Первичные документы» той же + организации + +#### Scenario: Документ открыт прямой ссылкой +- **WHEN** документ открыт прямой ссылкой в новой вкладке, аналитик выбрал в списке «Документ» другой документ и нажал + «Назад» экрана +- **THEN** открыт список групп компаний `/groups` diff --git a/openspec/changes/back-to-primary-docs/specs/primary-files-ui/spec.md b/openspec/changes/back-to-primary-docs/specs/primary-files-ui/spec.md new file mode 100644 index 0000000..5f94acf --- /dev/null +++ b/openspec/changes/back-to-primary-docs/specs/primary-files-ui/spec.md @@ -0,0 +1,16 @@ +## ADDED Requirements + +### Requirement: Открытая панель в адресе экрана консолидации +Открытая панель «Первичные документы» SHALL храниться в адресе экрана консолидации `/groups/:group_id` параметром +`?entity=` рядом с `?version=`. Клик по организации в панели «Организации» SHALL добавлять параметр, +закрытие панели — убирать его; оба перехода SHALL заменять текущую запись истории браузера, без новой записи. Адрес с +`?entity=` SHALL открывать панель этой организации. Режим «Все документы организации» в адрес не входит: панель, +открытая по адресу, показывает стол консолидации. + +#### Scenario: Возврат из документа +- **WHEN** аналитик открыл панель организации, перешёл по ссылке документа и вернулся «Назад» +- **THEN** открыт экран консолидации с той же версией и панелью «Первичные документы» той же организации + +#### Scenario: Закрытая панель не возвращается +- **WHEN** аналитик открыл панель организации, закрыл её и нажал «Назад» браузера +- **THEN** открыт экран, с которого пришли на экран консолидации, панель не открывается снова diff --git a/openspec/changes/back-to-primary-docs/tasks.md b/openspec/changes/back-to-primary-docs/tasks.md new file mode 100644 index 0000000..800a25e --- /dev/null +++ b/openspec/changes/back-to-primary-docs/tasks.md @@ -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]. diff --git a/web/src/layouts/ConsolidationsList.tsx b/web/src/layouts/ConsolidationsList.tsx index 52a952b..4a26eee 100644 --- a/web/src/layouts/ConsolidationsList.tsx +++ b/web/src/layouts/ConsolidationsList.tsx @@ -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, }}> - { - setDrawerOpen(true); - }} - /> + { - setDrawerOpen(false); + setPanelEntity(null); setReloadKey((k) => k + 1); }} /> diff --git a/web/src/utils/consolidations.test.ts b/web/src/utils/consolidations.test.ts index f8094b8..9dd568e 100644 --- a/web/src/utils/consolidations.test.ts +++ b/web/src/utils/consolidations.test.ts @@ -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}`, diff --git a/web/src/utils/consolidations.ts b/web/src/utils/consolidations.ts index 1d5fa02..592e5ad 100644 --- a/web/src/utils/consolidations.ts +++ b/web/src/utils/consolidations.ts @@ -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']; diff --git a/web/src/widgets/ConsoleLegalEntities/ConsoleLegalEntities.tsx b/web/src/widgets/ConsoleLegalEntities/ConsoleLegalEntities.tsx index 6fabf87..cde77fa 100644 --- a/web/src/widgets/ConsoleLegalEntities/ConsoleLegalEntities.tsx +++ b/web/src/widgets/ConsoleLegalEntities/ConsoleLegalEntities.tsx @@ -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) {