diff --git a/src/webparts/requestPortal/forms/RecruitingHire/hireTypeFromSelection.test.ts b/src/webparts/requestPortal/forms/RecruitingHire/hireTypeFromSelection.test.ts new file mode 100644 index 0000000..7b538ee --- /dev/null +++ b/src/webparts/requestPortal/forms/RecruitingHire/hireTypeFromSelection.test.ts @@ -0,0 +1,38 @@ +import { hireTypeFromSelection } from './hireTypeFromSelection'; +import { HIRE_TYPE_OPTIONS } from './model'; + +describe('hireTypeFromSelection', () => { + it('«Декрет» → тип «Декрет» + ФИО замещаемого', () => { + expect(hireTypeFromSelection('Декрет', 'Иванова Анна Ивановна')).toEqual({ + hireType: 'Декрет', + replacedEmployee: 'Иванова Анна Ивановна' + }); + }); + + it('«Замена уволенного сотрудника» → «Вместо уволенного» + ФИО', () => { + expect(hireTypeFromSelection('Замена уволенного сотрудника', 'Петров Пётр Петрович')).toEqual({ + hireType: 'Вместо уволенного', + replacedEmployee: 'Петров Пётр Петрович' + }); + }); + + it('«Новая штатная единица» → «Новый сотрудник» без ФИО', () => { + expect(hireTypeFromSelection('Новая штатная единица', 'что-то')).toEqual({ + hireType: 'Новый сотрудник', + replacedEmployee: '' + }); + }); + + it('перевод, «Другое» и пустая причина — без подстановки', () => { + expect(hireTypeFromSelection('Перевод сотрудника', 'Сидоров')).toBeUndefined(); + expect(hireTypeFromSelection('Другое', 'уточнение')).toBeUndefined(); + expect(hireTypeFromSelection('', '')).toBeUndefined(); + }); + + it('подставляемый тип всегда есть среди вариантов «Тип приёма»', () => { + const allowed = HIRE_TYPE_OPTIONS.map((o) => o.value); + ['Декрет', 'Замена уволенного сотрудника', 'Новая штатная единица'].forEach((reason) => { + expect(allowed).toContain(hireTypeFromSelection(reason, '')?.hireType); + }); + }); +}); diff --git a/src/webparts/requestPortal/forms/RecruitingHire/hireTypeFromSelection.ts b/src/webparts/requestPortal/forms/RecruitingHire/hireTypeFromSelection.ts new file mode 100644 index 0000000..215c8aa --- /dev/null +++ b/src/webparts/requestPortal/forms/RecruitingHire/hireTypeFromSelection.ts @@ -0,0 +1,36 @@ +import { + OPEN_REASON_REPLACE, + OPEN_REASON_NEW, + OPEN_REASON_MATERNITY +} from '../RecruitingSelection/model'; +import { HIRE_TYPE_NEW, HIRE_TYPE_REPLACEMENT, HIRE_TYPE_MATERNITY } from './model'; + +/** + * Причина открытия вакансии (заявка на подбор) → тип приёма (заявка на приём). + * «Перевод сотрудника» и «Другое» однозначного соответствия не имеют — их тут + * нет, тип приёма в таком случае выбирает инициатор сам. + */ +const REASON_TO_HIRE_TYPE: Record = { + [OPEN_REASON_REPLACE]: HIRE_TYPE_REPLACEMENT, + [OPEN_REASON_MATERNITY]: HIRE_TYPE_MATERNITY, + [OPEN_REASON_NEW]: HIRE_TYPE_NEW +}; + +/** Типы приёма, для которых переносим ФИО замещаемого сотрудника. */ +const HIRE_TYPES_WITH_REPLACED_EMPLOYEE = [HIRE_TYPE_REPLACEMENT, HIRE_TYPE_MATERNITY]; + +export interface IHireTypePrefill { + hireType: string; + replacedEmployee: string; +} + +/** + * Что подставить в «Тип приёма» / «Взамен кого» по связанной заявке на подбор. + * undefined — соответствия нет, поля приёма не трогаем. + */ +export function hireTypeFromSelection(openReason: string, openReasonDetails: string): IHireTypePrefill | undefined { + const hireType = REASON_TO_HIRE_TYPE[openReason]; + if (!hireType) return undefined; + const replacedEmployee = HIRE_TYPES_WITH_REPLACED_EMPLOYEE.indexOf(hireType) !== -1 ? openReasonDetails : ''; + return { hireType, replacedEmployee }; +} diff --git a/src/webparts/requestPortal/forms/RecruitingHire/model.ts b/src/webparts/requestPortal/forms/RecruitingHire/model.ts index 06028aa..7fb4420 100644 --- a/src/webparts/requestPortal/forms/RecruitingHire/model.ts +++ b/src/webparts/requestPortal/forms/RecruitingHire/model.ts @@ -124,13 +124,19 @@ export const EMPTY_DATA: IFormData = { /** Подпись флажка HireOnArrival рядом с датой приёма. */ export const HIRE_ON_ARRIVAL_LABEL = 'По приезду на объект'; +export const HIRE_TYPE_NEW = 'Новый сотрудник'; + /** Значение «Тип приёма», при котором показывается поле «Взамен кого». */ export const HIRE_TYPE_REPLACEMENT = 'Вместо уволенного'; +/** Приём на время декретного отпуска сотрудника — тоже спрашиваем «Взамен кого». */ +export const HIRE_TYPE_MATERNITY = 'Декрет'; + /** Варианты «Тип приёма» — совпадают с choices колонки HireType. */ export const HIRE_TYPE_OPTIONS: ISelectOption[] = [ - { value: 'Новый сотрудник', label: 'Новый сотрудник' }, - { value: HIRE_TYPE_REPLACEMENT, label: HIRE_TYPE_REPLACEMENT } + { value: HIRE_TYPE_NEW, label: HIRE_TYPE_NEW }, + { value: HIRE_TYPE_REPLACEMENT, label: HIRE_TYPE_REPLACEMENT }, + { value: HIRE_TYPE_MATERNITY, label: HIRE_TYPE_MATERNITY } ]; /** Значение-триггер «Другое»: при нём показываем поле ручного ввода. */ diff --git a/src/webparts/requestPortal/forms/RecruitingHire/provisioning.json b/src/webparts/requestPortal/forms/RecruitingHire/provisioning.json index 22e1beb..d2e92e8 100644 --- a/src/webparts/requestPortal/forms/RecruitingHire/provisioning.json +++ b/src/webparts/requestPortal/forms/RecruitingHire/provisioning.json @@ -9,7 +9,7 @@ { "internalName": "CompanySharesJson", "title": "Распределение ЗП по компаниям (JSON)", "type": "Note" }, { "internalName": "Phone", "title": "Телефон", "type": "Text" }, - { "internalName": "HireType", "title": "Тип приёма", "type": "Choice", "choices": ["Новый сотрудник", "Вместо уволенного"] }, + { "internalName": "HireType", "title": "Тип приёма", "type": "Choice", "choices": ["Новый сотрудник", "Вместо уволенного", "Декрет"] }, { "internalName": "HireReplacedEmployee", "title": "Взамен кого (Ф.И.О. уволенного)", "type": "Text" }, { "internalName": "Project", "title": "Проект", "type": "Text" }, diff --git a/src/webparts/requestPortal/forms/RecruitingHire/sections/ApplicationSection.tsx b/src/webparts/requestPortal/forms/RecruitingHire/sections/ApplicationSection.tsx index 717a1f0..b6e8a2e 100644 --- a/src/webparts/requestPortal/forms/RecruitingHire/sections/ApplicationSection.tsx +++ b/src/webparts/requestPortal/forms/RecruitingHire/sections/ApplicationSection.tsx @@ -1,17 +1,23 @@ import * as React from 'react'; import { CollapseSection, FormField, TextInput, PhoneInput, Select } from '../../../shared/ui'; import { CompanyPickerMulti, ProjectPicker, ICompany } from '../../../shared/dictionary'; -import { ISectionProps, HIRE_TYPE_OPTIONS, HIRE_TYPE_REPLACEMENT } from '../model'; +import { ISectionProps, HIRE_TYPE_OPTIONS, HIRE_TYPE_REPLACEMENT, HIRE_TYPE_MATERNITY } from '../model'; import { SelectionRequestField } from './SelectionRequestField'; import { CompanySharesField } from './CompanySharesField'; +/** Подпись поля «Взамен кого» — уточняем, чьё ФИО вписывать, по типу приёма. */ +const REPLACED_EMPLOYEE_LABELS: Record = { + [HIRE_TYPE_REPLACEMENT]: 'Взамен кого (Ф.И.О. уволенного, полностью, пример: Иванов Иван Иванович)', + [HIRE_TYPE_MATERNITY]: 'Взамен кого (Ф.И.О. сотрудника в декрете, полностью, пример: Иванова Анна Ивановна)' +}; + export const ApplicationSection: React.FC = ({ data, set, context }) => { - const isReplacement = data.HireType === HIRE_TYPE_REPLACEMENT; + const replacedEmployeeLabel = REPLACED_EMPLOYEE_LABELS[data.HireType]; const handleHireTypeChange = (value: string): void => { set('HireType', value); - if (value !== HIRE_TYPE_REPLACEMENT) { - set('HireReplacedEmployee', ''); // при не-замене очищаем скрытое поле + if (!REPLACED_EMPLOYEE_LABELS[value]) { + set('HireReplacedEmployee', ''); // тип без замещения — очищаем скрытое поле } }; @@ -77,9 +83,9 @@ export const ApplicationSection: React.FC = ({ data, set, context placeholder="— выберите —" /> - {isReplacement && ( + {replacedEmployeeLabel && ( diff --git a/src/webparts/requestPortal/forms/RecruitingHire/sections/SelectionRequestField.tsx b/src/webparts/requestPortal/forms/RecruitingHire/sections/SelectionRequestField.tsx index c52dc07..12b2795 100644 --- a/src/webparts/requestPortal/forms/RecruitingHire/sections/SelectionRequestField.tsx +++ b/src/webparts/requestPortal/forms/RecruitingHire/sections/SelectionRequestField.tsx @@ -5,6 +5,7 @@ import { routeUrl } from '../../../shared/routing'; import { getApprovedSelectionRequests, ISelectionRequestOption } from '../selectionLookup'; import { loadSelectionRequest } from '../../RecruitingSelection/load'; import { IFormData, SetField } from '../model'; +import { hireTypeFromSelection } from '../hireTypeFromSelection'; /** Слаг формы подбора в реестре форм — цель ссылки (rptype). */ const SELECTION_FORM_KEY = 'recruiting-selection'; @@ -104,6 +105,11 @@ export const SelectionRequestField: React.FC = ({ d set('ProbationPeriodOther', selection.SelectionProbationPeriodOther); set('SalaryProbation', selection.RateProbation); set('SalaryAfterProbation', selection.RateAfterProbation); + const hireTypePrefill = hireTypeFromSelection(selection.VacancyOpenReason, selection.VacancyOpenReasonDetails); + if (hireTypePrefill) { + set('HireType', hireTypePrefill.hireType); + set('HireReplacedEmployee', hireTypePrefill.replacedEmployee); + } }) .catch(() => undefined); // связь уже сохранена по id/коду — сбой обогащения не критичен }; diff --git a/src/webparts/requestPortal/forms/RecruitingSelection/model.ts b/src/webparts/requestPortal/forms/RecruitingSelection/model.ts index fd89157..84e46c5 100644 --- a/src/webparts/requestPortal/forms/RecruitingSelection/model.ts +++ b/src/webparts/requestPortal/forms/RecruitingSelection/model.ts @@ -161,12 +161,15 @@ export const OTHER_VALUE = 'Другое'; export const OPEN_REASON_REPLACE = 'Замена уволенного сотрудника'; export const OPEN_REASON_TRANSFER = 'Перевод сотрудника'; export const OPEN_REASON_NEW = 'Новая штатная единица'; +/** Декрет — временная вакансия на время отпуска сотрудника; ФИО замещаемого нужно кадрам для срочного договора. */ +export const OPEN_REASON_MATERNITY = 'Декрет'; /** Варианты «Причина открытия вакансии» — совпадают с choices колонки VacancyOpenReason. */ export const OPEN_REASON_OPTIONS: ISelectOption[] = [ { value: OPEN_REASON_REPLACE, label: OPEN_REASON_REPLACE }, { value: OPEN_REASON_TRANSFER, label: OPEN_REASON_TRANSFER }, { value: OPEN_REASON_NEW, label: OPEN_REASON_NEW }, + { value: OPEN_REASON_MATERNITY, label: OPEN_REASON_MATERNITY }, { value: OTHER_VALUE, label: OTHER_VALUE } ]; diff --git a/src/webparts/requestPortal/forms/RecruitingSelection/provisioning.json b/src/webparts/requestPortal/forms/RecruitingSelection/provisioning.json index 2d13a6e..d82649c 100644 --- a/src/webparts/requestPortal/forms/RecruitingSelection/provisioning.json +++ b/src/webparts/requestPortal/forms/RecruitingSelection/provisioning.json @@ -5,7 +5,7 @@ { "internalName": "RequestStatus", "title": "Статус", "type": "Choice", "choices": ["Черновик", "На согласовании", "Согласовано", "Отклонено", "Возвращена на доработку", "Выполнение", "Закрыта"] }, { "internalName": "Responsible", "title": "Ответственный за подачу заявки", "type": "User", "userSelectionMode": "PeopleOnly", "required": true }, { "internalName": "SubmitDate", "title": "Дата подачи заявки", "type": "DateTime", "dateOnly": true }, - { "internalName": "VacancyOpenReason", "title": "Причина открытия вакансии", "type": "Choice", "choices": ["Замена уволенного сотрудника", "Перевод сотрудника", "Новая штатная единица", "Другое"] }, + { "internalName": "VacancyOpenReason", "title": "Причина открытия вакансии", "type": "Choice", "choices": ["Замена уволенного сотрудника", "Перевод сотрудника", "Новая штатная единица", "Декрет", "Другое"] }, { "internalName": "VacancyOpenReasonDetails", "title": "Причина открытия — ФИО / уточнение", "type": "Text" }, { "internalName": "CompaniesJson", "title": "Компании (JSON)", "type": "Note" }, { "internalName": "ObjectProject", "title": "Объект/Проект", "type": "Text" }, diff --git a/src/webparts/requestPortal/forms/RecruitingSelection/sections/ApplicationSection.tsx b/src/webparts/requestPortal/forms/RecruitingSelection/sections/ApplicationSection.tsx index 663d51e..8cdc98a 100644 --- a/src/webparts/requestPortal/forms/RecruitingSelection/sections/ApplicationSection.tsx +++ b/src/webparts/requestPortal/forms/RecruitingSelection/sections/ApplicationSection.tsx @@ -7,14 +7,18 @@ import { OPEN_REASON_OPTIONS, OPEN_REASON_REPLACE, OPEN_REASON_TRANSFER, - OPEN_REASON_NEW + OPEN_REASON_NEW, + OPEN_REASON_MATERNITY } from '../model'; +/** Причины, где вакансия открывается под конкретного сотрудника — спрашиваем его ФИО. */ +const REASONS_WITH_EMPLOYEE_NAME = [OPEN_REASON_REPLACE, OPEN_REASON_TRANSFER, OPEN_REASON_MATERNITY]; + export const ApplicationSection: React.FC = ({ data, set, context, onResponsibleChange }) => { const reason = data.VacancyOpenReason; const showReasonDetails = reason !== '' && reason !== OPEN_REASON_NEW; const reasonDetailsLabel = - reason === OPEN_REASON_REPLACE || reason === OPEN_REASON_TRANSFER + REASONS_WITH_EMPLOYEE_NAME.indexOf(reason) !== -1 ? 'ФИО сотрудника (Полностью, пример: Иванов Иван Иванович)' : 'Уточните причину';