store: консолидация открытой версии и подпись консолидации у файла (#26)

Чистые функции findCurrentConsolidation, isInWork, consolidationLabel и хук
useCurrentConsolidation; FileOut.consolidation_id; тесты vitest.

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-10 00:52:33 +04:00
parent 490113cb06
commit 5b7b02a3f2
8 changed files with 159 additions and 1 deletions

View File

@ -35,6 +35,7 @@ describe('hasPendingFiles', () => {
version_no: 1, version_no: 1,
size_bytes: null, size_bytes: null,
uploaded_at: null, uploaded_at: null,
consolidation_id: null,
job: null, job: null,
documents: [], documents: [],
}); });

View File

@ -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);
});
});

View File

@ -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<ConsStatus> = new Set<ConsStatus>(['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);
}

View File

@ -63,6 +63,8 @@ export interface FileOut {
version_no: number; version_no: number;
size_bytes: number | null; size_bytes: number | null;
uploaded_at: string | null; uploaded_at: string | null;
/** консолидация, в контексте которой загружен файл (v5.18); null — без контекста */
consolidation_id: string | null;
job: JobBrief | null; job: JobBrief | null;
documents: DocumentBrief[]; documents: DocumentBrief[];
} }

View File

@ -1,6 +1,6 @@
import type { Approach, ConsRussianStatus, ConsStatus, LockOut, QuarterEnum, Scenario } from './common_types'; import type { Approach, ConsRussianStatus, ConsStatus, LockOut, QuarterEnum, Scenario } from './common_types';
interface IConsolidationVersion { export interface IConsolidationVersion {
id: string; id: string;
version_no: number; version_no: number;
status: ConsStatus; status: ConsStatus;

View File

@ -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('вне консолидации');
});
});

View File

@ -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)}` : 'другая консолидация';
}

View File

@ -26,6 +26,7 @@ const file = (over: Partial<FileOut> = {}): FileOut => ({
version_no: 1, version_no: 1,
size_bytes: 10, size_bytes: 10,
uploaded_at: '2024-03-12T10:00:00Z', uploaded_at: '2024-03-12T10:00:00Z',
consolidation_id: null,
job: null, job: null,
documents: [], documents: [],
...over, ...over,