Compare commits
2 Commits
a750db9d7f
...
82a9a68fbc
| Author | SHA1 | Date | |
|---|---|---|---|
| 82a9a68fbc | |||
| ac633c767e |
@@ -28,6 +28,9 @@
|
|||||||
- **Даты окончания нет**: отпуск задаётся началом и количеством дней, чтобы не считать
|
- **Даты окончания нет**: отпуск задаётся началом и количеством дней, чтобы не считать
|
||||||
праздники и выходные. Колонка `EndDate` не используется: из провижининга, типа контента и представления она убрана (само системное поле SharePoint `FromBaseType` удалить нельзя).
|
праздники и выходные. Колонка `EndDate` не используется: из провижининга, типа контента и представления она убрана (само системное поле SharePoint `FromBaseType` удалить нельзя).
|
||||||
- **Заголовок элемента** (`Title`): `Отпуск — {ФИО} — дд.мм.гггг`.
|
- **Заголовок элемента** (`Title`): `Отпуск — {ФИО} — дд.мм.гггг`.
|
||||||
|
- **Комментарий** (с 10-28, `VacationComment`, Note) — необязательный, заполняет инициатор,
|
||||||
|
последним полем секции; в просмотре пустой не показывается. В DOCX для ЭДО (`vacationDocument.ts`) не идёт; при печати
|
||||||
|
просмотра виден, как остальные поля секции.
|
||||||
|
|
||||||
## Подписание в ЭДО
|
## Подписание в ЭДО
|
||||||
> **Сейчас выключено** (`VACATION_EDO_SIGNING_ENABLED = false` в `shared/config.ts`): решение последнего
|
> **Сейчас выключено** (`VACATION_EDO_SIGNING_ENABLED = false` в `shared/config.ts`): решение последнего
|
||||||
@@ -71,7 +74,7 @@
|
|||||||
SLA на бэкенде approval-route этот код не вызывает — такое заявление на подпись не уйдёт.
|
SLA на бэкенде approval-route этот код не вызывает — такое заявление на подпись не уйдёт.
|
||||||
|
|
||||||
## Секции формы
|
## Секции формы
|
||||||
RequestSection (сотрудник · должность · дата начала · количество дней)
|
RequestSection (сотрудник · должность · дата начала · количество дней · замещающий · комментарий)
|
||||||
|
|
||||||
## Точки входа
|
## Точки входа
|
||||||
- rptype: `hr-vacation`
|
- rptype: `hr-vacation`
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export async function loadVacationRequest(itemId: number, listId?: string): Prom
|
|||||||
'DepartmentId',
|
'DepartmentId',
|
||||||
'Project',
|
'Project',
|
||||||
'ProjectId',
|
'ProjectId',
|
||||||
|
'VacationComment',
|
||||||
'RequestStatus',
|
'RequestStatus',
|
||||||
'RequestUniqueCode',
|
'RequestUniqueCode',
|
||||||
'Employee/Title',
|
'Employee/Title',
|
||||||
@@ -58,7 +59,8 @@ export async function loadVacationRequest(itemId: number, listId?: string): Prom
|
|||||||
Department: (item.Department as string) || '',
|
Department: (item.Department as string) || '',
|
||||||
DepartmentId: (item.DepartmentId as string) || '',
|
DepartmentId: (item.DepartmentId as string) || '',
|
||||||
Project: (item.Project as string) || '',
|
Project: (item.Project as string) || '',
|
||||||
ProjectId: (item.ProjectId as string) || ''
|
ProjectId: (item.ProjectId as string) || '',
|
||||||
|
VacationComment: (item.VacationComment as string) || ''
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ export interface IFormData {
|
|||||||
Project: string;
|
Project: string;
|
||||||
/** oid проекта ('' — если введён вручную без выбора из списка). */
|
/** oid проекта ('' — если введён вручную без выбора из списка). */
|
||||||
ProjectId: string;
|
ProjectId: string;
|
||||||
|
/** Комментарий инициатора к заявке — необязательный, для согласующих. */
|
||||||
|
VacationComment: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Обновление одного поля формы с сохранением типа значения. */
|
/** Обновление одного поля формы с сохранением типа значения. */
|
||||||
@@ -73,7 +75,8 @@ export const EMPTY_DATA: IFormData = {
|
|||||||
Department: '',
|
Department: '',
|
||||||
DepartmentId: '',
|
DepartmentId: '',
|
||||||
Project: '',
|
Project: '',
|
||||||
ProjectId: ''
|
ProjectId: '',
|
||||||
|
VacationComment: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
/** Плейсхолдер для поля выбора сотрудника. */
|
/** Плейсхолдер для поля выбора сотрудника. */
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
{ "internalName": "Department", "title": "Подразделение", "type": "Text" },
|
{ "internalName": "Department", "title": "Подразделение", "type": "Text" },
|
||||||
{ "internalName": "DepartmentId", "title": "Id подразделения (справочник)", "type": "Text" },
|
{ "internalName": "DepartmentId", "title": "Id подразделения (справочник)", "type": "Text" },
|
||||||
{ "internalName": "Project", "title": "Проект", "type": "Text" },
|
{ "internalName": "Project", "title": "Проект", "type": "Text" },
|
||||||
{ "internalName": "ProjectId", "title": "Id проекта (справочник)", "type": "Text" }
|
{ "internalName": "ProjectId", "title": "Id проекта (справочник)", "type": "Text" },
|
||||||
|
{ "internalName": "VacationComment", "title": "Комментарий", "type": "Note" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,75 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { FormGrid, FormField, TextInput, PeopleField, OrganizationTable } from '../../../shared/ui';
|
import { FormGrid, FormField, TextInput, TextArea, PeopleField, OrganizationTable, useFormReadOnly } from '../../../shared/ui';
|
||||||
import { ProjectPicker } from '../../../shared/dictionary';
|
import { ProjectPicker } from '../../../shared/dictionary';
|
||||||
import { ISectionProps, EMPLOYEE_PLACEHOLDER, SUBSTITUTE_PLACEHOLDER, MIN_NOTICE_DAYS, MAX_DAYS } from '../model';
|
import { ISectionProps, EMPLOYEE_PLACEHOLDER, SUBSTITUTE_PLACEHOLDER, MIN_NOTICE_DAYS, MAX_DAYS } from '../model';
|
||||||
import { isoDatePlusDays } from '../../../shared/dateUtils';
|
import { isoDatePlusDays } from '../../../shared/dateUtils';
|
||||||
|
|
||||||
/** Единственная секция формы «Заявка на отпуск»: сотрудник (должность и подразделение подтягиваются из справочника), даты. */
|
/** Единственная секция формы «Заявка на отпуск»: сотрудник (должность и подразделение подтягиваются из справочника), даты. */
|
||||||
export const RequestSection: React.FC<ISectionProps> = ({ data, set, context, onEmployeeChange, printOrgIndex }) => (
|
export const RequestSection: React.FC<ISectionProps> = ({ data, set, context, onEmployeeChange, printOrgIndex }) => {
|
||||||
<FormGrid>
|
const readOnly = useFormReadOnly();
|
||||||
<FormField label="Сотрудник" htmlFor="Employee" required full>
|
|
||||||
<PeopleField
|
return (
|
||||||
context={context}
|
<FormGrid>
|
||||||
value={data.Employee}
|
<FormField label="Сотрудник" htmlFor="Employee" required full>
|
||||||
onChange={onEmployeeChange ?? ((v) => set('Employee', v))}
|
<PeopleField
|
||||||
placeholder={EMPLOYEE_PLACEHOLDER}
|
context={context}
|
||||||
/>
|
value={data.Employee}
|
||||||
</FormField>
|
onChange={onEmployeeChange ?? ((v) => set('Employee', v))}
|
||||||
<OrganizationTable value={data.Organization} printIndex={printOrgIndex} />
|
placeholder={EMPLOYEE_PLACEHOLDER}
|
||||||
<FormField label="Проект" htmlFor="Project">
|
/>
|
||||||
<ProjectPicker
|
</FormField>
|
||||||
id="Project"
|
<OrganizationTable value={data.Organization} printIndex={printOrgIndex} />
|
||||||
context={context}
|
<FormField label="Проект" htmlFor="Project">
|
||||||
value={data.Project}
|
<ProjectPicker
|
||||||
onChange={(name, id) => {
|
id="Project"
|
||||||
set('Project', name);
|
context={context}
|
||||||
set('ProjectId', id);
|
value={data.Project}
|
||||||
}}
|
onChange={(name, id) => {
|
||||||
placeholder="Начните вводить название или код проекта…"
|
set('Project', name);
|
||||||
/>
|
set('ProjectId', id);
|
||||||
</FormField>
|
}}
|
||||||
<FormField label="Дата начала" htmlFor="StartDate" required>
|
placeholder="Начните вводить название или код проекта…"
|
||||||
<TextInput
|
/>
|
||||||
id="StartDate"
|
</FormField>
|
||||||
type="date"
|
<FormField label="Дата начала" htmlFor="StartDate" required>
|
||||||
value={data.StartDate}
|
<TextInput
|
||||||
onChange={(v) => set('StartDate', v)}
|
id="StartDate"
|
||||||
min={isoDatePlusDays(MIN_NOTICE_DAYS)}
|
type="date"
|
||||||
/>
|
value={data.StartDate}
|
||||||
</FormField>
|
onChange={(v) => set('StartDate', v)}
|
||||||
<FormField label="Количество дней" htmlFor="DaysCount" required>
|
min={isoDatePlusDays(MIN_NOTICE_DAYS)}
|
||||||
<TextInput
|
/>
|
||||||
id="DaysCount"
|
</FormField>
|
||||||
type="number"
|
<FormField label="Количество дней" htmlFor="DaysCount" required>
|
||||||
value={data.DaysCount}
|
<TextInput
|
||||||
onChange={(v) => set('DaysCount', v)}
|
id="DaysCount"
|
||||||
min={1}
|
type="number"
|
||||||
max={MAX_DAYS}
|
value={data.DaysCount}
|
||||||
placeholder={`от 1 до ${MAX_DAYS}`}
|
onChange={(v) => set('DaysCount', v)}
|
||||||
/>
|
min={1}
|
||||||
</FormField>
|
max={MAX_DAYS}
|
||||||
<FormField label="Лицо, замещающее на время отсутствия" htmlFor="Substitute" full>
|
placeholder={`от 1 до ${MAX_DAYS}`}
|
||||||
<PeopleField
|
/>
|
||||||
context={context}
|
</FormField>
|
||||||
value={data.Substitute}
|
<FormField label="Лицо, замещающее на время отсутствия" htmlFor="Substitute" full>
|
||||||
onChange={(v) => set('Substitute', v)}
|
<PeopleField
|
||||||
placeholder={SUBSTITUTE_PLACEHOLDER}
|
context={context}
|
||||||
/>
|
value={data.Substitute}
|
||||||
</FormField>
|
onChange={(v) => set('Substitute', v)}
|
||||||
</FormGrid>
|
placeholder={SUBSTITUTE_PLACEHOLDER}
|
||||||
);
|
/>
|
||||||
|
</FormField>
|
||||||
|
{/* Пустой комментарий в просмотре не показываем — пустое поле только мешает читать. */}
|
||||||
|
{(!readOnly || data.VacationComment.trim()) && (
|
||||||
|
<FormField label="Комментарий" htmlFor="VacationComment" full>
|
||||||
|
<TextArea
|
||||||
|
id="VacationComment"
|
||||||
|
value={data.VacationComment}
|
||||||
|
onChange={(v) => set('VacationComment', v)}
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
</FormField>
|
||||||
|
)}
|
||||||
|
</FormGrid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ function buildItem(
|
|||||||
Department: data.Department || null,
|
Department: data.Department || null,
|
||||||
DepartmentId: data.DepartmentId || null,
|
DepartmentId: data.DepartmentId || null,
|
||||||
Project: data.Project || null,
|
Project: data.Project || null,
|
||||||
ProjectId: data.ProjectId || null
|
ProjectId: data.ProjectId || null,
|
||||||
|
VacationComment: data.VacationComment.trim() || null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -157,20 +157,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Комментарий решения — мелким текстом под именем согласующего (в той же ячейке,
|
// Комментарий решения — плашкой под именем согласующего (в той же ячейке,
|
||||||
// а не отдельной строкой: иначе ломается rowSpan колонки «Шаг» у параллельных).
|
// а не отдельной строкой: иначе ломается rowSpan колонки «Шаг» у параллельных).
|
||||||
|
// Плашка, а не мелкий серый текст: пользователи его не замечали, а там бывает
|
||||||
|
// причина возврата/отклонения.
|
||||||
.commentInline {
|
.commentInline {
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 3px;
|
margin-top: $sp-1;
|
||||||
font-size: $fs-caption;
|
padding: 6px 10px;
|
||||||
color: $gray-500;
|
border-left: 3px solid $navy-600;
|
||||||
|
border-radius: 0 $r-sm $r-sm 0;
|
||||||
|
background: $navy-100;
|
||||||
|
font-size: $fs-sm;
|
||||||
|
color: $gray-900;
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
white-space: pre-wrap; // сохранить переносы строк из комментария
|
white-space: pre-wrap; // сохранить переносы строк из комментария
|
||||||
|
overflow-wrap: anywhere; // длинная ссылка без пробелов не распирает ячейку
|
||||||
}
|
}
|
||||||
|
|
||||||
.commentLabel {
|
.commentLabel {
|
||||||
color: $gray-400;
|
color: $navy-800;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Пометка «доп. согласующий, добавил: …» — под именем согласующего. Золотой
|
// Пометка «доп. согласующий, добавил: …» — под именем согласующего. Золотой
|
||||||
|
|||||||
Reference in New Issue
Block a user