feat: поле «Время работы» в заявке на приём
Tests / frontend-test (push) Successful in 2m6s

Пункт 4.5 — тот же выбор, что 5.6 в заявке на подбор, с «Другое» и ручным
вводом; подставляется из связанного подбора и обязателен для отправки.
Колонка сайта WorkHours общая с подбором (тот же тип, заголовок и варианты),
в список приёма её подключает провизионинг — до деплоя, иначе заявка не
сохранится. «Сдельная оплата труда» сдвинута на 4.6.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Тимур Городецкий
2026-10-16 15:59:32 +05:00
parent b3c46f32d5
commit 984eb65f5b
8 changed files with 56 additions and 4 deletions
@@ -17,6 +17,8 @@
(`CompanyPickerMulti`), хранится JSON-массивом `{Id,Name}` в Note-колонке `CompaniesJson`. (`CompanyPickerMulti`), хранится JSON-массивом `{Id,Name}` в Note-колонке `CompaniesJson`.
- **График работы (WorkSchedule)** — выпадающий список (набор совпадает с «Заявкой - **График работы (WorkSchedule)** — выпадающий список (набор совпадает с «Заявкой
на подбор»); при выборе «Другое» появляется ручной ввод `WorkScheduleOther`. на подбор»); при выборе «Другое» появляется ручной ввод `WorkScheduleOther`.
- **Время работы (WorkHours)** — так же, как график: набор и колонка сайта общие
с «Заявкой на подбор», оттуда подставляется; «Другое» → `WorkHoursOther`.
- **Испытательный срок (ProbationPeriod)** — выпадающий список частых сроков. - **Испытательный срок (ProbationPeriod)** — выпадающий список частых сроков.
- **Рекрутинговое агентство (условный блок, последняя секция)** — `Toggle` - **Рекрутинговое агентство (условный блок, последняя секция)** — `Toggle`
`RecruitingAgency`; при включении показываются «Название компании» (`AgencyName`) `RecruitingAgency`; при включении показываются «Название компании» (`AgencyName`)
@@ -25,6 +25,7 @@ const SCALAR_FIELDS: (keyof IFormData)[] = [
'HireCandidateName', 'HireHireDate', 'Position', 'HireCandidateName', 'HireHireDate', 'Position',
'Department', 'SiteLocation', 'PpeHeight', 'PpeClothingSize', 'PpeShoeSize', 'ProbationPeriod', 'ProbationPeriodOther', 'Department', 'SiteLocation', 'PpeHeight', 'PpeClothingSize', 'PpeShoeSize', 'ProbationPeriod', 'ProbationPeriodOther',
'SalaryProbation', 'SalaryAfterProbation', 'WorkSchedule', 'WorkScheduleOther', 'SalaryProbation', 'SalaryAfterProbation', 'WorkSchedule', 'WorkScheduleOther',
'WorkHours', 'WorkHoursOther',
'AgencyName', 'AgencyTerms', 'HireSecurityNote', 'HireApproverNote', 'AgencyName', 'AgencyTerms', 'HireSecurityNote', 'HireApproverNote',
'SelectionRequestId', 'SelectionRequestListId', 'SelectionRequestCode' 'SelectionRequestId', 'SelectionRequestListId', 'SelectionRequestCode'
]; ];
@@ -75,6 +75,12 @@ export interface IFormData {
SalaryAfterProbation: string; SalaryAfterProbation: string;
WorkSchedule: string; WorkSchedule: string;
WorkScheduleOther: string; WorkScheduleOther: string;
/**
* Время работы — та же колонка сайта WorkHours (Choice), что в заявке на подбор:
* оттуда и подставляется (см. selectionPrefill).
*/
WorkHours: string;
WorkHoursOther: string;
/** /**
* Сдельная оплата труда — информационная отметка, на обязательность окладов не * Сдельная оплата труда — информационная отметка, на обязательность окладов не
* влияет. Та же колонка сайта PieceworkPay (Boolean), что в заявке на подбор: * влияет. Та же колонка сайта PieceworkPay (Boolean), что в заявке на подбор:
@@ -132,6 +138,8 @@ export const EMPTY_DATA: IFormData = {
SalaryAfterProbation: '', SalaryAfterProbation: '',
WorkSchedule: '', WorkSchedule: '',
WorkScheduleOther: '', WorkScheduleOther: '',
WorkHours: '',
WorkHoursOther: '',
PieceworkPay: false, PieceworkPay: false,
RecruitingAgency: false, RecruitingAgency: false,
AgencyName: '', AgencyName: '',
@@ -201,5 +209,19 @@ export const WORK_SCHEDULE_OPTIONS: ISelectOption[] = [
{ value: OTHER_VALUE, label: OTHER_VALUE } { value: OTHER_VALUE, label: OTHER_VALUE }
]; ];
/**
* Варианты «Время работы» (24-часовой формат) — совпадают с набором в форме
* «Заявка на подбор» (WORK_HOURS_OPTIONS) и с choices колонки WorkHours:
* формы независимы, значения продублированы намеренно.
*/
export const WORK_HOURS_OPTIONS: ISelectOption[] = [
{ value: '08:00-17:00', label: '08:00-17:00' },
{ value: '09:00-18:00', label: '09:00-18:00' },
{ value: '07:00-19:00', label: '07:00-19:00' },
{ value: '08:00-20:00', label: '08:00-20:00' },
{ value: '09:00-21:00', label: '09:00-21:00' },
{ value: OTHER_VALUE, label: OTHER_VALUE }
];
/** Плейсхолдер для полей-людей. */ /** Плейсхолдер для полей-людей. */
export const PERSON_PLACEHOLDER = 'Начните вводить имя сотрудника…'; export const PERSON_PLACEHOLDER = 'Начните вводить имя сотрудника…';
@@ -41,6 +41,8 @@
{ "internalName": "SalaryAfterProbation", "title": "Оклад по завершении ИС", "type": "Number", "decimals": 2 }, { "internalName": "SalaryAfterProbation", "title": "Оклад по завершении ИС", "type": "Number", "decimals": 2 },
{ "internalName": "WorkSchedule", "title": "График работы", "type": "Text" }, { "internalName": "WorkSchedule", "title": "График работы", "type": "Text" },
{ "internalName": "WorkScheduleOther", "title": "График работы (другое)", "type": "Text" }, { "internalName": "WorkScheduleOther", "title": "График работы (другое)", "type": "Text" },
{ "internalName": "WorkHours", "title": "Время работы", "type": "Choice", "choices": ["08:00-17:00", "09:00-18:00", "07:00-19:00", "08:00-20:00", "09:00-21:00", "Другое"] },
{ "internalName": "WorkHoursOther", "title": "Время работы (другое)", "type": "Text" },
{ "internalName": "PieceworkPay", "title": "Сдельная оплата труда", "type": "Boolean" }, { "internalName": "PieceworkPay", "title": "Сдельная оплата труда", "type": "Boolean" },
{ "internalName": "RecruitingAgency", "title": "Рекрутинговое агентство", "type": "Boolean" }, { "internalName": "RecruitingAgency", "title": "Рекрутинговое агентство", "type": "Boolean" },
@@ -1,6 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { CollapseSection, FormField, TextInput, MoneyInput, Select, Toggle } from '../../../shared/ui'; import { CollapseSection, FormField, TextInput, MoneyInput, Select, Toggle } from '../../../shared/ui';
import { ISectionProps, OTHER_VALUE, WORK_SCHEDULE_OPTIONS, PROBATION_PERIOD_OPTIONS } from '../model'; import { ISectionProps, OTHER_VALUE, WORK_SCHEDULE_OPTIONS, WORK_HOURS_OPTIONS, PROBATION_PERIOD_OPTIONS } from '../model';
interface IConditionsSectionProps extends ISectionProps { interface IConditionsSectionProps extends ISectionProps {
/** Начальник расчётной группы правит оклады даже в режиме просмотра (см. approverRights.ts). */ /** Начальник расчётной группы правит оклады даже в режиме просмотра (см. approverRights.ts). */
@@ -67,8 +67,29 @@ export const ConditionsSection: React.FC<IConditionsSectionProps> = ({ data, set
/> />
</FormField> </FormField>
)} )}
<FormField label="4.5 Время работы" htmlFor="WorkHours" required>
<Select
id="WorkHours"
value={data.WorkHours}
onChange={(v) => {
set('WorkHours', v);
if (v !== OTHER_VALUE) set('WorkHoursOther', '');
}}
options={WORK_HOURS_OPTIONS}
placeholder="— выберите —"
/>
</FormField>
{data.WorkHours === OTHER_VALUE && (
<FormField label="Время работы (другое)" htmlFor="WorkHoursOther">
<TextInput
id="WorkHoursOther"
value={data.WorkHoursOther}
onChange={(v) => set('WorkHoursOther', v)}
/>
</FormField>
)}
{/* Просто отметка: обязательность окладов (4.2, 4.3) от неё не зависит. */} {/* Просто отметка: обязательность окладов (4.2, 4.3) от неё не зависит. */}
<FormField label="4.5 Сдельная оплата труда" fieldKey="PieceworkPay"> <FormField label="4.6 Сдельная оплата труда" fieldKey="PieceworkPay">
<Toggle checked={data.PieceworkPay} onChange={(v) => set('PieceworkPay', v)} onLabel="Да" /> <Toggle checked={data.PieceworkPay} onChange={(v) => set('PieceworkPay', v)} onLabel="Да" />
</FormField> </FormField>
</CollapseSection> </CollapseSection>
@@ -34,6 +34,8 @@ export function selectionPrefill(link: ISelectionLink, selection: ISelectionData
Department: selection.Department, Department: selection.Department,
WorkSchedule: selection.WorkSchedule, WorkSchedule: selection.WorkSchedule,
WorkScheduleOther: selection.WorkScheduleOther, WorkScheduleOther: selection.WorkScheduleOther,
WorkHours: selection.WorkHours,
WorkHoursOther: selection.WorkHoursOther,
ProbationPeriod: selection.SelectionProbationPeriod, ProbationPeriod: selection.SelectionProbationPeriod,
ProbationPeriodOther: selection.SelectionProbationPeriodOther, ProbationPeriodOther: selection.SelectionProbationPeriodOther,
SalaryProbation: selection.RateProbation, SalaryProbation: selection.RateProbation,
@@ -25,7 +25,8 @@ describe('RecruitingHire validation', () => {
'4.1 Испытательный срок', '4.1 Испытательный срок',
'4.2 Оклад при испытательном сроке', '4.2 Оклад при испытательном сроке',
'4.3 Оклад по завершении ИС', '4.3 Оклад по завершении ИС',
'4.4 График работы' '4.4 График работы',
'4.5 Время работы'
]); ]);
}); });
@@ -49,7 +49,8 @@ const REQUIRED_RULES: ReadonlyArray<IRequiredRule> = [
{ field: 'ProbationPeriod', label: '4.1 Испытательный срок', ok: (d) => !!d.ProbationPeriod }, { field: 'ProbationPeriod', label: '4.1 Испытательный срок', ok: (d) => !!d.ProbationPeriod },
{ field: 'SalaryProbation', label: '4.2 Оклад при испытательном сроке', ok: (d) => !!d.SalaryProbation.trim() }, { field: 'SalaryProbation', label: '4.2 Оклад при испытательном сроке', ok: (d) => !!d.SalaryProbation.trim() },
{ field: 'SalaryAfterProbation', label: '4.3 Оклад по завершении ИС', ok: (d) => !!d.SalaryAfterProbation.trim() }, { field: 'SalaryAfterProbation', label: '4.3 Оклад по завершении ИС', ok: (d) => !!d.SalaryAfterProbation.trim() },
{ field: 'WorkSchedule', label: '4.4 График работы', ok: (d) => !!d.WorkSchedule } { field: 'WorkSchedule', label: '4.4 График работы', ok: (d) => !!d.WorkSchedule },
{ field: 'WorkHours', label: '4.5 Время работы', ok: (d) => !!d.WorkHours }
]; ];
function toIssue({ field, label, message }: IRequiredRule): IFieldIssue { function toIssue({ field, label, message }: IRequiredRule): IFieldIssue {