feat: кадровик в маршруте отпуска зависит от проекта
Кадры назначают кадровика по объекту, а в офисе — по компании, поэтому маршрут #9 теперь учитывает проект: форма передаёт его в approval-route вместе с организацией. Поле «Проект» стало обязательным и принимается только из справочника — название, вписанное руками, условия маршрута не узнают, и заявка ушла бы без кадровика. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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<IRequestFormProps> = ({
|
||||
// маршрут читается из уже записанных строк, чтобы не затереть правки инициатора.
|
||||
//
|
||||
// Кадровый сотрудник и исполнитель в маршруте зависят от организации
|
||||
// сотрудника (условия «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(
|
||||
() =>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<IFormData, 'Organization' | 'Project'>;
|
||||
|
||||
/**
|
||||
* Значения полей для условных шагов маршрута (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;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export const RequestSection: React.FC<ISectionProps> = ({ data, set, context, on
|
||||
/>
|
||||
</FormField>
|
||||
<OrganizationTable value={data.Organization} printIndex={printOrgIndex} />
|
||||
<FormField label="Проект" htmlFor="Project">
|
||||
<FormField label="Проект" htmlFor="Project" required>
|
||||
<ProjectPicker
|
||||
id="Project"
|
||||
context={context}
|
||||
|
||||
@@ -19,6 +19,17 @@ function startDateIssue(data: IFormData): IFieldIssue | undefined {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Проект: обязателен и выбран из справочника. От него зависит кадровик в
|
||||
* маршруте (условия «Project содержит …»), а название, вписанное руками,
|
||||
* условия могут не узнать — поэтому без ProjectId заявку не пропускаем.
|
||||
*/
|
||||
function projectIssue(data: IFormData): IFieldIssue | undefined {
|
||||
if (!data.Project.trim()) return { field: 'Project', label: 'Проект' };
|
||||
if (data.ProjectId) return undefined;
|
||||
return { field: 'Project', label: 'Проект', message: 'выберите проект из списка' };
|
||||
}
|
||||
|
||||
/** Количество дней: обязательно, целое, от 1 до MAX_DAYS. */
|
||||
function daysCountIssue(data: IFormData): IFieldIssue | undefined {
|
||||
if (!data.DaysCount.trim()) return { field: 'DaysCount', label: 'Количество дней' };
|
||||
@@ -36,6 +47,7 @@ function daysCountIssue(data: IFormData): IFieldIssue | undefined {
|
||||
export function formIssues(data: IFormData): IFieldIssue[] {
|
||||
const issues: Array<IFieldIssue | undefined> = [
|
||||
data.Employee ? undefined : { field: 'Employee', label: 'Сотрудник' },
|
||||
projectIssue(data),
|
||||
startDateIssue(data),
|
||||
daysCountIssue(data)
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user