паралельный последовательный удалены

This commit is contained in:
gorbenko.pavel
2026-07-31 14:38:31 +05:00
parent d38962754d
commit de60fd0fdb
19 changed files with 48 additions and 115 deletions
@@ -3,7 +3,7 @@ import styles from './ResolveScreen.module.scss';
import { ApprovalRouteApi } from '../../services/api';
import { PortalDictionaryClient } from '../../services/portalDictionary';
import { Project, RouteStepStatus } from '../../models';
import { routeStepStatusLabels, routeTypeLabels, texts } from '../../texts';
import { routeStepStatusLabels, texts } from '../../texts';
import { useRequestTypes } from '../../hooks/useRequestTypes';
import { useResolveRoute } from '../../hooks/useResolveRoute';
import { usePortalEmployees } from '../../hooks/usePortalEmployees';
@@ -137,7 +137,7 @@ export const ResolveScreen: React.FC<IResolveScreenProps> = ({ api, dictionary,
{result && (
<>
<Alert kind="ok">
{texts.resolve.summary(result.routeId, routeTypeLabels[result.routeType], result.requiresControl)}
{texts.resolve.summary(result.routeId, result.requiresControl)}
</Alert>
{unresolvedCount > 0 && <Alert kind="warn">{texts.resolve.unresolved(unresolvedCount)}</Alert>}
<DataTable
@@ -1,6 +1,6 @@
import * as React from 'react';
import { Company, Department, RequestType, RouteType, TemplateRoute } from '../../models';
import { routeTypeLabels, texts } from '../../texts';
import { Company, Department, RequestType, TemplateRoute } from '../../models';
import { texts } from '../../texts';
import { useAsyncSubmit } from '../../hooks/useAsyncSubmit';
import { ApprovalRouteApi } from '../../services/api';
import { Checkbox, Field, FormGrid, FormModal, SearchableSelect, Select } from '../ui';
@@ -21,22 +21,16 @@ export interface IRouteFormModalProps {
interface IFormState {
requestTypeId: string;
routeType: RouteType;
companyId: string;
departmentId: string;
byProject: boolean;
requiresControl: boolean;
}
const ROUTE_TYPE_OPTIONS = Object.values(RouteType)
.filter(v => v !== RouteType.None)
.map(v => ({ value: v, label: routeTypeLabels[v] }));
function toFormState(editing: TemplateRoute | undefined, initialRequestTypeId: string): IFormState {
if (!editing) {
return {
requestTypeId: initialRequestTypeId,
routeType: RouteType.Sequential,
companyId: '',
departmentId: '',
byProject: false,
@@ -45,7 +39,6 @@ function toFormState(editing: TemplateRoute | undefined, initialRequestTypeId: s
}
return {
requestTypeId: String(editing.requestTypeId),
routeType: editing.routeType,
companyId: editing.companyId,
departmentId: editing.departmentId ?? '',
byProject: editing.byProject,
@@ -88,14 +81,12 @@ export const RouteFormModal: React.FC<IRouteFormModalProps> = ({
const departmentId = form.departmentId.trim() ? form.departmentId.trim() : null;
const request = editing
? api.templateRoutes.update(editing.id, {
routeType: form.routeType,
companyId: form.companyId.trim(),
departmentId,
byProject: form.byProject,
requiresControl: form.requiresControl
})
: api.templateRoutes.create({
routeType: form.routeType,
requestTypeId: Number(form.requestTypeId),
companyId: form.companyId.trim(),
departmentId,
@@ -129,13 +120,6 @@ export const RouteFormModal: React.FC<IRouteFormModalProps> = ({
onChange={e => setForm({ ...form, requestTypeId: e.target.value })}
/>
</Field>
<Field label={texts.routes.routeTypeLabel} required full>
<Select
options={ROUTE_TYPE_OPTIONS}
value={form.routeType}
onChange={e => setForm({ ...form, routeType: e.target.value as RouteType })}
/>
</Field>
<Field label={texts.routes.companyLabel} required full hint={texts.routes.companyHint}>
<SearchableSelect
options={companies.map(c => ({ value: c.id, label: c.name }))}
@@ -4,7 +4,7 @@ import { ApprovalRouteApi } from '../../services/api';
import { PortalDictionaryClient } from '../../services/portalDictionary';
import { SharePointListsClient } from '../../services/sharePointLists';
import { RequestType, RouteStepStatus, TemplateRoute, TemplateRouteStep } from '../../models';
import { routeStepStatusLabels, routeTypeLabels, texts } from '../../texts';
import { routeStepStatusLabels, texts } from '../../texts';
import { describeStepCondition, describeStepExecutor } from '../../utils/stepExecutor';
import { useRouteSteps } from '../../hooks/useRouteSteps';
import { useDeleteTarget } from '../../hooks/useDeleteTarget';
@@ -76,7 +76,7 @@ export const RouteStepsScreen: React.FC<IRouteStepsScreenProps> = ({ api, route,
/>
<PageHeader
icon="flag-check"
subtitle={texts.routeSteps.subtitle(requestTypeName, routeTypeLabels[route.routeType])}
subtitle={texts.routeSteps.subtitle(requestTypeName)}
title={texts.routeSteps.title(route.id)}
actions={
<>
@@ -3,8 +3,8 @@ import styles from './RoutesScreen.module.scss';
import { RouteFormModal } from './RouteFormModal';
import { ApprovalRouteApi } from '../../services/api';
import { PortalDictionaryClient } from '../../services/portalDictionary';
import { RouteType, TemplateRoute } from '../../models';
import { routeTypeLabels, texts } from '../../texts';
import { TemplateRoute } from '../../models';
import { texts } from '../../texts';
import { shortGuid } from '../../utils/guid';
import { useRequestTypes } from '../../hooks/useRequestTypes';
import { useTemplateRoutes } from '../../hooks/useTemplateRoutes';
@@ -92,11 +92,6 @@ export const RoutesScreen: React.FC<IRoutesScreenProps> = ({ api, dictionary, on
header: texts.routes.columnRequestType,
render: r => <PrimaryCell>{requestTypeName(r.requestTypeId)}</PrimaryCell>
},
{
key: 'routeType',
header: texts.routes.columnRouteType,
render: r => <Badge variant={r.routeType === RouteType.Parallel ? 'blue' : 'gold'}>{routeTypeLabels[r.routeType]}</Badge>
},
{ key: 'company', header: texts.routes.columnCompany, render: r => companyName(r.companyId) },
{ key: 'department', header: texts.routes.columnDepartment, render: r => (r.departmentId ? departmentName(r.departmentId) : texts.common.empty) },
{
@@ -2,12 +2,6 @@
// Значения должны 1:1 соответствовать C#-стороне: они ходят по сети как строки.
// Человекочитаемые подписи к этим значениям живут не здесь, а в texts.ts.
export enum RouteType {
None = 'None',
Parallel = 'Parallel',
Sequential = 'Sequential'
}
export enum RouteStepStatus {
None = 'None',
Approval = 'Approval',
@@ -1,7 +1,7 @@
// DTO эндпоинта api/template-routes/resolve
// (Application/TemplateRoutes/ResolveTemplateRouteDto.cs).
import { RouteStepStatus, RouteType } from './enums';
import { RouteStepStatus } from './enums';
export interface ResolveTemplateRouteQuery {
requestTypeId: number;
@@ -22,7 +22,6 @@ export interface ResolvedRouteStep {
export interface ResolveTemplateRouteResult {
routeId: number;
routeType: RouteType;
requiresControl: boolean;
steps: ResolvedRouteStep[];
}
@@ -1,10 +1,7 @@
// DTO эндпоинта api/template-routes (Application/TemplateRoutes/*.cs).
import { RouteType } from './enums';
export interface TemplateRoute {
id: number;
routeType: RouteType;
requestTypeId: number;
departmentId: string | null;
companyId: string;
@@ -13,7 +10,6 @@ export interface TemplateRoute {
}
export interface CreateTemplateRouteInput {
routeType: RouteType;
requestTypeId: number;
departmentId: string | null;
companyId: string;
@@ -23,7 +19,6 @@ export interface CreateTemplateRouteInput {
// Тип заявки у существующего маршрута не меняется — его нет в update-контракте.
export interface UpdateTemplateRouteInput {
routeType: RouteType;
departmentId: string | null;
companyId: string;
byProject: boolean;
@@ -31,7 +26,6 @@ export interface UpdateTemplateRouteInput {
}
export interface PatchTemplateRouteInput {
routeType?: RouteType;
departmentId?: string | null;
companyId?: string;
byProject?: boolean;
+4 -13
View File
@@ -4,19 +4,13 @@
//
// Строки с подстановкой оформлены функциями, чтобы шаблон и данные не разъезжались.
import { ComparisonOperator, HeadType, ProjectRole, RouteStepStatus, RouteType } from './models';
import { ComparisonOperator, HeadType, ProjectRole, RouteStepStatus } from './models';
import { pluralRu } from './utils/plural';
// ---- Подписи enum'ов ----
// Record<enum, string> гарантирует, что при добавлении значения в enum
// компилятор потребует и подпись к нему.
export const routeTypeLabels: Record<RouteType, string> = {
[RouteType.None]: 'Не задан',
[RouteType.Parallel]: 'Параллельный',
[RouteType.Sequential]: 'Последовательный'
};
export const routeStepStatusLabels: Record<RouteStepStatus, string> = {
[RouteStepStatus.None]: 'Не задан',
[RouteStepStatus.Approval]: 'Согласование',
@@ -149,7 +143,6 @@ export const texts = {
dictionaryFailed: (message: string): string => `Справочник компаний и отделов недоступен: ${message}`,
emptyList: 'Маршрутов пока нет.',
columnRequestType: 'Тип заявки',
columnRouteType: 'Тип маршрута',
columnCompany: 'Компания',
columnDepartment: 'Отдел',
columnByProject: 'По проекту',
@@ -160,7 +153,6 @@ export const texts = {
editTitle: (routeId: number): string => `Маршрут #${routeId}`,
requestTypeLabel: 'Тип заявки',
requestTypePlaceholder: 'Выберите тип заявки',
routeTypeLabel: 'Тип маршрута',
companyLabel: 'Компания',
companyHint: 'Справочник 1С (api.tnsu.kz).',
companyPlaceholder: 'Выберите компанию',
@@ -185,8 +177,7 @@ export const texts = {
routeSteps: {
breadcrumb: (routeId: number): string => `Шаги маршрута #${routeId}`,
title: (routeId: number): string => `Маршрут #${routeId}`,
subtitle: (requestTypeName: string, routeTypeLabel: string): string =>
`${requestTypeName} · ${routeTypeLabel}`,
subtitle: (requestTypeName: string): string => requestTypeName,
back: 'К маршрутам',
add: 'Добавить шаг',
emptyList: 'Шагов пока нет — добавьте первый.',
@@ -260,8 +251,8 @@ export const texts = {
run: 'Проверить',
running: 'Проверка...',
validationRequestType: 'Выберите тип заявки.',
summary: (routeId: number, routeTypeLabel: string, requiresControl: boolean): string =>
`Маршрут #${routeId} · ${routeTypeLabel}${requiresControl ? ' · требуется контроль исполнения' : ''}`,
summary: (routeId: number, requiresControl: boolean): string =>
`Маршрут #${routeId}${requiresControl ? ' · требуется контроль исполнения' : ''}`,
unresolved: (count: number): string =>
`Исполнитель не определён: ${count} ${pluralRu(count, ['шаг', 'шага', 'шагов'])} — причина указана в строке.`,
positionMissing: 'Должность не указана в карточке сотрудника.',