feat: feat: брендированная печать, поле «Подразделение» и подача ТМЦ за другого
- Печать доступна только после согласования (Отдел рекрутинга); выбор организации и логотип-шапка для форм с несколькими юрлицами - «СЗ на перемещение ТМЦ»: заявителя можно выбрать (подача за другого), маршрут резолвится на выбранного - «Реферальная рекомендация»: организация вакансии (мультивыбор) и подразделение/проект вакансии - Поле «Подразделение» (справочник) — Vacation, UnpaidLeave, Clearance, WorkplaceSetup, RecruitingHire, RecruitingSelection, TmcTransfer - Поле «Проект» — Vacation, UnpaidLeave, Clearance
This commit is contained in:
@@ -35,6 +35,7 @@ import {
|
||||
IAttachmentInfo
|
||||
} from '../../shared/attachments/attachments.service';
|
||||
import { effectiveUserEmail } from '../../shared/impersonation';
|
||||
import { getEmployeeOrganizations, ICompany, IEmployeeOrganization } from '../../shared/dictionary';
|
||||
import { friendlyErrorMessage } from '../../shared/diagnostics';
|
||||
import { IRequestFormProps } from '../../models/types';
|
||||
import {
|
||||
@@ -98,6 +99,57 @@ const RecruitingSelectionForm: React.FC<IRequestFormProps> = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [editId, editListId]);
|
||||
|
||||
// Компании ответственного (Employee/ByEmail.company/companyId) → предзаполнение
|
||||
// «1.6 Организация» (Companies/CompanyPickerMulti). companyId из Employee/ByEmail
|
||||
// совпадает с Id из Dictionary/Companies (тот же источник) — доп. запрос не нужен.
|
||||
// Поле остаётся редактируемым: значения только ПРЕДЛАГАЮТСЯ, как и DepartmentName.
|
||||
const toCompanies = (records: IEmployeeOrganization[]): ICompany[] =>
|
||||
records.filter((r) => r.companyId && r.company).map((r) => ({ Id: r.companyId, Name: r.company }));
|
||||
|
||||
// Новая заявка — по умолчанию ответственный: сам подающий, подразделение и
|
||||
// организация — из Employee/ByEmail (Section/company), как в остальных формах
|
||||
// с авто-«Организацией» (см. shared/dictionary/employeeOrganizations.service.ts).
|
||||
useEffect(() => {
|
||||
if (editId) {
|
||||
return;
|
||||
}
|
||||
let alive = true;
|
||||
const user = context.pageContext.user;
|
||||
setData((prev) => ({ ...prev, Responsible: prev.Responsible || user.email }));
|
||||
getEmployeeOrganizations(context, user.email)
|
||||
.then((records) => {
|
||||
if (alive) {
|
||||
setData((prev) => ({
|
||||
...prev,
|
||||
DepartmentName: prev.DepartmentName || records[0]?.section || prev.DepartmentName,
|
||||
Companies: prev.Companies.length > 0 ? prev.Companies : toCompanies(records)
|
||||
}));
|
||||
}
|
||||
})
|
||||
.catch(() => undefined);
|
||||
return () => {
|
||||
alive = false;
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [editId]);
|
||||
|
||||
// Выбор другого ответственного подтягивает его подразделение и организацию.
|
||||
const handleResponsibleChange = (login: string): void => {
|
||||
setData((prev) => ({ ...prev, Responsible: login }));
|
||||
if (!login) {
|
||||
return;
|
||||
}
|
||||
getEmployeeOrganizations(context, login)
|
||||
.then((records) =>
|
||||
setData((prev) => ({
|
||||
...prev,
|
||||
DepartmentName: records[0]?.section || prev.DepartmentName,
|
||||
Companies: toCompanies(records)
|
||||
}))
|
||||
)
|
||||
.catch(() => undefined);
|
||||
};
|
||||
|
||||
// Маршрут согласования резолвится по email инициатора (текущего пользователя).
|
||||
// У заявки на редактировании маршрут читается из уже записанных строк, чтобы не
|
||||
// затереть правки инициатора при первом сохранении.
|
||||
@@ -242,7 +294,7 @@ const RecruitingSelectionForm: React.FC<IRequestFormProps> = ({
|
||||
const handleSaveDraft = (): Promise<void> => submit.run('draft', save(REQUEST_STATUS.draft), validate);
|
||||
const handleSubmit = (): Promise<void> => submit.run('submit', save(REQUEST_STATUS.onApproval), validateSubmit);
|
||||
|
||||
const sectionProps = { data, set, context };
|
||||
const sectionProps = { data, set, context, onResponsibleChange: handleResponsibleChange };
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
ApprovalMatrix,
|
||||
useMatrixEditor,
|
||||
notifyActiveStep,
|
||||
useRequestTypeId
|
||||
useRequestTypeId,
|
||||
isApprovedStatus
|
||||
} from '../../shared/approval';
|
||||
import { getAttachments, IAttachmentInfo } from '../../shared/attachments/attachments.service';
|
||||
import { friendlyErrorMessage } from '../../shared/diagnostics';
|
||||
@@ -118,7 +119,7 @@ const RecruitingSelectionView: React.FC<IRecruitingSelectionViewProps> = ({ cont
|
||||
return (
|
||||
<div>
|
||||
<div className={`${styles.toolbar} rp-no-print`}>
|
||||
<PrintButton />
|
||||
{isApprovedStatus(status) && <PrintButton />}
|
||||
</div>
|
||||
|
||||
<div className="rp-print-root">
|
||||
|
||||
@@ -25,7 +25,16 @@ export interface IFormData {
|
||||
ObjectProjectId: string;
|
||||
ObjectProjectCode: string;
|
||||
// Отдел
|
||||
/** Подразделение ОТВЕТСТВЕННОГО за подачу (Employee/ByEmail.Section при выборе Responsible). */
|
||||
DepartmentName: string;
|
||||
/**
|
||||
* Подразделение по справочнику (Dictionary/Departments, см.
|
||||
* shared/dictionary/DepartmentPicker) — как в WorkplaceSetup: ручной выбор,
|
||||
* чисто информационное, на маршрут согласования НЕ влияет.
|
||||
*/
|
||||
Department: string;
|
||||
/** Id отдела из справочника; '' — если введён вручную без выбора из списка. */
|
||||
DepartmentId: string;
|
||||
OpenVacanciesCount: string;
|
||||
// Вакансия
|
||||
Position: string;
|
||||
@@ -71,6 +80,12 @@ export interface ISectionProps {
|
||||
data: IFormData;
|
||||
set: SetField;
|
||||
context: WebPartContext;
|
||||
/**
|
||||
* Выбор ответственного подтягивает его подразделение (Employee/ByEmail →
|
||||
* Section) — поэтому у поля отдельный обработчик. Если не передан (режим
|
||||
* чтения) — правка через set.
|
||||
*/
|
||||
onResponsibleChange?: (login: string) => void;
|
||||
}
|
||||
|
||||
export const EMPTY_DATA: IFormData = {
|
||||
@@ -84,6 +99,8 @@ export const EMPTY_DATA: IFormData = {
|
||||
ObjectProjectId: '',
|
||||
ObjectProjectCode: '',
|
||||
DepartmentName: '',
|
||||
Department: '',
|
||||
DepartmentId: '',
|
||||
OpenVacanciesCount: '',
|
||||
Position: '',
|
||||
EmploymentType: '',
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
{ "internalName": "ObjectProjectCode", "title": "Объект/Проект — код", "type": "Text" },
|
||||
{ "internalName": "ClosingDeadline", "title": "Крайний срок закрытия", "type": "DateTime", "dateOnly": true },
|
||||
|
||||
{ "internalName": "DepartmentName", "title": "Название отдела/департамента", "type": "Text", "required": true },
|
||||
{ "internalName": "DepartmentName", "title": "Подразделение ответственного за подачу", "type": "Text", "required": true },
|
||||
{ "internalName": "Department", "title": "Подразделение (справочник)", "type": "Text" },
|
||||
{ "internalName": "DepartmentId", "title": "Id подразделения (справочник)", "type": "Text" },
|
||||
{ "internalName": "OpenVacanciesCount", "title": "Количество открытых вакансий", "type": "Number" },
|
||||
|
||||
{ "internalName": "Position", "title": "Должность", "type": "Text", "required": true },
|
||||
|
||||
+15
-7
@@ -10,7 +10,7 @@ import {
|
||||
OPEN_REASON_NEW
|
||||
} from '../model';
|
||||
|
||||
export const ApplicationSection: React.FC<ISectionProps> = ({ data, set, context }) => {
|
||||
export const ApplicationSection: React.FC<ISectionProps> = ({ data, set, context, onResponsibleChange }) => {
|
||||
const reason = data.OpenReason;
|
||||
const showReasonDetails = reason !== '' && reason !== OPEN_REASON_NEW;
|
||||
const reasonDetailsLabel =
|
||||
@@ -22,14 +22,22 @@ export const ApplicationSection: React.FC<ISectionProps> = ({ data, set, context
|
||||
<PeopleField
|
||||
context={context}
|
||||
value={data.Responsible}
|
||||
onChange={(v) => set('Responsible', v)}
|
||||
onChange={onResponsibleChange ?? ((v) => set('Responsible', v))}
|
||||
placeholder={PERSON_PLACEHOLDER}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="1.2 Дата подачи заявки" htmlFor="SubmitDate">
|
||||
<FormField label="1.2 Подразделение ответственного за подачу" htmlFor="DepartmentName" required full>
|
||||
<TextInput
|
||||
id="DepartmentName"
|
||||
value={data.DepartmentName}
|
||||
onChange={(v) => set('DepartmentName', v)}
|
||||
placeholder="Подставится автоматически по выбранному ответственному"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="1.3 Дата подачи заявки" htmlFor="SubmitDate">
|
||||
<TextInput id="SubmitDate" type="date" value={data.SubmitDate} onChange={(v) => set('SubmitDate', v)} />
|
||||
</FormField>
|
||||
<FormField label="1.3 Крайний срок закрытия" htmlFor="ClosingDeadline">
|
||||
<FormField label="1.4 Крайний срок закрытия" htmlFor="ClosingDeadline">
|
||||
<TextInput
|
||||
id="ClosingDeadline"
|
||||
type="date"
|
||||
@@ -37,7 +45,7 @@ export const ApplicationSection: React.FC<ISectionProps> = ({ data, set, context
|
||||
onChange={(v) => set('ClosingDeadline', v)}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="1.4 Причина открытия вакансии" htmlFor="OpenReason">
|
||||
<FormField label="1.5 Причина открытия вакансии" htmlFor="OpenReason">
|
||||
<Select
|
||||
id="OpenReason"
|
||||
value={data.OpenReason}
|
||||
@@ -58,7 +66,7 @@ export const ApplicationSection: React.FC<ISectionProps> = ({ data, set, context
|
||||
/>
|
||||
</FormField>
|
||||
)}
|
||||
<FormField label="1.5 Организация" htmlFor="Companies" full>
|
||||
<FormField label="1.6 Организация" htmlFor="Companies" full>
|
||||
<CompanyPickerMulti
|
||||
context={context}
|
||||
id="Companies"
|
||||
@@ -67,7 +75,7 @@ export const ApplicationSection: React.FC<ISectionProps> = ({ data, set, context
|
||||
placeholder="Начните вводить название организации…"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="1.6 Объект/Проект" htmlFor="ObjectProject" full>
|
||||
<FormField label="1.7 Объект/Проект" htmlFor="ObjectProject" full>
|
||||
<ProjectPicker
|
||||
context={context}
|
||||
id="ObjectProject"
|
||||
|
||||
+12
-3
@@ -1,11 +1,20 @@
|
||||
import * as React from 'react';
|
||||
import { CollapseSection, FormField, TextInput } from '../../../shared/ui';
|
||||
import { DepartmentPicker } from '../../../shared/dictionary';
|
||||
import { ISectionProps } from '../model';
|
||||
|
||||
export const DepartmentSection: React.FC<ISectionProps> = ({ data, set }) => (
|
||||
export const DepartmentSection: React.FC<ISectionProps> = ({ data, set, context }) => (
|
||||
<CollapseSection title="2. Отдел / Подразделение" icon="building">
|
||||
<FormField label="2.1 Название отдела/департамента" htmlFor="DepartmentName" required full>
|
||||
<TextInput id="DepartmentName" value={data.DepartmentName} onChange={(v) => set('DepartmentName', v)} />
|
||||
<FormField label="2.1 Подразделение" htmlFor="Department" full>
|
||||
<DepartmentPicker
|
||||
id="Department"
|
||||
context={context}
|
||||
value={data.Department}
|
||||
onChange={(name, id) => {
|
||||
set('Department', name);
|
||||
set('DepartmentId', id);
|
||||
}}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="2.2 Количество открытых вакансий" htmlFor="OpenVacanciesCount">
|
||||
<TextInput
|
||||
|
||||
Reference in New Issue
Block a user