feat: отметка «Согласован СБ» с примечанием в заявке на приём
Инициатор при подаче отмечает, что кандидат согласован со службой безопасности; при отметке обязательно примечание СБ (без отметки поле скрыто и очищается). Поле информационное, на маршрут не влияет. Раздел «6. Служба безопасности» — перед вложениями, «Примечание для согласующих» стало разделом 7. Колонки HireSecurityApproved (Boolean), HireSecurityNote (Note). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -73,6 +73,7 @@ import { PpeSection } from './sections/PpeSection';
|
|||||||
import { ConditionsSection } from './sections/ConditionsSection';
|
import { ConditionsSection } from './sections/ConditionsSection';
|
||||||
import { AgencySection } from './sections/AgencySection';
|
import { AgencySection } from './sections/AgencySection';
|
||||||
import { NoteSection } from './sections/NoteSection';
|
import { NoteSection } from './sections/NoteSection';
|
||||||
|
import { SecuritySection } from './sections/SecuritySection';
|
||||||
import { useStableCallback } from '../../shared/useStableCallback';
|
import { useStableCallback } from '../../shared/useStableCallback';
|
||||||
|
|
||||||
const RecruitingHireForm: React.FC<IRequestFormProps> = ({
|
const RecruitingHireForm: React.FC<IRequestFormProps> = ({
|
||||||
@@ -409,6 +410,7 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({
|
|||||||
<PpeSection {...sectionProps} />
|
<PpeSection {...sectionProps} />
|
||||||
<ConditionsSection {...sectionProps} />
|
<ConditionsSection {...sectionProps} />
|
||||||
<AgencySection {...sectionProps} />
|
<AgencySection {...sectionProps} />
|
||||||
|
<SecuritySection {...sectionProps} />
|
||||||
</FormModeProvider>
|
</FormModeProvider>
|
||||||
</FieldChangesProvider>
|
</FieldChangesProvider>
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ import { PpeSection } from './sections/PpeSection';
|
|||||||
import { ConditionsSection } from './sections/ConditionsSection';
|
import { ConditionsSection } from './sections/ConditionsSection';
|
||||||
import { AgencySection } from './sections/AgencySection';
|
import { AgencySection } from './sections/AgencySection';
|
||||||
import { NoteSection } from './sections/NoteSection';
|
import { NoteSection } from './sections/NoteSection';
|
||||||
|
import { SecuritySection } from './sections/SecuritySection';
|
||||||
import { ApproverNoteBanner } from '../../shared/approverNote';
|
import { ApproverNoteBanner } from '../../shared/approverNote';
|
||||||
import { useStableCallback } from '../../shared/useStableCallback';
|
import { useStableCallback } from '../../shared/useStableCallback';
|
||||||
|
|
||||||
@@ -416,6 +417,7 @@ const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemI
|
|||||||
<PpeSection {...sectionProps} />
|
<PpeSection {...sectionProps} />
|
||||||
<ConditionsSection {...sectionProps} />
|
<ConditionsSection {...sectionProps} />
|
||||||
<AgencySection {...sectionProps} />
|
<AgencySection {...sectionProps} />
|
||||||
|
<SecuritySection {...sectionProps} />
|
||||||
</FormModeProvider>
|
</FormModeProvider>
|
||||||
</FieldChangesProvider>
|
</FieldChangesProvider>
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +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',
|
||||||
'AgencyName', 'AgencyTerms', 'HireApproverNote',
|
'AgencyName', 'AgencyTerms', 'HireSecurityNote', 'HireApproverNote',
|
||||||
'SelectionRequestId', 'SelectionRequestListId', 'SelectionRequestCode'
|
'SelectionRequestId', 'SelectionRequestListId', 'SelectionRequestCode'
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -62,6 +62,7 @@ export async function loadHireRequest(itemId: number, listId?: string): Promise<
|
|||||||
'ProjectCode',
|
'ProjectCode',
|
||||||
'RecruitingAgency',
|
'RecruitingAgency',
|
||||||
'HireOnArrival',
|
'HireOnArrival',
|
||||||
|
'HireSecurityApproved',
|
||||||
'HireApproverNoteImportant',
|
'HireApproverNoteImportant',
|
||||||
APPROVER_CHANGES_FIELD,
|
APPROVER_CHANGES_FIELD,
|
||||||
'RequestStatus',
|
'RequestStatus',
|
||||||
@@ -79,6 +80,7 @@ export async function loadHireRequest(itemId: number, listId?: string): Promise<
|
|||||||
// Флаг агентства — булево поле (Yes/No), не строка.
|
// Флаг агентства — булево поле (Yes/No), не строка.
|
||||||
(data as unknown as Record<string, unknown>).RecruitingAgency = !!item.RecruitingAgency;
|
(data as unknown as Record<string, unknown>).RecruitingAgency = !!item.RecruitingAgency;
|
||||||
(data as unknown as Record<string, unknown>).HireOnArrival = !!item.HireOnArrival;
|
(data as unknown as Record<string, unknown>).HireOnArrival = !!item.HireOnArrival;
|
||||||
|
data.HireSecurityApproved = !!item.HireSecurityApproved;
|
||||||
data.HireApproverNoteImportant = !!item.HireApproverNoteImportant;
|
data.HireApproverNoteImportant = !!item.HireApproverNoteImportant;
|
||||||
|
|
||||||
SCALAR_FIELDS.forEach((key) => {
|
SCALAR_FIELDS.forEach((key) => {
|
||||||
|
|||||||
@@ -79,6 +79,11 @@ export interface IFormData {
|
|||||||
RecruitingAgency: boolean;
|
RecruitingAgency: boolean;
|
||||||
AgencyName: string;
|
AgencyName: string;
|
||||||
AgencyTerms: string;
|
AgencyTerms: string;
|
||||||
|
// Служба безопасности: инициатор при подаче отмечает, что кандидат согласован
|
||||||
|
// с СБ; при отметке обязательно примечание (см. validation.ts). Информационное —
|
||||||
|
// на маршрут и решения не влияет.
|
||||||
|
HireSecurityApproved: boolean;
|
||||||
|
HireSecurityNote: string;
|
||||||
// Примечание для согласующих (shared/approverNote): текст + «Важное».
|
// Примечание для согласующих (shared/approverNote): текст + «Важное».
|
||||||
// Префикс Hire — чтобы internalName не совпал с одноимёнными колонками других форм.
|
// Префикс Hire — чтобы internalName не совпал с одноимёнными колонками других форм.
|
||||||
HireApproverNote: string;
|
HireApproverNote: string;
|
||||||
@@ -124,6 +129,8 @@ export const EMPTY_DATA: IFormData = {
|
|||||||
RecruitingAgency: false,
|
RecruitingAgency: false,
|
||||||
AgencyName: '',
|
AgencyName: '',
|
||||||
AgencyTerms: '',
|
AgencyTerms: '',
|
||||||
|
HireSecurityApproved: false,
|
||||||
|
HireSecurityNote: '',
|
||||||
HireApproverNote: '',
|
HireApproverNote: '',
|
||||||
HireApproverNoteImportant: false
|
HireApproverNoteImportant: false
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,6 +46,9 @@
|
|||||||
{ "internalName": "AgencyName", "title": "Название компании (агентство)", "type": "Text" },
|
{ "internalName": "AgencyName", "title": "Название компании (агентство)", "type": "Text" },
|
||||||
{ "internalName": "AgencyTerms", "title": "Условия (агентство)", "type": "Note" },
|
{ "internalName": "AgencyTerms", "title": "Условия (агентство)", "type": "Note" },
|
||||||
|
|
||||||
|
{ "internalName": "HireSecurityApproved", "title": "Согласован СБ", "type": "Boolean" },
|
||||||
|
{ "internalName": "HireSecurityNote", "title": "Примечание СБ", "type": "Note" },
|
||||||
|
|
||||||
{ "internalName": "HireApproverNote", "title": "Примечание для согласующих", "type": "Note" },
|
{ "internalName": "HireApproverNote", "title": "Примечание для согласующих", "type": "Note" },
|
||||||
{ "internalName": "HireApproverNoteImportant", "title": "Примечание — важное", "type": "Boolean" }
|
{ "internalName": "HireApproverNoteImportant", "title": "Примечание — важное", "type": "Boolean" }
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ const NOTE_FIELD_KEYS: IApproverNoteFieldKeys = {
|
|||||||
important: 'HireApproverNoteImportant'
|
important: 'HireApproverNoteImportant'
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 6. Примечание для согласующих — общий раздел shared/approverNote на полях приёма. */
|
/** 7. Примечание для согласующих — общий раздел shared/approverNote на полях приёма. */
|
||||||
export const NoteSection: React.FC<ISectionProps> = ({ data, set }) => (
|
export const NoteSection: React.FC<ISectionProps> = ({ data, set }) => (
|
||||||
<ApproverNoteSection
|
<ApproverNoteSection
|
||||||
title="6. Примечание для согласующих"
|
title="7. Примечание для согласующих"
|
||||||
value={{ text: data.HireApproverNote, important: data.HireApproverNoteImportant }}
|
value={{ text: data.HireApproverNote, important: data.HireApproverNoteImportant }}
|
||||||
onChange={(note) => {
|
onChange={(note) => {
|
||||||
set('HireApproverNote', note.text);
|
set('HireApproverNote', note.text);
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { CollapseSection, FormField, TextArea, Toggle } from '../../../shared/ui';
|
||||||
|
import { ISectionProps } from '../model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 6. Служба безопасности: инициатор отмечает, что кандидат согласован с СБ, и
|
||||||
|
* тогда обязательно пишет примечание (см. validation.ts). Поле информационное —
|
||||||
|
* на маршрут и решения согласующих не влияет. При снятии отметки примечание
|
||||||
|
* очищается, чтобы в заявку не ушёл текст скрытого поля (как в AgencySection).
|
||||||
|
*/
|
||||||
|
export const SecuritySection: React.FC<ISectionProps> = ({ data, set }) => {
|
||||||
|
const handleToggle = (value: boolean): void => {
|
||||||
|
set('HireSecurityApproved', value);
|
||||||
|
if (!value) set('HireSecurityNote', '');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CollapseSection title="6. Служба безопасности" icon="user-check">
|
||||||
|
<FormField label="6.1 Согласован СБ" fieldKey="HireSecurityApproved">
|
||||||
|
<Toggle checked={data.HireSecurityApproved} onChange={handleToggle} onLabel="Да" />
|
||||||
|
</FormField>
|
||||||
|
{data.HireSecurityApproved && (
|
||||||
|
<FormField label="6.2 Примечание СБ" htmlFor="HireSecurityNote" required full>
|
||||||
|
<TextArea
|
||||||
|
id="HireSecurityNote"
|
||||||
|
value={data.HireSecurityNote}
|
||||||
|
onChange={(v) => set('HireSecurityNote', v)}
|
||||||
|
placeholder="Кто и когда согласовал, замечания СБ"
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
)}
|
||||||
|
</CollapseSection>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -38,6 +38,16 @@ describe('RecruitingHire validation', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('«Согласован СБ» требует примечание СБ, без отметки — не требует', () => {
|
||||||
|
expect(labels(submitIssues(DRAFT_READY))).not.toContain('6.2 Примечание СБ');
|
||||||
|
expect(labels(submitIssues({ ...DRAFT_READY, HireSecurityApproved: true }))).toContain('6.2 Примечание СБ');
|
||||||
|
expect(
|
||||||
|
labels(submitIssues({ ...DRAFT_READY, HireSecurityApproved: true, HireSecurityNote: 'Проверка пройдена' }))
|
||||||
|
).not.toContain('6.2 Примечание СБ');
|
||||||
|
// Черновик без примечания сохранить можно.
|
||||||
|
expect(draftIssues({ ...DRAFT_READY, HireSecurityApproved: true })).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
it('«По приезду на объект» снимает обязательность даты приёма', () => {
|
it('«По приезду на объект» снимает обязательность даты приёма', () => {
|
||||||
expect(labels(submitIssues({ ...DRAFT_READY, HireOnArrival: true }))).not.toContain('2.2 Дата приёма');
|
expect(labels(submitIssues({ ...DRAFT_READY, HireOnArrival: true }))).not.toContain('2.2 Дата приёма');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ interface IRequiredRule extends IFieldIssue {
|
|||||||
* агентство» не проверяется — у булевых нет пустого состояния.
|
* агентство» не проверяется — у булевых нет пустого состояния.
|
||||||
* 2.2 «Дата приёма» не нужна, если отмечено «По приезду на объект»
|
* 2.2 «Дата приёма» не нужна, если отмечено «По приезду на объект»
|
||||||
* (HireOnArrival) — тогда дату проставляет исполнитель на своём шаге.
|
* (HireOnArrival) — тогда дату проставляет исполнитель на своём шаге.
|
||||||
|
* 6.2 «Примечание СБ» обязательно только при отметке 6.1 «Согласован СБ».
|
||||||
*
|
*
|
||||||
* 1.3 «Проект» — хотя бы один проект из справочника: первый уходит в resolve
|
* 1.3 «Проект» — хотя бы один проект из справочника: первый уходит в resolve
|
||||||
* маршрута (projectId), а без него approval-route отвечал 404 и заявка ушла бы
|
* маршрута (projectId), а без него approval-route отвечал 404 и заявка ушла бы
|
||||||
@@ -48,7 +49,13 @@ 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: 'HireSecurityNote',
|
||||||
|
label: '6.2 Примечание СБ',
|
||||||
|
message: 'отмечено «Согласован СБ» — укажите примечание',
|
||||||
|
ok: (d) => !d.HireSecurityApproved || !!d.HireSecurityNote.trim()
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
function toIssue({ field, label, message }: IRequiredRule): IFieldIssue {
|
function toIssue({ field, label, message }: IRequiredRule): IFieldIssue {
|
||||||
|
|||||||
Reference in New Issue
Block a user