arm-core/web/src/utils/consolidations.ts
Andrey Epifancev 46d553b074 fix(web): правила экранов консолидаций — в utils/consolidations.ts с тестами (AURORA-1589, PR #166)
Подписи и пояснения статусов версии, ручные переходы MANUAL_TRANSITIONS (зеркало
api/src/services/consolidation_status.py, синхронная константа вместо мока), своя
блокировка, активность «Изменить статус», причина ≤ 1000 после обрезки, «Версия»
(1 — пусто, 2+ — номер), разбор ?in_work_only (по умолчанию выключен), «Взять в работу»
по коду статуса CANCELLED. Подпись CANCELLED — «Отменена»; у строки списка — код статуса
(status_code). Хук useBeforeUnload. Тесты vitest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 01:15:55 +04:00

94 lines
7.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Чистые правила экранов консолидаций (спека `consolidations-ui`): подписи и пояснения статусов версии, ручные
// переходы, блокировка, «Версия», фильтр «Только в работе». Без React, store и API.
import type { ConsRussianStatus, ConsStatus, LockOut } from '../types/consolidations/common_types';
/** Подписи статусов версии — одни для списка, фильтра «Статус», шапки карточки и модалки «Изменить статус». */
export const CONS_STATUS_LABEL: Record<ConsStatus, ConsRussianStatus> = {
INITIATED: 'Инициация',
DOCUMENTS_PROCESSING: 'Обработка документов',
DATA_CORRECTION: 'Корректировка данных',
CONSOLIDATION_RF: 'Консолидация РФ',
VERIFICATION_RF: 'Верификация РФ',
CONSOLIDATION_GO: 'Консолидация ГО',
VERIFICATION_GO: 'Верификация ГО',
VERIFIED: 'Верифицирован',
CANCELLED: 'Отменена',
ADJUSTMENT: 'Корректировка',
ARCHIVED: 'В архиве',
REJECTED: 'Отклонён',
};
/**
* Пояснение статуса под заголовком карточки версии. `CONSOLIDATION_RF`, `VERIFIED` — тексты макета [30], [33-01];
* `INITIATED`, `DOCUMENTS_PROCESSING`, `DATA_CORRECTION`, `CANCELLED` — временные из ТЗ «Статусная модель», пока нет
* текстов аналитика (design.md change consolidations-ui, решение (б)).
*/
export const CONS_STATUS_DESCRIPTION: Partial<Record<ConsStatus, string>> = {
INITIATED: 'Система зафиксировала состав ГСК на отчетную дату и инициатора (КА РФ или КА ГО)',
DOCUMENTS_PROCESSING:
'КА загружает документы; система отправляет pdf файлы в ИС Сканирование, производит парсинг ОСВ, бухгалтерского баланса и ОФР',
DATA_CORRECTION:
'Выявлены некорректные данные в ИС Сканирование, проводится ручная корректировка. Пользователь валидирует распарсенные данные и вносит правки',
CONSOLIDATION_RF: 'Автоматическая консолидация, ручные проверки и сравнение КФО клиента и банка',
CONSOLIDATION_GO: 'Формирование и проверка КФО, внесение корректировок на уровне ГО',
VERIFICATION_RF: 'Проверка и подтверждение КФО на уровне РФ',
VERIFICATION_GO: 'Проверка и подтверждение КФО на уровне ГО',
VERIFIED: 'Отчет верифицирован ГО, КА ГО внес в систему номер и дату решения УОБ',
CANCELLED: 'Заявка на формирование/подтверждение КФО аннулирована по инициативе пользователя',
};
/** Пояснение статуса; нет текста — пусто (название статуса под заголовком не повторяем). */
export const consStatusDescription = (status: ConsStatus): string => CONS_STATUS_DESCRIPTION[status] ?? '';
/**
* Ручные переходы статуса версии — зеркало `MANUAL_TRANSITIONS` из `api/src/services/consolidation_status.py`
* (API допустимые переходы не отдаёт). Расхождение с бэком сервер ловит 409 `TRANSITION_NOT_ALLOWED`. С маршрутом
* согласования (Срез 7) заменяется ответом API.
*/
export const MANUAL_TRANSITIONS: Partial<Record<ConsStatus, readonly ConsStatus[]>> = {
INITIATED: ['CANCELLED'],
DOCUMENTS_PROCESSING: ['CANCELLED', 'INITIATED'],
DATA_CORRECTION: ['CANCELLED', 'INITIATED'],
};
/** Допустимые ручные переходы из статуса; нет — пустой список. */
export const manualTransitions = (status: ConsStatus): readonly ConsStatus[] => MANUAL_TRANSITIONS[status] ?? [];
/** Версию держит текущий пользователь. */
export const isLockedBy = (lock: LockOut | null | undefined, userId: string): boolean => !!lock && lock.locked_by === userId;
/** Кнопка «Изменить статус» в шапке карточки: своя блокировка и есть ручные переходы. */
export const canChangeStatus = (status: ConsStatus, lock: LockOut | null | undefined, userId: string): boolean =>
isLockedBy(lock, userId) && manualTransitions(status).length > 0;
/** Лимит причины перехвата и комментария к смене статуса (`MAX_TAKEOVER_REASON`, `MAX_STATUS_COMMENT` бэка). */
export const MAX_REASON_LENGTH = 1000;
/** Причина перехвата / комментарий: непусто после обрезки пробелов и не длиннее лимита. */
export const isValidReason = (text: string | null | undefined): boolean => {
const trimmed = (text ?? '').trim();
return trimmed.length > 0 && trimmed.length <= MAX_REASON_LENGTH;
};
/** «Изменить статус» в модалке: выбран новый этап и комментарий непуст. */
export const canSubmitStatusChange = (toStatus: ConsStatus | null | undefined, comment: string | null | undefined): boolean =>
!!toStatus && isValidReason(comment);
/** Колонка «Версия»: у первой версии пусто, у последующих — номер. */
export const versionLabel = (versionNo: number): string => (versionNo === 1 ? '' : String(versionNo));
/** Флажок «Только в работе» из `?in_work_only=`: включён только явным `true` (по умолчанию выключен, как в API). */
export const parseInWorkOnly = (param: string | null): boolean => param === 'true';
/** «Взять в работу» недоступна у отменённой версии — по коду статуса, а не по подписи. */
export const canTakeInWork = (status: ConsStatus): boolean => status !== 'CANCELLED';
/** Точка в конце фразы после ФИО: «Соколов А. В.» уже заканчивается точкой — вторую не ставим. */
export const endSentence = (text: string): string => (text.endsWith('.') ? text : `${text}.`);
/** «+ Создать» в модалке «Новая консолидация»: в консолидации не меньше двух ЮЛ (иначе 422 `PERIMETER_TOO_SMALL`). */
export const MIN_PERIMETER = 2;
/** Модалка [31-00] нужна при уходе со вкладки «ОСВ», пока есть несопоставленные строки. */
export const needsUnmappedWarning = (unmappedTotal: number | null | undefined): boolean => (unmappedTotal ?? 0) > 0;