+ {form.conditions.length > 1 && (
+
+
+ )}
+
+ {form.conditions.map((condition, index) => {
+ const kind = fieldKindOf(condition.fieldName);
+ const fieldNameOptions = fieldNameOptionsFor(condition.fieldName);
+ return (
+ // eslint-disable-next-line react/no-array-index-key -- строки не переупорядочиваются, только добавляются/удаляются с конца или явным индексом
+
+
+ );
+ })}
+ {sharePointFields.error &&
{texts.routeSteps.conditionFieldNameError(sharePointFields.error)}
}
+
+
+
+ )}
);
};
diff --git a/frontend/src/webparts/approvalAdmin/models/enums.ts b/frontend/src/webparts/approvalAdmin/models/enums.ts
index 40586d7..72e0272 100644
--- a/frontend/src/webparts/approvalAdmin/models/enums.ts
+++ b/frontend/src/webparts/approvalAdmin/models/enums.ts
@@ -26,7 +26,11 @@ export enum HeadType {
// Резолвится по departmentId из запроса на resolve (см. ResolveTemplateRouteQuery),
// а не по цепочке подчинённости инициатора, как остальные типы, — поэтому на
// экране «Проверка маршрута» появляется отдельное поле выбора отдела.
- DepartmentHead = 'DepartmentHead'
+ DepartmentHead = 'DepartmentHead',
+ // Единственное значение, которое резолвится в НЕСКОЛЬКО строк результата — по
+ // директору на каждую компанию инициатора (сотрудник может числиться в
+ // нескольких), все с одним и тем же orderNo (см. TemplateRouteService.ResolveStepAsync).
+ CompanyDirectors = 'CompanyDirectors'
}
export enum ComparisonOperator {
@@ -40,3 +44,9 @@ export enum ComparisonOperator {
Contains = 'Contains',
NotContains = 'NotContains'
}
+
+/** Как объединяются условия шага между собой — не действует, если условие одно или их нет. */
+export enum ConditionsLogic {
+ And = 'And',
+ Or = 'Or'
+}
diff --git a/frontend/src/webparts/approvalAdmin/models/resolve.ts b/frontend/src/webparts/approvalAdmin/models/resolve.ts
index 23cc4a1..00623c7 100644
--- a/frontend/src/webparts/approvalAdmin/models/resolve.ts
+++ b/frontend/src/webparts/approvalAdmin/models/resolve.ts
@@ -2,7 +2,7 @@
// (Application/TemplateRoutes/ResolveTemplateRouteDto.cs).
/* eslint-disable @rushstack/no-new-null -- поля 1:1 зеркалят nullable JSON-контракт бэкенда */
-import { RouteStepStatus } from './enums';
+import { HeadType, RouteStepStatus } from './enums';
/** Значение поля списка SharePoint (field_name шага) для проверки условий шагов. */
export interface ResolveFieldValue {
@@ -28,6 +28,12 @@ export interface ResolvedRouteStep {
allowReassignToOtherApprovers: boolean;
/** Причина, по которой исполнитель шага не определился (текст с бэкенда). */
warning: string | null;
+ /**
+ * HeadType шага (Chief/DirectionHead/...), если исполнитель задан типом
+ * руководителя, иначе null. В отличие от position (реальная должность
+ * резолвнутого сотрудника из справочника) — это код роли из самого шага.
+ */
+ positionCode: HeadType | null;
}
export interface ResolveTemplateRouteResult {
diff --git a/frontend/src/webparts/approvalAdmin/models/templateRouteSteps.ts b/frontend/src/webparts/approvalAdmin/models/templateRouteSteps.ts
index f9b0459..36b2e46 100644
--- a/frontend/src/webparts/approvalAdmin/models/templateRouteSteps.ts
+++ b/frontend/src/webparts/approvalAdmin/models/templateRouteSteps.ts
@@ -2,7 +2,16 @@
// (Application/TemplateRouteSteps/*.cs).
/* eslint-disable @rushstack/no-new-null -- поля 1:1 зеркалят nullable JSON-контракт бэкенда (C# nullable-типы сериализуются в null, не в отсутствие поля) */
-import { ComparisonOperator, HeadType, ProjectRole, RouteStepStatus } from './enums';
+import { ComparisonOperator, ConditionsLogic, HeadType, ProjectRole, RouteStepStatus } from './enums';
+
+/** Одно условие появления шага — см. Application/TemplateRouteSteps/RouteStepConditionDto. */
+export interface RouteStepCondition {
+ /** Столбец списка SharePoint (internal name), по которому проверяется условие. */
+ fieldName: string;
+ operation: ComparisonOperator;
+ /** Значение для сравнения с fieldName. */
+ value: string;
+}
export interface TemplateRouteStep {
id: number;
@@ -14,11 +23,10 @@ export interface TemplateRouteStep {
status: RouteStepStatus;
routeId: number;
allowReassignToOtherApprovers: boolean;
- /** Столбец списка SharePoint (internal name), по которому проверяется условие. */
- fieldName: string | null;
- operation: ComparisonOperator | null;
- /** Значение для сравнения с fieldName. */
- value: string | null;
+ /** Условия появления шага. Пусто — шаг применяется всегда. */
+ conditions: RouteStepCondition[];
+ /** Как объединяются conditions между собой — не действует, если условий меньше двух. */
+ conditionsLogic: ConditionsLogic;
/**
* SLA шага в часах — перекрывает SLA типа заявки.
* null — «не задано, наследовать»; 0 — «напоминать сразу».
@@ -38,9 +46,8 @@ export interface CreateTemplateRouteStepInput {
headType: HeadType | null;
status: RouteStepStatus;
allowReassignToOtherApprovers: boolean;
- fieldName: string | null;
- operation: ComparisonOperator | null;
- value: string | null;
+ conditions: RouteStepCondition[] | null;
+ conditionsLogic: ConditionsLogic;
slaHours: number | null;
askOverdueReason: boolean | null;
}
@@ -55,9 +62,8 @@ export interface PatchTemplateRouteStepInput {
headType?: HeadType | null;
status?: RouteStepStatus;
allowReassignToOtherApprovers?: boolean;
- fieldName?: string | null;
- operation?: ComparisonOperator | null;
- value?: string | null;
+ conditions?: RouteStepCondition[] | null;
+ conditionsLogic?: ConditionsLogic;
slaHours?: number | null;
askOverdueReason?: boolean | null;
}
diff --git a/frontend/src/webparts/approvalAdmin/texts.ts b/frontend/src/webparts/approvalAdmin/texts.ts
index 4ecda82..3792bd2 100644
--- a/frontend/src/webparts/approvalAdmin/texts.ts
+++ b/frontend/src/webparts/approvalAdmin/texts.ts
@@ -4,7 +4,7 @@
//
// Строки с подстановкой оформлены функциями, чтобы шаблон и данные не разъезжались.
-import { ComparisonOperator, HeadType, ProjectRole, RouteStepStatus } from './models';
+import { ComparisonOperator, ConditionsLogic, HeadType, ProjectRole, RouteStepStatus } from './models';
import { pluralRu } from './utils/plural';
// ---- Подписи enum'ов ----
@@ -32,7 +32,8 @@ export const headTypeLabels: Record