diff --git a/PostFundManagement.Api/Endpoints/Governance/CreateInstrumentEndpoint.cs b/PostFundManagement.Api/Endpoints/Governance/CreateInstrumentEndpoint.cs new file mode 100644 index 0000000..886a543 --- /dev/null +++ b/PostFundManagement.Api/Endpoints/Governance/CreateInstrumentEndpoint.cs @@ -0,0 +1,36 @@ +using PostFundManagement.Application.Abstractions.Services; +using PostFundManagement.Application.Extensions; +using PostFundManagement.Application.Services.Governance; +using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Errors; +using PostFundManagement.Domain.Extensions; +using PostFundManagement.Domain.Security; +using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; + +namespace PostFundManagement.Api.Endpoints.Governance; + +[ApiVersionTarget(1)] +public sealed class CreateInstrumentEndpoint : IEndpoint +{ + public void Map(IEndpointRouteBuilder builder) + { + builder.MapPost("api/governance/instruments", async (CreateInstrumentRequest request, HttpContext context, + IGovernanceService service, CancellationToken cancellationToken = default) => + await service.CreateInstrumentAsync(request, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true} => Results.Ok(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()) + }) + .WithDescription("Creates a new programme funding instrument") + .RequireAuthorization(Constants.MainScope) + .WithName(typeof(CreateInstrumentEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Governance) + .CacheOutput(); + } +} diff --git a/PostFundManagement.Api/Endpoints/Governance/UpdateInstrumentEndpoint.cs b/PostFundManagement.Api/Endpoints/Governance/UpdateInstrumentEndpoint.cs new file mode 100644 index 0000000..ecbc6ef --- /dev/null +++ b/PostFundManagement.Api/Endpoints/Governance/UpdateInstrumentEndpoint.cs @@ -0,0 +1,36 @@ +using PostFundManagement.Application.Abstractions.Services; +using PostFundManagement.Application.Extensions; +using PostFundManagement.Application.Services.Governance; +using PostFundManagement.Domain.Abstractions; +using PostFundManagement.Domain.Errors; +using PostFundManagement.Domain.Extensions; +using PostFundManagement.Domain.Security; +using Problems = Microsoft.AspNetCore.Mvc.ProblemDetails; + +namespace PostFundManagement.Api.Endpoints.Governance; + +[ApiVersionTarget(1)] +public sealed class UpdateInstrumentEndpoint : IEndpoint +{ + public void Map(IEndpointRouteBuilder builder) + { + builder.MapPut("api/governance/instruments", async (UpdateInstrumentRequest request, HttpContext context, + IGovernanceService service, CancellationToken cancellationToken = default) => + await service.UpdateInstrumentAsync(request, context.GetUserId(), cancellationToken) switch + { + { IsSuccess: true } => Results.Ok(), + var r when r.HasError() => Results.BadRequest(r.Errors.ToProblems()), + var r => Results.InternalServerError(r.Errors.ToProblems()), + }) + .WithDescription("Updates a programme funding instrument") + .RequireAuthorization(Constants.MainScope) + .WithName(typeof(UpdateInstrumentEndpoint).ToEndpointName()) + .MapToApiVersion(new ApiVersion(1)) + .Produces(StatusCodes.Status200OK) + .Produces(StatusCodes.Status401Unauthorized) + .Produces(StatusCodes.Status400BadRequest) + .Produces(StatusCodes.Status500InternalServerError) + .WithTags(EndpointTags.Governance) + .CacheOutput(); + } +} diff --git a/PostFundManagement.Application/Abstractions/Services/IGovernanceService.cs b/PostFundManagement.Application/Abstractions/Services/IGovernanceService.cs index fc3a708..6ae9d81 100644 --- a/PostFundManagement.Application/Abstractions/Services/IGovernanceService.cs +++ b/PostFundManagement.Application/Abstractions/Services/IGovernanceService.cs @@ -7,6 +7,14 @@ namespace PostFundManagement.Application.Abstractions.Services; public interface IGovernanceService { + [Log] + [Audit] + Task UpdateInstrumentAsync(UpdateInstrumentRequest request, Guid userId, CancellationToken cancellationToken = default); + + [Log] + [Audit] + Task CreateInstrumentAsync(CreateInstrumentRequest request, Guid userId, CancellationToken cancellationToken = default); + [Log] Task> GetProgrammesAsync(string portfolioIdHash, CancellationToken cancellationToken = default); diff --git a/PostFundManagement.Application/Services/Governance/GovernanceCacheService.cs b/PostFundManagement.Application/Services/Governance/GovernanceCacheService.cs index 16a7aac..e9bdd22 100644 --- a/PostFundManagement.Application/Services/Governance/GovernanceCacheService.cs +++ b/PostFundManagement.Application/Services/Governance/GovernanceCacheService.cs @@ -11,6 +11,9 @@ namespace PostFundManagement.Application.Services.Governance; public sealed class GovernanceCacheService(GovernanceService service, IDistributedCache cache, IFeatureManager featureManager) : ServiceCacheBase, IGovernanceService { + public async Task CreateInstrumentAsync(CreateInstrumentRequest request, Guid userId, CancellationToken cancellationToken = default) => + await service.CreateInstrumentAsync(request, userId, cancellationToken); + public async Task CreateOrganisationAsync(CreateOrganisationRequest request, Guid userId, CancellationToken cancellationToken = default) => await service.CreateOrganisationAsync(request, userId, cancellationToken); @@ -311,6 +314,9 @@ public sealed class GovernanceCacheService(GovernanceService service, IDistribut return Result.Ok(cached.FromJson()); })); + public async Task UpdateInstrumentAsync(UpdateInstrumentRequest request, Guid userId, CancellationToken cancellationToken = default) => + await service.UpdateInstrumentAsync(request, userId, cancellationToken); + public async Task> UpdateOrganisationAsync(UpdateOrganisationRequest request, Guid userId, CancellationToken cancellationToken = default) => await service.UpdateOrganisationAsync(request, userId, cancellationToken); diff --git a/PostFundManagement.Application/Services/Governance/GovernanceService.cs b/PostFundManagement.Application/Services/Governance/GovernanceService.cs index 61d40fb..eb6a375 100644 --- a/PostFundManagement.Application/Services/Governance/GovernanceService.cs +++ b/PostFundManagement.Application/Services/Governance/GovernanceService.cs @@ -435,4 +435,40 @@ public sealed class GovernanceService(IDbContextFactory co return Result.FailIf(tenantOrganisation is null, NotFoundError.Create("Failed to find tenant organisation map")) .ToResult(tenantOrganisation!.Map()); })); + + public async Task UpdateInstrumentAsync(UpdateInstrumentRequest request, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + var changedTotal = await context.Instruments + .Where(i => i.Id == request.InstructionId) + .ExecuteUpdateAsync(f => f + .SetProperty(u => u.Name, request.Name.Humanize(LetterCasing.Title).Trim()) + .SetProperty(u => u.Code, request.Code) + .SetProperty(u => u.Description, request.Description) + .SetProperty(u => u.Status, request.Status), cancellationToken); + + return Result.OkIf(changedTotal > 0, UpdateError.Create("Failed to update instrument")); + })); + + public async Task CreateInstrumentAsync(CreateInstrumentRequest request, Guid userId, CancellationToken cancellationToken = default) => + await Result.Try((Func>)(async () => + { + await using var context = await contextFactory.CreateDbContextAsync(cancellationToken); + + context.Instruments.Add(new Domain.Entities.Instrument + { + Code = request.Code, + CreatedAt = DateTime.UtcNow, + CreatedBy = userId, + Description = request.Description, + Name = request.Name.Humanize(LetterCasing.Title).Trim(), + Status = ActivityStatus.Active, + ProgrammeId = request.ProgrammeId, + TenantId = tenantContext.TenantId + }); + + return Result.OkIf(await context.SaveChangesAsync(cancellationToken) > 0, CreateError.Create("Failed to create instrument")); + })); } diff --git a/PostFundManagement.Application/Services/Governance/Records.cs b/PostFundManagement.Application/Services/Governance/Records.cs index 3b85c12..51cc316 100644 --- a/PostFundManagement.Application/Services/Governance/Records.cs +++ b/PostFundManagement.Application/Services/Governance/Records.cs @@ -3,6 +3,17 @@ using static PostFundManagement.Domain.Extensions.Constants; namespace PostFundManagement.Application.Services.Governance; +public sealed record UpdateInstrumentRequest([Required] long InstructionId, + [Required][MaxLength(LabelLength)] string Name, + [Required][MaxLength(LabelLength)] string Code, + [Required][MaxLength(TextLength)] string Description, + [Required] ActivityStatus Status); + +public sealed record CreateInstrumentRequest([Required] long ProgrammeId, + [Required][MaxLength(LabelLength)] string Name, + [Required][MaxLength(LabelLength)] string Code, + [Required][MaxLength(TextLength)] string Description); + public sealed record CreateTenantRequest([Required][MaxLength(LabelLength)] string Name); public sealed record UpdateTenantUserRequest([Required] Guid UserId, [Required] ActivityStatus Status);