feat: вложения в изменении условий ТД — «Результаты аттестации»
Колонка ТЗ «Что приложить» становится подписанными слотами вложений по виду; обязательны там, где ТЗ прямо требует документ. Первый слот — «Результаты аттестации» при доплате за вредные условия. Обязательность проверяется только при отправке, черновик сохраняется и без файла; файлы скрытого слота не загружаются. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,9 @@ import { loadContractChangeRequest } from './load';
|
|||||||
import { useContractEmployee } from './useContractEmployee';
|
import { useContractEmployee } from './useContractEmployee';
|
||||||
import { RequestSection } from './sections/RequestSection';
|
import { RequestSection } from './sections/RequestSection';
|
||||||
import { ChangeTypeSections } from './byType/ChangeTypeSections';
|
import { ChangeTypeSections } from './byType/ChangeTypeSections';
|
||||||
|
import { missingRequiredAttachments, prepareFilesFor } from './byType/attachmentSlots';
|
||||||
|
import { useAttachmentEditor } from '../../shared/attachments/useAttachmentEditor';
|
||||||
|
import { getAttachments } from '../../shared/attachments/attachments.service';
|
||||||
import { useStableCallback } from '../../shared/useStableCallback';
|
import { useStableCallback } from '../../shared/useStableCallback';
|
||||||
|
|
||||||
const ContractChangeForm: React.FC<IRequestFormProps> = ({
|
const ContractChangeForm: React.FC<IRequestFormProps> = ({
|
||||||
@@ -76,17 +79,22 @@ const ContractChangeForm: React.FC<IRequestFormProps> = ({
|
|||||||
const approval = useMemo(() => contractChangeApprovalService(editListId), [editListId]);
|
const approval = useMemo(() => contractChangeApprovalService(editListId), [editListId]);
|
||||||
const requestTypeIdState = useRequestTypeId(context, requestType.id);
|
const requestTypeIdState = useRequestTypeId(context, requestType.id);
|
||||||
const requestTypeId = requestTypeIdState.id;
|
const requestTypeId = requestTypeIdState.id;
|
||||||
|
// Вложения (подписанные слоты по ТЗ «Что приложить», см. byType/attachmentSlots).
|
||||||
|
const dataListRef = editListId || dataListTitle(FORM_ID);
|
||||||
|
const attachments = useAttachmentEditor(dataListRef, requestId);
|
||||||
|
const { setItems: setAttachmentItems } = attachments;
|
||||||
|
|
||||||
// Открыта СВОЯ заявка на редактирование (черновик/возврат) — подгружаем содержимое и статус.
|
// Открыта СВОЯ заявка на редактирование (черновик/возврат) — подгружаем содержимое, статус и вложения.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editId) {
|
if (!editId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let alive = true;
|
let alive = true;
|
||||||
loadContractChangeRequest(editId, editListId)
|
Promise.all([loadContractChangeRequest(editId, editListId), getAttachments(dataListRef, editId)])
|
||||||
.then((loaded) => {
|
.then(([loaded, files]) => {
|
||||||
if (!alive) return;
|
if (!alive) return;
|
||||||
setData(loaded.data);
|
setData(loaded.data);
|
||||||
|
setAttachmentItems(files);
|
||||||
setRequestStatus(loaded.status);
|
setRequestStatus(loaded.status);
|
||||||
setRequestCode(loaded.code || undefined);
|
setRequestCode(loaded.code || undefined);
|
||||||
if (loaded.code) notifyCode(loaded.code);
|
if (loaded.code) notifyCode(loaded.code);
|
||||||
@@ -99,7 +107,7 @@ const ContractChangeForm: React.FC<IRequestFormProps> = ({
|
|||||||
return () => {
|
return () => {
|
||||||
alive = false;
|
alive = false;
|
||||||
};
|
};
|
||||||
}, [editId, editListId, notifyCode, setSubmitError]);
|
}, [editId, editListId, dataListRef, notifyCode, setSubmitError, setAttachmentItems]);
|
||||||
|
|
||||||
// Сотрудник + его организация (как в командировке); новая заявка — по умолчанию на себя.
|
// Сотрудник + его организация (как в командировке); новая заявка — по умолчанию на себя.
|
||||||
const employeeControl = useContractEmployee(context, applicantEmail, !editId, data.Employee, (record) =>
|
const employeeControl = useContractEmployee(context, applicantEmail, !editId, data.Employee, (record) =>
|
||||||
@@ -170,9 +178,20 @@ const ContractChangeForm: React.FC<IRequestFormProps> = ({
|
|||||||
status,
|
status,
|
||||||
writeListId
|
writeListId
|
||||||
);
|
);
|
||||||
|
// id фиксируем ДО загрузки файлов: если она упадёт, повторное сохранение
|
||||||
|
// обновит эту же заявку, а не создаст вторую.
|
||||||
setRequestId(id);
|
setRequestId(id);
|
||||||
setRequestStatus(status);
|
setRequestStatus(status);
|
||||||
if (code) setRequestCode(code);
|
if (code) setRequestCode(code);
|
||||||
|
// Файлы — после записи элемента (нужен его id), с подписью-префиксом слота.
|
||||||
|
try {
|
||||||
|
await attachments.uploadPending(prepareFilesFor(data), {
|
||||||
|
listRef: writeListId || dataListTitle(FORM_ID),
|
||||||
|
itemId: id
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`Заявка сохранена, но файлы не прикрепились: ${friendlyErrorMessage(e)}. Сохраните ещё раз.`);
|
||||||
|
}
|
||||||
if (onSaved) onSaved();
|
if (onSaved) onSaved();
|
||||||
// Отправка — уведомляем первый активный шаг; списки той партиции, куда записали.
|
// Отправка — уведомляем первый активный шаг; списки той партиции, куда записали.
|
||||||
if (status === REQUEST_STATUS.onApproval || status === REQUEST_STATUS.execution) {
|
if (status === REQUEST_STATUS.onApproval || status === REQUEST_STATUS.execution) {
|
||||||
@@ -188,6 +207,9 @@ const ContractChangeForm: React.FC<IRequestFormProps> = ({
|
|||||||
const validateSubmit = (): string | undefined => {
|
const validateSubmit = (): string | undefined => {
|
||||||
const formError = validate();
|
const formError = validate();
|
||||||
if (formError) return formError;
|
if (formError) return formError;
|
||||||
|
// Обязательные документы — только на отправку: черновик можно сохранить и без файла.
|
||||||
|
const missingFiles = missingRequiredAttachments(data, attachments);
|
||||||
|
if (missingFiles.length > 0) return `Приложите обязательные документы: ${missingFiles.join(', ')}.`;
|
||||||
if (matrix.error) {
|
if (matrix.error) {
|
||||||
return `Не удалось определить маршрут согласования: ${matrix.error}. Попробуйте ещё раз или обратитесь к администратору.`;
|
return `Не удалось определить маршрут согласования: ${matrix.error}. Попробуйте ещё раз или обратитесь к администратору.`;
|
||||||
}
|
}
|
||||||
@@ -258,7 +280,7 @@ const ContractChangeForm: React.FC<IRequestFormProps> = ({
|
|||||||
|
|
||||||
<FormModeProvider readOnly={readOnly}>
|
<FormModeProvider readOnly={readOnly}>
|
||||||
<RequestSection data={data} set={set} context={context} employeeControl={employeeControl} />
|
<RequestSection data={data} set={set} context={context} employeeControl={employeeControl} />
|
||||||
<ChangeTypeSections data={data} set={set} context={context} />
|
<ChangeTypeSections data={data} set={set} context={context} attachments={attachments} />
|
||||||
</FormModeProvider>
|
</FormModeProvider>
|
||||||
|
|
||||||
<ApprovalMatrix
|
<ApprovalMatrix
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import { loadContractChangeRequest } from './load';
|
|||||||
import { contractChangeApprovalService } from './submit';
|
import { contractChangeApprovalService } from './submit';
|
||||||
import { RequestSection } from './sections/RequestSection';
|
import { RequestSection } from './sections/RequestSection';
|
||||||
import { ChangeTypeSections } from './byType/ChangeTypeSections';
|
import { ChangeTypeSections } from './byType/ChangeTypeSections';
|
||||||
|
import { useAttachmentEditor } from '../../shared/attachments/useAttachmentEditor';
|
||||||
|
import { getAttachments } from '../../shared/attachments/attachments.service';
|
||||||
import { useStableCallback } from '../../shared/useStableCallback';
|
import { useStableCallback } from '../../shared/useStableCallback';
|
||||||
|
|
||||||
interface IContractChangeViewProps {
|
interface IContractChangeViewProps {
|
||||||
@@ -64,6 +66,9 @@ const ContractChangeView: React.FC<IContractChangeViewProps> = ({ context, itemI
|
|||||||
// listId (url) для архивной партиции, Title текущей — см. ApproverService.dataListRef.
|
// listId (url) для архивной партиции, Title текущей — см. ApproverService.dataListRef.
|
||||||
const dataListRef = listId || dataListTitle(FORM_ID);
|
const dataListRef = listId || dataListTitle(FORM_ID);
|
||||||
const approverService = useMemo(() => new ApproverService(approval, dataListRef), [approval, dataListRef]);
|
const approverService = useMemo(() => new ApproverService(approval, dataListRef), [approval, dataListRef]);
|
||||||
|
// Вложения — только для показа (секции рисуют их под FormModeProvider readOnly).
|
||||||
|
const attachments = useAttachmentEditor(dataListRef, itemId);
|
||||||
|
const { setItems: setAttachmentItems } = attachments;
|
||||||
|
|
||||||
// decisionSeq в reloadKey: после решения строки на сервере меняются — перечитываем матрицу.
|
// decisionSeq в reloadKey: после решения строки на сервере меняются — перечитываем матрицу.
|
||||||
const [decisionSeq, setDecisionSeq] = useState<number>(0);
|
const [decisionSeq, setDecisionSeq] = useState<number>(0);
|
||||||
@@ -76,10 +81,11 @@ const ContractChangeView: React.FC<IContractChangeViewProps> = ({ context, itemI
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let alive = true;
|
let alive = true;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
loadContractChangeRequest(itemId, listId)
|
Promise.all([loadContractChangeRequest(itemId, listId), getAttachments(dataListRef, itemId)])
|
||||||
.then((loaded) => {
|
.then(([loaded, files]) => {
|
||||||
if (!alive) return;
|
if (!alive) return;
|
||||||
setData(loaded.data);
|
setData(loaded.data);
|
||||||
|
setAttachmentItems(files);
|
||||||
setStatus(loaded.status);
|
setStatus(loaded.status);
|
||||||
setCode(loaded.code);
|
setCode(loaded.code);
|
||||||
notifyCode(loaded.code);
|
notifyCode(loaded.code);
|
||||||
@@ -94,7 +100,7 @@ const ContractChangeView: React.FC<IContractChangeViewProps> = ({ context, itemI
|
|||||||
return () => {
|
return () => {
|
||||||
alive = false;
|
alive = false;
|
||||||
};
|
};
|
||||||
}, [itemId, listId, notifyCode]);
|
}, [itemId, listId, dataListRef, notifyCode, setAttachmentItems]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div className={styles.state}>Загрузка заявки…</div>;
|
return <div className={styles.state}>Загрузка заявки…</div>;
|
||||||
@@ -138,7 +144,7 @@ const ContractChangeView: React.FC<IContractChangeViewProps> = ({ context, itemI
|
|||||||
>
|
>
|
||||||
<FormModeProvider readOnly>
|
<FormModeProvider readOnly>
|
||||||
<RequestSection data={data} set={NOOP_SET} context={context} />
|
<RequestSection data={data} set={NOOP_SET} context={context} />
|
||||||
<ChangeTypeSections data={data} set={NOOP_SET} context={context} />
|
<ChangeTypeSections data={data} set={NOOP_SET} context={context} attachments={attachments} />
|
||||||
</FormModeProvider>
|
</FormModeProvider>
|
||||||
</FormCard>
|
</FormCard>
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ RequestTypeId» в `docs/forms.md`). Тип и маршрут в админке
|
|||||||
| Вид | Секция | Поля (все необязательные, без ограничений) |
|
| Вид | Секция | Поля (все необязательные, без ограничений) |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Повышение должностного оклада | `SalaryIncrease` | текущий оклад, новый оклад (`MoneyInput`, Number), дата изменения, причина (испытательный срок / расширение функционала / иное), обоснование — при «иное» это и есть текст причины |
|
| Повышение должностного оклада | `SalaryIncrease` | текущий оклад, новый оклад (`MoneyInput`, Number), дата изменения, причина (испытательный срок / расширение функционала / иное), обоснование — при «иное» это и есть текст причины |
|
||||||
| Установление надбавки / доплаты | `Allowance` | основание (а–д: надбавка или доплата видно из пункта), дата начала, дата окончания или «бессрочно» (флажок прячет и очищает дату), размер + переключатель «% / ₸» (`UnitSwitch`, по умолчанию %), обоснование — в т.ч. объём доп. работы при совмещении и текст «иное». Письменное согласие работника на совмещение оформляет кадровик после согласования — не в форме. Вложение «Результаты аттестации» (обязательно при основании «г») — следующим шагом |
|
| Установление надбавки / доплаты | `Allowance` | основание (а–д: надбавка или доплата видно из пункта), дата начала, дата окончания или «бессрочно» (флажок прячет и очищает дату), размер + переключатель «% / ₸» (`UnitSwitch`, по умолчанию %), обоснование — в т.ч. объём доп. работы при совмещении и текст «иное». Письменное согласие работника на совмещение оформляет кадровик после согласования — не в форме. Вложение **«Результаты аттестации»** — только при основании «г», обязательно |
|
||||||
| Остальные 8 | — | ещё не разобраны с кадрами |
|
| Остальные 8 | — | ещё не разобраны с кадрами |
|
||||||
|
|
||||||
**Вложения по колонке ТЗ «Что приложить»** (решение 2026-10-28): фиксированные
|
**Вложения по колонке ТЗ «Что приложить»** (решение 2026-10-28): фиксированные
|
||||||
@@ -66,6 +66,14 @@ RequestTypeId» в `docs/forms.md`). Тип и маршрут в админке
|
|||||||
прямо требует документ (вид 2 «г» — аттестация, вид 3 — ДИ, вид 8 — новая ДИ;
|
прямо требует документ (вид 2 «г» — аттестация, вид 3 — ДИ, вид 8 — новая ДИ;
|
||||||
вид 9 — текст ИЛИ файл), «можно приложить» — необязательно (вид 7).
|
вид 9 — текст ИЛИ файл), «можно приложить» — необязательно (вид 7).
|
||||||
|
|
||||||
|
Как устроено: вложения элемента списка данных, подпись — префикс в имени файла
|
||||||
|
(`shared/attachments/attachmentCategories`, тот же механизм, что в «Приёме»), поле
|
||||||
|
слота — `AttachmentSlotField`. Какие слоты видны и обязательны — одна функция
|
||||||
|
`byType/attachmentSlots.ts` (`activeAttachmentSlots`); новый слот = строка там +
|
||||||
|
`AttachmentSlotField` в секции вида. Файлы грузятся после записи элемента; файлы
|
||||||
|
скрытого слота (сменили вид/основание) не грузятся. Обязательность проверяется
|
||||||
|
только при отправке — черновик сохраняется и без файла.
|
||||||
|
|
||||||
## Открытые вопросы (проверить на тесте)
|
## Открытые вопросы (проверить на тесте)
|
||||||
- Выдаёт ли бэкенд (вебхуки) права на элемент по People-колонке `Employee`? Если да —
|
- Выдаёт ли бэкенд (вебхуки) права на элемент по People-колонке `Employee`? Если да —
|
||||||
сотрудник, за которого подали заявку, её не увидит (такой колонки здесь нет).
|
сотрудник, за которого подали заявку, её не увидит (такой колонки здесь нет).
|
||||||
|
|||||||
+21
-4
@@ -1,8 +1,10 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { CollapseSection, FormField, MoneyInput, TextInput, TextArea, Select, Checkbox } from '../../../../shared/ui';
|
import { CollapseSection, FormField, MoneyInput, TextInput, TextArea, Select, Checkbox } from '../../../../shared/ui';
|
||||||
|
import { useFormReadOnly } from '@tnsu/ui-kit';
|
||||||
|
import { AttachmentSlotField } from '../../../../shared/attachments/AttachmentSlotField';
|
||||||
import { CHANGE_TYPE_LABELS } from '../../changeTypes';
|
import { CHANGE_TYPE_LABELS } from '../../changeTypes';
|
||||||
import { ISectionProps } from '../../model';
|
import { ISectionProps } from '../../model';
|
||||||
import { ALLOWANCE_REASONS } from './options';
|
import { ALLOWANCE_REASONS, ATTESTATION_ATTACHMENT, requiresAttestation } from './options';
|
||||||
import { UnitSwitch } from './UnitSwitch';
|
import { UnitSwitch } from './UnitSwitch';
|
||||||
import { sanitizePercentInput, amountForUnit } from './amount';
|
import { sanitizePercentInput, amountForUnit } from './amount';
|
||||||
import styles from './AllowanceSection.module.scss';
|
import styles from './AllowanceSection.module.scss';
|
||||||
@@ -10,11 +12,15 @@ import styles from './AllowanceSection.module.scss';
|
|||||||
/**
|
/**
|
||||||
* Поля вида «Установление надбавки / доплаты». Состав согласован с Тимуром по ТЗ
|
* Поля вида «Установление надбавки / доплаты». Состав согласован с Тимуром по ТЗ
|
||||||
* кадров: основание (а–д), срок с/по или бессрочно, размер в % или ₸, обоснование
|
* кадров: основание (а–д), срок с/по или бессрочно, размер в % или ₸, обоснование
|
||||||
* (в т.ч. объём доп. работы при совмещении). Все необязательные.
|
* (в т.ч. объём доп. работы при совмещении). Поля необязательные; вложение
|
||||||
|
* «Результаты аттестации» — только при основании «г» и там обязательно (по ТЗ).
|
||||||
*
|
*
|
||||||
* Поля — прямо в CollapseSection, без FormGrid (тело секции само сетка).
|
* Поля — прямо в CollapseSection, без FormGrid (тело секции само сетка).
|
||||||
*/
|
*/
|
||||||
export const AllowanceSection: React.FC<ISectionProps> = ({ data, set }) => (
|
export const AllowanceSection: React.FC<ISectionProps> = ({ data, set, attachments }) => {
|
||||||
|
const readOnly = useFormReadOnly();
|
||||||
|
|
||||||
|
return (
|
||||||
<CollapseSection title={CHANGE_TYPE_LABELS.allowance} icon="calculator">
|
<CollapseSection title={CHANGE_TYPE_LABELS.allowance} icon="calculator">
|
||||||
<FormField label="Основание" htmlFor="AllowanceReason" full>
|
<FormField label="Основание" htmlFor="AllowanceReason" full>
|
||||||
<Select
|
<Select
|
||||||
@@ -83,5 +89,16 @@ export const AllowanceSection: React.FC<ISectionProps> = ({ data, set }) => (
|
|||||||
onChange={(v) => set('AllowanceJustification', v)}
|
onChange={(v) => set('AllowanceJustification', v)}
|
||||||
/>
|
/>
|
||||||
</FormField>
|
</FormField>
|
||||||
|
{attachments && requiresAttestation(data.AllowanceReason) && (
|
||||||
|
<AttachmentSlotField
|
||||||
|
label={ATTESTATION_ATTACHMENT}
|
||||||
|
editor={attachments}
|
||||||
|
editable={!readOnly}
|
||||||
|
groupKey={ATTESTATION_ATTACHMENT}
|
||||||
|
category={ATTESTATION_ATTACHMENT}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</CollapseSection>
|
</CollapseSection>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -19,6 +19,16 @@ export const ALLOWANCE_REASONS: ISelectOption[] = [
|
|||||||
ALLOWANCE_REASON_OTHER
|
ALLOWANCE_REASON_OTHER
|
||||||
].map((reason) => ({ value: reason, label: reason }));
|
].map((reason) => ({ value: reason, label: reason }));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Вложение «Результаты аттестации» — по ТЗ прикладывается документ при доплате
|
||||||
|
* за вредные условия (пункт «г»); там же оно обязательно.
|
||||||
|
*/
|
||||||
|
export const ATTESTATION_ATTACHMENT = 'Результаты аттестации';
|
||||||
|
|
||||||
|
export function requiresAttestation(reason: string): boolean {
|
||||||
|
return reason === ALLOWANCE_REASON_HAZARDOUS;
|
||||||
|
}
|
||||||
|
|
||||||
/** Единица размера; значения = choices колонки AllowanceUnit. */
|
/** Единица размера; значения = choices колонки AllowanceUnit. */
|
||||||
export type AllowanceUnit = '%' | '₸';
|
export type AllowanceUnit = '%' | '₸';
|
||||||
export const ALLOWANCE_UNITS: AllowanceUnit[] = ['%', '₸'];
|
export const ALLOWANCE_UNITS: AllowanceUnit[] = ['%', '₸'];
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { PrepareFiles, IAttachmentEditor } from '../../../shared/attachments/useAttachmentEditor';
|
||||||
|
import { attachmentsOfCategory, prefixFiles } from '../../../shared/attachments/attachmentCategories';
|
||||||
|
import { IFormData } from '../model';
|
||||||
|
import { ATTESTATION_ATTACHMENT, requiresAttestation } from './Allowance/options';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Подписанный слот вложений по колонке ТЗ «Что приложить». Подпись (category)
|
||||||
|
* одновременно и префикс в имени файла, и ключ группы в editor.pending.
|
||||||
|
*/
|
||||||
|
export interface IAttachmentSlot {
|
||||||
|
category: string;
|
||||||
|
/** Обязателен там, где ТЗ прямо требует документ. */
|
||||||
|
required: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Слоты, видимые при текущих данных формы. Новый слот вида = строка здесь. */
|
||||||
|
export function activeAttachmentSlots(data: IFormData): IAttachmentSlot[] {
|
||||||
|
const slots: IAttachmentSlot[] = [];
|
||||||
|
if (data.ChangeTypes.includes('allowance') && requiresAttestation(data.AllowanceReason)) {
|
||||||
|
slots.push({ category: ATTESTATION_ATTACHMENT, required: true });
|
||||||
|
}
|
||||||
|
return slots;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Подготовка выбранных файлов к загрузке: префикс-подпись в имени. Файлы слота,
|
||||||
|
* который сейчас скрыт (сменили вид или основание), не загружаем — их не видно.
|
||||||
|
*/
|
||||||
|
export function prepareFilesFor(data: IFormData): PrepareFiles {
|
||||||
|
const visible = activeAttachmentSlots(data).map((slot) => slot.category);
|
||||||
|
return (groupKey, files) => (visible.indexOf(groupKey) >= 0 ? prefixFiles(files, groupKey) : []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Подписи обязательных слотов, где нет ни прикреплённого, ни выбранного файла. */
|
||||||
|
export function missingRequiredAttachments(data: IFormData, editor: IAttachmentEditor): string[] {
|
||||||
|
return activeAttachmentSlots(data)
|
||||||
|
.filter((slot) => slot.required)
|
||||||
|
.filter((slot) => {
|
||||||
|
const uploaded = attachmentsOfCategory(editor.items, slot.category).length;
|
||||||
|
const selected = (editor.pending[slot.category] || []).length;
|
||||||
|
return uploaded + selected === 0;
|
||||||
|
})
|
||||||
|
.map((slot) => slot.category);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { RequestTypeId } from '../../models/types';
|
|||||||
import { IEmployeeFullBasePerson, IEmployeeFullBaseRecord } from '../../shared/dictionary';
|
import { IEmployeeFullBasePerson, IEmployeeFullBaseRecord } from '../../shared/dictionary';
|
||||||
import { ChangeType } from './changeTypes';
|
import { ChangeType } from './changeTypes';
|
||||||
import { AllowanceUnit, DEFAULT_ALLOWANCE_UNIT } from './byType/Allowance/options';
|
import { AllowanceUnit, DEFAULT_ALLOWANCE_UNIT } from './byType/Allowance/options';
|
||||||
|
import { IAttachmentEditor } from '../../shared/attachments/useAttachmentEditor';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Тип заявки этой формы — ключ реестра форм (forms/registry.ts). Имена списков
|
* Тип заявки этой формы — ключ реестра форм (forms/registry.ts). Имена списков
|
||||||
@@ -72,6 +73,8 @@ export interface ISectionProps {
|
|||||||
data: IFormData;
|
data: IFormData;
|
||||||
set: SetField;
|
set: SetField;
|
||||||
context: WebPartContext;
|
context: WebPartContext;
|
||||||
|
/** Вложения заявки — для подписанных слотов в секциях видов (см. byType/attachmentSlots). */
|
||||||
|
attachments?: IAttachmentEditor;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const EMPTY_DATA: IFormData = {
|
export const EMPTY_DATA: IFormData = {
|
||||||
|
|||||||
Reference in New Issue
Block a user