feat: замена и удаление исполнителей в заявке на подбор
Начальник отдела подбора (routeEditRights) назначает исполнителей, но сама в исполнении не стоит. В матрице «Исполнение» заявки на подбор у шагов «Ждёт очереди» и «На исполнении» появились кнопки «Заменить» и «Удалить»: - замена меняет только человека в шаге, активный шаг сразу переходит к новому исполнителю, ему уходит оповещение; - при удалении активного очередь переходит к следующему исполнителю; - выполненные шаги не трогаются, последнего невыполненного исполнителя можно только заменить — иначе заявка закрылась бы без исполнения. Остальные формы не затронуты: кнопки включаются пропсами ApprovalMatrix (canReplaceRow/onReplaceRow) только в RecruitingSelectionView. notifyActiveStep получил необязательный rowId, чтобы на параллельном шаге письмо ушло именно новому исполнителю. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,7 @@ import {
|
||||
canEditAllFields,
|
||||
REQUEST_STATUS,
|
||||
IMatrixRow,
|
||||
ISiteUser,
|
||||
APPROVAL_PHASE,
|
||||
ApproverDecision
|
||||
} from '../../shared/approval';
|
||||
@@ -192,6 +193,22 @@ const RecruitingSelectionView: React.FC<IRecruitingSelectionViewProps> = ({ cont
|
||||
await saveAllFields();
|
||||
};
|
||||
|
||||
// rowId — оповестить конкретную активную строку (см. notifyActiveStep).
|
||||
const notify = (rowId?: number): void => {
|
||||
if (!requestTypeId) return;
|
||||
notifyActiveStep(
|
||||
context,
|
||||
approval,
|
||||
itemId,
|
||||
{
|
||||
requestTypeId,
|
||||
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
||||
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
||||
},
|
||||
rowId
|
||||
).catch(() => undefined);
|
||||
};
|
||||
|
||||
// Удалить ещё не решённый шаг маршрута (см. ApproverService.deleteQueuedStep).
|
||||
const handleRemoveApprover = (row: IMatrixRow): void => {
|
||||
if (!row.id) return;
|
||||
@@ -202,6 +219,40 @@ const RecruitingSelectionView: React.FC<IRecruitingSelectionViewProps> = ({ cont
|
||||
.catch((e) => setRouteError(friendlyErrorMessage(e)));
|
||||
};
|
||||
|
||||
// Исполнителей тот же человек (routeEditRights.ts) заменяет и удаляет даже на
|
||||
// АКТИВНОМ шаге — только в этой форме. Выполненный шаг не трогаем, а
|
||||
// последнего невыполненного исполнителя можно только заменить (см.
|
||||
// ApproverService.deleteExecutionStep).
|
||||
const isPendingExecutor = (row: IMatrixRow): boolean =>
|
||||
row.status === REQUEST_STATUS.queued || row.status === REQUEST_STATUS.onExecution;
|
||||
const pendingExecutorsCount = executors.rows.filter(isPendingExecutor).length;
|
||||
|
||||
const handleReplaceExecutor = (row: IMatrixRow, user: ISiteUser): void => {
|
||||
if (!row.id) return;
|
||||
const rowId = row.id;
|
||||
setRouteError(undefined);
|
||||
approverService
|
||||
.replaceExecutionStep(itemId, rowId, user)
|
||||
.then((wasActive) => {
|
||||
setDecisionSeq((n) => n + 1);
|
||||
// Шаг уже был в работе — заявка сразу у нового исполнителя, сообщаем ему.
|
||||
if (wasActive) notify(rowId);
|
||||
})
|
||||
.catch((e) => setRouteError(friendlyErrorMessage(e)));
|
||||
};
|
||||
|
||||
const handleRemoveExecutor = (row: IMatrixRow): void => {
|
||||
if (!row.id) return;
|
||||
setRouteError(undefined);
|
||||
approverService
|
||||
.deleteExecutionStep(itemId, row.id)
|
||||
.then((activatedRowIds) => {
|
||||
setDecisionSeq((n) => n + 1);
|
||||
activatedRowIds.forEach((id) => notify(id));
|
||||
})
|
||||
.catch((e) => setRouteError(friendlyErrorMessage(e)));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let alive = true;
|
||||
setLoading(true);
|
||||
@@ -243,15 +294,6 @@ const RecruitingSelectionView: React.FC<IRecruitingSelectionViewProps> = ({ cont
|
||||
|
||||
const sectionProps = { data, set, context };
|
||||
|
||||
const notify = (): void => {
|
||||
if (!requestTypeId) return;
|
||||
notifyActiveStep(context, approval, itemId, {
|
||||
requestTypeId,
|
||||
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
||||
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
||||
}).catch(() => undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={`${styles.toolbar} rp-no-print`}>
|
||||
@@ -327,6 +369,12 @@ const RecruitingSelectionView: React.FC<IRecruitingSelectionViewProps> = ({ cont
|
||||
title="Исполнение"
|
||||
roleLabel="Исполнитель"
|
||||
emptyText="Исполнители не назначены."
|
||||
canReplaceRow={canRemoveApprover ? isPendingExecutor : undefined}
|
||||
onReplaceRow={handleReplaceExecutor}
|
||||
canRemoveRow={
|
||||
canRemoveApprover ? (row) => isPendingExecutor(row) && pendingExecutorsCount > 1 : undefined
|
||||
}
|
||||
onRemoveRow={handleRemoveExecutor}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user