import { WebPartContext } from '@microsoft/sp-webpart-base'; import { SPHttpClient } from '@microsoft/sp-http'; /** * ОТЛАДОЧНАЯ подмена «текущего пользователя»: портал ведёт себя так, будто им * пользуется указанный сотрудник. Нужна, чтобы проверять маршруты и права * согласующего (например, «карту СКУД правит начальник КДП» в обходном листе), * не выпрашивая учётку у каждого участника маршрута. * * Значение задаётся в свойствах веб-части (property pane, см. * RequestPortalWebPart.getPropertyPaneConfiguration) и живёт на уровне модуля — * иначе его пришлось бы протаскивать пропсами через всё дерево до каждой формы. * * ЧТО ЭТО МЕНЯЕТ: только то, ЧЕЙ email портал отправляет как инициатора при * планировании маршрута и по какому пользователю фильтрует «свои» заявки и * очередь согласования. * * ЧЕГО ЭТО НЕ МЕНЯЕТ: прав доступа. Запись в SharePoint по-прежнему идёт под * реальной учёткой (Author/Editor в списках — настоящий пользователь), токен к * approval-route API тоже настоящий. То есть подмена не даёт увидеть то, к чему * нет доступа, но ПОЗВОЛЯЕТ принять решение за чужой шаг согласования — поэтому * на бою поле должно быть пустым (см. предупреждающую полосу в RequestPortal). */ let impersonatedEmail = ''; let impersonatedDisplayName = ''; /** Вызывается веб-частью на каждый render — свойство могли поменять в панели. */ export function setImpersonatedIdentity(email: string | undefined, displayName?: string): void { impersonatedEmail = (email || '').trim(); impersonatedDisplayName = (displayName || '').trim(); } /** Заданная подмена ('' — подмены нет). Для предупреждающей полосы в интерфейсе. */ export function getImpersonatedEmail(): string { return impersonatedEmail; } /** ФИО подменённого сотрудника, если его удалось найти поиском по имени ('' — для e-mail, введённого напрямую). */ export function getImpersonatedDisplayName(): string { return impersonatedDisplayName; } /** Email, под которым портал работает: подменённый, если задан, иначе — из токена. */ export function effectiveUserEmail(context: WebPartContext): string { return impersonatedEmail || context.pageContext.user.email || ''; } export interface IResolvedDebugUser { email: string; displayName: string; } interface IPeopleSearchHit { displayName: string; email: string; } /** Сырой ответ ClientPeoplePickerSearchUser — value внутри двойного JSON. */ interface IPeoplePickerEntity { DisplayText?: string; Description?: string; EntityData?: { Email?: string }; } async function searchPeopleByText(context: WebPartContext, queryText: string): Promise { const url = `${context.pageContext.web.absoluteUrl}/_api/SP.UI.ApplicationPages.ClientPeoplePickerWebServiceInterface.clientPeoplePickerSearchUser`; const body = JSON.stringify({ queryParams: { AllowEmailAddresses: true, AllowMultipleEntities: false, MaximumEntitySuggestions: 5, PrincipalSource: 15, // All PrincipalType: 1, // User QueryString: queryText } }); const response = await context.spHttpClient.post(url, SPHttpClient.configurations.v1, { headers: { Accept: 'application/json;odata=nometadata', 'Content-Type': 'application/json;odata=nometadata' }, body }); if (!response.ok) return []; const json: { value?: string } = await response.json(); if (!json.value) return []; let entries: IPeoplePickerEntity[]; try { entries = JSON.parse(json.value); } catch { return []; } return entries .map((e) => ({ displayName: e.DisplayText || '', email: e.EntityData?.Email || e.Description || '' })) .filter((e) => !!e.email); } /** * Ищет сотрудника по имени для отладочной подмены (см. property pane — * PropertyPaneDebugUser/DebugUserField). Порядок «Фамилия Имя» или «Имя * Фамилия» не важен: если прямой запрос ничего не находит, пробуем с * переставленными первыми двумя словами. */ export async function resolveDebugUser(context: WebPartContext, nameQuery: string): Promise { const text = nameQuery.trim(); if (!text) return undefined; let hits = await searchPeopleByText(context, text); if (hits.length === 0) { const tokens = text.split(/\s+/).filter(Boolean); if (tokens.length >= 2) { const swapped = [tokens[1], tokens[0], ...tokens.slice(2)].join(' '); hits = await searchPeopleByText(context, swapped); } } return hits[0]; }