270 lines
10 KiB
C#
270 lines
10 KiB
C#
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.Grants;
|
|
|
|
public sealed class GrantsService(IDbContextFactory<ApplicationDbContext> contextFactory, HashService hashService) : IService
|
|
{
|
|
public async ValueTask<Result<Contract[]>> GetContractsAsync(Pagination pagination, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var contracts = await context.Contracts.AsNoTracking()
|
|
.OrderByDescending(c => c.CreatedAt)
|
|
.Skip((pagination.Page - 1) * pagination.PageSize)
|
|
.Take(pagination.PageSize)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
return Result.Ok(contracts?.Select(c => c.Map()).ToArray() ?? []);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result<Contract>> GetContractByIdAsync(string contractIdHash, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var decodeResult = hashService.DecodeLongIdHash(contractIdHash);
|
|
|
|
if (decodeResult.IsFailed)
|
|
return Result.Fail(decodeResult.Errors);
|
|
|
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var contract = await context.Contracts.AsNoTracking().FirstOrDefaultAsync(c => c.Id == decodeResult.Value, cancellationToken);
|
|
|
|
return contract is not null
|
|
? Result.Ok(contract.Map())
|
|
: Result.Fail("Failed to find specific contract");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result<Budget[]>> GetBudgetsAsync(Pagination pagination, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var budgets = await context.Budgets.AsNoTracking()
|
|
.OrderByDescending(b => b.CreatedAt)
|
|
.Skip((pagination.Page - 1) * pagination.PageSize)
|
|
.Take(pagination.PageSize)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
return Result.Ok(budgets?.Select(b => b.Map()).ToArray() ?? []);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result<Budget>> GetBudgetByIdAsync(string budgetIdHash, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var decodeResult = hashService.DecodeLongIdHash(budgetIdHash);
|
|
|
|
if (decodeResult.IsFailed)
|
|
return Result.Fail(decodeResult.Errors);
|
|
|
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var budget = await context.Budgets.AsNoTracking().FirstOrDefaultAsync(b => b.Id == decodeResult.Value, cancellationToken);
|
|
|
|
return budget is not null
|
|
? Result.Ok(budget.Map())
|
|
: Result.Fail("Failed to get specific budget.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result<Award[]>> GetAwardsAsync(Pagination pagination, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var awards = await context.Awards.AsNoTracking()
|
|
.OrderByDescending(a => a.CreatedAt)
|
|
.Skip((pagination.Page - 1) * pagination.PageSize)
|
|
.Take(pagination.PageSize)
|
|
.ToListAsync(cancellationToken);
|
|
|
|
return Result.Ok(awards?.Select(a => a.Map()).ToArray() ?? []);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result<Award>> GetAwardByIdAsync(string awardIdHash, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var decodeResult = hashService.DecodeLongIdHash(awardIdHash);
|
|
|
|
if (decodeResult.IsFailed)
|
|
return Result.Fail(decodeResult.Errors);
|
|
|
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var award = await context.Awards.AsNoTracking().FirstOrDefaultAsync(a => a.Id == decodeResult.Value, cancellationToken);
|
|
|
|
return award is not null
|
|
? Result.Ok(award.Map())
|
|
: Result.Fail("Award not found");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result> ExecuteContractAsync(ExecuteContractRequest request, Guid userId, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var changedTotal = await context.Contracts
|
|
.Where(c => c.Id == request.ContractId && c.Status != ContractStatus.Active)
|
|
.ExecuteUpdateAsync(f => f
|
|
.SetProperty(u => u.ExternalSignatureId, request.ExternalSignatureId)
|
|
.SetProperty(u => u.SignedDocumentUrl, request.SignedDocumentUrl)
|
|
.SetProperty(u => u.CreatedBy, userId)
|
|
.SetProperty(u => u.Status, ContractStatus.Active), cancellationToken);
|
|
|
|
return changedTotal > 0
|
|
? Result.Ok()
|
|
: Result.Fail("Contract activation failed");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result> CreateContractAsync(CreateContractRequest request, Guid userId, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
var award = await context.Awards.AsNoTracking()
|
|
.FirstOrDefaultAsync(a => a.Id == request.AwardId, cancellationToken);
|
|
|
|
if (award == null)
|
|
return Result.Fail($"Award with ID {request.AwardId} does not exist.");
|
|
|
|
var contract = new Domain.Entities.Contract
|
|
{
|
|
AwardId = request.AwardId,
|
|
TotalValue = request.TotalValue,
|
|
EffectiveAt = request.EffectiveAt,
|
|
ExpiresAt = request.ExpiresAt,
|
|
Status = ContractStatus.Draft,
|
|
CreatedBy = userId,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
context.Contracts.Add(contract);
|
|
|
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
? Result.Ok()
|
|
: Result.Fail("Failed to create contract");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result> CreateBudgetAsync(CreateBudgetRequest request, Guid userId, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
if (request.Amount <= 0)
|
|
return Result.Fail("Budget amount must be greater than zero.");
|
|
|
|
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.");
|
|
|
|
var budget = new Domain.Entities.Budget
|
|
{
|
|
ProjectId = request.ProjectId,
|
|
Name = request.Name,
|
|
Amount = request.Amount,
|
|
CreatedBy = userId,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
context.Budgets.Add(budget);
|
|
|
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
? Result.Ok()
|
|
: Result.Fail("Failed to create budget");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
|
|
public async ValueTask<Result> CreateAwardAsync(CreateAwardRequest request, Guid userId, CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
if (request.Amount <= 0)
|
|
return Result.Fail("Approved amount must be greater than zero.");
|
|
|
|
using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
|
|
|
|
if (!await context.Organisations.AnyAsync(o => o.Id == request.OrganisationId, cancellationToken))
|
|
return Result.Fail($"Organisation with ID {request.OrganisationId} does not exist.");
|
|
|
|
if (!await context.Programmes.AnyAsync(p => p.Id == request.ProgrammeId, cancellationToken))
|
|
return Result.Fail($"Programme with ID {request.ProgrammeId} does not exist.");
|
|
|
|
var award = new Domain.Entities.Award
|
|
{
|
|
OrganisationId = request.OrganisationId,
|
|
ProgrammeId = request.ProgrammeId,
|
|
Amount = request.Amount,
|
|
Status = ApprovalStatus.Pending,
|
|
CreatedBy = userId,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
context.Awards.Add(award);
|
|
|
|
return await context.SaveChangesAsync(cancellationToken) > 0
|
|
? Result.Ok()
|
|
: Result.Fail("Failed to create award.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error(ex.Message).CausedBy(ex));
|
|
}
|
|
}
|
|
}
|