using FluentResults; using Microsoft.EntityFrameworkCore; using Tnsu.ApprovalRoute.Application.Common.Errors; using Tnsu.ApprovalRoute.Domain.Entities; using Tnsu.ApprovalRoute.Domain.Enums; using Tnsu.ApprovalRoute.Infrastructure.Persistence; namespace Tnsu.ApprovalRoute.Application.TemplateRouteSteps; public sealed class TemplateRouteStepService(ApprovalRouteDbContext db) { public async Task>> ListAsync(Guid routeId, CancellationToken cancellationToken) { var items = await db.TemplateRouteSteps.AsNoTracking() .Where(x => x.RouteId == routeId) .OrderBy(x => x.OrderNo) .Select(x => new TemplateRouteStepDto( x.Id, x.OrderNo, x.ExecutorEmail, x.Position, x.ProjectRole, x.HeadType, x.Status, x.RouteId, x.AllowReassignToOtherApprovers)) .ToListAsync(cancellationToken); return Result.Ok>(items); } public async Task> GetAsync(Guid routeId, Guid id, CancellationToken cancellationToken) { var entity = await db.TemplateRouteSteps.AsNoTracking() .FirstOrDefaultAsync(x => x.Id == id && x.RouteId == routeId, cancellationToken); return entity is null ? Result.Fail(new NotFoundError("Шаг маршрута", id)) : Result.Ok(ToDto(entity)); } public async Task> CreateAsync(Guid routeId, CreateTemplateRouteStepRequest r, CancellationToken cancellationToken) { var route = await db.TemplateRoutes.FirstOrDefaultAsync(x => x.Id == routeId, cancellationToken); if (route is null) return Result.Fail(new NotFoundError("Маршрут", routeId)); var fieldsError = ValidateFields(r.ExecutorEmail, r.Position, r.ProjectRole, route.ByProject); if (fieldsError is not null) return Result.Fail(fieldsError); var entity = new TemplateRouteStep { RouteId = route.Id, OrderNo = r.OrderNo, ExecutorEmail = r.ExecutorEmail, Position = r.Position, ProjectRole = r.ProjectRole, HeadType = r.HeadType, Status = r.Status, AllowReassignToOtherApprovers = r.AllowReassignToOtherApprovers }; db.TemplateRouteSteps.Add(entity); await db.SaveChangesAsync(cancellationToken); var result = Result.Ok(ToDto(entity)); return await AppendMissingProjectRoleWarningAsync(route, result, cancellationToken); } public async Task> UpdateAsync( Guid routeId, Guid id, UpdateTemplateRouteStepRequest r, CancellationToken cancellationToken) { var route = await db.TemplateRoutes.FirstOrDefaultAsync(x => x.Id == routeId, cancellationToken); if (route is null) return Result.Fail(new NotFoundError("Маршрут", routeId)); var entity = await db.TemplateRouteSteps.FirstOrDefaultAsync(x => x.Id == id && x.RouteId == routeId, cancellationToken); if (entity is null) return Result.Fail(new NotFoundError("Шаг маршрута", id)); var fieldsError = ValidateFields(r.ExecutorEmail, r.Position, r.ProjectRole, route.ByProject); if (fieldsError is not null) return Result.Fail(fieldsError); entity.OrderNo = r.OrderNo; entity.ExecutorEmail = r.ExecutorEmail; entity.Position = r.Position; entity.ProjectRole = r.ProjectRole; entity.HeadType = r.HeadType; entity.Status = r.Status; entity.AllowReassignToOtherApprovers = r.AllowReassignToOtherApprovers; await db.SaveChangesAsync(cancellationToken); var result = Result.Ok(ToDto(entity)); return await AppendMissingProjectRoleWarningAsync(route, result, cancellationToken); } public async Task DeleteAsync(Guid routeId, Guid id, CancellationToken cancellationToken) { var entity = await db.TemplateRouteSteps.FirstOrDefaultAsync(x => x.Id == id && x.RouteId == routeId, cancellationToken); if (entity is null) return Result.Fail(new NotFoundError("Шаг маршрута", id)); db.TemplateRouteSteps.Remove(entity); await db.SaveChangesAsync(cancellationToken); return Result.Ok(); } private static IError? ValidateFields(string executorEmail, string position, ProjectRole projectRole, bool routeByProject) { if (string.IsNullOrWhiteSpace(executorEmail) && string.IsNullOrWhiteSpace(position) && projectRole == ProjectRole.None) return new ValidationError("Исполнитель, должность и роль в проекте не могут быть пустыми одновременно."); if (!routeByProject && projectRole != ProjectRole.None) return new ValidationError("Роль в проекте недопустима для шага маршрута, который не учитывает проект (by_project = false)."); return null; } private async Task> AppendMissingProjectRoleWarningAsync( TemplateRoute route, Result result, CancellationToken cancellationToken) { if (!route.ByProject) return result; var hasProjectRoleStep = await db.TemplateRouteSteps .AnyAsync(x => x.RouteId == route.Id && x.ProjectRole != ProjectRole.None, cancellationToken); return hasProjectRoleStep ? result : result.WithReason(new Warning("Должен быть хотя бы один шаг с ролью в проекте.")); } private static TemplateRouteStepDto ToDto(TemplateRouteStep e) => new( e.Id, e.OrderNo, e.ExecutorEmail, e.Position, e.ProjectRole, e.HeadType, e.Status, e.RouteId, e.AllowReassignToOtherApprovers); }