diff --git a/docs/permissions.md b/docs/permissions.md index eebf95b..17d045c 100644 --- a/docs/permissions.md +++ b/docs/permissions.md @@ -16,7 +16,8 @@ | Право | Кто | Как определяется | Где действует | Файл с проверкой | |---|---|---|---|---| -| Удалять согласующих, которые ещё ждут очереди | Алтынай Бултурукова | email в коде | Подбор, Приём, Реферальная рекомендация: и при составлении, и в поданной заявке | `shared/approval/routeEditRights.ts` → `canEditRoute` | +| Удалять согласующих, которые ещё ждут очереди | Алтынай Бултурукова, Павел Горбенко | email в коде | Подбор, Приём, Реферальная рекомендация: и при составлении, и в поданной заявке | `shared/approval/routeEditRights.ts` → `canEditRoute` | +| Удалять любых согласующих **при составлении** заявки | те же (`canEditRoute`) | email в коде | Все остальные формы с маршрутом согласования (отпуск, увольнение, 1С и т. д.); в поданной заявке — нет | `shared/approval/routeEditRights.ts` → `composeRemoveRowRule` | | Заменять и удалять исполнителей, **в том числе активного** («На исполнении») | Алтынай Бултурукова | тот же email (`canEditRoute`) | **Только** Заявка на подбор, матрица «Исполнение» | `canEditRoute` + `forms/RecruitingSelection/RecruitingSelectionView.tsx` | | Править **все поля** заявки на своём активном шаге согласования (без вложений) | Алтынай Бултурукова | email в коде | Подбор, Приём (Реферальная рекомендация — нет) | `shared/approval/fullEditRights.ts` → `canEditAllFields` | | Править оклад на ИС, оклад после ИС, «Отдел/Участок» на своём шаге согласования | Начальник расчётной группы (сейчас Ким Анна) | должность шага: «начальник/руководитель» + «расчет» | Заявка на приём | `forms/RecruitingHire/approverRights.ts` → `canEditPayrollFields` | diff --git a/src/webparts/requestPortal/forms/Clearance/ClearanceForm.tsx b/src/webparts/requestPortal/forms/Clearance/ClearanceForm.tsx index 0b22ae0..d8e8af6 100644 --- a/src/webparts/requestPortal/forms/Clearance/ClearanceForm.tsx +++ b/src/webparts/requestPortal/forms/Clearance/ClearanceForm.tsx @@ -13,6 +13,7 @@ import { useRequestSubmit } from '../../shared/ui'; import { + composeRemoveRowRule, ApprovalMatrix, ApprovalHistory, StatusBadge, @@ -27,6 +28,7 @@ import { APPROVAL_PHASE, unassignedStepMessage } from '../../shared/approval'; +import { effectiveUserEmail } from '../../shared/impersonation'; import { IRequestFormProps } from '../../models/types'; import { getEmployeeOrganizations, formatEmployeeOrganizations } from '../../shared/dictionary'; import { friendlyErrorMessage } from '../../shared/diagnostics'; @@ -326,7 +328,11 @@ const ClearanceForm: React.FC = ({ - approval.searchUsers(q)} /> + approval.searchUsers(q)} + canRemoveRow={composeRemoveRowRule(effectiveUserEmail(context))} + /> {/* Блок исполнителя показываем, только когда по маршруту реально есть исполнители (условие «IsManager» и т.п. могло не назначить ни одного). */} diff --git a/src/webparts/requestPortal/forms/ConcreteRequest/ConcreteRequestForm.tsx b/src/webparts/requestPortal/forms/ConcreteRequest/ConcreteRequestForm.tsx index c3e7f14..a7aea88 100644 --- a/src/webparts/requestPortal/forms/ConcreteRequest/ConcreteRequestForm.tsx +++ b/src/webparts/requestPortal/forms/ConcreteRequest/ConcreteRequestForm.tsx @@ -13,6 +13,7 @@ import { useRequestSubmit } from '../../shared/ui'; import { + composeRemoveRowRule, ApprovalMatrix, ApprovalHistory, StatusBadge, @@ -26,6 +27,7 @@ import { IApproverToWrite, unassignedStepMessage } from '../../shared/approval'; +import { effectiveUserEmail } from '../../shared/impersonation'; import { IRequestFormProps } from '../../models/types'; import { getEmployeeOrganizations, formatEmployeeOrganizations } from '../../shared/dictionary'; import { friendlyErrorMessage } from '../../shared/diagnostics'; @@ -323,6 +325,7 @@ const ConcreteRequestForm: React.FC = ({ approval.searchUsers(q)} waiting={!matrixReady} waitingText="Укажите начальника участка и проект, чтобы построить маршрут согласования." diff --git a/src/webparts/requestPortal/forms/Dismissal/DismissalForm.tsx b/src/webparts/requestPortal/forms/Dismissal/DismissalForm.tsx index 9d41b88..7de7791 100644 --- a/src/webparts/requestPortal/forms/Dismissal/DismissalForm.tsx +++ b/src/webparts/requestPortal/forms/Dismissal/DismissalForm.tsx @@ -13,6 +13,7 @@ import { useRequestSubmit } from '../../shared/ui'; import { + composeRemoveRowRule, ApprovalMatrix, ApprovalHistory, StatusBadge, @@ -26,6 +27,7 @@ import { unassignedStepMessage, resolveWriteListId } from '../../shared/approval'; +import { effectiveUserEmail } from '../../shared/impersonation'; import { IRequestFormProps } from '../../models/types'; import { getEmployeeOrganizations, getCompanyDirectorEmail, IEmployeeOrganization } from '../../shared/dictionary'; import { friendlyErrorMessage } from '../../shared/diagnostics'; @@ -340,7 +342,11 @@ const DismissalForm: React.FC = ({ - approval.searchUsers(q)} /> + approval.searchUsers(q)} + canRemoveRow={composeRemoveRowRule(effectiveUserEmail(context))} + /> {/* Автору на доработке важнее всего видеть, почему вернули или отклонили, — поэтому история и в форме, а не только в просмотре. diff --git a/src/webparts/requestPortal/forms/EmployeeTransfer/EmployeeTransferForm.tsx b/src/webparts/requestPortal/forms/EmployeeTransfer/EmployeeTransferForm.tsx index 9be0af3..77cf7b8 100644 --- a/src/webparts/requestPortal/forms/EmployeeTransfer/EmployeeTransferForm.tsx +++ b/src/webparts/requestPortal/forms/EmployeeTransfer/EmployeeTransferForm.tsx @@ -13,6 +13,7 @@ import { useRequestSubmit } from '../../shared/ui'; import { + composeRemoveRowRule, ApprovalMatrix, ApprovalHistory, StatusBadge, @@ -26,6 +27,7 @@ import { IApproverToWrite, unassignedStepMessage } from '../../shared/approval'; +import { effectiveUserEmail } from '../../shared/impersonation'; import { IRequestFormProps } from '../../models/types'; import { getEmployeeOrganizations, formatEmployeeOrganizations } from '../../shared/dictionary'; import { friendlyErrorMessage } from '../../shared/diagnostics'; @@ -302,7 +304,11 @@ const EmployeeTransferForm: React.FC = ({ - approval.searchUsers(q)} /> + approval.searchUsers(q)} + canRemoveRow={composeRemoveRowRule(effectiveUserEmail(context))} + /> {/* Автору на доработке важнее всего видеть, почему вернули или отклонили. Только у уже существующей заявки: у новой истории нет. */} diff --git a/src/webparts/requestPortal/forms/ServiceRequest/ServiceRequestForm.tsx b/src/webparts/requestPortal/forms/ServiceRequest/ServiceRequestForm.tsx index 0c2354f..768fc32 100644 --- a/src/webparts/requestPortal/forms/ServiceRequest/ServiceRequestForm.tsx +++ b/src/webparts/requestPortal/forms/ServiceRequest/ServiceRequestForm.tsx @@ -13,6 +13,7 @@ import { useRequestSubmit } from '../../shared/ui'; import { + composeRemoveRowRule, ApprovalMatrix, ApprovalHistory, StatusBadge, @@ -383,6 +384,7 @@ const ServiceRequestForm: React.FC = ({ approval.searchUsers(q)} waiting={!matrixReady} waitingText="Укажите заявителя и проект, чтобы построить маршрут согласования." diff --git a/src/webparts/requestPortal/forms/SkudExplanation/SkudExplanationForm.tsx b/src/webparts/requestPortal/forms/SkudExplanation/SkudExplanationForm.tsx index 92d9731..359b075 100644 --- a/src/webparts/requestPortal/forms/SkudExplanation/SkudExplanationForm.tsx +++ b/src/webparts/requestPortal/forms/SkudExplanation/SkudExplanationForm.tsx @@ -13,6 +13,7 @@ import { useRequestSubmit } from '../../shared/ui'; import { + composeRemoveRowRule, ApprovalMatrix, ApprovalHistory, StatusBadge, @@ -25,6 +26,7 @@ import { unassignedStepMessage, resolveWriteListId } from '../../shared/approval'; +import { effectiveUserEmail } from '../../shared/impersonation'; import { IRequestFormProps } from '../../models/types'; import { getEmployeeOrganizations, formatEmployeeOrganizations } from '../../shared/dictionary'; import { friendlyErrorMessage } from '../../shared/diagnostics'; @@ -268,7 +270,11 @@ const SkudExplanationForm: React.FC = ({ - approval.searchUsers(q)} /> + approval.searchUsers(q)} + canRemoveRow={composeRemoveRowRule(effectiveUserEmail(context))} + /> {!executors.loading && executors.rows.length > 0 && ( = ({ approval.searchUsers(q)} // Пустой маршрут — штатная ситуация: для части видов работ согласование // не нужно, заявка сразу уходит в Planner. Сбой загрузки маршрута diff --git a/src/webparts/requestPortal/forms/TmcTransfer/TmcTransferForm.tsx b/src/webparts/requestPortal/forms/TmcTransfer/TmcTransferForm.tsx index 6331bb0..6676050 100644 --- a/src/webparts/requestPortal/forms/TmcTransfer/TmcTransferForm.tsx +++ b/src/webparts/requestPortal/forms/TmcTransfer/TmcTransferForm.tsx @@ -17,6 +17,7 @@ import { useRequestSubmit } from '../../shared/ui'; import { + composeRemoveRowRule, ApprovalMatrix, ApprovalHistory, StatusBadge, @@ -498,6 +499,7 @@ const TmcTransferForm: React.FC = ({ approval.searchUsers(q)} waiting={!matrixReady} waitingText="Укажите заявителя и проект-источник («Откуда»), чтобы построить маршрут согласования." diff --git a/src/webparts/requestPortal/forms/UnpaidLeave/UnpaidLeaveForm.tsx b/src/webparts/requestPortal/forms/UnpaidLeave/UnpaidLeaveForm.tsx index 181407b..228b758 100644 --- a/src/webparts/requestPortal/forms/UnpaidLeave/UnpaidLeaveForm.tsx +++ b/src/webparts/requestPortal/forms/UnpaidLeave/UnpaidLeaveForm.tsx @@ -13,6 +13,7 @@ import { useRequestSubmit } from '../../shared/ui'; import { + composeRemoveRowRule, ApprovalMatrix, ApprovalHistory, StatusBadge, @@ -26,6 +27,7 @@ import { unassignedStepMessage, resolveWriteListId } from '../../shared/approval'; +import { effectiveUserEmail } from '../../shared/impersonation'; import { IRequestFormProps } from '../../models/types'; import { getEmployeeOrganizations, formatEmployeeOrganizations } from '../../shared/dictionary'; import { friendlyErrorMessage } from '../../shared/diagnostics'; @@ -314,7 +316,11 @@ const UnpaidLeaveForm: React.FC = ({ - approval.searchUsers(q)} /> + approval.searchUsers(q)} + canRemoveRow={composeRemoveRowRule(effectiveUserEmail(context))} + /> {/* Автору на доработке важнее всего видеть, почему вернули или отклонили. Только у уже существующей заявки: у новой истории нет. */} diff --git a/src/webparts/requestPortal/forms/Vacation/VacationForm.tsx b/src/webparts/requestPortal/forms/Vacation/VacationForm.tsx index 21f6480..6950f14 100644 --- a/src/webparts/requestPortal/forms/Vacation/VacationForm.tsx +++ b/src/webparts/requestPortal/forms/Vacation/VacationForm.tsx @@ -13,6 +13,7 @@ import { useRequestSubmit } from '../../shared/ui'; import { + composeRemoveRowRule, ApprovalMatrix, ApprovalHistory, StatusBadge, @@ -26,6 +27,7 @@ import { IApproverToWrite, unassignedStepMessage } from '../../shared/approval'; +import { effectiveUserEmail } from '../../shared/impersonation'; import { IRequestFormProps } from '../../models/types'; import { getEmployeeOrganizations, formatEmployeeOrganizations } from '../../shared/dictionary'; import { friendlyErrorMessage } from '../../shared/diagnostics'; @@ -313,7 +315,11 @@ const VacationForm: React.FC = ({ - approval.searchUsers(q)} /> + approval.searchUsers(q)} + canRemoveRow={composeRemoveRowRule(effectiveUserEmail(context))} + /> {/* Автору на доработке важнее всего видеть, почему вернули или отклонили. Только у уже существующей заявки: у новой истории нет. */} diff --git a/src/webparts/requestPortal/forms/WorkplaceSetup/WorkplaceSetupForm.tsx b/src/webparts/requestPortal/forms/WorkplaceSetup/WorkplaceSetupForm.tsx index 672cc90..77c2199 100644 --- a/src/webparts/requestPortal/forms/WorkplaceSetup/WorkplaceSetupForm.tsx +++ b/src/webparts/requestPortal/forms/WorkplaceSetup/WorkplaceSetupForm.tsx @@ -17,6 +17,7 @@ import { useRequestSubmit } from '../../shared/ui'; import { + composeRemoveRowRule, ApprovalMatrix, ApprovalHistory, StatusBadge, @@ -391,7 +392,11 @@ const WorkplaceSetupForm: React.FC = ({ )} - approval.searchUsers(q)} /> + approval.searchUsers(q)} + canRemoveRow={composeRemoveRowRule(effectiveUserEmail(context))} + /> {!executors.loading && executors.rows.length > 0 && ( = new Set([ - 'a.bulturukova@tnsu.kz' // Алтынай Бултурукова + 'a.bulturukova@tnsu.kz', // Алтынай Бултурукова + 'gorbenko.pavel@tnsu.kz' // Павел Горбенко — составляет свой маршрут при подаче (обкатка форм) ]); /** Может ли этот email удалять согласующих из маршрута (см. описание выше). */ export function canEditRoute(email: string | undefined): boolean { return !!email && ROUTE_EDITOR_EMAILS.has(email.trim().toLowerCase()); } + +/** + * Правило для ApprovalMatrix.canRemoveRow при СОСТАВЛЕНИИ заявки: редактор маршрута удаляет + * любого согласующего, остальным кнопки удаления нет. Удаление тут чисто локальное — при + * отправке маршрут целиком пишется из строк матрицы, поэтому записи на сервер не нужно. + * В поданной заявке так нельзя: там своя реализация (см. RecruitingHireView). + */ +export function composeRemoveRowRule(email: string | undefined): (() => boolean) | undefined { + return canEditRoute(email) ? () => true : undefined; +}