feat: added notifications workflow
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import * as React from 'react';
|
||||
import { useState, useMemo, useCallback } from 'react';
|
||||
import { useState, useMemo, useCallback, useEffect } from 'react';
|
||||
import styles from './RecruitingHireForm.module.scss';
|
||||
import {
|
||||
Alert,
|
||||
FormCard,
|
||||
FormModeProvider,
|
||||
FileDropzone,
|
||||
AttachmentList,
|
||||
CollapseSection,
|
||||
FormField,
|
||||
Icon,
|
||||
@@ -18,24 +19,66 @@ import {
|
||||
StatusBadge,
|
||||
useMatrixEditor,
|
||||
getPlannedMatrix,
|
||||
notifyActiveStep,
|
||||
REQUEST_STATUS,
|
||||
isEditableStatus,
|
||||
IApproverToWrite
|
||||
} from '../../shared/approval';
|
||||
import { uploadAttachments } from '../../shared/attachments/attachments.service';
|
||||
import {
|
||||
uploadAttachments,
|
||||
getAttachments,
|
||||
deleteAttachment,
|
||||
IAttachmentInfo
|
||||
} from '../../shared/attachments/attachments.service';
|
||||
import { IRequestFormProps } from '../../models/types';
|
||||
import { IFormData, EMPTY_DATA, SetField } from './model';
|
||||
import { saveHireRequest, hireApprovalService, HIRE_REQUEST_TYPE_ID, DATA_LIST_TITLE } from './submit';
|
||||
import { loadHireRequest } from './load';
|
||||
import {
|
||||
saveHireRequest,
|
||||
hireApprovalService,
|
||||
HIRE_REQUEST_TYPE_ID,
|
||||
DATA_LIST_TITLE,
|
||||
APPROVAL_LIST_TITLE
|
||||
} from './submit';
|
||||
import { ApplicationSection } from './sections/ApplicationSection';
|
||||
import { CandidateSection } from './sections/CandidateSection';
|
||||
import { PpeSection } from './sections/PpeSection';
|
||||
import { ConditionsSection } from './sections/ConditionsSection';
|
||||
|
||||
const RecruitingHireForm: React.FC<IRequestFormProps> = ({ requestType, context, onCancel, onSaved }) => {
|
||||
const RecruitingHireForm: React.FC<IRequestFormProps> = ({
|
||||
requestType,
|
||||
context,
|
||||
onCancel,
|
||||
onSaved,
|
||||
editId
|
||||
}) => {
|
||||
const [data, setData] = useState<IFormData>(EMPTY_DATA);
|
||||
const [files, setFiles] = useState<File[]>([]);
|
||||
const [existingAttachments, setExistingAttachments] = useState<IAttachmentInfo[]>([]);
|
||||
const [requestId, setRequestId] = useState<number | undefined>(undefined);
|
||||
const [requestStatus, setRequestStatus] = useState<string | undefined>(undefined);
|
||||
|
||||
// Режим правки: грузим существующую заявку (id/статус → сохранение как update) и
|
||||
// её уже прикреплённые вложения.
|
||||
useEffect(() => {
|
||||
if (!editId) {
|
||||
return;
|
||||
}
|
||||
let alive = true;
|
||||
Promise.all([loadHireRequest(editId), getAttachments(DATA_LIST_TITLE, editId)])
|
||||
.then(([loaded, atts]) => {
|
||||
if (alive) {
|
||||
setData(loaded.data);
|
||||
setRequestId(editId);
|
||||
setRequestStatus(loaded.status);
|
||||
setExistingAttachments(atts);
|
||||
}
|
||||
})
|
||||
.catch(() => undefined);
|
||||
return () => {
|
||||
alive = false;
|
||||
};
|
||||
}, [editId]);
|
||||
const submit = useRequestSubmit();
|
||||
|
||||
const readOnly = !isEditableStatus(requestStatus);
|
||||
@@ -70,6 +113,16 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({ requestType, context,
|
||||
const toApprovers = (rows: typeof matrix.rows): IApproverToWrite[] =>
|
||||
rows.map((r) => ({ email: r.email, position: r.position, allowReassign: r.allowReassign }));
|
||||
|
||||
// Удаление уже прикреплённого файла (в правке) — сразу из заявки.
|
||||
const handleRemoveAttachment = (fileName: string): void => {
|
||||
if (requestId === undefined) {
|
||||
return;
|
||||
}
|
||||
deleteAttachment(DATA_LIST_TITLE, requestId, fileName)
|
||||
.then(() => setExistingAttachments((prev) => prev.filter((a) => a.fileName !== fileName)))
|
||||
.catch(() => undefined);
|
||||
};
|
||||
|
||||
const save = (status: string): (() => Promise<void>) => async (): Promise<void> => {
|
||||
const id = await saveHireRequest(
|
||||
requestId,
|
||||
@@ -83,10 +136,20 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({ requestType, context,
|
||||
if (files.length > 0) {
|
||||
await uploadAttachments(DATA_LIST_TITLE, id, files); // файлы — после создания элемента
|
||||
setFiles([]); // выгруженные файлы убираем, чтобы пересохранение не дублировало
|
||||
setExistingAttachments(await getAttachments(DATA_LIST_TITLE, id)); // показать добавленные
|
||||
}
|
||||
if (onSaved) {
|
||||
onSaved();
|
||||
}
|
||||
// Отправка на согласование — уведомляем первого активного согласующего
|
||||
// (поток оповещения). Черновик не уведомляет. Ошибки внутри проглатываются.
|
||||
if (status === REQUEST_STATUS.onApproval) {
|
||||
await notifyActiveStep(context, approval, id, {
|
||||
requestTypeId: HIRE_REQUEST_TYPE_ID,
|
||||
dataListName: DATA_LIST_TITLE,
|
||||
approvalListName: APPROVAL_LIST_TITLE,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveDraft = (): Promise<void> =>
|
||||
@@ -131,7 +194,12 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({ requestType, context,
|
||||
|
||||
{!readOnly && (
|
||||
<CollapseSection title="Вложения" icon="upload">
|
||||
<FormField label="Файлы" full>
|
||||
{existingAttachments.length > 0 && (
|
||||
<FormField label="Прикреплённые файлы" full>
|
||||
<AttachmentList items={existingAttachments} onRemove={handleRemoveAttachment} />
|
||||
</FormField>
|
||||
)}
|
||||
<FormField label={existingAttachments.length > 0 ? 'Добавить файлы' : 'Файлы'} full>
|
||||
<FileDropzone files={files} onChange={setFiles} />
|
||||
</FormField>
|
||||
</CollapseSection>
|
||||
|
||||
@@ -9,13 +9,15 @@ import {
|
||||
StatusBadge,
|
||||
ApprovalMatrix,
|
||||
useMatrixEditor,
|
||||
notifyActiveStep,
|
||||
isEditableStatus,
|
||||
APPROVAL_PHASE
|
||||
} from '../../shared/approval';
|
||||
import { navigate } from '../../shared/routing';
|
||||
import { getAttachments, IAttachmentInfo } from '../../shared/attachments/attachments.service';
|
||||
import { IFormData, EMPTY_DATA, SetField } from './model';
|
||||
import { loadHireRequest } from './load';
|
||||
import { hireApprovalService, DATA_LIST_TITLE } from './submit';
|
||||
import { hireApprovalService, HIRE_REQUEST_TYPE_ID, DATA_LIST_TITLE, APPROVAL_LIST_TITLE } from './submit';
|
||||
import { ApplicationSection } from './sections/ApplicationSection';
|
||||
import { CandidateSection } from './sections/CandidateSection';
|
||||
import { PpeSection } from './sections/PpeSection';
|
||||
@@ -119,6 +121,17 @@ const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemI
|
||||
return (
|
||||
<div>
|
||||
<div className={`${styles.toolbar} rp-no-print`}>
|
||||
{isEditableStatus(status) && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
navigate({ mode: 'edit', nav: 'new', formKey: 'recruiting-hire', itemId })
|
||||
}
|
||||
>
|
||||
<Icon name="edit" size="sm" />
|
||||
Редактировать
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() =>
|
||||
@@ -174,6 +187,12 @@ const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemI
|
||||
onDecided={(newStatus) => {
|
||||
setStatus(newStatus);
|
||||
setDecisionSeq((n) => n + 1);
|
||||
// Очередь сдвинулась — уведомляем следующий активный шаг.
|
||||
notifyActiveStep(context, approval, itemId, {
|
||||
requestTypeId: HIRE_REQUEST_TYPE_ID,
|
||||
dataListName: DATA_LIST_TITLE,
|
||||
approvalListName: APPROVAL_LIST_TITLE,
|
||||
}).catch(() => undefined);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"dataListTitle": "Прием на работу — данные",
|
||||
"columns": [
|
||||
{ "internalName": "RequestUniqueCode", "title": "Уникальный код заявки", "type": "Text" },
|
||||
{ "internalName": "Status", "title": "Статус", "type": "Choice", "choices": ["Черновик", "На согласовании", "Согласовано", "Отклонено", "Возвращена на доработку", "Выполнение", "Закрыта"] },
|
||||
|
||||
{ "internalName": "Phone", "title": "Телефон", "type": "Text" },
|
||||
|
||||
@@ -61,6 +61,8 @@ export async function saveHireRequest(
|
||||
} else {
|
||||
const added = await list.items.add(item);
|
||||
id = (added as { Id: number }).Id;
|
||||
// Уникальный код заявки: пока = id (id известен только после создания).
|
||||
await list.items.getById(id).update({ RequestUniqueCode: String(id) });
|
||||
}
|
||||
|
||||
await writeRoute(hireApprovalService(), id, approvers, executors, status);
|
||||
|
||||
Reference in New Issue
Block a user