feat: добавил поле организация во все заявки, а также расширил компонет выбора даты для ui-kit

This commit is contained in:
Тимур Городецкий
2026-08-25 11:43:57 +05:00
parent a146877327
commit 3b4ee93b48
55 changed files with 641 additions and 143 deletions
@@ -0,0 +1,59 @@
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 { IEmployeeOrganization } from './models/IEmployeeOrganization';
// GET /Employee/ByEmail?Email=... — организации сотрудника (см. IEmployeeOrganization:
// один email может вернуть несколько записей). 404 (email не найден в системе
// кадров) — не ошибка формы, просто пусто: не у каждого приглашённого/внешнего
// пользователя портала есть карточка сотрудника.
async function fetchEmployeeOrganizations(context: WebPartContext, email: string): Promise<IEmployeeOrganization[]> {
const client = await context.aadHttpClientFactory.getClient(APPROVAL_API_CLIENT_ID);
const response: HttpClientResponse = await client.get(
`${DICTIONARY_API_BASE_URL}/Employee/ByEmail?Email=${encodeURIComponent(email)}`,
AadHttpClient.configurations.v1
);
if (response.status === 404) return [];
if (!response.ok) {
throw new Error(`Employee/ByEmail: ${response.status} ${response.statusText}`);
}
const body = (await response.json()) as Array<Record<string, unknown>>;
return body.map((r) => ({
id: (r.Id as string) || '',
fio: (r.Fio as string) || '',
email: (r.Email as string) || '',
company: (r.Company as string) || '',
companyId: (r.CompanyId as string) || '',
section: (r.Section as string) || '',
departmentId: (r.DepartmentId as string) || '',
position: (r.Position as string) || '',
mobile: (r.Mobile as string) || ''
}));
}
// Кеш по e-mail (не один общий промис, как у companies/departments — там список
// один на всех, здесь результат зависит от аргумента). Ошибку не кешируем — см.
// companies.service.ts, тот же принцип.
const cache = new Map<string, Promise<IEmployeeOrganization[]>>();
/** Организации сотрудника по e-mail. Пустой email → [] без обращения к API. */
export function getEmployeeOrganizations(context: WebPartContext, email: string): Promise<IEmployeeOrganization[]> {
if (!email) return Promise.resolve([]);
const cached = cache.get(email);
if (cached) return cached;
const promise = fetchEmployeeOrganizations(context, email).catch((e) => {
cache.delete(email);
throw e;
});
cache.set(email, promise);
return promise;
}
/**
* «Компания — должность» по каждой записи, по одной на строку (`\n`) — общий
* формат и для отображения (построчно), и для хранения в списке SharePoint
* (колонка Organization, Note — поддерживает переносы строк).
*/
export function formatEmployeeOrganizations(records: IEmployeeOrganization[]): string {
return records.map((r) => (r.position ? `${r.company} — ${r.position}` : r.company)).join('\n');
}
@@ -10,3 +10,5 @@ export { getCompanies } from './companies.service';
export type { ICompany } from './models/ICompany';
export { CompanyPickerMulti } from './ui/CompanyPickerMulti';
export type { ICompanyPickerMultiProps } from './ui/CompanyPickerMulti';
export { getEmployeeOrganizations, formatEmployeeOrganizations } from './employeeOrganizations.service';
export type { IEmployeeOrganization } from './models/IEmployeeOrganization';
@@ -0,0 +1,17 @@
/**
* Одна запись из GET /Employee/ByEmail — сотрудник МОЖЕТ состоять сразу в
* нескольких организациях (совместительство/аффилированные ТОО), тогда на один
* и тот же e-mail эндпоинт возвращает НЕСКОЛЬКО записей с разными Company/
* CompanyId/Section/DepartmentId/Position.
*/
export interface IEmployeeOrganization {
id: string;
fio: string;
email: string;
company: string;
companyId: string;
section: string;
departmentId: string;
position: string;
mobile: string;
}