import { AadHttpClient, HttpClientResponse } from '@microsoft/sp-http'; import { WebPartContext } from '@microsoft/sp-webpart-base'; import { APPROVAL_API_CLIENT_ID, getApprovalApiBaseUrl } from '../config'; import { ApprovalStatus, IApiResponse, IRequestType, IResolvedRoute, IResolveFieldValue } from './models/IApprovalRouteApi'; // Все обращения к внешнему approval-route API — только здесь. // Клиент авторизуется через AadHttpClient (SSO по текущему пользователю). async function client(context: WebPartContext): Promise { return context.aadHttpClientFactory.getClient(APPROVAL_API_CLIENT_ID); } // GET /api/request-types — список типов заявок (id + имя). Пригодится, чтобы // сопоставить форму с RequestTypeId, если он неизвестен заранее. export async function getRequestTypes(context: WebPartContext): Promise { const c = await client(context); const response: HttpClientResponse = await c.get( `${getApprovalApiBaseUrl()}/api/request-types`, AadHttpClient.configurations.v1 ); if (!response.ok) { throw new Error(`request-types: ${response.status} ${response.statusText}`); } const body: IApiResponse = await response.json(); return body.data ?? []; } // Кеш на уровне МОДУЛЯ (не useRef/useState) — список типов заявок админ меняет // редко, а без кеша каждая форма и каждый переход в режим согласующего дёргали // бы /api/request-types заново. Живёт до перезагрузки страницы — тот же принцип, // что и у features/myRequests/pageCache.ts. Ошибку НЕ кешируем: если запрос // упал (сеть, 401 на холодном старте), следующий вызов должен попробовать заново, // а не молча повторять один и тот же провал до конца сессии. let requestTypesPromise: Promise | undefined; async function getRequestTypesCached(context: WebPartContext): Promise { if (!requestTypesPromise) { requestTypesPromise = getRequestTypes(context).catch((e) => { requestTypesPromise = undefined; throw e; }); } return requestTypesPromise; } /** * Числовой RequestTypeId по pageUrl (rptype) формы. Id типа заявки выдаётся * сервером при регистрации типа в админке approval-route и НЕПРЕДСКАЗУЕМ * заранее (см. README форм — «в старой pto-tracker было 1, при заведении типа * в админке получил новый id 11»), а pageUrl задаём мы сами в data/requests.ts * и он стабилен. Поэтому формы резолвят числовой id отсюда (через * useRequestTypeId), а не хардкодят его. */ export async function getRequestTypeIdByPageUrl(context: WebPartContext, pageUrl: string): Promise { const types = await getRequestTypesCached(context); const found = types.filter((t) => t.pageUrl === pageUrl)[0]; if (!found) { throw new Error( `Тип заявки с pageUrl «${pageUrl}» не найден в approval-route API — зарегистрируйте его в админке маршрутов (Типы заявок).` ); } return found.id; } // POST /api/template-routes/resolve — планируемый маршрут для типа заявки и // пользователя. Бэкенд сам подставляет исполнителей шага (executorEmail/position) // по ролям исходя из userEmail. Параметр status выбирает ФАЗУ маршрута: // 'Approval' — согласующие (кто одобряет), // 'Execution' — исполнители (кто выполняет после согласования). // Один и тот же маршрут, разные наборы шагов. // // fieldValues — условие видимости шагов (см. IResolveFieldValue): для форм с // шагами, зависящими от значения колонки списка (напр. WorkType), передать // текущее значение поля здесь. Формы без условных шагов его не передают — // бэкенд получает пустой массив, как раньше. export async function resolveRoute( context: WebPartContext, requestTypeId: number, userEmail: string, status: ApprovalStatus = 'Approval', departmentId: string | null = null, fieldValues: IResolveFieldValue[] = [], projectId: string | null = null ): Promise { const c = await client(context); const url = `${getApprovalApiBaseUrl()}/api/template-routes/resolve`; // Эндпоинт сменился с GET (query) на POST (JSON-тело). // projectId (uuid проекта) — для форм, где согласующий зависит от проекта // (напр. «руководитель проекта-источника» в СЗ на перемещение ТМЦ): бэкенд по // нему подставляет исполнителя роли. Формы без привязки к проекту шлют null. const response: HttpClientResponse = await c.post(url, AadHttpClient.configurations.v1, { headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body: JSON.stringify({ requestTypeId, userEmail, projectId: projectId || null, status, fieldValues, departmentId: departmentId || null }) }); if (!response.ok) { throw new Error(`resolve: ${response.status} ${response.statusText}`); } const body: IApiResponse = await response.json(); return body.data; }