diff --git a/src/webparts/requestPortal/forms/Referral/ReferralForm.tsx b/src/webparts/requestPortal/forms/Referral/ReferralForm.tsx index 4e714cd..056278b 100644 --- a/src/webparts/requestPortal/forms/Referral/ReferralForm.tsx +++ b/src/webparts/requestPortal/forms/Referral/ReferralForm.tsx @@ -23,7 +23,8 @@ import { IApproverToWrite, unassignedStepMessage, resolveWriteListId, - APPROVAL_PHASE + APPROVAL_PHASE, + initialSubmitStatus } from '../../shared/approval'; import { IRequestFormProps } from '../../models/types'; import { friendlyErrorMessage } from '../../shared/diagnostics'; @@ -253,10 +254,11 @@ const ReferralForm: React.FC = ({ if (onSaved) { onSaved(); } - // Отправка на согласование — уведомляем первый активный шаг (поток оповещения). - // Имена списков — той же партиции, куда фактически записана заявка (writeListId), - // не текущей: поток ищет строку маршрута именно в НЕЙ. - if (status === REQUEST_STATUS.onApproval) { + // Отправка (на согласование ИЛИ, если согласующих нет, сразу на исполнение) — + // уведомляем первый активный шаг (поток оповещения). Имена списков — той же + // партиции, куда фактически записана заявка (writeListId), не текущей: + // поток ищет строку маршрута именно в НЕЙ. + if (status === REQUEST_STATUS.onApproval || status === REQUEST_STATUS.execution) { await notifyActiveStep(context, approval, id, { requestTypeId: rtId, dataListName: writeListId ? dataListTitleForListId(FORM_ID, writeListId) : dataListTitle(FORM_ID), @@ -286,8 +288,21 @@ const ReferralForm: React.FC = ({ return undefined; }; + // Маршрута может не быть вообще (нет согласующих — только исполнитель, как у + // рефералки): initialSubmitStatus сразу пишет «Выполнение», минуя «На + // согласовании», которого в таком маршруте попросту не бывает. const handleSaveDraft = (): Promise => submit.run('draft', save(REQUEST_STATUS.draft), validate); - const handleSubmit = (): Promise => submit.run('submit', save(REQUEST_STATUS.onApproval), validateSubmit); + const handleSubmit = (): Promise => + submit.run( + 'submit', + save(initialSubmitStatus(matrix.rows.length, executors.rows.length)), + validateSubmit + ); + + // Согласующих в маршруте нет вообще (подтверждено — матрица загружена без + // ошибки и пуста), а не просто «ещё загружается»: до этого момента считаем, + // что фаза согласования есть — исторический дефолт, без мигания подписи. + const noApprovalPhase = !matrix.loading && !matrix.error && matrix.rows.length === 0; const sectionProps = { data, set, context, onReferrerChange: handleReferrerChange }; @@ -318,7 +333,11 @@ const ReferralForm: React.FC = ({ > {submit.savedKind && !submit.error && ( - {submit.savedKind === 'draft' ? 'Черновик сохранён.' : 'Заявка отправлена на согласование.'} + {submit.savedKind === 'draft' + ? 'Черновик сохранён.' + : noApprovalPhase + ? 'Заявка отправлена на исполнение.' + : 'Заявка отправлена на согласование.'} )} {submit.error && {submit.error}} @@ -331,13 +350,16 @@ const ReferralForm: React.FC = ({ + {/* Блок остаётся видимым и пустым (не скрываем по noApprovalPhase) — + маршрут без согласующих не значит, что их нельзя добавить: матрица + редактируема, заявитель вправе вписать согласующего сам. */} approval.searchUsers(q)} /> {!executors.loading && executors.rows.length > 0 && ( approval.searchUsers(q)} - title="Исполнение (после согласования)" + title={noApprovalPhase ? 'Исполнение' : 'Исполнение (после согласования)'} roleLabel="Исполнитель" emptyText="Исполнители не заданы по маршруту." /> @@ -350,7 +372,7 @@ const ReferralForm: React.FC = ({ onCancel={onCancel} onSaveDraft={handleSaveDraft} onSubmit={handleSubmit} - submitLabel="Отправить на согласование" + submitLabel={noApprovalPhase ? 'Отправить на исполнение' : 'Отправить на согласование'} /> diff --git a/src/webparts/requestPortal/shared/approval/index.ts b/src/webparts/requestPortal/shared/approval/index.ts index 3fc9d9f..9f96dcc 100644 --- a/src/webparts/requestPortal/shared/approval/index.ts +++ b/src/webparts/requestPortal/shared/approval/index.ts @@ -7,6 +7,7 @@ export { APPROVAL_PHASE_CHOICES, EDITABLE_STATUSES, isEditableStatus, + initialSubmitStatus, } from './models/statuses'; export type { ISiteUser } from './models/ISiteUser'; export type { IMatrixRow } from './models/IMatrixRow'; diff --git a/src/webparts/requestPortal/shared/approval/models/statuses.ts b/src/webparts/requestPortal/shared/approval/models/statuses.ts index 95c484b..da186a7 100644 --- a/src/webparts/requestPortal/shared/approval/models/statuses.ts +++ b/src/webparts/requestPortal/shared/approval/models/statuses.ts @@ -43,6 +43,24 @@ export function isEditableStatus(status: string | undefined): boolean { return !status || EDITABLE_STATUSES.indexOf(status) !== -1; } +/** + * Статус ЗАЯВКИ в момент ОТПРАВКИ — до записи строк маршрута (см. writeRoute), + * когда реальных строк ещё нет и решать по ним, как computeRequestStatus в + * approverDecision.service.ts, нельзя. Формы раньше писали «На согласовании» + * безусловно; это неверно для маршрута без согласующих (только исполнение, + * см. форму «Реферальная рекомендация») — там заявка должна сразу войти в + * фазу исполнения, минуя несуществующую фазу согласования. + * + * Порядок фаз — тот же, что у computeRequestStatus (согласование → исполнение + * → закрыта), только по КОЛИЧЕСТВУ шагов, а не по фактическим статусам строк + * (при отправке все строки ещё «Ждёт очереди»/не существуют). + */ +export function initialSubmitStatus(approversCount: number, executorsCount: number): string { + if (approversCount > 0) return REQUEST_STATUS.onApproval; + if (executorsCount > 0) return REQUEST_STATUS.execution; + return REQUEST_STATUS.approved; +} + // Наборов значений ДВА, и они не совпадают — это разные сущности: // // 1) статус ЗАЯВКИ — колонка RequestStatus в списке «… — данные». Пишется diff --git a/src/webparts/requestPortal/shared/approval/ui/ApprovalMatrix.tsx b/src/webparts/requestPortal/shared/approval/ui/ApprovalMatrix.tsx index fcf5b2d..4779bd8 100644 --- a/src/webparts/requestPortal/shared/approval/ui/ApprovalMatrix.tsx +++ b/src/webparts/requestPortal/shared/approval/ui/ApprovalMatrix.tsx @@ -42,6 +42,11 @@ export const ApprovalMatrix: React.FC = ({ }) => { const { rows, loading, error, editable, blocks } = editor; + // Подпись «доп. согласующий»/«доп. исполнитель» — из roleLabel, а не + // хардкод: иначе блок «Исполнение» (roleLabel="Исполнитель") показывал бы + // добавленному через переназначение исполнителю подпись «Доп. согласующий». + const addedByLabel = `Доп. ${roleLabel.charAt(0).toLowerCase()}${roleLabel.slice(1)}, добавил:`; + // Ключи строк и анимация их перестановки. Ключи обязаны быть устойчивыми // (не индекс) — иначе React переписывает строки на месте, и «переезжать» // при перестановке шага будет нечему (см. matrixRowKeys / useFlipRows). @@ -155,13 +160,13 @@ export const ApprovalMatrix: React.FC = ({ {!r.who && r.warning && ( {r.warning} )} - {/* Доп. согласующий, добавленный другим согласующим (с правом - переназначения) — помечаем, кто его добавил. Пусто у обычных - строк маршрута. Как и комментарий — inline под именем, чтобы - не ломать rowSpan колонки «Шаг» у параллельных шагов. */} + {/* Доп. участник шага, добавленный переназначением — помечаем, + кто его добавил. Пусто у обычных строк маршрута. Как и + комментарий — inline под именем, чтобы не ломать rowSpan + колонки «Шаг» у параллельных шагов. */} {r.addedBy && ( - Доп. согласующий, добавил:{' '} + {addedByLabel}{' '} {r.addedBy} )} diff --git a/src/webparts/requestPortal/shared/approval/ui/DecisionPanel.tsx b/src/webparts/requestPortal/shared/approval/ui/DecisionPanel.tsx index 6b00abb..4615937 100644 --- a/src/webparts/requestPortal/shared/approval/ui/DecisionPanel.tsx +++ b/src/webparts/requestPortal/shared/approval/ui/DecisionPanel.tsx @@ -142,12 +142,18 @@ export const DecisionPanel: React.FC = ({ } if (delegated > 0) { + // Склоняем по фазе шага: у исполнения нет «согласующих» — тот, кого + // добавили, реально выполняет заявку, а не рассматривает её. + const who = isExecution ? 'Исполнитель' : 'Согласующий'; + const whoMany = isExecution ? 'Исполнителей' : 'Согласующих'; + const verb = isExecution ? 'выполнит' : 'рассмотрит'; + const verbMany = isExecution ? 'выполнят' : 'рассмотрят'; return (
{delegated === 1 - ? 'Согласующий добавлен перед вами — он рассмотрит заявку раньше вас. Ваш шаг остаётся в маршруте.' - : `Согласующих добавлено: ${delegated}. Они рассмотрят заявку раньше вас, в порядке добавления. ` + + ? `${who} добавлен перед вами — он ${verb} заявку раньше вас. Ваш шаг остаётся в маршруте.` + : `${whoMany} добавлено: ${delegated}. Они ${verbMany} заявку раньше вас, в порядке добавления. ` + 'Ваш шаг остаётся в маршруте.'}
@@ -234,9 +240,11 @@ export const DecisionPanel: React.FC = ({
{isExecution ? 'Исполнение' : 'Ваше решение'}
- Заявку сейчас рассматривает сотрудник, которого вы добавили перед собой. Ваш шаг никуда - не делся — он вернётся к вам, когда очередь дойдёт. Если добавленный сотрудник - недоступен, можно добавить ещё. + {isExecution + ? 'Заявку сейчас выполняет сотрудник, которого вы добавили перед собой.' + : 'Заявку сейчас рассматривает сотрудник, которого вы добавили перед собой.'}{' '} + Ваш шаг никуда не делся — он вернётся к вам, когда очередь дойдёт. Если добавленный + сотрудник недоступен, можно добавить ещё. {error && {error}} {delegateBlock}