новая форма

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