using PostFundManagement.Application.Abstractions.Services; using PostFundManagement.Application.Services.Shared; using PostFundManagement.Domain; using PostFundManagement.Domain.Errors; 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) : IExecutionService { public async Task> GetRisksAsync(Pagination pagination, CancellationToken cancellationToken = default) => await Result.Try((Func>>)(async () => { await 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()); })); public async Task> GetDisbursementsAsync(Pagination pagination, CancellationToken cancellationToken = default) => await Result.Try((Func>>)(async () => { await 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) .ToArrayAsync(cancellationToken); return Result.Ok(disbursements.Select(i => i.Map()).ToArray()); })); public async Task CreateRiskAsync(CreateRiskRequest request, Guid userId, CancellationToken cancellationToken = default) => await Result.Try((Func>)(async () => { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Projects.AnyAsync(p => p.Id == request.ProjectId, cancellationToken)) return Result.Fail(NotFoundError.New); 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 Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.New); })); public async Task CreateDisbursementAsync(CreateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default) => await Result.Try((Func>)(async () => { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); if (!await context.Projects.AnyAsync(p => p.Id == request.ProjectId, cancellationToken)) return Result.Fail(NotFoundError.New); 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 Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.New); })); public async Task ApproveDisbursementAsync(string disbursementIdHash, Guid userId, CancellationToken cancellationToken = default) => await Result.Try((Func>)(async () => { var decodeResult = hashService.DecodeLongIdHash(disbursementIdHash); if (decodeResult.IsFailed) return Result.Fail(IdDecodeError.Create() .CausedBy(decodeResult.Errors)); await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var changedTotal = 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 Result.OkIf(changedTotal > 0, UpdateError.New); })); public async Task> GetDisbursementAsync(string disbursementIdHash, CancellationToken cancellationToken) => await Result.Try((Func>>)(async () => { var decodeResult = hashService.DecodeLongIdHash(disbursementIdHash); if (decodeResult.IsFailed) return Result.Fail(IdDecodeError.Create() .CausedBy(decodeResult.Errors)); await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var disbursement = await context.Disbursements.AsNoTracking() .FirstOrDefaultAsync(d => d.Id == decodeResult.Value, cancellationToken); return Result.FailIf(disbursement is null, NotFoundError.New) .ToResult(disbursement!.Map()); })); public async Task> GetRiskAsync(string riskIdHash, CancellationToken cancellationToken = default) => await Result.Try((Func>>)(async () => { var decodeResult = hashService.DecodeLongIdHash(riskIdHash); if (decodeResult.IsFailed) return Result.Fail(IdDecodeError.Create() .CausedBy(decodeResult.Errors)); await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var risk = await context.Risks.AsNoTracking() .FirstOrDefaultAsync(r => r.Id == decodeResult.Value, cancellationToken); return Result.FailIf(risk is null, NotFoundError.New) .ToResult(risk!.Map()); })); public async Task UpdateRiskAsync(UpdateRiskRequest request, CancellationToken cancellationToken = default) => await Result.Try((Func>)(async () => { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var changedTotal = await context.Risks .Where(r => r.Id == request.RiskId) .ExecuteUpdateAsync(f => f .SetProperty(u => u.Name, request.Name) .SetProperty(u => u.Description, request.Description) .SetProperty(u => u.Likelihood, request.Likelihood) .SetProperty(u => u.Impact, request.Impact), cancellationToken); return Result.OkIf(changedTotal > 0, UpdateError.New); })); public async Task UpdateDisbursementAsync(UpdateDisbursementRequest request, Guid userId, CancellationToken cancellationToken = default) => await Result.Try((Func>)(async () => { await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); var changedTotal = await context.Disbursements .Where(f => f.Id == request.DisbursementId) .ExecuteUpdateAsync(f => f .SetProperty(d => d.MilestoneId, request.MilestoneId) .SetProperty(d => d.Amount, request.Amount) .SetProperty(d => d.UpdatedAt, DateTime.UtcNow) .SetProperty(d => d.UpdatedBy, userId), cancellationToken); return Result.OkIf(changedTotal > 0, UpdateError.New); })); }