Skip to content

Commit

Permalink
Merge pull request #1831 from SkillsFundingAgency/APPMAN-1019-Add-LTM
Browse files Browse the repository at this point in the history
APPMAN-1019 added LTM for selecting funding
  • Loading branch information
Najamuddin-Muhammad authored Jan 15, 2025
2 parents a9e3b14 + 782dd26 commit 62b8509
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 59 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using AutoFixture.NUnit3;
using FluentAssertions;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Moq;
using NUnit.Framework;
using SFA.DAS.Approvals.Api.Controllers;
using SFA.DAS.Approvals.Application.LevyTransferMatching.Queries.GetApprovedAccountApplication;
using SFA.DAS.Approvals.Application.SelectDirectTransferConnection.Queries;
using SFA.DAS.Testing.AutoFixture;

namespace SFA.DAS.Approvals.Api.UnitTests.Controllers.SelectLevyConnection;

public class WhenGettingSelectLevyConnection
{
[Test, MoqAutoData]
public async Task Then_Get_Returns_LevyConnections_From_Mediator(
long accountId,
GetAcceptedEmployerAccountApplicationsQueryResult mediatorResult,
[Frozen] Mock<IMediator> mockMediator,
[Greedy] SelectAcceptedLevyApplicationsController controller)
{
mockMediator.Setup(mediator => mediator.Send(
It.Is<GetAcceptedEmployerAccountApplicationsQuery>(x => x.EmployerAccountId == accountId),
It.IsAny<CancellationToken>()))
.ReturnsAsync(mediatorResult);

var controllerResult = await controller.Get(accountId) as ObjectResult;

controllerResult.Should().NotBeNull();
controllerResult.StatusCode.Should().Be((int)HttpStatusCode.OK);
var model = controllerResult.Value as GetAcceptedEmployerAccountApplicationsQueryResult;
model.Should().NotBeNull();
model.Should().BeEquivalentTo(mediatorResult);
}

[Test, MoqAutoData]
public async Task And_Exception_Then_Returns_Bad_Request(
long accountId,
[Frozen] Mock<IMediator> mockMediator,
[Greedy] SelectAcceptedLevyApplicationsController controller)
{
mockMediator
.Setup(mediator => mediator.Send(
It.IsAny<GetAcceptedEmployerAccountApplicationsQuery>(),
It.IsAny<CancellationToken>()))
.Throws<InvalidOperationException>();

var controllerResult = await controller.Get(accountId) as StatusCodeResult;

controllerResult.StatusCode.Should().Be((int)HttpStatusCode.InternalServerError);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Net;
using System.Threading.Tasks;
using MediatR;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using SFA.DAS.Approvals.Application.LevyTransferMatching.Queries.GetApprovedAccountApplication;

namespace SFA.DAS.Approvals.Api.Controllers
{
[ApiController]
public class SelectAcceptedLevyApplicationsController : Controller
{
private readonly IMediator _mediator;
private readonly ILogger<SelectAcceptedLevyApplicationsController> _logger;

public SelectAcceptedLevyApplicationsController(IMediator mediator, ILogger<SelectAcceptedLevyApplicationsController> logger)
{
_mediator = mediator;
_logger = logger;
}

[HttpGet]
[Route("{accountId}/unapproved/add/select-funding/select-accepted-levy-connection")]
public async Task<IActionResult> Get(long accountId)
{
try
{
_logger.LogInformation("Getting Levy Transfer Connections for Account {accountId}", accountId);
var result = await _mediator.Send(new GetAcceptedEmployerAccountApplicationsQuery { EmployerAccountId = accountId});
return Ok(result);
}
catch (Exception e)
{
_logger.LogError(e, "Error when getting Levy Transfer Connections for Account {accountId}", accountId);
return new StatusCodeResult((int) HttpStatusCode.InternalServerError);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Threading;
using System.Threading.Tasks;
using AutoFixture.NUnit3;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using SFA.DAS.Approvals.Application.LevyTransferMatching.Queries.GetApprovedAccountApplication;
using SFA.DAS.Approvals.InnerApi.Requests;
using SFA.DAS.SharedOuterApi.Configuration;
using SFA.DAS.SharedOuterApi.InnerApi.Responses.LevyTransferMatching;
using SFA.DAS.SharedOuterApi.Interfaces;
using SFA.DAS.Testing.AutoFixture;

namespace SFA.DAS.Approvals.UnitTests.Application.SelectAcceptedEmployerAccountApplications;

public class WhenGettingAcceptedEmployerAccountApplications
{
[Test, MoqAutoData]
public async Task Then_The_Api_To_GetAcceptedEmployerAccountApplications_Returns_ExpectedValues(
GetAcceptedEmployerAccountApplicationsQuery query,
GetApplicationsResponse response,
[Frozen] Mock<ILevyTransferMatchingApiClient<LevyTransferMatchingApiConfiguration>> client,
GetAcceptedEmployerAccountApplicationsQueryHandler handler
)
{
client.Setup(x =>
x.Get<GetApplicationsResponse>(
It.Is<GetAcceptedEmployerAccountPledgeApplicationsRequest>(x =>
x.EmployerAccountId == query.EmployerAccountId)))
.ReturnsAsync(response);

var actual = await handler.Handle(query, CancellationToken.None);

actual.Applications.Should().BeEquivalentTo(response.Applications);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using SFA.DAS.SharedOuterApi.Configuration;
using SFA.DAS.SharedOuterApi.InnerApi.Requests.EmployerFinance;
using SFA.DAS.SharedOuterApi.InnerApi.Responses.EmployerFinance;
using SFA.DAS.SharedOuterApi.InnerApi.Responses.LevyTransferMatching;
using SFA.DAS.SharedOuterApi.Interfaces;
using SFA.DAS.Testing.AutoFixture;

Expand All @@ -22,31 +23,16 @@ public class WhenGettingFundingOption
[Test, MoqAutoData]
public async Task ThenDirectTransfersAreAvailable_WhenTransferConnectionsExist(
GetSelectFundingOptionsQuery query,
GetAccountReservationsStatusResponse reservationsResponse,
IEnumerable<GetTransferConnectionsResponse.TransferConnection> directTransfersResponse,
GetAccountResponse accountResponse,
[Frozen] Mock<IReservationApiClient<ReservationApiConfiguration>> reservationsApiClient,
[Frozen] Mock<IFinanceApiClient<FinanceApiConfiguration>> financeApiClient,
[Frozen] Mock<IAccountsApiClient<AccountsConfiguration>> accountsApiClient,
GetSelectFundingOptionsQueryHandler handler
)
{
reservationsApiClient.Setup(x =>
x.Get<GetAccountReservationsStatusResponse>(
It.Is<GetAccountReservationsStatusRequest>(x =>
x.AccountId == query.AccountId && x.TransferSenderId == null)))
.ReturnsAsync(reservationsResponse);

financeApiClient.Setup(x =>
x.Get<IEnumerable<GetTransferConnectionsResponse.TransferConnection>>(
It.Is<GetTransferConnectionsRequest>(x => x.AccountId == query.AccountId)))
.ReturnsAsync(directTransfersResponse);

accountsApiClient.Setup(x =>
x.Get<GetAccountResponse>(
It.Is<GetAccountRequest>(x => x.HashedAccountId == query.AccountId.ToString())))
.ReturnsAsync(accountResponse);


var actual = await handler.Handle(query, CancellationToken.None);

actual.HasDirectTransfersAvailable.Should().BeTrue();
Expand All @@ -56,10 +42,7 @@ GetSelectFundingOptionsQueryHandler handler
public async Task ThenHasReservationsAvailable_WhenLimitNotReached(
GetSelectFundingOptionsQuery query,
GetAccountReservationsStatusResponse reservationsResponse,
GetAccountResponse accountResponse,
[Frozen] Mock<IReservationApiClient<ReservationApiConfiguration>> reservationsApiClient,
[Frozen] Mock<IFinanceApiClient<FinanceApiConfiguration>> financeApiClient,
[Frozen] Mock<IAccountsApiClient<AccountsConfiguration>> accountsApiClient,
GetSelectFundingOptionsQueryHandler handler
)
{
Expand All @@ -71,16 +54,6 @@ GetSelectFundingOptionsQueryHandler handler
x.AccountId == query.AccountId && x.TransferSenderId == null)))
.ReturnsAsync(reservationsResponse);

financeApiClient.Setup(x =>
x.Get<GetTransferConnectionsResponse>(
It.Is<GetTransferConnectionsRequest>(x => x.AccountId == query.AccountId)))
.ReturnsAsync(new GetTransferConnectionsResponse());

accountsApiClient.Setup(x =>
x.Get<GetAccountResponse>(
It.Is<GetAccountRequest>(x => x.HashedAccountId == query.AccountId.ToString())))
.ReturnsAsync(accountResponse);

var actual = await handler.Handle(query, CancellationToken.None);

actual.HasAdditionalReservationFundsAvailable.Should().BeTrue();
Expand All @@ -90,10 +63,7 @@ GetSelectFundingOptionsQueryHandler handler
public async Task ThenHasUnallocatedReservations_WhenPendingReservationsExist(
GetSelectFundingOptionsQuery query,
GetAccountReservationsStatusResponse reservationsResponse,
GetAccountResponse accountResponse,
[Frozen] Mock<IReservationApiClient<ReservationApiConfiguration>> reservationsApiClient,
[Frozen] Mock<IFinanceApiClient<FinanceApiConfiguration>> financeApiClient,
[Frozen] Mock<IAccountsApiClient<AccountsConfiguration>> accountsApiClient,
GetSelectFundingOptionsQueryHandler handler
)
{
Expand All @@ -105,16 +75,6 @@ GetSelectFundingOptionsQueryHandler handler
x.AccountId == query.AccountId && x.TransferSenderId == null)))
.ReturnsAsync(reservationsResponse);

financeApiClient.Setup(x =>
x.Get<GetTransferConnectionsResponse>(
It.Is<GetTransferConnectionsRequest>(x => x.AccountId == query.AccountId)))
.ReturnsAsync(new GetTransferConnectionsResponse());

accountsApiClient.Setup(x =>
x.Get<GetAccountResponse>(
It.Is<GetAccountRequest>(x => x.HashedAccountId == query.AccountId.ToString())))
.ReturnsAsync(accountResponse);

var actual = await handler.Handle(query, CancellationToken.None);

actual.HasUnallocatedReservationsAvailable.Should().BeTrue();
Expand All @@ -123,27 +83,13 @@ GetSelectFundingOptionsQueryHandler handler
[Test, MoqAutoData]
public async Task ThenIsLevyAccount_WhenEmploymentTypeIsLevy(
GetSelectFundingOptionsQuery query,
GetAccountReservationsStatusResponse reservationsResponse,
GetAccountResponse accountResponse,
[Frozen] Mock<IReservationApiClient<ReservationApiConfiguration>> reservationsApiClient,
[Frozen] Mock<IFinanceApiClient<FinanceApiConfiguration>> financeApiClient,
[Frozen] Mock<IAccountsApiClient<AccountsConfiguration>> accountsApiClient,
GetSelectFundingOptionsQueryHandler handler
)
{
accountResponse.ApprenticeshipEmployerType = "LEVY";

reservationsApiClient.Setup(x =>
x.Get<GetAccountReservationsStatusResponse>(
It.Is<GetAccountReservationsStatusRequest>(x =>
x.AccountId == query.AccountId && x.TransferSenderId == null)))
.ReturnsAsync(reservationsResponse);

financeApiClient.Setup(x =>
x.Get<GetTransferConnectionsResponse>(
It.Is<GetTransferConnectionsRequest>(x => x.AccountId == query.AccountId)))
.ReturnsAsync(new GetTransferConnectionsResponse());

accountsApiClient.Setup(x =>
x.Get<GetAccountResponse>(
It.Is<GetAccountRequest>(x => x.HashedAccountId == query.AccountId.ToString())))
Expand All @@ -153,4 +99,23 @@ GetSelectFundingOptionsQueryHandler handler

actual.IsLevyAccount.Should().BeTrue();
}

[Test, MoqAutoData]
public async Task ThenHasLtmTransfers_WhenApprovedApplicationsExist(
GetSelectFundingOptionsQuery query,
GetApplicationsResponse ltmTransfersResponse,
[Frozen] Mock<ILevyTransferMatchingApiClient<LevyTransferMatchingApiConfiguration>> ltmApiClient,
GetSelectFundingOptionsQueryHandler handler
)
{
ltmApiClient.Setup(x =>
x.Get<GetApplicationsResponse>(
It.Is<GetAcceptedEmployerAccountPledgeApplicationsRequest>(x => x.EmployerAccountId == query.AccountId)))
.ReturnsAsync(ltmTransfersResponse);

var actual = await handler.Handle(query, CancellationToken.None);

actual.HasLtmTransfersAvailable.Should().BeTrue();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using MediatR;

namespace SFA.DAS.Approvals.Application.LevyTransferMatching.Queries.GetApprovedAccountApplication
{
public class GetAcceptedEmployerAccountApplicationsQuery : IRequest<GetAcceptedEmployerAccountApplicationsQueryResult>
{
public long EmployerAccountId { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using SFA.DAS.Approvals.InnerApi.Requests;
using SFA.DAS.SharedOuterApi.Configuration;
using SFA.DAS.SharedOuterApi.InnerApi.Responses.LevyTransferMatching;
using SFA.DAS.SharedOuterApi.Interfaces;

namespace SFA.DAS.Approvals.Application.LevyTransferMatching.Queries.GetApprovedAccountApplication
{
public class GetAcceptedEmployerAccountApplicationsQueryHandler : IRequestHandler<GetAcceptedEmployerAccountApplicationsQuery, GetAcceptedEmployerAccountApplicationsQueryResult>
{
private readonly ILevyTransferMatchingApiClient<LevyTransferMatchingApiConfiguration> _apiClient;

public GetAcceptedEmployerAccountApplicationsQueryHandler(ILevyTransferMatchingApiClient<LevyTransferMatchingApiConfiguration> apiClient)
{
_apiClient = apiClient;
}

public async Task<GetAcceptedEmployerAccountApplicationsQueryResult> Handle(GetAcceptedEmployerAccountApplicationsQuery request, CancellationToken cancellationToken)
{
var result = await _apiClient.Get<GetApplicationsResponse>(new GetAcceptedEmployerAccountPledgeApplicationsRequest(request.EmployerAccountId));

if (result == null)
return null;

return new GetAcceptedEmployerAccountApplicationsQueryResult
{
Applications = result.Applications
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Collections.Generic;
using SFA.DAS.SharedOuterApi.InnerApi.Responses.LevyTransferMatching;

namespace SFA.DAS.Approvals.Application.LevyTransferMatching.Queries.GetApprovedAccountApplication
{
public class GetAcceptedEmployerAccountApplicationsQueryResult
{
public IEnumerable<GetApplicationsResponse.Application> Applications { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using SFA.DAS.SharedOuterApi.Configuration;
using SFA.DAS.SharedOuterApi.InnerApi.Requests.EmployerFinance;
using SFA.DAS.SharedOuterApi.InnerApi.Responses.EmployerFinance;
using SFA.DAS.SharedOuterApi.InnerApi.Responses.LevyTransferMatching;
using SFA.DAS.SharedOuterApi.Interfaces;

namespace SFA.DAS.Approvals.Application.SelectFunding.Queries;
Expand All @@ -17,32 +18,38 @@ public class GetSelectFundingOptionsQueryHandler : IRequestHandler<GetSelectFund
private readonly IFinanceApiClient<FinanceApiConfiguration> _financeApiClient;
private readonly IReservationApiClient<ReservationApiConfiguration> _reservationsApiClient;
private readonly IAccountsApiClient<AccountsConfiguration> _accountsApiClient;
private readonly ILevyTransferMatchingApiClient<LevyTransferMatchingApiConfiguration> _ltmApiClient;

public GetSelectFundingOptionsQueryHandler(IFinanceApiClient<FinanceApiConfiguration> financeApiClient,
IReservationApiClient<ReservationApiConfiguration> reservationsApiClient,
IAccountsApiClient<AccountsConfiguration> accountsApiClient)
IAccountsApiClient<AccountsConfiguration> accountsApiClient,
ILevyTransferMatchingApiClient<LevyTransferMatchingApiConfiguration> ltmApiClient)
{
_financeApiClient = financeApiClient;
_reservationsApiClient = reservationsApiClient;
_accountsApiClient = accountsApiClient;
_ltmApiClient = ltmApiClient;
}

public async Task<GetSelectFundingOptionsQueryResult> Handle(GetSelectFundingOptionsQuery request, CancellationToken cancellationToken)
{
var statusTask = _reservationsApiClient.Get<GetAccountReservationsStatusResponse>(new GetAccountReservationsStatusRequest(request.AccountId, null));
var connectionsTask = _financeApiClient.Get<IEnumerable<GetTransferConnectionsResponse.TransferConnection>>(new GetTransferConnectionsRequest { AccountId = request.AccountId});
var accountTask = _accountsApiClient.Get<GetAccountResponse>(new GetAccountRequest(request.AccountId.ToString()));
var ltmTask = _ltmApiClient.Get<GetApplicationsResponse>(new GetAcceptedEmployerAccountPledgeApplicationsRequest(request.AccountId));

await Task.WhenAll(statusTask, connectionsTask, accountTask);
await Task.WhenAll(statusTask, connectionsTask, accountTask, ltmTask);

var status = await statusTask;
var connections = await connectionsTask;
var account = await accountTask;
var ltm = await ltmTask;

return new GetSelectFundingOptionsQueryResult
{
IsLevyAccount = account.ApprenticeshipEmployerType.Equals("Levy", StringComparison.InvariantCultureIgnoreCase),
IsLevyAccount = account.ApprenticeshipEmployerType?.Equals("Levy", StringComparison.InvariantCultureIgnoreCase) ?? false,
HasDirectTransfersAvailable = connections?.Any() ?? false,
HasLtmTransfersAvailable = ltm.Applications?.Any() ?? false,
HasAdditionalReservationFundsAvailable = !status.HasReachedReservationsLimit,
HasUnallocatedReservationsAvailable = status.HasPendingReservations
};
Expand Down
Loading

0 comments on commit 62b8509

Please sign in to comment.