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.Execution; public sealed class ExecutionService(IDbContextFactory contextFactory, HashService hashService) : IService { public async Task> GetRisksAsync(Pagination pagination, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var risks = await context.Risks.AsNoTracking() .OrderByDescending(r => r.CreatedAt) .Skip((pagination.Page - 1) * pagination.PageSize) .Take(pagination.PageSize) .ToListAsync(cancellationToken); return Result.Ok(risks?.Select(i => i.Map()).ToArray() ?? []); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetProjectRisksAsync(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 risks = await context.Risks.AsNoTracking() .Where(r => r.ProjectId == decodeResult.Value) .OrderByDescending(r => r.CreatedAt) .ToListAsync(cancellationToken); return Result.Ok(risks?.Select(i => i.Map()).ToArray() ?? []); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask> GetDisbursementsAsync(Pagination pagination, CancellationToken cancellationToken = default) { try { using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var disbursements = await context.Disbursements.AsNoTracking() .OrderByDescending(d => d.CreatedAt) .Skip((pagination.Page - 1) * pagination.PageSize) .Take(pagination.PageSize) .ToListAsync(cancellationToken); return Result.Ok(disbursements?.Select(i => i.Map()).ToArray() ?? []); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask CreateRiskAsync(CreateRiskRequest 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 with ID {request.ProjectId} does not exist."); context.Risks.Add(new Domain.Entities.Risk { ProjectId = request.ProjectId, Likelihood = request.Likelihood, Impact = request.Impact, Name = request.Name, Description = request.Description, CreatedBy = userId, CreatedAt = DateTime.UtcNow }); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail("Failed to create project risk"); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask CreateDisbursementAsync(CreateDisbursementRequest 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 with ID {request.ProjectId} does not exist."); context.Disbursements.Add(new Domain.Entities.Disbursement { ProjectId = request.ProjectId, MilestoneId = request.MilestoneId, Amount = request.Amount, Status = ApprovalStatus.Pending, CreatedBy = userId, CreatedAt = DateTime.UtcNow }); return await context.SaveChangesAsync(cancellationToken) > 0 ? Result.Ok() : Result.Fail("Failed to create disbursement"); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } public async ValueTask ApproveDisbursementAsync(string disbursementIdHash, Guid userId, CancellationToken cancellationToken = default) { try { var decodeResult = hashService.DecodeLongIdHash(disbursementIdHash); if (decodeResult.IsFailed) return Result.Fail("Invalid disbursementId"); using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var totalSaved = await context.Disbursements.Where(d => d.Id == decodeResult.Value) .ExecuteUpdateAsync(f => f.SetProperty(f => f.Status, ApprovalStatus.Approved) .SetProperty(f => f.UpdatedBy, userId) .SetProperty(f => f.UpdatedAt, DateTime.UtcNow), cancellationToken); return totalSaved > 0 ? Result.Ok() : Result.Fail("Failed to approve payment"); } catch (Exception ex) { return Result.Fail(new Error(ex.Message).CausedBy(ex)); } } }