Обходной лист #7

Merged
gorbenko.pavel merged 12 commits from сlearance-form into main 2026-08-17 05:43:39 +00:00
3 changed files with 38 additions and 11 deletions
Showing only changes of commit fa551caf5e - Show all commits
@@ -70,17 +70,19 @@ const ClearanceForm: React.FC<IRequestFormProps> = ({ requestType, context, onCa
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [editId]);
// Выбор увольняемого сотрудника подтягивает его текущую должность — это только
// отправная точка для поля «Должность» (её можно поправить вручную, например
// если человек занимал руководящую должность до перевода), а не жёсткая связь.
// Должность/подразделение больше не вводятся вручную — подтягиваются из
// SharePoint User Profile (SPS-JobTitle/SPS-Department) при выборе сотрудника.
// Если профиль не синхронизирован и поле там пусто — пока просто остаётся
// пустым (TODO: фолбэк на справочник api.tnsu.kz, если это окажется частой
// проблемой на практике).
const handleEmployeeChange = (login: string): void => {
setData((prev) => ({ ...prev, Employee: login }));
setData((prev) => ({ ...prev, Employee: login, Position: '', Department: '' }));
if (!login) {
return;
}
approval
.getJobTitle(login)
.then((title) => setData((prev) => ({ ...prev, Position: title || prev.Position })))
.getProfileFields(login)
.then((profile) => setData((prev) => ({ ...prev, Position: profile.jobTitle, Department: profile.department })))
.catch(() => undefined);
};
@@ -2,6 +2,11 @@ import * as React from 'react';
import { CollapseSection, FormField, TextInput, MoneyInput, Toggle, PeopleField } from '../../../shared/ui';
import { ISectionProps, EMPLOYEE_PLACEHOLDER } from '../model';
// Должность/подразделение подтягиваются автоматически при выборе сотрудника
// (см. ClearanceForm.handleEmployeeChange — SharePoint User Profile) —
// свободный ввод здесь намеренно убран, чтобы не расходиться с профилем.
const NOOP = (): void => undefined;
/** Единственная секция формы «Обходной лист»: увольняемый, должность, подразделение, даты, карты, обучение. */
export const RequestSection: React.FC<ISectionProps> = ({ data, set, context, onEmployeeChange }) => (
<CollapseSection title="Обходной лист" icon="people">
@@ -14,10 +19,22 @@ export const RequestSection: React.FC<ISectionProps> = ({ data, set, context, on
/>
</FormField>
<FormField label="Должность" htmlFor="Position" required>
<TextInput id="Position" value={data.Position} onChange={(v) => set('Position', v)} />
<TextInput
id="Position"
value={data.Position}
onChange={NOOP}
readOnly
placeholder="Подставится автоматически по выбранному сотруднику"
/>
</FormField>
<FormField label="Подразделение" htmlFor="Department">
<TextInput id="Department" value={data.Department} onChange={(v) => set('Department', v)} />
<TextInput
id="Department"
value={data.Department}
onChange={NOOP}
readOnly
placeholder="Подставится автоматически по выбранному сотруднику"
/>
</FormField>
<FormField label="Дата расторжения договора" htmlFor="TerminationDate" required>
<TextInput
@@ -301,7 +301,15 @@ export class ApprovalListService {
// сотрудника отдаёт именно e-mail (см. pickedLogin), а getPropertiesFor ждёт
// account name — приводим e-mail к клеймовому виду так же, как ensure().
public async getJobTitle(loginName: string): Promise<string> {
if (!loginName) return '';
return (await this.getProfileFields(loginName)).jobTitle;
}
// Должность + подразделение из профиля SharePoint (SPS-JobTitle/SPS-Department)
// ОДНИМ запросом — используется там, где нужны оба поля (напр. предзаполнение
// «Обходного листа»), чтобы не дёргать getPropertiesFor дважды подряд.
// '' по каждому полю, если недоступно/профиль не синхронизирован.
public async getProfileFields(loginName: string): Promise<{ jobTitle: string; department: string }> {
if (!loginName) return { jobTitle: '', department: '' };
const account = loginName.indexOf('|') >= 0 ? loginName : `i:0#.f|membership|${loginName}`;
try {
const props = (await getSP().profiles.getPropertiesFor(account)) as {
@@ -313,9 +321,9 @@ export class ApprovalListService {
: ((raw as { results?: Array<{ Key: string; Value: string }> })?.results ?? []);
const map: Record<string, string> = {};
for (const p of list) map[p.Key] = p.Value;
return map['SPS-JobTitle'] || map.Title || '';
return { jobTitle: map['SPS-JobTitle'] || map.Title || '', department: map['SPS-Department'] || '' };
} catch {
return '';
return { jobTitle: '', department: '' };
}
}