feat: место для подписи сотрудника на печати заявлений на отпуск
Печатное заявление подписывают от руки, а на листе было негде. В правом верхнем углу шапки, под статусом, теперь ФИО сотрудника, место под роспись и линия «(подпись)» — только при печати. ФИО именно сотрудника, а не инициатора: заявку могут оформить за него. Общий компонент shared/ui/PrintSignature, подключён в «Заявку на отпуск» и «Отпуск без содержания». Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,9 @@
|
|||||||
(черновик проверяется так же строго, как отправка).
|
(черновик проверяется так же строго, как отправка).
|
||||||
- **Замещающий** (`Substitute`, People) — необязателен.
|
- **Замещающий** (`Substitute`, People) — необязателен.
|
||||||
- **Заголовок элемента** (`Title`): `Отпуск без содержания — {ФИО} — дд.мм.гггг`.
|
- **Заголовок элемента** (`Title`): `Отпуск без содержания — {ФИО} — дд.мм.гггг`.
|
||||||
|
- **Место для подписи на печати** (с 10-29, `PrintSignature`) — как в «Заявке на отпуск»:
|
||||||
|
столбиком «ФИО / линия / (подпись)» в правом верхнем углу шапки под статусом; ФИО сотрудника,
|
||||||
|
а не инициатора.
|
||||||
|
|
||||||
## Отличия от «Заявка на отпуск» (hr-vacation)
|
## Отличия от «Заявка на отпуск» (hr-vacation)
|
||||||
- Те же «дата начала + количество дней»: даты окончания нет, праздники и выходные не считаются.
|
- Те же «дата начала + количество дней»: даты окончания нет, праздники и выходные не считаются.
|
||||||
|
|||||||
@@ -15,3 +15,12 @@
|
|||||||
gap: $sp-3;
|
gap: $sp-3;
|
||||||
margin-bottom: $sp-4;
|
margin-bottom: $sp-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Правый угол шапки: статус, под ним (только в печати) подпись сотрудника.
|
||||||
|
// headerRight у FormCard раскладывает содержимое в строку — здесь нужен столбик.
|
||||||
|
.headerAside {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: $sp-3;
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import {
|
|||||||
FormModeProvider,
|
FormModeProvider,
|
||||||
PrintSplitButton,
|
PrintSplitButton,
|
||||||
OrgLetterhead,
|
OrgLetterhead,
|
||||||
LeaveNotice
|
LeaveNotice,
|
||||||
|
PrintSignature
|
||||||
} from '../../shared/ui';
|
} from '../../shared/ui';
|
||||||
import {
|
import {
|
||||||
ApproverService,
|
ApproverService,
|
||||||
@@ -64,6 +65,8 @@ const UnpaidLeaveView: React.FC<IUnpaidLeaveViewProps> = ({ context, itemId, for
|
|||||||
const [data, setData] = useState<IFormData>(EMPTY_DATA);
|
const [data, setData] = useState<IFormData>(EMPTY_DATA);
|
||||||
const [status, setStatus] = useState<string>('');
|
const [status, setStatus] = useState<string>('');
|
||||||
const [code, setCode] = useState<string>('');
|
const [code, setCode] = useState<string>('');
|
||||||
|
// Ф.И.О. сотрудника (Employee/Title) — для строки подписи на печатном заявлении.
|
||||||
|
const [employeeName, setEmployeeName] = useState<string>('');
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
// Какая из организаций сотрудника (см. OrganizationField) идёт в печать —
|
// Какая из организаций сотрудника (см. OrganizationField) идёт в печать —
|
||||||
@@ -97,6 +100,7 @@ const UnpaidLeaveView: React.FC<IUnpaidLeaveViewProps> = ({ context, itemId, for
|
|||||||
setData(loaded.data);
|
setData(loaded.data);
|
||||||
setStatus(loaded.status);
|
setStatus(loaded.status);
|
||||||
setCode(loaded.code);
|
setCode(loaded.code);
|
||||||
|
setEmployeeName(loaded.employeeName);
|
||||||
notifyCode(loaded.code);
|
notifyCode(loaded.code);
|
||||||
setError(null);
|
setError(null);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -158,7 +162,13 @@ const UnpaidLeaveView: React.FC<IUnpaidLeaveViewProps> = ({ context, itemId, for
|
|||||||
itemId={itemId}
|
itemId={itemId}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
headerRight={status ? <StatusBadge status={status} /> : undefined}
|
headerRight={
|
||||||
|
<div className={styles.headerAside}>
|
||||||
|
{status && <StatusBadge status={status} />}
|
||||||
|
{/* Подписывает сотрудник, а не инициатор: заявку могут оформить за него. */}
|
||||||
|
<PrintSignature name={employeeName} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<LeaveNotice title={NOTICE_TITLE} items={NOTICE_ITEMS} />
|
<LeaveNotice title={NOTICE_TITLE} items={NOTICE_ITEMS} />
|
||||||
<FormModeProvider readOnly>
|
<FormModeProvider readOnly>
|
||||||
|
|||||||
@@ -31,6 +31,9 @@
|
|||||||
- **Комментарий** (с 10-28, `VacationComment`, Note) — необязательный, заполняет инициатор,
|
- **Комментарий** (с 10-28, `VacationComment`, Note) — необязательный, заполняет инициатор,
|
||||||
последним полем секции; в просмотре пустой не показывается. В DOCX для ЭДО (`vacationDocument.ts`) не идёт; в печать
|
последним полем секции; в просмотре пустой не показывается. В DOCX для ЭДО (`vacationDocument.ts`) не идёт; в печать
|
||||||
тоже не попадает (обёртка `rp-no-print` с `display: contents`, чтобы на экране поле оставалось ячейкой сетки).
|
тоже не попадает (обёртка `rp-no-print` с `display: contents`, чтобы на экране поле оставалось ячейкой сетки).
|
||||||
|
- **Место для подписи на печати** (с 10-29, `PrintSignature` из `shared/ui`) — столбиком «ФИО /
|
||||||
|
линия / (подпись)» в правом верхнем углу шапки под статусом (`headerAside`), только при печати. ФИО — **сотрудника** (`Employee/Title`), а не
|
||||||
|
инициатора: заявку могут оформить за сотрудника, а заявление подписывает он сам.
|
||||||
|
|
||||||
## Подписание в ЭДО
|
## Подписание в ЭДО
|
||||||
> **Сейчас выключено** (`VACATION_EDO_SIGNING_ENABLED = false` в `shared/config.ts`): решение последнего
|
> **Сейчас выключено** (`VACATION_EDO_SIGNING_ENABLED = false` в `shared/config.ts`): решение последнего
|
||||||
|
|||||||
@@ -15,3 +15,12 @@
|
|||||||
gap: $sp-3;
|
gap: $sp-3;
|
||||||
margin-bottom: $sp-4;
|
margin-bottom: $sp-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Правый угол шапки: статус, под ним (только в печати) подпись сотрудника.
|
||||||
|
// headerRight у FormCard раскладывает содержимое в строку — здесь нужен столбик.
|
||||||
|
.headerAside {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: $sp-3;
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import {
|
|||||||
FormModeProvider,
|
FormModeProvider,
|
||||||
PrintSplitButton,
|
PrintSplitButton,
|
||||||
OrgLetterhead,
|
OrgLetterhead,
|
||||||
LeaveNotice
|
LeaveNotice,
|
||||||
|
PrintSignature
|
||||||
} from '../../shared/ui';
|
} from '../../shared/ui';
|
||||||
import {
|
import {
|
||||||
ApproverService,
|
ApproverService,
|
||||||
@@ -220,7 +221,13 @@ const VacationView: React.FC<IVacationViewProps> = ({ context, itemId, formKey,
|
|||||||
itemId={itemId}
|
itemId={itemId}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
headerRight={status ? <StatusBadge status={status} /> : undefined}
|
headerRight={
|
||||||
|
<div className={styles.headerAside}>
|
||||||
|
{status && <StatusBadge status={status} />}
|
||||||
|
{/* Подписывает сотрудник, а не инициатор: заявку могут оформить за него. */}
|
||||||
|
<PrintSignature name={employeeName} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<LeaveNotice title={NOTICE_TITLE} items={NOTICE_ITEMS} />
|
<LeaveNotice title={NOTICE_TITLE} items={NOTICE_ITEMS} />
|
||||||
<FormModeProvider readOnly>
|
<FormModeProvider readOnly>
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
@import '../../../../../styles/design-tokens';
|
||||||
|
|
||||||
|
// Длина линии ≈ 5 см на листе A4 — хватает на роспись от руки.
|
||||||
|
$signature-line-width: 200px;
|
||||||
|
// Место под роспись между ФИО и линией.
|
||||||
|
$signature-height: $sp-8;
|
||||||
|
|
||||||
|
// На экране блока нет: расписываться негде.
|
||||||
|
.signature {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
// Столбик, а не строка «ФИО ____»: в шапке рядом с заголовком места мало,
|
||||||
|
// строка в ширину сжала бы заголовок до трёх строк.
|
||||||
|
.signature {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
// min-width, а не width: длинное ФИО растягивает столбик (и линию), а не
|
||||||
|
// вылезает за край листа.
|
||||||
|
min-width: $signature-line-width;
|
||||||
|
font-size: $fs-sm;
|
||||||
|
color: #000;
|
||||||
|
break-inside: avoid;
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
white-space: nowrap; // перенос фамилии на бланке выглядит как опечатка
|
||||||
|
}
|
||||||
|
|
||||||
|
.line {
|
||||||
|
align-self: stretch;
|
||||||
|
height: $signature-height;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caption {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: $fs-caption;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import styles from './PrintSignature.module.scss';
|
||||||
|
|
||||||
|
const CAPTION = '(подпись)';
|
||||||
|
|
||||||
|
interface IPrintSignatureProps {
|
||||||
|
/** ФИО того, кто подписывает. Пусто — печатается только линия. */
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Место для подписи на печатном заявлении — столбиком: ФИО, под ним место под
|
||||||
|
* роспись и линия, под линией «(подпись)». Узкий столбик рассчитан на правый
|
||||||
|
* верхний угол шапки (FormCard headerRight), как угол бумажного заявления.
|
||||||
|
* Видно только при печати — на экране расписываться негде.
|
||||||
|
*/
|
||||||
|
export const PrintSignature: React.FC<IPrintSignatureProps> = ({ name }) => (
|
||||||
|
<div className={styles.signature}>
|
||||||
|
{name && <span className={styles.name}>{name}</span>}
|
||||||
|
<span className={styles.line} />
|
||||||
|
<span className={styles.caption}>{CAPTION}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
@@ -50,6 +50,7 @@ export { OrganizationField } from './OrganizationField/OrganizationField';
|
|||||||
export { OrganizationTable } from './OrganizationField/OrganizationTable';
|
export { OrganizationTable } from './OrganizationField/OrganizationTable';
|
||||||
export { PrintSplitButton } from './PrintSplitButton/PrintSplitButton';
|
export { PrintSplitButton } from './PrintSplitButton/PrintSplitButton';
|
||||||
export { OrgLetterhead } from './OrgLetterhead/OrgLetterhead';
|
export { OrgLetterhead } from './OrgLetterhead/OrgLetterhead';
|
||||||
|
export { PrintSignature } from './PrintSignature/PrintSignature';
|
||||||
export { LinkField } from './LinkField/LinkField';
|
export { LinkField } from './LinkField/LinkField';
|
||||||
export { ErrorOverlay } from './ErrorOverlay/ErrorOverlay';
|
export { ErrorOverlay } from './ErrorOverlay/ErrorOverlay';
|
||||||
export { SubmitActions } from './SubmitActions/SubmitActions';
|
export { SubmitActions } from './SubmitActions/SubmitActions';
|
||||||
|
|||||||
Reference in New Issue
Block a user