Подписи и пояснения статусов версии, ручные переходы 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>
94 lines
7.1 KiB
TypeScript
94 lines
7.1 KiB
TypeScript
// Чистые правила экранов консолидаций (спека `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;
|