From 614a9d7e37890912a2ddf3a12face5b78cb8e067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A2=D0=B8=D0=BC=D1=83=D1=80=20=D0=93=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D0=B4=D0=B5=D1=86=D0=BA=D0=B8=D0=B9?= Date: Thu, 29 Oct 2026 12:10:22 +0500 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=BA=D0=B0=D0=B4=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B8=D0=BA=20=D0=B2=20=D0=BC=D0=B0=D1=80=D1=88=D1=80=D1=83?= =?UTF-8?q?=D1=82=D0=B5=20=D0=BE=D1=82=D0=BF=D1=83=D1=81=D0=BA=D0=B0=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8=D1=82=20=D0=BE=D1=82=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Кадры назначают кадровика по объекту, а в офисе — по компании, поэтому маршрут #9 теперь учитывает проект: форма передаёт его в approval-route вместе с организацией. Поле «Проект» стало обязательным и принимается только из справочника — название, вписанное руками, условия маршрута не узнают, и заявка ушла бы без кадровика. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../forms/Vacation/VacationForm.tsx | 11 ++++++---- .../requestPortal/forms/Vacation/model.ts | 2 +- .../forms/Vacation/routeFields.ts | 20 +++++++++++++++++++ .../Vacation/sections/RequestSection.tsx | 2 +- .../forms/Vacation/validation.ts | 12 +++++++++++ 5 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 src/webparts/requestPortal/forms/Vacation/routeFields.ts diff --git a/src/webparts/requestPortal/forms/Vacation/VacationForm.tsx b/src/webparts/requestPortal/forms/Vacation/VacationForm.tsx index a23479c..d317554 100644 --- a/src/webparts/requestPortal/forms/Vacation/VacationForm.tsx +++ b/src/webparts/requestPortal/forms/Vacation/VacationForm.tsx @@ -48,7 +48,7 @@ import { } from '../../shared/listTitles'; import { FORM_ID, IFormData, EMPTY_DATA, SetField, NOTICE_TITLE, NOTICE_ITEMS } from './model'; import { formIssues } from './validation'; -import { organizationRouteFieldValues } from '../../shared/dictionary/organizationRouteFields'; +import { vacationRouteFieldValues } from './routeFields'; import { saveVacationRequest, vacationApprovalService } from './submit'; import { loadVacationRequest } from './load'; import { RequestSection } from './sections/RequestSection'; @@ -175,11 +175,14 @@ const VacationForm: React.FC = ({ // маршрут читается из уже записанных строк, чтобы не затереть правки инициатора. // // Кадровый сотрудник и исполнитель в маршруте зависят от организации - // сотрудника (условия «Organization содержит …» в админке) — см. - // shared/dictionary/organizationRouteFields.ts. Организация приходит позже + // сотрудника и проекта (условия «Organization содержит …», «Project + // содержит …» в админке) — см. routeFields.ts. Организация приходит позже // выбора сотрудника (Employee/ByEmail), поэтому она в ключе перезагрузки: // маршрут пересчитается, когда придёт, а не останется рассчитанным без условий. - const routeFieldValues = useMemo(() => organizationRouteFieldValues(data.Organization), [data.Organization]); + const routeFieldValues = useMemo( + () => vacationRouteFieldValues({ Organization: data.Organization, Project: data.Project }), + [data.Organization, data.Project] + ); const routeOrgKey = routeFieldValues.map((f) => f.value).join('|'); const loadMatrix = useCallback( () => diff --git a/src/webparts/requestPortal/forms/Vacation/model.ts b/src/webparts/requestPortal/forms/Vacation/model.ts index 11b49c5..becb64b 100644 --- a/src/webparts/requestPortal/forms/Vacation/model.ts +++ b/src/webparts/requestPortal/forms/Vacation/model.ts @@ -40,7 +40,7 @@ export interface IFormData { Department: string; /** Id отдела из справочника; '' — если введён вручную без выбора из списка. */ DepartmentId: string; - /** Проект (справочник /Project, см. shared/dictionary/ProjectPicker) — необязательное, справочное. */ + /** Проект (справочник /Project, см. shared/dictionary/ProjectPicker) — обязательный, от него зависит кадровик в маршруте. */ Project: string; /** oid проекта ('' — если введён вручную без выбора из списка). */ ProjectId: string; diff --git a/src/webparts/requestPortal/forms/Vacation/routeFields.ts b/src/webparts/requestPortal/forms/Vacation/routeFields.ts new file mode 100644 index 0000000..0bdfd04 --- /dev/null +++ b/src/webparts/requestPortal/forms/Vacation/routeFields.ts @@ -0,0 +1,20 @@ +import type { IResolveFieldValue } from '../../shared/approval/models/IApprovalRouteApi'; +import { organizationRouteFieldValues } from '../../shared/dictionary/organizationRouteFields'; +import { IFormData } from './model'; + +/** Поля заявки, от которых зависит маршрут отпуска (условия шагов в админке маршрута #9). */ +export type VacationRouteInput = Pick; + +/** + * Значения полей для условных шагов маршрута (fieldValues при resolve). + * + * Кадровик на шаге 1 зависит и от компании, и от проекта: в офисе — кадровик + * компании («Organization содержит TANSU И Project содержит Офис TANSU»), на + * объекте — кадровик объекта («Project содержит Топар»). Проект — название из + * справочника /Project, как его показывает поле формы. + */ +export function vacationRouteFieldValues(input: VacationRouteInput): IResolveFieldValue[] { + const fieldValues = organizationRouteFieldValues(input.Organization); + const project = input.Project.trim(); + return project ? [...fieldValues, { fieldName: 'Project', value: project }] : fieldValues; +} diff --git a/src/webparts/requestPortal/forms/Vacation/sections/RequestSection.tsx b/src/webparts/requestPortal/forms/Vacation/sections/RequestSection.tsx index 6023068..547b989 100644 --- a/src/webparts/requestPortal/forms/Vacation/sections/RequestSection.tsx +++ b/src/webparts/requestPortal/forms/Vacation/sections/RequestSection.tsx @@ -20,7 +20,7 @@ export const RequestSection: React.FC = ({ data, set, context, on /> - + = [ data.Employee ? undefined : { field: 'Employee', label: 'Сотрудник' }, + projectIssue(data), startDateIssue(data), daysCountIssue(data) ];