From 5b7b02a3f2c7eb6ea0cf590c126a853c8ff18dbd Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sat, 10 Oct 2026 00:52:33 +0400 Subject: [PATCH] =?UTF-8?q?store:=20=D0=BA=D0=BE=D0=BD=D1=81=D0=BE=D0=BB?= =?UTF-8?q?=D0=B8=D0=B4=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BE=D1=82=D0=BA=D1=80?= =?UTF-8?q?=D1=8B=D1=82=D0=BE=D0=B9=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D0=B8?= =?UTF-8?q?=20=D0=B8=20=D0=BF=D0=BE=D0=B4=D0=BF=D0=B8=D1=81=D1=8C=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BD=D1=81=D0=BE=D0=BB=D0=B8=D0=B4=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D1=83=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0=20(#26)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Чистые функции findCurrentConsolidation, isInWork, consolidationLabel и хук useCurrentConsolidation; FileOut.consolidation_id; тесты vitest. Co-Authored-By: Claude Haiku 5.5 --- src/services/api/files.test.ts | 1 + .../slices/consolidations/selectors.test.ts | 84 +++++++++++++++++++ src/store/slices/consolidations/selectors.ts | 31 +++++++ src/types/api.ts | 2 + src/types/consolidations/consolidations.ts | 2 +- .../FilesDrawer/consolidationLabel.test.ts | 25 ++++++ src/widgets/FilesDrawer/consolidationLabel.ts | 14 ++++ src/widgets/FilesDrawer/documentRows.test.ts | 1 + 8 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 src/store/slices/consolidations/selectors.test.ts create mode 100644 src/store/slices/consolidations/selectors.ts create mode 100644 src/widgets/FilesDrawer/consolidationLabel.test.ts create mode 100644 src/widgets/FilesDrawer/consolidationLabel.ts diff --git a/src/services/api/files.test.ts b/src/services/api/files.test.ts index 3a73fa9..ef60fa3 100644 --- a/src/services/api/files.test.ts +++ b/src/services/api/files.test.ts @@ -35,6 +35,7 @@ describe('hasPendingFiles', () => { version_no: 1, size_bytes: null, uploaded_at: null, + consolidation_id: null, job: null, documents: [], }); diff --git a/src/store/slices/consolidations/selectors.test.ts b/src/store/slices/consolidations/selectors.test.ts new file mode 100644 index 0000000..91a429a --- /dev/null +++ b/src/store/slices/consolidations/selectors.test.ts @@ -0,0 +1,84 @@ +import { describe, expect, it } from 'vitest'; +import type { IConsolidation } from '../../../types/consolidations/consolidations'; +import { findCurrentConsolidation, isInWork } from './selectors'; + +const cons: IConsolidation[] = [ + { + consolidation_id: 'c-2024q3', + year: 2024, + quarter: 'Q3', + reporting_date: '2024-10-01', + versions: [ + { + id: 'v1', + version_no: 1, + status: 'VERIFIED', + scenario: 'CREATE', + approach: 'BASIC', + lock: null, + created_at: null, + members_count: 2, + }, + { + id: 'v2', + version_no: 2, + status: 'INITIATED', + scenario: 'CREATE', + approach: 'BASIC', + lock: null, + created_at: null, + members_count: 2, + }, + ], + }, + { + consolidation_id: 'c-2024q4', + year: 2024, + quarter: 'Q4', + reporting_date: '2025-01-01', + versions: [ + { + id: 'v3', + version_no: 1, + status: 'CANCELLED', + scenario: 'CREATE', + approach: 'BASIC', + lock: null, + created_at: null, + members_count: 2, + }, + ], + }, +]; + +describe('findCurrentConsolidation', () => { + it('находит консолидацию и статус версии по id версии', () => { + expect(findCurrentConsolidation(cons, 'v2')).toEqual({ + consolidation_id: 'c-2024q3', + year: 2024, + quarter: 'Q3', + version_status: 'INITIATED', + }); + expect(findCurrentConsolidation(cons, 'v3')).toEqual({ + consolidation_id: 'c-2024q4', + year: 2024, + quarter: 'Q4', + version_status: 'CANCELLED', + }); + }); + it('null без версии, при неизвестной версии и при пустом списке (прямая ссылка до загрузки)', () => { + expect(findCurrentConsolidation(cons, null)).toBeNull(); + expect(findCurrentConsolidation(cons, 'nope')).toBeNull(); + expect(findCurrentConsolidation([], 'v2')).toBeNull(); + }); +}); + +describe('isInWork', () => { + it('в работе — три статуса', () => { + expect(isInWork('INITIATED')).toBe(true); + expect(isInWork('DOCUMENTS_PROCESSING')).toBe(true); + expect(isInWork('DATA_CORRECTION')).toBe(true); + expect(isInWork('VERIFIED')).toBe(false); + expect(isInWork('CANCELLED')).toBe(false); + }); +}); diff --git a/src/store/slices/consolidations/selectors.ts b/src/store/slices/consolidations/selectors.ts new file mode 100644 index 0000000..ee50b7f --- /dev/null +++ b/src/store/slices/consolidations/selectors.ts @@ -0,0 +1,31 @@ +import type { ConsStatus, QuarterEnum } from '../../../types/consolidations/common_types'; +import type { IConsolidation } from '../../../types/consolidations/consolidations'; +import { useAppSelector } from '../../store'; + +/** Консолидация открытой версии и статус этой версии (design D-1): выводится из списка ГК, в сторе не хранится. */ +export interface CurrentConsolidation { + consolidation_id: string; + year: number; + quarter: QuarterEnum; + version_status: ConsStatus; +} + +/** Статусы «в работе» (§9.1): только в них разрешена загрузка файлов в консолидацию. */ +export const IN_WORK_STATUSES: ReadonlySet = new Set(['INITIATED', 'DOCUMENTS_PROCESSING', 'DATA_CORRECTION']); + +export const isInWork = (status: ConsStatus): boolean => IN_WORK_STATUSES.has(status); + +export function findCurrentConsolidation(consolidations: IConsolidation[], versionId: string | null): CurrentConsolidation | null { + if (!versionId) return null; + for (const c of consolidations) { + const version = c.versions.find((v) => v.id === versionId); + if (version) return { consolidation_id: c.consolidation_id, year: c.year, quarter: c.quarter, version_status: version.status }; + } + return null; +} + +export function useCurrentConsolidation(): CurrentConsolidation | null { + const consolidations = useAppSelector((s) => s.consolidations.consolidations); + const versionId = useAppSelector((s) => s.consolidations.current_version_id); + return findCurrentConsolidation(consolidations, versionId); +} diff --git a/src/types/api.ts b/src/types/api.ts index 91e2c7d..222a082 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -63,6 +63,8 @@ export interface FileOut { version_no: number; size_bytes: number | null; uploaded_at: string | null; + /** консолидация, в контексте которой загружен файл (v5.18); null — без контекста */ + consolidation_id: string | null; job: JobBrief | null; documents: DocumentBrief[]; } diff --git a/src/types/consolidations/consolidations.ts b/src/types/consolidations/consolidations.ts index 23bebbb..f546b70 100644 --- a/src/types/consolidations/consolidations.ts +++ b/src/types/consolidations/consolidations.ts @@ -1,6 +1,6 @@ import type { Approach, ConsRussianStatus, ConsStatus, LockOut, QuarterEnum, Scenario } from './common_types'; -interface IConsolidationVersion { +export interface IConsolidationVersion { id: string; version_no: number; status: ConsStatus; diff --git a/src/widgets/FilesDrawer/consolidationLabel.test.ts b/src/widgets/FilesDrawer/consolidationLabel.test.ts new file mode 100644 index 0000000..3c54bfc --- /dev/null +++ b/src/widgets/FilesDrawer/consolidationLabel.test.ts @@ -0,0 +1,25 @@ +import { describe, expect, it } from 'vitest'; +import type { IConsolidation } from '../../types/consolidations/consolidations'; +import { consolidationLabel } from './consolidationLabel'; + +const cons: IConsolidation[] = [ + { consolidation_id: 'c-2024q3', year: 2024, quarter: 'Q3', reporting_date: '2024-10-01', versions: [] }, + { consolidation_id: 'c-2025q1', year: 2025, quarter: 'Q1', reporting_date: '2025-04-01', versions: [] }, +]; + +describe('consolidationLabel', () => { + it('файл текущей консоли — без подписи', () => { + expect(consolidationLabel('c-2024q3', cons, 'c-2024q3')).toBeNull(); + }); + it('другая консоль из списка — год и квартал', () => { + expect(consolidationLabel('c-2025q1', cons, 'c-2024q3')).toBe('2025 К1'); + }); + it('без контекста и неизвестный id', () => { + expect(consolidationLabel(null, cons, 'c-2024q3')).toBe('вне консолидации'); + expect(consolidationLabel('ghost', cons, 'c-2024q3')).toBe('другая консолидация'); + }); + it('без текущей консоли подпись есть у всех, кроме null-текущей', () => { + expect(consolidationLabel('c-2024q3', cons, null)).toBe('2024 К3'); + expect(consolidationLabel(null, cons, null)).toBe('вне консолидации'); + }); +}); diff --git a/src/widgets/FilesDrawer/consolidationLabel.ts b/src/widgets/FilesDrawer/consolidationLabel.ts new file mode 100644 index 0000000..b406016 --- /dev/null +++ b/src/widgets/FilesDrawer/consolidationLabel.ts @@ -0,0 +1,14 @@ +import type { IConsolidation } from '../../types/consolidations/consolidations'; +import { quarterShortLabel } from '../../utils/labels'; + +/** Подпись консолидации у файла в режиме «Все документы организации» (design D-4): null — файл текущей консоли. */ +export function consolidationLabel( + fileConsolidationId: string | null, + consolidations: IConsolidation[], + currentConsolidationId: string | null, +): string | null { + if (fileConsolidationId === null) return 'вне консолидации'; + if (fileConsolidationId === currentConsolidationId) return null; + const found = consolidations.find((c) => c.consolidation_id === fileConsolidationId); + return found ? `${found.year} ${quarterShortLabel(found.quarter)}` : 'другая консолидация'; +} diff --git a/src/widgets/FilesDrawer/documentRows.test.ts b/src/widgets/FilesDrawer/documentRows.test.ts index 33c6c9a..b762bfe 100644 --- a/src/widgets/FilesDrawer/documentRows.test.ts +++ b/src/widgets/FilesDrawer/documentRows.test.ts @@ -26,6 +26,7 @@ const file = (over: Partial = {}): FileOut => ({ version_no: 1, size_bytes: 10, uploaded_at: '2024-03-12T10:00:00Z', + consolidation_id: null, job: null, documents: [], ...over,