feat: письмо инициатору при отклонении и возврате заявки
Поток RequestsNotification умеет ветки «Отклонено» и «Возврат на доработку», но портал их не вызывал: notifyActiveStep ищет только активные строки, а после такого решения их нет — инициатор узнавал о решении, только открыв заявку. notifyInitiatorOfReturn шлёт в тот же поток RowID строки, по которой решил текущий пользователь; подключён в onDecided всех форм, кроме «Изменения условий ТД» (доработаем позже). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
|||||||
StatusBadge,
|
StatusBadge,
|
||||||
ApprovalHistory,
|
ApprovalHistory,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
|
notifyInitiatorOfReturn,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
usePhasedRoute
|
usePhasedRoute
|
||||||
} from '../../shared/approval';
|
} from '../../shared/approval';
|
||||||
@@ -108,14 +109,17 @@ const BusinessTripView: React.FC<IBusinessTripViewProps> = ({ context, itemId, f
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const notify = (): void => {
|
const notify = (newStatus?: string): void => {
|
||||||
// Решение уже применено; без requestTypeId просто некого уведомлять — UI не роняем.
|
// Решение уже применено; без requestTypeId просто некого уведомлять — UI не роняем.
|
||||||
if (!requestTypeId) return;
|
if (!requestTypeId) return;
|
||||||
notifyActiveStep(context, approval, itemId, {
|
const target = {
|
||||||
requestTypeId,
|
requestTypeId,
|
||||||
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
||||||
}).catch(() => undefined);
|
};
|
||||||
|
notifyActiveStep(context, approval, itemId, target).catch(() => undefined);
|
||||||
|
// Отклонили или вернули на доработку — письмо инициатору (при других статусах ничего не делает).
|
||||||
|
if (newStatus) notifyInitiatorOfReturn(context, approval, itemId, target, newStatus).catch(() => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
const sectionProps = { data, set: NOOP_SET, context };
|
const sectionProps = { data, set: NOOP_SET, context };
|
||||||
@@ -173,7 +177,7 @@ const BusinessTripView: React.FC<IBusinessTripViewProps> = ({ context, itemId, f
|
|||||||
onDecided={(newStatus) => {
|
onDecided={(newStatus) => {
|
||||||
setStatus(newStatus);
|
setStatus(newStatus);
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
notify();
|
notify(newStatus);
|
||||||
}}
|
}}
|
||||||
onDelegated={() => {
|
onDelegated={() => {
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
ApprovalHistory,
|
ApprovalHistory,
|
||||||
useMatrixEditor,
|
useMatrixEditor,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
|
notifyInitiatorOfReturn,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
APPROVAL_PHASE
|
APPROVAL_PHASE
|
||||||
} from '../../shared/approval';
|
} from '../../shared/approval';
|
||||||
@@ -255,16 +256,19 @@ const ClearanceView: React.FC<IClearanceViewProps> = ({ context, itemId, formKey
|
|||||||
printOrgIndex
|
printOrgIndex
|
||||||
};
|
};
|
||||||
|
|
||||||
const notify = (): void => {
|
const notify = (newStatus?: string): void => {
|
||||||
// requestTypeId ещё не резолвился — решение уже применилось (строка списка
|
// requestTypeId ещё не резолвился — решение уже применилось (строка списка
|
||||||
// обновлена), просто нечего уведомлять на следующем шаге; не проваливаем
|
// обновлена), просто нечего уведомлять на следующем шаге; не проваливаем
|
||||||
// весь UI ради необязательного оповещения.
|
// весь UI ради необязательного оповещения.
|
||||||
if (!requestTypeId) return;
|
if (!requestTypeId) return;
|
||||||
notifyActiveStep(context, approval, itemId, {
|
const target = {
|
||||||
requestTypeId,
|
requestTypeId,
|
||||||
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
||||||
}).catch(() => undefined);
|
};
|
||||||
|
notifyActiveStep(context, approval, itemId, target).catch(() => undefined);
|
||||||
|
// Отклонили или вернули на доработку — письмо инициатору (при других статусах ничего не делает).
|
||||||
|
if (newStatus) notifyInitiatorOfReturn(context, approval, itemId, target, newStatus).catch(() => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -348,7 +352,7 @@ const ClearanceView: React.FC<IClearanceViewProps> = ({ context, itemId, formKey
|
|||||||
onDecided={(newStatus) => {
|
onDecided={(newStatus) => {
|
||||||
setStatus(newStatus);
|
setStatus(newStatus);
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
notify();
|
notify(newStatus);
|
||||||
}}
|
}}
|
||||||
onDelegated={() => {
|
onDelegated={() => {
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
ApprovalHistory,
|
ApprovalHistory,
|
||||||
useMatrixEditor,
|
useMatrixEditor,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
|
notifyInitiatorOfReturn,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
useViewExecutors,
|
useViewExecutors,
|
||||||
approvalPhaseRows
|
approvalPhaseRows
|
||||||
@@ -118,16 +119,19 @@ const ConcreteRequestView: React.FC<IConcreteRequestViewProps> = ({ context, ite
|
|||||||
|
|
||||||
const sectionProps = { data, set: NOOP_SET, context, printOrgIndex };
|
const sectionProps = { data, set: NOOP_SET, context, printOrgIndex };
|
||||||
|
|
||||||
const notify = (): void => {
|
const notify = (newStatus?: string): void => {
|
||||||
// requestTypeId ещё не резолвился — решение уже применилось (строка списка
|
// requestTypeId ещё не резолвился — решение уже применилось (строка списка
|
||||||
// обновлена), просто нечего уведомлять на следующем шаге; не проваливаем
|
// обновлена), просто нечего уведомлять на следующем шаге; не проваливаем
|
||||||
// весь UI ради необязательного оповещения.
|
// весь UI ради необязательного оповещения.
|
||||||
if (!requestTypeId) return;
|
if (!requestTypeId) return;
|
||||||
notifyActiveStep(context, approval, itemId, {
|
const target = {
|
||||||
requestTypeId,
|
requestTypeId,
|
||||||
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
||||||
}).catch(() => undefined);
|
};
|
||||||
|
notifyActiveStep(context, approval, itemId, target).catch(() => undefined);
|
||||||
|
// Отклонили или вернули на доработку — письмо инициатору (при других статусах ничего не делает).
|
||||||
|
if (newStatus) notifyInitiatorOfReturn(context, approval, itemId, target, newStatus).catch(() => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -175,7 +179,7 @@ const ConcreteRequestView: React.FC<IConcreteRequestViewProps> = ({ context, ite
|
|||||||
onDecided={(newStatus) => {
|
onDecided={(newStatus) => {
|
||||||
setStatus(newStatus);
|
setStatus(newStatus);
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
notify();
|
notify(newStatus);
|
||||||
}}
|
}}
|
||||||
onDelegated={() => {
|
onDelegated={() => {
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
ApprovalHistory,
|
ApprovalHistory,
|
||||||
useMatrixEditor,
|
useMatrixEditor,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
|
notifyInitiatorOfReturn,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
REQUEST_STATUS,
|
REQUEST_STATUS,
|
||||||
approvalPhaseRows,
|
approvalPhaseRows,
|
||||||
@@ -218,16 +219,19 @@ const DismissalView: React.FC<IDismissalViewProps> = ({ context, itemId, formKey
|
|||||||
await updateAssetsAcceptor(itemId, data.AssetsAcceptor, listId);
|
await updateAssetsAcceptor(itemId, data.AssetsAcceptor, listId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const notify = (): void => {
|
const notify = (newStatus?: string): void => {
|
||||||
// requestTypeId ещё не резолвился — решение уже применилось (строка списка
|
// requestTypeId ещё не резолвился — решение уже применилось (строка списка
|
||||||
// обновлена), просто нечего уведомлять на следующем шаге; не проваливаем
|
// обновлена), просто нечего уведомлять на следующем шаге; не проваливаем
|
||||||
// весь UI ради необязательного оповещения.
|
// весь UI ради необязательного оповещения.
|
||||||
if (!requestTypeId) return;
|
if (!requestTypeId) return;
|
||||||
notifyActiveStep(context, approval, itemId, {
|
const target = {
|
||||||
requestTypeId,
|
requestTypeId,
|
||||||
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
||||||
}).catch(() => undefined);
|
};
|
||||||
|
notifyActiveStep(context, approval, itemId, target).catch(() => undefined);
|
||||||
|
// Отклонили или вернули на доработку — письмо инициатору (при других статусах ничего не делает).
|
||||||
|
if (newStatus) notifyInitiatorOfReturn(context, approval, itemId, target, newStatus).catch(() => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Заявление согласовано целиком → сразу заводим обходной лист по нему (данные
|
// Заявление согласовано целиком → сразу заводим обходной лист по нему (данные
|
||||||
@@ -311,7 +315,7 @@ const DismissalView: React.FC<IDismissalViewProps> = ({ context, itemId, formKey
|
|||||||
onDecided={(newStatus) => {
|
onDecided={(newStatus) => {
|
||||||
setStatus(newStatus);
|
setStatus(newStatus);
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
notify();
|
notify(newStatus);
|
||||||
// «Согласовано» = маршрут заявления пройден целиком (фазы исполнения у
|
// «Согласовано» = маршрут заявления пройден целиком (фазы исполнения у
|
||||||
// него нет, см. README формы) — значит увольнение подтверждено и пора
|
// него нет, см. README формы) — значит увольнение подтверждено и пора
|
||||||
// заводить обходной лист. Промис ВОЗВРАЩАЕМ: пока он не разрешится,
|
// заводить обходной лист. Промис ВОЗВРАЩАЕМ: пока он не разрешится,
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
ExecutorsMatrix,
|
ExecutorsMatrix,
|
||||||
approvalPhaseRows,
|
approvalPhaseRows,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
|
notifyInitiatorOfReturn,
|
||||||
useRequestTypeId
|
useRequestTypeId
|
||||||
} from '../../shared/approval';
|
} from '../../shared/approval';
|
||||||
import { friendlyErrorMessage } from '../../shared/diagnostics';
|
import { friendlyErrorMessage } from '../../shared/diagnostics';
|
||||||
@@ -117,16 +118,19 @@ const EmployeeTransferView: React.FC<IEmployeeTransferViewProps> = ({ context, i
|
|||||||
|
|
||||||
const sectionProps = { data, set: NOOP_SET, context, printOrgIndex };
|
const sectionProps = { data, set: NOOP_SET, context, printOrgIndex };
|
||||||
|
|
||||||
const notify = (): void => {
|
const notify = (newStatus?: string): void => {
|
||||||
// requestTypeId ещё не резолвился — решение уже применилось (строка списка
|
// requestTypeId ещё не резолвился — решение уже применилось (строка списка
|
||||||
// обновлена), просто нечего уведомлять на следующем шаге; не проваливаем
|
// обновлена), просто нечего уведомлять на следующем шаге; не проваливаем
|
||||||
// весь UI ради необязательного оповещения.
|
// весь UI ради необязательного оповещения.
|
||||||
if (!requestTypeId) return;
|
if (!requestTypeId) return;
|
||||||
notifyActiveStep(context, approval, itemId, {
|
const target = {
|
||||||
requestTypeId,
|
requestTypeId,
|
||||||
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
||||||
}).catch(() => undefined);
|
};
|
||||||
|
notifyActiveStep(context, approval, itemId, target).catch(() => undefined);
|
||||||
|
// Отклонили или вернули на доработку — письмо инициатору (при других статусах ничего не делает).
|
||||||
|
if (newStatus) notifyInitiatorOfReturn(context, approval, itemId, target, newStatus).catch(() => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -172,7 +176,7 @@ const EmployeeTransferView: React.FC<IEmployeeTransferViewProps> = ({ context, i
|
|||||||
onDecided={(newStatus) => {
|
onDecided={(newStatus) => {
|
||||||
setStatus(newStatus);
|
setStatus(newStatus);
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
notify();
|
notify(newStatus);
|
||||||
}}
|
}}
|
||||||
onDelegated={() => {
|
onDelegated={() => {
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
ApprovalHistory,
|
ApprovalHistory,
|
||||||
useMatrixEditor,
|
useMatrixEditor,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
|
notifyInitiatorOfReturn,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
APPROVAL_PHASE,
|
APPROVAL_PHASE,
|
||||||
isApprovedStatus,
|
isApprovedStatus,
|
||||||
@@ -283,13 +284,16 @@ const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemI
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Выше ранних return (загрузка/ошибка): зовётся и из колбэков хуков ниже.
|
// Выше ранних return (загрузка/ошибка): зовётся и из колбэков хуков ниже.
|
||||||
const notify = (): void => {
|
const notify = (newStatus?: string): void => {
|
||||||
if (!requestTypeId) return;
|
if (!requestTypeId) return;
|
||||||
notifyActiveStep(context, approval, itemId, {
|
const target = {
|
||||||
requestTypeId,
|
requestTypeId,
|
||||||
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID),
|
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID),
|
||||||
}).catch(() => undefined);
|
};
|
||||||
|
notifyActiveStep(context, approval, itemId, target).catch(() => undefined);
|
||||||
|
// Отклонили или вернули на доработку — письмо инициатору (при других статусах ничего не делает).
|
||||||
|
if (newStatus) notifyInitiatorOfReturn(context, approval, itemId, target, newStatus).catch(() => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Добавить согласующего/исполнителя в поданную заявку — то же право
|
// Добавить согласующего/исполнителя в поданную заявку — то же право
|
||||||
@@ -479,7 +483,7 @@ const RecruitingHireView: React.FC<IRecruitingHireViewProps> = ({ context, itemI
|
|||||||
setStatus(newStatus);
|
setStatus(newStatus);
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
// Очередь сдвинулась — уведомляем следующий активный шаг.
|
// Очередь сдвинулась — уведомляем следующий активный шаг.
|
||||||
notify();
|
notify(newStatus);
|
||||||
}}
|
}}
|
||||||
onDelegated={() => {
|
onDelegated={() => {
|
||||||
// Шаг переназначен: активной стала строка делегата — обновляем
|
// Шаг переназначен: активной стала строка делегата — обновляем
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
ApprovalHistory,
|
ApprovalHistory,
|
||||||
useMatrixEditor,
|
useMatrixEditor,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
|
notifyInitiatorOfReturn,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
isApprovedStatus,
|
isApprovedStatus,
|
||||||
resolveApproverOrgInfo,
|
resolveApproverOrgInfo,
|
||||||
@@ -204,20 +205,25 @@ const RecruitingSelectionView: React.FC<IRecruitingSelectionViewProps> = ({ cont
|
|||||||
await saveAllFields();
|
await saveAllFields();
|
||||||
};
|
};
|
||||||
|
|
||||||
// rowId — оповестить конкретную активную строку (см. notifyActiveStep).
|
// Куда смотрит поток оповещения; undefined — тип заявки ещё не резолвился.
|
||||||
const notify = (rowId?: number): void => {
|
const notifyTarget = requestTypeId
|
||||||
if (!requestTypeId) return;
|
? {
|
||||||
notifyActiveStep(
|
|
||||||
context,
|
|
||||||
approval,
|
|
||||||
itemId,
|
|
||||||
{
|
|
||||||
requestTypeId,
|
requestTypeId,
|
||||||
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
||||||
},
|
}
|
||||||
rowId
|
: undefined;
|
||||||
).catch(() => undefined);
|
|
||||||
|
// rowId — оповестить конкретную активную строку (см. notifyActiveStep).
|
||||||
|
const notify = (rowId?: number): void => {
|
||||||
|
if (!notifyTarget) return;
|
||||||
|
notifyActiveStep(context, approval, itemId, notifyTarget, rowId).catch(() => undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Отклонили или вернули на доработку — письмо инициатору (при других статусах ничего не делает).
|
||||||
|
const notifyInitiator = (newStatus: string): void => {
|
||||||
|
if (!notifyTarget) return;
|
||||||
|
notifyInitiatorOfReturn(context, approval, itemId, notifyTarget, newStatus).catch(() => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Удалить ещё не решённый шаг маршрута (см. ApproverService.deleteQueuedStep).
|
// Удалить ещё не решённый шаг маршрута (см. ApproverService.deleteQueuedStep).
|
||||||
@@ -467,6 +473,7 @@ const RecruitingSelectionView: React.FC<IRecruitingSelectionViewProps> = ({ cont
|
|||||||
setStatus(newStatus);
|
setStatus(newStatus);
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
notify();
|
notify();
|
||||||
|
notifyInitiator(newStatus);
|
||||||
}}
|
}}
|
||||||
onDelegated={() => {
|
onDelegated={() => {
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
ApprovalHistory,
|
ApprovalHistory,
|
||||||
useMatrixEditor,
|
useMatrixEditor,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
|
notifyInitiatorOfReturn,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
APPROVAL_PHASE,
|
APPROVAL_PHASE,
|
||||||
isApprovedStatus,
|
isApprovedStatus,
|
||||||
@@ -169,16 +170,19 @@ const ReferralView: React.FC<IReferralViewProps> = ({ context, itemId, formKey,
|
|||||||
|
|
||||||
const sectionProps = { data, set: NOOP_SET, context, printOrgIndex };
|
const sectionProps = { data, set: NOOP_SET, context, printOrgIndex };
|
||||||
|
|
||||||
const notify = (): void => {
|
const notify = (newStatus?: string): void => {
|
||||||
// requestTypeId ещё не резолвился — решение уже применилось (строка списка
|
// requestTypeId ещё не резолвился — решение уже применилось (строка списка
|
||||||
// обновлена), просто нечего уведомлять на следующем шаге; не проваливаем
|
// обновлена), просто нечего уведомлять на следующем шаге; не проваливаем
|
||||||
// весь UI ради необязательного оповещения.
|
// весь UI ради необязательного оповещения.
|
||||||
if (!requestTypeId) return;
|
if (!requestTypeId) return;
|
||||||
notifyActiveStep(context, approval, itemId, {
|
const target = {
|
||||||
requestTypeId,
|
requestTypeId,
|
||||||
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
||||||
}).catch(() => undefined);
|
};
|
||||||
|
notifyActiveStep(context, approval, itemId, target).catch(() => undefined);
|
||||||
|
// Отклонили или вернули на доработку — письмо инициатору (при других статусах ничего не делает).
|
||||||
|
if (newStatus) notifyInitiatorOfReturn(context, approval, itemId, target, newStatus).catch(() => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -253,7 +257,7 @@ const ReferralView: React.FC<IReferralViewProps> = ({ context, itemId, formKey,
|
|||||||
onDecided={(newStatus) => {
|
onDecided={(newStatus) => {
|
||||||
setStatus(newStatus);
|
setStatus(newStatus);
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
notify();
|
notify(newStatus);
|
||||||
}}
|
}}
|
||||||
onDelegated={() => {
|
onDelegated={() => {
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
ExecutorsMatrix,
|
ExecutorsMatrix,
|
||||||
approvalPhaseRows,
|
approvalPhaseRows,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
|
notifyInitiatorOfReturn,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
isEditableStatus
|
isEditableStatus
|
||||||
} from '../../shared/approval';
|
} from '../../shared/approval';
|
||||||
@@ -141,13 +142,16 @@ const ServiceRequestView: React.FC<IServiceRequestViewProps> = ({ context, itemI
|
|||||||
|
|
||||||
const sectionProps = { data, set, context, printOrgIndex, attachmentUrls };
|
const sectionProps = { data, set, context, printOrgIndex, attachmentUrls };
|
||||||
|
|
||||||
const notify = (): void => {
|
const notify = (newStatus?: string): void => {
|
||||||
if (!requestTypeId) return;
|
if (!requestTypeId) return;
|
||||||
notifyActiveStep(context, approval, itemId, {
|
const target = {
|
||||||
requestTypeId,
|
requestTypeId,
|
||||||
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
||||||
}).catch(() => undefined);
|
};
|
||||||
|
notifyActiveStep(context, approval, itemId, target).catch(() => undefined);
|
||||||
|
// Отклонили или вернули на доработку — письмо инициатору (при других статусах ничего не делает).
|
||||||
|
if (newStatus) notifyInitiatorOfReturn(context, approval, itemId, target, newStatus).catch(() => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -205,7 +209,7 @@ const ServiceRequestView: React.FC<IServiceRequestViewProps> = ({ context, itemI
|
|||||||
onDecided={(newStatus) => {
|
onDecided={(newStatus) => {
|
||||||
setStatus(newStatus);
|
setStatus(newStatus);
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
notify();
|
notify(newStatus);
|
||||||
}}
|
}}
|
||||||
onDelegated={() => {
|
onDelegated={() => {
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
StatusBadge,
|
StatusBadge,
|
||||||
ApprovalHistory,
|
ApprovalHistory,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
|
notifyInitiatorOfReturn,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
usePhasedRoute
|
usePhasedRoute
|
||||||
} from '../../shared/approval';
|
} from '../../shared/approval';
|
||||||
@@ -110,14 +111,17 @@ const SkudExplanationView: React.FC<ISkudExplanationViewProps> = ({ context, ite
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const notify = (): void => {
|
const notify = (newStatus?: string): void => {
|
||||||
// Решение уже применено; без requestTypeId просто некого уведомлять — UI не роняем.
|
// Решение уже применено; без requestTypeId просто некого уведомлять — UI не роняем.
|
||||||
if (!requestTypeId) return;
|
if (!requestTypeId) return;
|
||||||
notifyActiveStep(context, approval, itemId, {
|
const target = {
|
||||||
requestTypeId,
|
requestTypeId,
|
||||||
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
||||||
}).catch(() => undefined);
|
};
|
||||||
|
notifyActiveStep(context, approval, itemId, target).catch(() => undefined);
|
||||||
|
// Отклонили или вернули на доработку — письмо инициатору (при других статусах ничего не делает).
|
||||||
|
if (newStatus) notifyInitiatorOfReturn(context, approval, itemId, target, newStatus).catch(() => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
const sectionProps = { data, set: NOOP_SET, context, printOrgIndex };
|
const sectionProps = { data, set: NOOP_SET, context, printOrgIndex };
|
||||||
@@ -175,7 +179,7 @@ const SkudExplanationView: React.FC<ISkudExplanationViewProps> = ({ context, ite
|
|||||||
onDecided={(newStatus) => {
|
onDecided={(newStatus) => {
|
||||||
setStatus(newStatus);
|
setStatus(newStatus);
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
notify();
|
notify(newStatus);
|
||||||
}}
|
}}
|
||||||
onDelegated={() => {
|
onDelegated={() => {
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
ApprovalHistory,
|
ApprovalHistory,
|
||||||
useMatrixEditor,
|
useMatrixEditor,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
|
notifyInitiatorOfReturn,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
isEditableStatus,
|
isEditableStatus,
|
||||||
APPROVAL_PHASE
|
APPROVAL_PHASE
|
||||||
@@ -102,20 +103,25 @@ const Ticket1CView: React.FC<ITicket1CViewProps> = ({ context, itemId, formKey,
|
|||||||
approval.getJobTitle(l)
|
approval.getJobTitle(l)
|
||||||
);
|
);
|
||||||
|
|
||||||
// rowId — оповестить конкретную активную строку (см. notifyActiveStep).
|
// Куда смотрит поток оповещения; undefined — тип заявки ещё не резолвился.
|
||||||
const notify = (rowId?: number): void => {
|
const notifyTarget = requestTypeId
|
||||||
if (!requestTypeId) return;
|
? {
|
||||||
notifyActiveStep(
|
|
||||||
context,
|
|
||||||
approval,
|
|
||||||
itemId,
|
|
||||||
{
|
|
||||||
requestTypeId,
|
requestTypeId,
|
||||||
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
||||||
},
|
}
|
||||||
rowId
|
: undefined;
|
||||||
).catch(() => undefined);
|
|
||||||
|
// rowId — оповестить конкретную активную строку (см. notifyActiveStep).
|
||||||
|
const notify = (rowId?: number): void => {
|
||||||
|
if (!notifyTarget) return;
|
||||||
|
notifyActiveStep(context, approval, itemId, notifyTarget, rowId).catch(() => undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Отклонили или вернули на доработку — письмо инициатору (при других статусах ничего не делает).
|
||||||
|
const notifyInitiator = (newStatus: string): void => {
|
||||||
|
if (!notifyTarget) return;
|
||||||
|
notifyInitiatorOfReturn(context, approval, itemId, notifyTarget, newStatus).catch(() => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
const executorEditing = useExecutorEditing({
|
const executorEditing = useExecutorEditing({
|
||||||
@@ -246,6 +252,7 @@ const Ticket1CView: React.FC<ITicket1CViewProps> = ({ context, itemId, formKey,
|
|||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
// Очередь сдвинулась — уведомляем следующий активный шаг.
|
// Очередь сдвинулась — уведомляем следующий активный шаг.
|
||||||
notify();
|
notify();
|
||||||
|
notifyInitiator(newStatus);
|
||||||
|
|
||||||
// Дошла до исполнения / завершилась / отклонена — карточка в Planner
|
// Дошла до исполнения / завершилась / отклонена — карточка в Planner
|
||||||
// (повторно не создаётся, см. plannerHandoff.ts).
|
// (повторно не создаётся, см. plannerHandoff.ts).
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import {
|
|||||||
ApprovalHistory,
|
ApprovalHistory,
|
||||||
useMatrixEditor,
|
useMatrixEditor,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
|
notifyInitiatorOfReturn,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
isEditableStatus,
|
isEditableStatus,
|
||||||
APPROVAL_PHASE
|
APPROVAL_PHASE
|
||||||
@@ -144,13 +145,16 @@ const TmcTransferView: React.FC<ITmcTransferViewProps> = ({ context, itemId, for
|
|||||||
|
|
||||||
const sectionProps = { data, set, context, printOrgIndex };
|
const sectionProps = { data, set, context, printOrgIndex };
|
||||||
|
|
||||||
const notify = (): void => {
|
const notify = (newStatus?: string): void => {
|
||||||
if (!requestTypeId) return;
|
if (!requestTypeId) return;
|
||||||
notifyActiveStep(context, approval, itemId, {
|
const target = {
|
||||||
requestTypeId,
|
requestTypeId,
|
||||||
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
||||||
}).catch(() => undefined);
|
};
|
||||||
|
notifyActiveStep(context, approval, itemId, target).catch(() => undefined);
|
||||||
|
// Отклонили или вернули на доработку — письмо инициатору (при других статусах ничего не делает).
|
||||||
|
if (newStatus) notifyInitiatorOfReturn(context, approval, itemId, target, newStatus).catch(() => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -222,7 +226,7 @@ const TmcTransferView: React.FC<ITmcTransferViewProps> = ({ context, itemId, for
|
|||||||
onDecided={(newStatus) => {
|
onDecided={(newStatus) => {
|
||||||
setStatus(newStatus);
|
setStatus(newStatus);
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
notify();
|
notify(newStatus);
|
||||||
}}
|
}}
|
||||||
onDelegated={() => {
|
onDelegated={() => {
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
useViewExecutors,
|
useViewExecutors,
|
||||||
approvalPhaseRows,
|
approvalPhaseRows,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
|
notifyInitiatorOfReturn,
|
||||||
useRequestTypeId
|
useRequestTypeId
|
||||||
} from '../../shared/approval';
|
} from '../../shared/approval';
|
||||||
import { friendlyErrorMessage } from '../../shared/diagnostics';
|
import { friendlyErrorMessage } from '../../shared/diagnostics';
|
||||||
@@ -125,16 +126,19 @@ const UnpaidLeaveView: React.FC<IUnpaidLeaveViewProps> = ({ context, itemId, for
|
|||||||
|
|
||||||
const sectionProps = { data, set: NOOP_SET, context, printOrgIndex };
|
const sectionProps = { data, set: NOOP_SET, context, printOrgIndex };
|
||||||
|
|
||||||
const notify = (): void => {
|
const notify = (newStatus?: string): void => {
|
||||||
// requestTypeId ещё не резолвился — решение уже применилось (строка списка
|
// requestTypeId ещё не резолвился — решение уже применилось (строка списка
|
||||||
// обновлена), просто нечего уведомлять на следующем шаге; не проваливаем
|
// обновлена), просто нечего уведомлять на следующем шаге; не проваливаем
|
||||||
// весь UI ради необязательного оповещения.
|
// весь UI ради необязательного оповещения.
|
||||||
if (!requestTypeId) return;
|
if (!requestTypeId) return;
|
||||||
notifyActiveStep(context, approval, itemId, {
|
const target = {
|
||||||
requestTypeId,
|
requestTypeId,
|
||||||
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
||||||
}).catch(() => undefined);
|
};
|
||||||
|
notifyActiveStep(context, approval, itemId, target).catch(() => undefined);
|
||||||
|
// Отклонили или вернули на доработку — письмо инициатору (при других статусах ничего не делает).
|
||||||
|
if (newStatus) notifyInitiatorOfReturn(context, approval, itemId, target, newStatus).catch(() => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -182,7 +186,7 @@ const UnpaidLeaveView: React.FC<IUnpaidLeaveViewProps> = ({ context, itemId, for
|
|||||||
onDecided={(newStatus) => {
|
onDecided={(newStatus) => {
|
||||||
setStatus(newStatus);
|
setStatus(newStatus);
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
notify();
|
notify(newStatus);
|
||||||
}}
|
}}
|
||||||
onDelegated={() => {
|
onDelegated={() => {
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
ExecutorsMatrix,
|
ExecutorsMatrix,
|
||||||
approvalPhaseRows,
|
approvalPhaseRows,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
|
notifyInitiatorOfReturn,
|
||||||
useRequestTypeId
|
useRequestTypeId
|
||||||
} from '../../shared/approval';
|
} from '../../shared/approval';
|
||||||
import { completesApprovalPhase } from '../../shared/approval/models/statuses';
|
import { completesApprovalPhase } from '../../shared/approval/models/statuses';
|
||||||
@@ -143,16 +144,19 @@ const VacationView: React.FC<IVacationViewProps> = ({ context, itemId, formKey,
|
|||||||
|
|
||||||
const sectionProps = { data, set: NOOP_SET, context, printOrgIndex };
|
const sectionProps = { data, set: NOOP_SET, context, printOrgIndex };
|
||||||
|
|
||||||
const notify = (): void => {
|
const notify = (newStatus?: string): void => {
|
||||||
// requestTypeId ещё не резолвился — решение уже применилось (строка списка
|
// requestTypeId ещё не резолвился — решение уже применилось (строка списка
|
||||||
// обновлена), просто нечего уведомлять на следующем шаге; не проваливаем
|
// обновлена), просто нечего уведомлять на следующем шаге; не проваливаем
|
||||||
// весь UI ради необязательного оповещения.
|
// весь UI ради необязательного оповещения.
|
||||||
if (!requestTypeId) return;
|
if (!requestTypeId) return;
|
||||||
notifyActiveStep(context, approval, itemId, {
|
const target = {
|
||||||
requestTypeId,
|
requestTypeId,
|
||||||
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID)
|
||||||
}).catch(() => undefined);
|
};
|
||||||
|
notifyActiveStep(context, approval, itemId, target).catch(() => undefined);
|
||||||
|
// Отклонили или вернули на доработку — письмо инициатору (при других статусах ничего не делает).
|
||||||
|
if (newStatus) notifyInitiatorOfReturn(context, approval, itemId, target, newStatus).catch(() => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Заявление уходит на подпись в ЭДО (по документу на каждую компанию сотрудника, одним запросом).
|
// Заявление уходит на подпись в ЭДО (по документу на каждую компанию сотрудника, одним запросом).
|
||||||
@@ -263,7 +267,7 @@ const VacationView: React.FC<IVacationViewProps> = ({ context, itemId, formKey,
|
|||||||
onDecided={(newStatus) => {
|
onDecided={(newStatus) => {
|
||||||
setStatus(newStatus);
|
setStatus(newStatus);
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
notify();
|
notify(newStatus);
|
||||||
return VACATION_EDO_SIGNING_ENABLED && sentBeforeDecision.current ?'Документы отправлены на подписание в ЭДО.' : undefined;
|
return VACATION_EDO_SIGNING_ENABLED && sentBeforeDecision.current ?'Документы отправлены на подписание в ЭДО.' : undefined;
|
||||||
}}
|
}}
|
||||||
onDelegated={() => {
|
onDelegated={() => {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import {
|
|||||||
ApprovalHistory,
|
ApprovalHistory,
|
||||||
useMatrixEditor,
|
useMatrixEditor,
|
||||||
notifyActiveStep,
|
notifyActiveStep,
|
||||||
|
notifyInitiatorOfReturn,
|
||||||
useRequestTypeId,
|
useRequestTypeId,
|
||||||
isEditableStatus,
|
isEditableStatus,
|
||||||
APPROVAL_PHASE,
|
APPROVAL_PHASE,
|
||||||
@@ -224,13 +225,16 @@ const WorkplaceSetupView: React.FC<IWorkplaceSetupViewProps> = ({ context, itemI
|
|||||||
const sectionProps = { data, set, context, printOrgIndex };
|
const sectionProps = { data, set, context, printOrgIndex };
|
||||||
const canEditAnything = canEditAll || canEditReviewFields;
|
const canEditAnything = canEditAll || canEditReviewFields;
|
||||||
|
|
||||||
const notify = (): void => {
|
const notify = (newStatus?: string): void => {
|
||||||
if (!requestTypeId) return;
|
if (!requestTypeId) return;
|
||||||
notifyActiveStep(context, approval, itemId, {
|
const target = {
|
||||||
requestTypeId,
|
requestTypeId,
|
||||||
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
dataListName: listId ? dataListTitleForListId(FORM_ID, listId) : dataListTitle(FORM_ID),
|
||||||
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID),
|
approvalListName: listId ? approvalListTitleForListId(FORM_ID, listId) : approvalListTitle(FORM_ID),
|
||||||
}).catch(() => undefined);
|
};
|
||||||
|
notifyActiveStep(context, approval, itemId, target).catch(() => undefined);
|
||||||
|
// Отклонили или вернули на доработку — письмо инициатору (при других статусах ничего не делает).
|
||||||
|
if (newStatus) notifyInitiatorOfReturn(context, approval, itemId, target, newStatus).catch(() => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -316,7 +320,7 @@ const WorkplaceSetupView: React.FC<IWorkplaceSetupViewProps> = ({ context, itemI
|
|||||||
setStatus(newStatus);
|
setStatus(newStatus);
|
||||||
setDecisionSeq((n) => n + 1);
|
setDecisionSeq((n) => n + 1);
|
||||||
// Очередь сдвинулась — уведомляем следующий активный шаг.
|
// Очередь сдвинулась — уведомляем следующий активный шаг.
|
||||||
notify();
|
notify(newStatus);
|
||||||
}}
|
}}
|
||||||
onDelegated={() => {
|
onDelegated={() => {
|
||||||
// Шаг переназначен: активной стала строка делегата — обновляем
|
// Шаг переназначен: активной стала строка делегата — обновляем
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export { approvalServiceFor } from './approvalServiceFactory';
|
|||||||
export { ApproverService } from './approverDecision.service';
|
export { ApproverService } from './approverDecision.service';
|
||||||
export type { ApproverDecision, IPendingRow } from './approverDecision.service';
|
export type { ApproverDecision, IPendingRow } from './approverDecision.service';
|
||||||
export { getPlannedMatrix, writeApprovers, writeRoute } from './approvalSubmission.service';
|
export { getPlannedMatrix, writeApprovers, writeRoute } from './approvalSubmission.service';
|
||||||
export { notifyActiveStep, postToFlow } from './notify.service';
|
export { notifyActiveStep, notifyInitiatorOfReturn, postToFlow } from './notify.service';
|
||||||
export type { INotifyTarget } from './notify.service';
|
export type { INotifyTarget } from './notify.service';
|
||||||
export { useMatrixEditor, findUnassignedStep, unassignedStepMessage } from './useMatrixEditor';
|
export { useMatrixEditor, findUnassignedStep, unassignedStepMessage } from './useMatrixEditor';
|
||||||
export type { IUseMatrixEditor } from './useMatrixEditor';
|
export type { IUseMatrixEditor } from './useMatrixEditor';
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { AadHttpClient } from '@microsoft/sp-http';
|
import { AadHttpClient } from '@microsoft/sp-http';
|
||||||
import { WebPartContext } from '@microsoft/sp-webpart-base';
|
import { WebPartContext } from '@microsoft/sp-webpart-base';
|
||||||
import { FLOW_RESOURCE, FLOW_NOTIFY_URL, getNotificationsEnabled, getNotifyOnlyTo } from '../config';
|
import { FLOW_RESOURCE, FLOW_NOTIFY_URL, getNotificationsEnabled, getNotifyOnlyTo } from '../config';
|
||||||
|
import { getCreatedBy } from '../createdBy';
|
||||||
import { ApprovalListService } from './approvalList.service';
|
import { ApprovalListService } from './approvalList.service';
|
||||||
import { IApprovalRow } from './models/IApprovalRow';
|
import { IApprovalRow } from './models/IApprovalRow';
|
||||||
|
import { REQUEST_STATUS } from './models/statuses';
|
||||||
import { pickRowsToNotify } from './notifyTargets';
|
import { pickRowsToNotify } from './notifyTargets';
|
||||||
|
|
||||||
// Данные формы для тела запроса к потоку (постоянны для формы).
|
// Данные формы для тела запроса к потоку (постоянны для формы).
|
||||||
@@ -42,11 +44,17 @@ export async function postToFlow(context: WebPartContext, flowUrl: string, body:
|
|||||||
// живому человеку лучше не слать».
|
// живому человеку лучше не слать».
|
||||||
async function isNotifyAllowed(approval: ApprovalListService, approverId: number): Promise<boolean> {
|
async function isNotifyAllowed(approval: ApprovalListService, approverId: number): Promise<boolean> {
|
||||||
if (!getNotificationsEnabled()) return false;
|
if (!getNotificationsEnabled()) return false;
|
||||||
|
if (!getNotifyOnlyTo()) return true;
|
||||||
|
return isRecipientAllowed(await approval.resolveUserEmail(approverId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// То же ограничение notifyOnlyTo, когда адрес получателя уже известен.
|
||||||
|
function isRecipientAllowed(email: string): boolean {
|
||||||
|
if (!getNotificationsEnabled()) return false;
|
||||||
|
|
||||||
const onlyTo = getNotifyOnlyTo();
|
const onlyTo = getNotifyOnlyTo();
|
||||||
if (!onlyTo) return true;
|
if (!onlyTo) return true;
|
||||||
|
|
||||||
const email = await approval.resolveUserEmail(approverId);
|
|
||||||
if (email.toLowerCase() === onlyTo.toLowerCase()) return true;
|
if (email.toLowerCase() === onlyTo.toLowerCase()) return true;
|
||||||
|
|
||||||
console.warn(
|
console.warn(
|
||||||
@@ -109,3 +117,51 @@ export async function notifyActiveStep(
|
|||||||
pickRowsToNotify(rows, rowId).map((row) => notifyRow(context, approval, requestId, target, row))
|
pickRowsToNotify(rows, rowId).map((row) => notifyRow(context, approval, requestId, target, row))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Решения, после которых заявка уходит обратно к инициатору и ему нужно письмо.
|
||||||
|
const RETURNED_TO_INITIATOR_STATUSES: string[] = [REQUEST_STATUS.rejected, REQUEST_STATUS.returned];
|
||||||
|
|
||||||
|
// Сообщить инициатору, что заявку отклонили или вернули на доработку. Вызывать
|
||||||
|
// в onDecided с новым статусом заявки — при любом другом статусе ничего не делает.
|
||||||
|
//
|
||||||
|
// Поток тот же, что у notifyActiveStep: у него есть ветки «Отклонено» и «Возврат
|
||||||
|
// на доработку», в которых он пишет не согласующему строки, а автору заявки.
|
||||||
|
// notifyActiveStep до них не доходит — после такого решения активных строк нет.
|
||||||
|
//
|
||||||
|
// Строка — та, по которой решил ТЕКУЩИЙ пользователь: он только что принял
|
||||||
|
// решение. Искать «любую отклонённую» нельзя — на параллельном шаге второй
|
||||||
|
// участник может решить по уже отклонённой заявке, и в письме оказался бы чужой
|
||||||
|
// комментарий.
|
||||||
|
//
|
||||||
|
// Некритично, как и notifyActiveStep: ошибки глотаем.
|
||||||
|
export async function notifyInitiatorOfReturn(
|
||||||
|
context: WebPartContext,
|
||||||
|
approval: ApprovalListService,
|
||||||
|
requestId: number,
|
||||||
|
target: INotifyTarget,
|
||||||
|
requestStatus: string
|
||||||
|
): Promise<void> {
|
||||||
|
if (RETURNED_TO_INITIATOR_STATUSES.indexOf(requestStatus) === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const [rows, userId, initiator] = await Promise.all([
|
||||||
|
approval.getForRequest(requestId),
|
||||||
|
approval.getCurrentUserId(),
|
||||||
|
getCreatedBy(target.dataListName, requestId),
|
||||||
|
]);
|
||||||
|
const decidedRow = rows.filter((r) => r.status === requestStatus && r.approverId === userId)[0];
|
||||||
|
if (!decidedRow || !isRecipientAllowed(initiator?.email || '')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await postToFlow(context, FLOW_NOTIFY_URL, {
|
||||||
|
RequestTypeId: target.requestTypeId,
|
||||||
|
RowID: decidedRow.id,
|
||||||
|
RequestID: requestId,
|
||||||
|
ListName: target.dataListName,
|
||||||
|
ApproveListName: target.approvalListName,
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// не критично — просто не отправили уведомление
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user