Compare commits
2 Commits
80d8790ae1
...
ec5c2a8d86
| Author | SHA1 | Date | |
|---|---|---|---|
| ec5c2a8d86 | |||
| 4825443a9d |
@@ -23,7 +23,7 @@ import {
|
||||
isEditableStatus,
|
||||
IApproverToWrite,
|
||||
APPROVAL_PHASE,
|
||||
findUnassignedStep
|
||||
unassignedStepMessage
|
||||
} from '../../shared/approval';
|
||||
import { IRequestFormProps } from '../../models/types';
|
||||
import {
|
||||
@@ -236,14 +236,16 @@ const ClearanceForm: React.FC<IRequestFormProps> = ({
|
||||
if (matrix.error) {
|
||||
return `Не удалось определить маршрут согласования: ${matrix.error}. Попробуйте ещё раз или обратитесь к администратору.`;
|
||||
}
|
||||
const unassigned = findUnassignedStep(matrix.rows);
|
||||
if (unassigned) {
|
||||
return `Шаг ${unassigned.order} маршрута согласования («${unassigned.position || 'без должности'}») без назначенного человека — выберите согласующего или удалите шаг.`;
|
||||
}
|
||||
const unassignedExecutor = findUnassignedStep(executors.rows);
|
||||
if (unassignedExecutor) {
|
||||
return `Шаг ${unassignedExecutor.order} исполнения («${unassignedExecutor.position || 'без должности'}») без назначенного человека — выберите исполнителя или удалите шаг.`;
|
||||
}
|
||||
const unassignedApproverError = unassignedStepMessage(
|
||||
matrix.rows,
|
||||
(r) => `Шаг ${r.order} маршрута согласования («${r.position || 'без должности'}») без назначенного человека — выберите согласующего или удалите шаг.`
|
||||
);
|
||||
if (unassignedApproverError) return unassignedApproverError;
|
||||
const unassignedExecutorError = unassignedStepMessage(
|
||||
executors.rows,
|
||||
(r) => `Шаг ${r.order} исполнения («${r.position || 'без должности'}») без назначенного человека — выберите исполнителя или удалите шаг.`
|
||||
);
|
||||
if (unassignedExecutorError) return unassignedExecutorError;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
isEditableStatus,
|
||||
IApproverToWrite,
|
||||
APPROVAL_PHASE,
|
||||
findUnassignedStep
|
||||
unassignedStepMessage
|
||||
} from '../../shared/approval';
|
||||
import {
|
||||
uploadAttachments,
|
||||
@@ -237,14 +237,16 @@ const RecruitingHireForm: React.FC<IRequestFormProps> = ({
|
||||
const validateSubmit = (): string | undefined => {
|
||||
const formError = validate();
|
||||
if (formError) return formError;
|
||||
const unassigned = findUnassignedStep(matrix.rows);
|
||||
if (unassigned) {
|
||||
return `Шаг ${unassigned.order} маршрута согласования («${unassigned.position || 'без должности'}») без назначенного человека — выберите согласующего или удалите шаг.`;
|
||||
}
|
||||
const unassignedExecutor = findUnassignedStep(executors.rows);
|
||||
if (unassignedExecutor) {
|
||||
return `Шаг ${unassignedExecutor.order} исполнения («${unassignedExecutor.position || 'без должности'}») без назначенного человека — выберите исполнителя или удалите шаг.`;
|
||||
}
|
||||
const unassignedApproverError = unassignedStepMessage(
|
||||
matrix.rows,
|
||||
(r) => `Шаг ${r.order} маршрута согласования («${r.position || 'без должности'}») без назначенного человека — выберите согласующего или удалите шаг.`
|
||||
);
|
||||
if (unassignedApproverError) return unassignedApproverError;
|
||||
const unassignedExecutorError = unassignedStepMessage(
|
||||
executors.rows,
|
||||
(r) => `Шаг ${r.order} исполнения («${r.position || 'без должности'}») без назначенного человека — выберите исполнителя или удалите шаг.`
|
||||
);
|
||||
if (unassignedExecutorError) return unassignedExecutorError;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
REQUEST_STATUS,
|
||||
isEditableStatus,
|
||||
IApproverToWrite,
|
||||
findUnassignedStep
|
||||
unassignedStepMessage
|
||||
} from '../../shared/approval';
|
||||
import {
|
||||
uploadAttachments,
|
||||
@@ -211,10 +211,11 @@ const RecruitingSelectionForm: React.FC<IRequestFormProps> = ({
|
||||
if (matrix.error) {
|
||||
return `Не удалось определить маршрут согласования: ${matrix.error}. Попробуйте ещё раз или обратитесь к администратору.`;
|
||||
}
|
||||
const unassigned = findUnassignedStep(matrix.rows);
|
||||
if (unassigned) {
|
||||
return `Шаг ${unassigned.order} маршрута согласования («${unassigned.position || 'без должности'}») без назначенного человека — выберите согласующего или удалите шаг.`;
|
||||
}
|
||||
const unassignedError = unassignedStepMessage(
|
||||
matrix.rows,
|
||||
(r) => `Шаг ${r.order} маршрута согласования («${r.position || 'без должности'}») без назначенного человека — выберите согласующего или удалите шаг.`
|
||||
);
|
||||
if (unassignedError) return unassignedError;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
REQUEST_STATUS,
|
||||
isEditableStatus,
|
||||
IApproverToWrite,
|
||||
findUnassignedStep
|
||||
unassignedStepMessage
|
||||
} from '../../shared/approval';
|
||||
import {
|
||||
uploadAttachments,
|
||||
@@ -296,10 +296,11 @@ const Ticket1CForm: React.FC<IRequestFormProps> = ({
|
||||
// Матрица нередактируема (маршрут задаётся видом работ, а не заявителем) —
|
||||
// незаполненный шаг заявитель исправить сам не может, только администратор
|
||||
// маршрутов (роль в шаге не разрешилась в конкретного человека).
|
||||
const unassigned = findUnassignedStep(matrix.rows);
|
||||
if (unassigned) {
|
||||
return `Шаг ${unassigned.order} маршрута согласования («${unassigned.position || 'без должности'}») не назначен на конкретного человека. Обратитесь к администратору маршрутов согласования.`;
|
||||
}
|
||||
const unassignedError = unassignedStepMessage(
|
||||
matrix.rows,
|
||||
(r) => `Шаг ${r.order} маршрута согласования («${r.position || 'без должности'}») не назначен на конкретного человека. Обратитесь к администратору маршрутов согласования.`
|
||||
);
|
||||
if (unassignedError) return unassignedError;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import {
|
||||
isEditableStatus,
|
||||
IApproverToWrite,
|
||||
APPROVAL_PHASE,
|
||||
findUnassignedStep,
|
||||
unassignedStepMessage,
|
||||
resolveWriteListId
|
||||
} from '../../shared/approval';
|
||||
import {
|
||||
@@ -245,14 +245,16 @@ const TmcTransferForm: React.FC<IRequestFormProps> = ({
|
||||
const validateSubmit = (): string | undefined => {
|
||||
const formError = validate();
|
||||
if (formError) return formError;
|
||||
const unassigned = findUnassignedStep(matrix.rows);
|
||||
if (unassigned) {
|
||||
return `Шаг ${unassigned.order} маршрута согласования («${unassigned.position || 'без должности'}») без назначенного человека — выберите согласующего или удалите шаг.`;
|
||||
}
|
||||
const unassignedExecutor = findUnassignedStep(executors.rows);
|
||||
if (unassignedExecutor) {
|
||||
return `Шаг ${unassignedExecutor.order} исполнения («${unassignedExecutor.position || 'без должности'}») без назначенного человека — выберите исполнителя или удалите шаг.`;
|
||||
}
|
||||
const unassignedApproverError = unassignedStepMessage(
|
||||
matrix.rows,
|
||||
(r) => `Шаг ${r.order} маршрута согласования («${r.position || 'без должности'}») без назначенного человека — выберите согласующего или удалите шаг.`
|
||||
);
|
||||
if (unassignedApproverError) return unassignedApproverError;
|
||||
const unassignedExecutorError = unassignedStepMessage(
|
||||
executors.rows,
|
||||
(r) => `Шаг ${r.order} исполнения («${r.position || 'без должности'}») без назначенного человека — выберите исполнителя или удалите шаг.`
|
||||
);
|
||||
if (unassignedExecutorError) return unassignedExecutorError;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
REQUEST_STATUS,
|
||||
isEditableStatus,
|
||||
IApproverToWrite,
|
||||
findUnassignedStep,
|
||||
unassignedStepMessage,
|
||||
resolveWriteListId
|
||||
} from '../../shared/approval';
|
||||
import { IRequestFormProps } from '../../models/types';
|
||||
@@ -217,10 +217,11 @@ const UnpaidLeaveForm: React.FC<IRequestFormProps> = ({
|
||||
if (matrix.error) {
|
||||
return `Не удалось определить маршрут согласования: ${matrix.error}. Попробуйте ещё раз или обратитесь к администратору.`;
|
||||
}
|
||||
const unassigned = findUnassignedStep(matrix.rows);
|
||||
if (unassigned) {
|
||||
return `Шаг ${unassigned.order} маршрута согласования («${unassigned.position || 'без должности'}») без назначенного человека — выберите согласующего или удалите шаг.`;
|
||||
}
|
||||
const unassignedError = unassignedStepMessage(
|
||||
matrix.rows,
|
||||
(r) => `Шаг ${r.order} маршрута согласования («${r.position || 'без должности'}») без назначенного человека — выберите согласующего или удалите шаг.`
|
||||
);
|
||||
if (unassignedError) return unassignedError;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
REQUEST_STATUS,
|
||||
isEditableStatus,
|
||||
IApproverToWrite,
|
||||
findUnassignedStep
|
||||
unassignedStepMessage
|
||||
} from '../../shared/approval';
|
||||
import { IRequestFormProps } from '../../models/types';
|
||||
import {
|
||||
@@ -215,10 +215,11 @@ const VacationForm: React.FC<IRequestFormProps> = ({
|
||||
if (matrix.error) {
|
||||
return `Не удалось определить маршрут согласования: ${matrix.error}. Попробуйте ещё раз или обратитесь к администратору.`;
|
||||
}
|
||||
const unassigned = findUnassignedStep(matrix.rows);
|
||||
if (unassigned) {
|
||||
return `Шаг ${unassigned.order} маршрута согласования («${unassigned.position || 'без должности'}») без назначенного человека — выберите согласующего или удалите шаг.`;
|
||||
}
|
||||
const unassignedError = unassignedStepMessage(
|
||||
matrix.rows,
|
||||
(r) => `Шаг ${r.order} маршрута согласования («${r.position || 'без должности'}») без назначенного человека — выберите согласующего или удалите шаг.`
|
||||
);
|
||||
if (unassignedError) return unassignedError;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
isEditableStatus,
|
||||
IApproverToWrite,
|
||||
APPROVAL_PHASE,
|
||||
findUnassignedStep
|
||||
unassignedStepMessage
|
||||
} from '../../shared/approval';
|
||||
import {
|
||||
uploadAttachments,
|
||||
@@ -262,14 +262,16 @@ const WorkplaceSetupForm: React.FC<IRequestFormProps> = ({
|
||||
const validateSubmit = (): string | undefined => {
|
||||
const formError = validate();
|
||||
if (formError) return formError;
|
||||
const unassigned = findUnassignedStep(matrix.rows);
|
||||
if (unassigned) {
|
||||
return `Шаг ${unassigned.order} маршрута согласования («${unassigned.position || 'без должности'}») без назначенного человека — выберите согласующего или удалите шаг.`;
|
||||
}
|
||||
const unassignedExecutor = findUnassignedStep(executors.rows);
|
||||
if (unassignedExecutor) {
|
||||
return `Шаг ${unassignedExecutor.order} исполнения («${unassignedExecutor.position || 'без должности'}») без назначенного человека — выберите исполнителя или удалите шаг.`;
|
||||
}
|
||||
const unassignedApproverError = unassignedStepMessage(
|
||||
matrix.rows,
|
||||
(r) => `Шаг ${r.order} маршрута согласования («${r.position || 'без должности'}») без назначенного человека — выберите согласующего или удалите шаг.`
|
||||
);
|
||||
if (unassignedApproverError) return unassignedApproverError;
|
||||
const unassignedExecutorError = unassignedStepMessage(
|
||||
executors.rows,
|
||||
(r) => `Шаг ${r.order} исполнения («${r.position || 'без должности'}») без назначенного человека — выберите исполнителя или удалите шаг.`
|
||||
);
|
||||
if (unassignedExecutorError) return unassignedExecutorError;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
||||
@@ -38,6 +38,7 @@ export async function getPlannedMatrix(
|
||||
phase,
|
||||
slaHours: s.slaHours,
|
||||
askOverdueReason: s.askOverdueReason,
|
||||
warning: s.warning,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ export type { ApproverDecision, IPendingRow } from './approverDecision.service';
|
||||
export { getPlannedMatrix, writeApprovers, writeRoute } from './approvalSubmission.service';
|
||||
export { notifyActiveStep } from './notify.service';
|
||||
export type { INotifyTarget } from './notify.service';
|
||||
export { useMatrixEditor, findUnassignedStep } from './useMatrixEditor';
|
||||
export { useMatrixEditor, findUnassignedStep, unassignedStepMessage } from './useMatrixEditor';
|
||||
export type { IUseMatrixEditor } from './useMatrixEditor';
|
||||
export { ApprovalMatrix } from './ui/ApprovalMatrix';
|
||||
export { UserPicker } from './ui/UserPicker';
|
||||
|
||||
@@ -31,4 +31,16 @@ export interface IMatrixRow {
|
||||
slaHours?: number | null;
|
||||
// eslint-disable-next-line @rushstack/no-new-null -- см. slaHours
|
||||
askOverdueReason?: boolean | null;
|
||||
/**
|
||||
* Предупреждение approval-route по ЭТОМУ шагу (см. IResolvedRouteStep.warning) —
|
||||
* например, роль не разрешилась в конкретного человека, и почему именно.
|
||||
* Только у ПЛАНИРУЕМОГО маршрута (см. getPlannedMatrix): у уже записанных строк
|
||||
* (loadXxxRequest) неоткуда взяться — резолв запускался один раз при подаче.
|
||||
*
|
||||
* findUnassignedStep показывает его вместо своего общего текста, когда он есть —
|
||||
* бэкенд знает причину точнее («роль не назначена в справочнике отдела» и т.п.),
|
||||
* а не просто «нет назначенного человека».
|
||||
*/
|
||||
// eslint-disable-next-line @rushstack/no-new-null -- зеркалит nullable-контракт бэкенда (IResolvedRouteStep.warning)
|
||||
warning?: string | null;
|
||||
}
|
||||
|
||||
@@ -144,6 +144,17 @@ export const ApprovalMatrix: React.FC<IApprovalMatrixProps> = ({
|
||||
)}
|
||||
<td data-label={roleLabel}>
|
||||
{r.who}
|
||||
{/* Причина, по которой approval-route не назначил на этот шаг
|
||||
конкретного человека (роль не разрешилась и т.п., см.
|
||||
IResolvedRouteStep.warning/IMatrixRow.warning) — та же строка,
|
||||
что уходит в ошибку валидации при отправке (см.
|
||||
unassignedStepMessage), но видна СРАЗУ в таблице, а не только
|
||||
на попытке отправить: пустая строка «Согласующий» сама по себе
|
||||
не объясняет, что делать — вручную выбрать человека здесь
|
||||
нельзя, шаг заведомо требует правки маршрута в админке. */}
|
||||
{!r.who && r.warning && (
|
||||
<span className={styles.warningInline}>{r.warning}</span>
|
||||
)}
|
||||
{/* Доп. согласующий, добавленный другим согласующим (с правом
|
||||
переназначения) — помечаем, кто его добавил. Пусто у обычных
|
||||
строк маршрута. Как и комментарий — inline под именем, чтобы
|
||||
|
||||
@@ -169,6 +169,19 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
// Причина, по которой approval-route не назначил человека на шаг (см.
|
||||
// ApprovalMatrix.tsx/r.warning) — красным, как настоящая проблема маршрута
|
||||
// (в отличие от нейтрального addedByInline/commentInline): пустая строка
|
||||
// «Согласующий» без этого текста выглядит как баг, а не как «маршрут просит
|
||||
// вмешательства администратора».
|
||||
.warningInline {
|
||||
display: block;
|
||||
margin-top: 3px;
|
||||
font-size: $fs-caption;
|
||||
color: $red-500;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
// ВАЖНО: не display:flex — это сняло бы с ячейки display:table-cell, и она
|
||||
// перестала бы вставать в строку рядом с соседней (кнопки уезжали на вторую
|
||||
// строку). Ячейка остаётся табличной, кнопки держатся в строке через nowrap.
|
||||
|
||||
@@ -118,6 +118,25 @@ export function findUnassignedStep(rows: IMatrixRow[]): IMatrixRow | undefined {
|
||||
return rows.filter((r) => !r.email).sort((a, b) => a.order - b.order)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Текст ошибки под первый незанятый шаг — то, что раньше было скопировано в
|
||||
* validateSubmit каждой формы (findUnassignedStep + `row.warning || <шаблон>`).
|
||||
* fallback принимает саму строку и решает, что писать, если approval-route не
|
||||
* прислал свою причину (warning) — у большинства форм это общий шаблон «без
|
||||
* назначенного человека — выберите …», но у некоторых (см. Ticket1CForm) текст
|
||||
* другой, поэтому шаблон не зашит сюда, а передаётся вызывающей стороной.
|
||||
*
|
||||
* undefined — назначенных не хватает не для этих rows (или все шаги заняты).
|
||||
*/
|
||||
export function unassignedStepMessage(
|
||||
rows: IMatrixRow[],
|
||||
fallback: (row: IMatrixRow) => string
|
||||
): string | undefined {
|
||||
const row = findUnassignedStep(rows);
|
||||
if (!row) return undefined;
|
||||
return row.warning || fallback(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Загружает матрицу согласования и (если editable) позволяет её редактировать.
|
||||
* @param load загрузчик строк (планируемый маршрут из resolve или строки заявки).
|
||||
|
||||
Reference in New Issue
Block a user