feat: feat: брендированная печать, поле «Подразделение» и подача ТМЦ за другого
- Печать доступна только после согласования (Отдел рекрутинга); выбор организации и логотип-шапка для форм с несколькими юрлицами - «СЗ на перемещение ТМЦ»: заявителя можно выбрать (подача за другого), маршрут резолвится на выбранного - «Реферальная рекомендация»: организация вакансии (мультивыбор) и подразделение/проект вакансии - Поле «Подразделение» (справочник) — Vacation, UnpaidLeave, Clearance, WorkplaceSetup, RecruitingHire, RecruitingSelection, TmcTransfer - Поле «Проект» — Vacation, UnpaidLeave, Clearance
This commit is contained in:
@@ -9,6 +9,9 @@
|
||||
|
||||
.toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: $sp-3;
|
||||
margin-bottom: $sp-4;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as React from 'react';
|
||||
import { useState, useEffect, useMemo, useCallback } from 'react';
|
||||
import type { WebPartContext } from '@microsoft/sp-webpart-base';
|
||||
import styles from './VacationView.module.scss';
|
||||
import { Alert, FormCard, InitiatorLine, FormModeProvider, PrintButton } from '../../shared/ui';
|
||||
import { Alert, FormCard, InitiatorLine, FormModeProvider, PrintButton, PrintOrgSelect, OrgLetterhead } from '../../shared/ui';
|
||||
import {
|
||||
ApproverService,
|
||||
DecisionPanel,
|
||||
@@ -45,6 +45,9 @@ const VacationView: React.FC<IVacationViewProps> = ({ context, itemId, formKey,
|
||||
const [code, setCode] = useState<string>('');
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
// Какая из организаций сотрудника (см. OrganizationField) идёт в печать —
|
||||
// выбор в тулбаре через PrintOrgSelect, по умолчанию первая.
|
||||
const [printOrgIndex, setPrintOrgIndex] = useState<number>(0);
|
||||
|
||||
// listId уже проверен в ApproveFeature (не смонтировались бы сюда иначе) —
|
||||
// vacationApprovalService можно звать не опасаясь исключения.
|
||||
@@ -98,7 +101,7 @@ const VacationView: React.FC<IVacationViewProps> = ({ context, itemId, formKey,
|
||||
);
|
||||
}
|
||||
|
||||
const sectionProps = { data, set: NOOP_SET, context };
|
||||
const sectionProps = { data, set: NOOP_SET, context, printOrgIndex };
|
||||
|
||||
const notify = (): void => {
|
||||
// requestTypeId ещё не резолвился — решение уже применилось (строка списка
|
||||
@@ -115,10 +118,12 @@ const VacationView: React.FC<IVacationViewProps> = ({ context, itemId, formKey,
|
||||
return (
|
||||
<div>
|
||||
<div className={`${styles.toolbar} rp-no-print`}>
|
||||
<PrintOrgSelect value={data.Organization} selectedIndex={printOrgIndex} onChange={setPrintOrgIndex} />
|
||||
<PrintButton />
|
||||
</div>
|
||||
|
||||
<div className="rp-print-root">
|
||||
<OrgLetterhead value={data.Organization} selectedIndex={printOrgIndex} />
|
||||
<FormCard
|
||||
title="Заявка на трудовой отпуск"
|
||||
subtitle={`№${code || itemId}`}
|
||||
|
||||
@@ -29,6 +29,10 @@ export async function loadVacationRequest(itemId: number, listId?: string): Prom
|
||||
'StartDate',
|
||||
'DaysCount',
|
||||
'Organization',
|
||||
'Department',
|
||||
'DepartmentId',
|
||||
'Project',
|
||||
'ProjectId',
|
||||
'RequestStatus',
|
||||
'RequestUniqueCode',
|
||||
'Employee/Title',
|
||||
@@ -49,7 +53,11 @@ export async function loadVacationRequest(itemId: number, listId?: string): Prom
|
||||
StartDate: start.slice(0, 10), // ISO → YYYY-MM-DD для input type=date
|
||||
DaysCount: days === null || days === undefined ? '' : String(days),
|
||||
Substitute: substitute?.EMail || '',
|
||||
Organization: (item.Organization as string) || ''
|
||||
Organization: (item.Organization as string) || '',
|
||||
Department: (item.Department as string) || '',
|
||||
DepartmentId: (item.DepartmentId as string) || '',
|
||||
Project: (item.Project as string) || '',
|
||||
ProjectId: (item.ProjectId as string) || ''
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -32,6 +32,18 @@ export interface IFormData {
|
||||
* сразу в нескольких организациях — тогда строк несколько.
|
||||
*/
|
||||
Organization: string;
|
||||
/**
|
||||
* Подразделение (справочник Dictionary/Departments, см.
|
||||
* shared/dictionary/DepartmentPicker) — необязательное, справочное, на
|
||||
* маршрут согласования НЕ влияет.
|
||||
*/
|
||||
Department: string;
|
||||
/** Id отдела из справочника; '' — если введён вручную без выбора из списка. */
|
||||
DepartmentId: string;
|
||||
/** Проект (справочник /Project, см. shared/dictionary/ProjectPicker) — необязательное, справочное. */
|
||||
Project: string;
|
||||
/** oid проекта ('' — если введён вручную без выбора из списка). */
|
||||
ProjectId: string;
|
||||
}
|
||||
|
||||
/** Обновление одного поля формы с сохранением типа значения. */
|
||||
@@ -47,6 +59,8 @@ export interface ISectionProps {
|
||||
* отдельный обработчик. Если не передан (режим чтения) — правка через set.
|
||||
*/
|
||||
onEmployeeChange?: (login: string) => void;
|
||||
/** Индекс организации для печати (см. shared/ui/PrintOrgSelect и OrganizationField.printIndex). */
|
||||
printOrgIndex?: number;
|
||||
}
|
||||
|
||||
export const EMPTY_DATA: IFormData = {
|
||||
@@ -55,7 +69,11 @@ export const EMPTY_DATA: IFormData = {
|
||||
StartDate: '',
|
||||
DaysCount: '',
|
||||
Substitute: '',
|
||||
Organization: ''
|
||||
Organization: '',
|
||||
Department: '',
|
||||
DepartmentId: '',
|
||||
Project: '',
|
||||
ProjectId: ''
|
||||
};
|
||||
|
||||
/** Плейсхолдер для поля выбора сотрудника. */
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
{ "internalName": "EndDate", "title": "Дата окончания", "type": "DateTime", "dateOnly": true },
|
||||
{ "internalName": "DaysCount", "title": "Количество дней", "type": "Number" },
|
||||
{ "internalName": "Substitute", "title": "Лицо, замещающее на время отсутствия", "type": "User", "userSelectionMode": "PeopleOnly" },
|
||||
{ "internalName": "Organization", "title": "Организация", "type": "Note" }
|
||||
{ "internalName": "Organization", "title": "Организация", "type": "Note" },
|
||||
{ "internalName": "Department", "title": "Подразделение", "type": "Text" },
|
||||
{ "internalName": "DepartmentId", "title": "Id подразделения (справочник)", "type": "Text" },
|
||||
{ "internalName": "Project", "title": "Проект", "type": "Text" },
|
||||
{ "internalName": "ProjectId", "title": "Id проекта (справочник)", "type": "Text" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import { CollapseSection, FormField, TextInput, PeopleField, OrganizationField } from '../../../shared/ui';
|
||||
import { DepartmentPicker, ProjectPicker } from '../../../shared/dictionary';
|
||||
import { ISectionProps, EMPLOYEE_PLACEHOLDER, SUBSTITUTE_PLACEHOLDER, MIN_NOTICE_DAYS } from '../model';
|
||||
import { isoDatePlusDays } from '../../../shared/dateUtils';
|
||||
|
||||
/** Единственная секция формы «Заявка на отпуск»: сотрудник, должность, даты. */
|
||||
export const RequestSection: React.FC<ISectionProps> = ({ data, set, context, onEmployeeChange }) => (
|
||||
export const RequestSection: React.FC<ISectionProps> = ({ data, set, context, onEmployeeChange, printOrgIndex }) => (
|
||||
<CollapseSection title="Заявка на трудовой отпуск" icon="people">
|
||||
<FormField label="Сотрудник" htmlFor="Employee" required full>
|
||||
<PeopleField
|
||||
@@ -17,7 +18,30 @@ export const RequestSection: React.FC<ISectionProps> = ({ data, set, context, on
|
||||
<FormField label="Должность" htmlFor="Position" required>
|
||||
<TextInput id="Position" value={data.Position} onChange={(v) => set('Position', v)} />
|
||||
</FormField>
|
||||
<OrganizationField value={data.Organization} />
|
||||
<OrganizationField value={data.Organization} printIndex={printOrgIndex} />
|
||||
<FormField label="Подразделение" htmlFor="Department">
|
||||
<DepartmentPicker
|
||||
id="Department"
|
||||
context={context}
|
||||
value={data.Department}
|
||||
onChange={(name, id) => {
|
||||
set('Department', name);
|
||||
set('DepartmentId', id);
|
||||
}}
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Проект" htmlFor="Project">
|
||||
<ProjectPicker
|
||||
id="Project"
|
||||
context={context}
|
||||
value={data.Project}
|
||||
onChange={(name, id) => {
|
||||
set('Project', name);
|
||||
set('ProjectId', id);
|
||||
}}
|
||||
placeholder="Начните вводить название или код проекта…"
|
||||
/>
|
||||
</FormField>
|
||||
<FormField label="Дата начала" htmlFor="StartDate" required>
|
||||
<TextInput
|
||||
id="StartDate"
|
||||
|
||||
@@ -47,7 +47,11 @@ function buildItem(
|
||||
DaysCount: Number.isFinite(days) && days > 0 ? days : null,
|
||||
EndDate: computeEndDate(data.StartDate, days) || null,
|
||||
SubstituteId: substituteId ?? null,
|
||||
Organization: data.Organization || null
|
||||
Organization: data.Organization || null,
|
||||
Department: data.Department || null,
|
||||
DepartmentId: data.DepartmentId || null,
|
||||
Project: data.Project || null,
|
||||
ProjectId: data.ProjectId || null
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user