// Домен вкладки «Редактирование заявок»: живёт целиком в SharePoint-списках // «<форма> — данные» / «<форма> — согласование», которыми управляет // request-portal (см. request_type.list_id/site_url — ссылки именно на список // данных; список согласования вычисляется по конвенции имени, см. approvalListTitle // в requestManageService.ts). Это НЕ сущности Tnsu.ApprovalRoute.Api — совпадений // с models/templateRoutes.ts нет, здесь другое хранилище. // // Значения статусов — Choice-колонки SharePoint, 1:1 с // request-portal/src/webparts/requestPortal/shared/approval/models/statuses.ts // (тот же самый список, то же самое хранилище — просто другой клиент). export const REQUEST_STATUS = { draft: 'Черновик', onApproval: 'На согласовании', queued: 'Ждёт очереди', onExecution: 'На исполнении', executed: 'Выполнено', approved: 'Согласовано', rejected: 'Отклонено', returned: 'Возвращена на доработку', execution: 'Выполнение', closed: 'Закрыта' } as const; export const APPROVAL_PHASE = { approval: 'Согласование', execution: 'Исполнение' } as const; // Строка маршрута в одном из этих статусов ЕЩЁ НЕ решена согласующим/исполнителем // ОКОНЧАТЕЛЬНО — её можно менять (переназначить человека, удалить, добавить новую // рядом). «Возвращена на доработку» сюда включена нарочно: это не финальное // решение (заявка вернётся на новый круг после доработки), поэтому согласующего // на этом шаге всё ещё можно переназначить. approved/executed/rejected — // финальные решения, трогать нельзя ни при каких условиях. // // ВАЖНО: это НЕ то же самое, что «можно переставить» (см. isMovableRowStatus // ниже) — «Возвращена на доработку» уже означает, что своё слово по этому шагу // согласующий сказал (просто мы разрешаем потом переназначить человека), поэтому // двигать такой шаг/менять его параллельность нельзя, а переназначить — можно. const EDITABLE_ROW_STATUSES: string[] = [ REQUEST_STATUS.draft, REQUEST_STATUS.queued, REQUEST_STATUS.onApproval, REQUEST_STATUS.onExecution, REQUEST_STATUS.returned ]; export function isEditableRowStatus(status: string): boolean { return EDITABLE_ROW_STATUSES.indexOf(status) !== -1; } // Строка в одном из этих статусов можно двигать (стрелки вверх/вниз) и менять её // параллельность с соседней — строго подмножество EDITABLE_ROW_STATUSES, без // «Возвращена на доработку»: решение по шагу уже принято, менять его МЕСТО в // маршруте (а значит и то, когда он активируется повторно) не должно быть можно, // хотя переназначить исполнителя на нём — можно (см. isEditableRowStatus). const MOVABLE_ROW_STATUSES: string[] = [ REQUEST_STATUS.draft, REQUEST_STATUS.queued, REQUEST_STATUS.onApproval, REQUEST_STATUS.onExecution ]; export function isMovableRowStatus(status: string): boolean { return MOVABLE_ROW_STATUSES.indexOf(status) !== -1; } // Заявка в этом статусе прошла полностью (все фазы завершены) — маршрут можно // только посмотреть, редактирование выключается целиком независимо от статусов // отдельных строк. export function isFullyClosedRequestStatus(status: string): boolean { return status === REQUEST_STATUS.closed; } export interface ManagedRequestSummary { id: number; title: string; status: string; created: string; // ISO authorName: string; /** * Уникальный код заявки (колонка RequestUniqueCode списка данных) — то, что * реально видит заявитель, в отличие от служебного числового Id списка * SharePoint. null — у заявки его нет (старая заявка, колонка не заполнена) * или список данных этой колонки не заводит; тогда в UI показывается id. */ // eslint-disable-next-line @rushstack/no-new-null -- зеркалит SharePoint: колонки может не быть uniqueCode: string | null; /** * Список-партиция, в котором физически лежит эта заявка. У разных партиций * независимая нумерация Id (см. ТЗ по архивации), поэтому один и тот же * requestId в разных партициях — это РАЗНЫЕ заявки: без listId открыть * заявку однозначно нельзя. */ listId: string; } export interface ManagedRequestPage { items: ManagedRequestSummary[]; hasMore: boolean; } // Строка списка «… — согласование». approverId/approverEmail нужны и для // отображения, и как исходное значение при открытии формы переназначения. export interface ManagedApprovalRow { id: number; ticketId: number; // eslint-disable-next-line @rushstack/no-new-null -- lookup-поле SharePoint, у ghost-строки (см. handleAddApprover) значения ещё нет approverId: number | null; approverName: string; approverEmail: string; position: string; status: string; date: string; /** * Та же дата, что в `date`, но необработанная ISO-строка (или '', если решения * ещё не было) — нужна для расчёта просрочки (см. utils/sla.ts/isRowOverdue): * `date` отформатирована в «дд.мм.гггг» и назад в Date уже не превращается * надёжно (зависит от локали при парсинге). */ dateIso: string; comment: string; order: number; allowReassign: boolean; phase: string; /** * SLA именно ЭТОЙ строки в часах. Значение проставляется при создании * маршрута (портал берёт его из resolve, где наследование «шаг перекрывает * тип» уже применено), и здесь его можно поправить для конкретной заявки. * null — в строке не задано, работает SLA типа заявки. */ // eslint-disable-next-line @rushstack/no-new-null -- пусто в SharePoint приходит как null и значит «не задано», это не 0 slaHours: number | null; /** Спрашивать ли причину просрочки по этой строке. null — берётся от типа заявки. */ // eslint-disable-next-line @rushstack/no-new-null -- см. slaHours askOverdueReason: boolean | null; } export interface ManagedRequestMatrix { request: ManagedRequestSummary; /** Абсолютный URL сайта, где лежат списки данных/согласования этого типа заявки. */ siteUrl: string; /** Title списка «… — данные» (для тела запроса к потоку оповещения, см. RequestManageService.notifyRow). */ dataListTitle: string; /** * Абсолютный URL списка данных — заявки нумерованные (списки-партиции, см. * ТЗ по архивации), и по мере роста текущий список меняется. Ссылка нужна, * чтобы администратор видел, в каком физическом списке лежит открытая * заявка, и мог перейти туда напрямую, не выясняя это вручную. */ dataListUrl: string; /** Title списка «… — согласование» (вычислен из Title списка данных). */ approvalListTitle: string; /** Обе фазы вместе, упорядочены по StepOrder. */ rows: ManagedApprovalRow[]; }