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)); if (!route.ByProject && r.ProjectRole != ProjectRole.None) return Result.Fail(new ValidationError( "Роль в проекте недопустима для шага маршрута, который не учитывает проект (by_project = false).")); 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)); if (!route.ByProject && r.ProjectRole != ProjectRole.None) return Result.Fail(new ValidationError( "Роль в проекте недопустима для шага маршрута, который не учитывает проект (by_project = false).")); 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 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); }