import { AadHttpClient, HttpClientResponse } from '@microsoft/sp-http'; import { WebPartContext } from '@microsoft/sp-webpart-base'; import { APPROVAL_API_CLIENT_ID, DICTIONARY_API_BASE_URL } from '../config'; import { IEmployeeFullBaseRecord } from './models/IEmployeeFullBaseRecord'; import { IEmployeeFullBasePerson } from './models/IEmployeeFullBasePerson'; // null из API → '' (см. IEmployeeFullBaseRecord). function textOrEmpty(value: unknown): string { return typeof value === 'string' ? value.trim() : ''; } function toRecord(r: Record): IEmployeeFullBaseRecord { return { id: textOrEmpty(r.id), fio: textOrEmpty(r.fio), iin: textOrEmpty(r.iin), code: textOrEmpty(r.code), company: textOrEmpty(r.company), // Должности в базе бывают с хвостовым пробелом («Монтажник ») — textOrEmpty его срезает. position: textOrEmpty(r.position), department: textOrEmpty(r.department), // В API опечатка «admision» — переименовываем на входе, дальше по коду правильное имя. admissionDate: textOrEmpty(r.admisionDate), dismissalDate: textOrEmpty(r.dismissalDate), email: textOrEmpty(r.email) }; } /** * GET /Employee/FullBase?Fio=... — работающие сотрудники по части Ф.И.О. * * Без фильтра эндпоинт отдаёт всю базу (тысячи записей, с уволенными), поэтому * ищем на сервере по мере ввода, как PositionPicker — и ничего не кешируем. * * Уволенных отсекаем по dismissalDate на клиенте. Параметр Status у эндпоинта * есть, но его значения не задокументированы — ДОПУЩЕНИЕ, проверить и, если он * фильтрует работающих, перейти на него. */ export async function searchActiveEmployees( context: WebPartContext, fio: string, maxResults: number ): Promise { const query = fio.trim(); if (!query) return []; const client = await context.aadHttpClientFactory.getClient(APPROVAL_API_CLIENT_ID); const response: HttpClientResponse = await client.get( `${DICTIONARY_API_BASE_URL}/Employee/FullBase?Fio=${encodeURIComponent(query)}`, AadHttpClient.configurations.v1 ); if (response.status === 404) return []; if (!response.ok) { throw new Error(`Employee/FullBase: ${response.status} ${response.statusText}`); } const body = (await response.json()) as Array>; return body .map(toRecord) .filter((r) => !r.dismissalDate) .slice(0, maxResults); } /** Чей это человек: ИИН, а без него — id записи (тогда запись считается отдельным человеком). */ export function personKey(record: IEmployeeFullBaseRecord): string { return record.iin || record.id; } /** * Уникальный ключ места работы — для value в выпадающем списке. Не только id: * у совместителя на каждое место работы может прийти один и тот же id человека. */ export function workplaceKey(record: IEmployeeFullBaseRecord): string { return `${record.id}|${record.company}|${record.position}`; } /** Человек из одной записи — когда остальные его места работы неизвестны (напр. просмотр заявки). */ export function personFromWorkplace(record: IEmployeeFullBaseRecord): IEmployeeFullBasePerson { return { key: personKey(record), fio: record.fio, iin: record.iin, email: record.email, workplaces: [record] }; } /** Записи FullBase → люди (по ИИН), порядок — по первому появлению человека в ответе. */ export function groupEmployeesByPerson(records: IEmployeeFullBaseRecord[]): IEmployeeFullBasePerson[] { const people: IEmployeeFullBasePerson[] = []; records.forEach((record) => { const person = people.find((p) => p.key === personKey(record)); if (!person) { people.push(personFromWorkplace(record)); return; } person.workplaces.push(record); if (!person.email) person.email = record.email; }); return people; }