Склонять «согласование» → «исполнение», когда согласующих в маршруте нет
Три общих (не привязанных к одной форме) места хардкодили лексику согласования независимо от того, есть ли реально фаза согласования: - initialSubmitStatus (shared/approval) — статус заявки при ОТПРАВКЕ считался «На согласовании» безусловно, даже когда маршрут состоит только из исполнителей (0 согласующих). Теперь статус зависит от количества шагов каждой фазы: 0 согласующих + есть исполнители → сразу «Выполнение»; ни тех, ни других → «Согласовано». - ApprovalMatrix — подпись «Доп. согласующий, добавил:» у переназначенного участника была захардкожена; теперь выводится из roleLabel, поэтому у блока «Исполнение» (roleLabel="Исполнитель") корректно пишет «Доп. исполнитель». - DecisionPanel — сообщения после переназначения («Согласующий добавлен перед вами…», «сейчас рассматривает…») не различали фазу шага; теперь склоняются по isExecution. В ReferralForm — единственной форме, где маршрут реально может быть без согласующих (только исполнитель) — использованы initialSubmitStatus и локальный noApprovalPhase (matrix.rows.length === 0, подтверждено загрузкой) для подписи кнопки отправки и текста успеха. Уведомление активного шага теперь срабатывает и при статусе «Выполнение», не только «На согласовании». Блок «Маршрут согласования» остаётся видимым и пустым, даже когда шагов нет — матрица редактируема, заявитель может дописать согласующего вручную. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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<IRequestFormProps> = ({
|
||||
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<IRequestFormProps> = ({
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// Маршрута может не быть вообще (нет согласующих — только исполнитель, как у
|
||||
// рефералки): initialSubmitStatus сразу пишет «Выполнение», минуя «На
|
||||
// согласовании», которого в таком маршруте попросту не бывает.
|
||||
const handleSaveDraft = (): Promise<void> => submit.run('draft', save(REQUEST_STATUS.draft), validate);
|
||||
const handleSubmit = (): Promise<void> => submit.run('submit', save(REQUEST_STATUS.onApproval), validateSubmit);
|
||||
const handleSubmit = (): Promise<void> =>
|
||||
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<IRequestFormProps> = ({
|
||||
>
|
||||
{submit.savedKind && !submit.error && (
|
||||
<Alert variant="ok">
|
||||
{submit.savedKind === 'draft' ? 'Черновик сохранён.' : 'Заявка отправлена на согласование.'}
|
||||
{submit.savedKind === 'draft'
|
||||
? 'Черновик сохранён.'
|
||||
: noApprovalPhase
|
||||
? 'Заявка отправлена на исполнение.'
|
||||
: 'Заявка отправлена на согласование.'}
|
||||
</Alert>
|
||||
)}
|
||||
{submit.error && <Alert variant="err">{submit.error}</Alert>}
|
||||
@@ -331,13 +350,16 @@ const ReferralForm: React.FC<IRequestFormProps> = ({
|
||||
<AssessmentSection {...sectionProps} />
|
||||
</FormModeProvider>
|
||||
|
||||
{/* Блок остаётся видимым и пустым (не скрываем по noApprovalPhase) —
|
||||
маршрут без согласующих не значит, что их нельзя добавить: матрица
|
||||
редактируема, заявитель вправе вписать согласующего сам. */}
|
||||
<ApprovalMatrix editor={matrix} search={(q) => approval.searchUsers(q)} />
|
||||
|
||||
{!executors.loading && executors.rows.length > 0 && (
|
||||
<ApprovalMatrix
|
||||
editor={executors}
|
||||
search={(q) => approval.searchUsers(q)}
|
||||
title="Исполнение (после согласования)"
|
||||
title={noApprovalPhase ? 'Исполнение' : 'Исполнение (после согласования)'}
|
||||
roleLabel="Исполнитель"
|
||||
emptyText="Исполнители не заданы по маршруту."
|
||||
/>
|
||||
@@ -350,7 +372,7 @@ const ReferralForm: React.FC<IRequestFormProps> = ({
|
||||
onCancel={onCancel}
|
||||
onSaveDraft={handleSaveDraft}
|
||||
onSubmit={handleSubmit}
|
||||
submitLabel="Отправить на согласование"
|
||||
submitLabel={noApprovalPhase ? 'Отправить на исполнение' : 'Отправить на согласование'}
|
||||
/>
|
||||
</FormCard>
|
||||
</div>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 в списке «… — данные». Пишется
|
||||
|
||||
@@ -42,6 +42,11 @@ export const ApprovalMatrix: React.FC<IApprovalMatrixProps> = ({
|
||||
}) => {
|
||||
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<IApprovalMatrixProps> = ({
|
||||
{!r.who && r.warning && (
|
||||
<span className={styles.warningInline}>{r.warning}</span>
|
||||
)}
|
||||
{/* Доп. согласующий, добавленный другим согласующим (с правом
|
||||
переназначения) — помечаем, кто его добавил. Пусто у обычных
|
||||
строк маршрута. Как и комментарий — inline под именем, чтобы
|
||||
не ломать rowSpan колонки «Шаг» у параллельных шагов. */}
|
||||
{/* Доп. участник шага, добавленный переназначением — помечаем,
|
||||
кто его добавил. Пусто у обычных строк маршрута. Как и
|
||||
комментарий — inline под именем, чтобы не ломать rowSpan
|
||||
колонки «Шаг» у параллельных шагов. */}
|
||||
{r.addedBy && (
|
||||
<span className={styles.addedByInline}>
|
||||
<span className={styles.addedByLabel}>Доп. согласующий, добавил:</span>{' '}
|
||||
<span className={styles.addedByLabel}>{addedByLabel}</span>{' '}
|
||||
{r.addedBy}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -142,12 +142,18 @@ export const DecisionPanel: React.FC<IDecisionPanelProps> = ({
|
||||
}
|
||||
|
||||
if (delegated > 0) {
|
||||
// Склоняем по фазе шага: у исполнения нет «согласующих» — тот, кого
|
||||
// добавили, реально выполняет заявку, а не рассматривает её.
|
||||
const who = isExecution ? 'Исполнитель' : 'Согласующий';
|
||||
const whoMany = isExecution ? 'Исполнителей' : 'Согласующих';
|
||||
const verb = isExecution ? 'выполнит' : 'рассмотрит';
|
||||
const verbMany = isExecution ? 'выполнят' : 'рассмотрят';
|
||||
return (
|
||||
<div className={styles.panel}>
|
||||
<Alert variant="ok">
|
||||
{delegated === 1
|
||||
? 'Согласующий добавлен перед вами — он рассмотрит заявку раньше вас. Ваш шаг остаётся в маршруте.'
|
||||
: `Согласующих добавлено: ${delegated}. Они рассмотрят заявку раньше вас, в порядке добавления. ` +
|
||||
? `${who} добавлен перед вами — он ${verb} заявку раньше вас. Ваш шаг остаётся в маршруте.`
|
||||
: `${whoMany} добавлено: ${delegated}. Они ${verbMany} заявку раньше вас, в порядке добавления. ` +
|
||||
'Ваш шаг остаётся в маршруте.'}
|
||||
</Alert>
|
||||
</div>
|
||||
@@ -234,9 +240,11 @@ export const DecisionPanel: React.FC<IDecisionPanelProps> = ({
|
||||
<div className={styles.panel}>
|
||||
<div className={styles.title}>{isExecution ? 'Исполнение' : 'Ваше решение'}</div>
|
||||
<Alert variant="warn">
|
||||
Заявку сейчас рассматривает сотрудник, которого вы добавили перед собой. Ваш шаг никуда
|
||||
не делся — он вернётся к вам, когда очередь дойдёт. Если добавленный сотрудник
|
||||
недоступен, можно добавить ещё.
|
||||
{isExecution
|
||||
? 'Заявку сейчас выполняет сотрудник, которого вы добавили перед собой.'
|
||||
: 'Заявку сейчас рассматривает сотрудник, которого вы добавили перед собой.'}{' '}
|
||||
Ваш шаг никуда не делся — он вернётся к вам, когда очередь дойдёт. Если добавленный
|
||||
сотрудник недоступен, можно добавить ещё.
|
||||
</Alert>
|
||||
{error && <Alert variant="err">{error}</Alert>}
|
||||
{delegateBlock}
|
||||
|
||||
Reference in New Issue
Block a user