using PostFundManagement.Application.Shared; using PostFundManagement.Domain; using PostFundManagement.Domain.Abstractions; using PostFundManagement.Domain.Extensions; using PostFundManagement.Domain.Models; using PostFundManagement.Infrastructure.Database; namespace PostFundManagement.Application.Services.Projects; public sealed class ProjectService(IDbContextFactory contextFactory, HashService hashService) : IService { public async ValueTask UpdateProjectStatusAsync(UpdateProjectStatusRequest request, Guid userId, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var project = await context.Projects.FirstOrDefaultAsync(p => p.Id == request.ProjectId, cancellationToken); if (project == null) return Result.Fail("Project does not exist."); project.Status = request.Status; project.UpdatedBy = userId; project.UpdatedAt = DateTime.UtcNow; context.Projects.Update(project); var changedTotal = await context.Projects .Where(p => p.Id != request.ProjectId) .ExecuteUpdateAsync(f => f .SetProperty(p => p.Status, request.Status) .SetProperty(p => p.UpdatedBy, userId) .SetProperty(p => p.UpdatedAt, DateTime.UtcNow), cancellationToken); return changedTotal > 0 ? Result.Ok() : Result.Fail("Failed to update project"); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetProjectsAsync(Pagination pagination, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var projects = await context.Projects.AsNoTracking().OrderByDescending(p => p.CreatedAt) .Skip((pagination.Page - 1) * pagination.PageSize) .Take(pagination.PageSize) .ToListAsync(cancellationToken); return Result.Ok(projects?.Select(p => p.Map()).ToArray() ?? []); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetProjectByIdAsync(string projectIdHash, CancellationToken cancellationToken = default) { try { var decodeResult = hashService.DecodeLongIdHash(projectIdHash); if (decodeResult.IsFailed) return Result.Fail(decodeResult.Errors); using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var project = await context.Projects.AsNoTracking().FirstOrDefaultAsync(p => p.Id == decodeResult.Value, cancellationToken); return project is not null ? Result.Ok(project.Map()) : Result.Fail("Failed to get specific project."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetActivitiesAsync(string projectIdHash, CancellationToken cancellationToken = default) { try { var decodeResult = hashService.DecodeLongIdHash(projectIdHash); if (decodeResult.IsFailed) return Result.Fail(decodeResult.Errors); using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var activities = await context.Activities.AsNoTracking() .Where(a => a.ProjectId == decodeResult.Value) .OrderByDescending(a => a.CreatedAt) .ToListAsync(cancellationToken); return Result.Ok(activities?.Select(a => a.Map()).ToArray() ?? []); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask CreateProjectAsync(CreateProjectRequest request, Guid userId, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (await context.Projects.AnyAsync(p => p.Name == request.Name && p.Sector == request.Sector && p.ProjectType == request.ProjectType, cancellationToken)) return Result.Fail("A similarly named project exists of the same project type and sector"); if (!await context.Programmes.AnyAsync(p => p.Id == request.ProgrammeId, cancellationToken)) return Result.Fail($"Programme does not exist."); context.Projects.Add(new Domain.Entities.Project { ProgrammeId = request.ProgrammeId, Name = request.Name, Description = request.Description, Sector = request.Sector, ProjectType = request.ProjectType, Status = ApprovalStatus.Pending, CreatedBy = userId, CreatedAt = DateTime.UtcNow }); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail("Failed to create project"); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask CreateActivityAsync(CreateActivityRequest request, Guid userId, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Projects.AnyAsync(p => p.Id == request.ProjectId, cancellationToken)) return Result.Fail($"Project does not exist."); if (request.MilestoneId.HasValue) if (!await context.Milestones.AnyAsync(m => m.Id == request.MilestoneId.Value, cancellationToken)) return Result.Fail($"Milestone does not exist."); context.Activities.Add(new Domain.Entities.Activity { ProjectId = request.ProjectId, MilestoneId = request.MilestoneId, Category = request.Category, Amount = request.Amount, Title = request.Title, Description = request.Description, PerformedAt = request.PerformedAt, Status = ApprovalStatus.Pending, CreatedBy = userId, CreatedAt = DateTime.UtcNow }); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail("Failed to create activity."); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } }