diff --git a/src/Earnings/SFA.DAS.Earnings.Api.UnitTests/Controllers/LearnerData/WhenGettingLearnerData.cs b/src/Earnings/SFA.DAS.Earnings.Api.UnitTests/Controllers/LearnerData/WhenGettingLearnerData.cs new file mode 100644 index 0000000000..88dae65146 --- /dev/null +++ b/src/Earnings/SFA.DAS.Earnings.Api.UnitTests/Controllers/LearnerData/WhenGettingLearnerData.cs @@ -0,0 +1,68 @@ +using AutoFixture.NUnit3; +using FluentAssertions; +using MediatR; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Moq; +using SFA.DAS.Earnings.Api.Controllers; +using SFA.DAS.Earnings.Application.LearnerData.GetLearnerData; +using SFA.DAS.Testing.AutoFixture; + +namespace SFA.DAS.Earnings.Api.UnitTests.Controllers.LearnerData; + +public class WhenGettingLearnerData +{ + [Test, MoqAutoData] + public async Task Then_Should_Return_Learner_Data_Response( + [Frozen] Mock mediator, + [Greedy]LearnerDataController controller) + { + // Arrange + const int ukprn = 1000001; + const int academicYear = 2425; + const int page = 1; + const int pageSize = 10; + + const int totalRecords = 30; + + var learnerDataResponse = new GetLearnerDataQueryResult + { + TotalRecords = totalRecords + }; + + SetUpHttpContext(controller, ukprn, academicYear); + + mediator.Setup(m => m.Send(It.IsAny(), It.IsAny())) + .ReturnsAsync(learnerDataResponse); + + // Act + var result = await controller.Search(ukprn, academicYear, page, pageSize) as OkObjectResult; + + // Assert + result.Should().NotBeNull(); + result.Should().BeAssignableTo(); + result!.Value.Should().BeEquivalentTo(learnerDataResponse); + + // Verify the headers + controller.Response.Headers.Should().ContainKey("Link"); + var linkHeader = controller.Response.Headers.Link.ToString(); + + var expectedBaseUrl = $"http://localhost/providers/{ukprn}/academicyears/{academicYear}/apprenticeships"; + var expectedNextPage = $"<{expectedBaseUrl}?page=2&pageSize={pageSize}>; rel=\"next\""; + + linkHeader.Should().Contain(expectedNextPage); + } + + private void SetUpHttpContext(LearnerDataController controller, long ukprn, int academicYear) + { + var context = new DefaultHttpContext(); + controller.ControllerContext = new ControllerContext + { + HttpContext = context + }; + controller.ControllerContext.HttpContext.Request.Scheme = "http"; // Setting scheme + controller.ControllerContext.HttpContext.Request.Host = new Microsoft.AspNetCore.Http.HostString("localhost"); // Set Host + controller.ControllerContext.HttpContext.Request.PathBase = string.Empty; // Set PathBase (in case you use any path) + controller.ControllerContext.HttpContext.Request.Path = $"/providers/{ukprn}/academicyears/{academicYear}/apprenticeships"; // Set Path + } +} \ No newline at end of file diff --git a/src/Earnings/SFA.DAS.Earnings.Api.UnitTests/SFA.DAS.Earnings.Api.UnitTests.csproj b/src/Earnings/SFA.DAS.Earnings.Api.UnitTests/SFA.DAS.Earnings.Api.UnitTests.csproj index f33e358ed4..c74921767b 100644 --- a/src/Earnings/SFA.DAS.Earnings.Api.UnitTests/SFA.DAS.Earnings.Api.UnitTests.csproj +++ b/src/Earnings/SFA.DAS.Earnings.Api.UnitTests/SFA.DAS.Earnings.Api.UnitTests.csproj @@ -31,8 +31,4 @@ - - - - diff --git a/src/Earnings/SFA.DAS.Earnings.Api/Controllers/LearnerDataController.cs b/src/Earnings/SFA.DAS.Earnings.Api/Controllers/LearnerDataController.cs new file mode 100644 index 0000000000..1c24c71395 --- /dev/null +++ b/src/Earnings/SFA.DAS.Earnings.Api/Controllers/LearnerDataController.cs @@ -0,0 +1,66 @@ +using MediatR; +using Microsoft.AspNetCore.Mvc; +using SFA.DAS.Earnings.Api.LearnerData; +using SFA.DAS.Earnings.Application.LearnerData.GetLearnerData; + +namespace SFA.DAS.Earnings.Api.Controllers; + +[ApiController] +[Route("leanerdata")] +public class LearnerDataController( + IMediator mediator) : Controller +{ + [HttpGet] + [Route("/providers/{ukprn}/academicyears/{academicyear}/apprenticeships")] + public async Task Search(long ukprn, int academicyear, [FromQuery] int page, [FromQuery] int pageSize) + { + if (pageSize is < 1 or > 100) + { + return BadRequest("Page size must be between 1 and 100"); + } + + if (page < 1) + { + return BadRequest("Invalid page requested"); + } + + var result = await mediator.Send(new GetLearnerDataQuery(ukprn, academicyear, page, pageSize)); + var response = new PagedResult + { + Apprenticeships = result.Apprenticeships.ConvertAll(app => new Apprenticeship { Uln = app.Uln }), + TotalRecords = result.TotalRecords, + Page = page, + PageSize = pageSize, + TotalPages = (int)Math.Ceiling((double)result.TotalRecords / pageSize) + }; + + // Add Link headers for pagination + var baseUrl = $"{Request.Scheme}://{Request.Host}{Request.PathBase}{Request.Path}"; + var nextPage = page * pageSize < result.TotalRecords ? page + 1 : page; + var prevPage = page > 1 ? page - 1 : 1; + + var header = ""; + + if (prevPage != page) + { + header += $"<{baseUrl}?page={prevPage}&pageSize={pageSize}>; rel=\"prev\", "; + } + + if (nextPage != page) + { + if (header.Length > 0) + { + header += ", "; + } + + header += $"<{baseUrl}?page={nextPage}&pageSize={pageSize}>; rel=\"next\""; + } + + if (header.Length > 0) + { + Response.Headers.Link = header; + } + + return Ok(response); + } +} \ No newline at end of file diff --git a/src/Earnings/SFA.DAS.Earnings.Api/LearnerData/Apprenticeship.cs b/src/Earnings/SFA.DAS.Earnings.Api/LearnerData/Apprenticeship.cs new file mode 100644 index 0000000000..8e7fa2c9bd --- /dev/null +++ b/src/Earnings/SFA.DAS.Earnings.Api/LearnerData/Apprenticeship.cs @@ -0,0 +1,5 @@ +namespace SFA.DAS.Earnings.Api.LearnerData; + +public class Apprenticeship { + public long Uln { get; set; } +} \ No newline at end of file diff --git a/src/Earnings/SFA.DAS.Earnings.Api/LearnerData/PagedResult.cs b/src/Earnings/SFA.DAS.Earnings.Api/LearnerData/PagedResult.cs new file mode 100644 index 0000000000..c1685f8061 --- /dev/null +++ b/src/Earnings/SFA.DAS.Earnings.Api/LearnerData/PagedResult.cs @@ -0,0 +1,9 @@ +namespace SFA.DAS.Earnings.Api.LearnerData; + +public class PagedResult { + public List Apprenticeships { get; set; } = []; + public long TotalRecords { get; set; } + public int Page { get; set; } + public int PageSize { get; set; } + public int TotalPages { get; set; } +} \ No newline at end of file diff --git a/src/Earnings/SFA.DAS.Earnings.Api/Learnerdata/Apprenticeship.cs b/src/Earnings/SFA.DAS.Earnings.Api/Learnerdata/Apprenticeship.cs new file mode 100644 index 0000000000..8e7fa2c9bd --- /dev/null +++ b/src/Earnings/SFA.DAS.Earnings.Api/Learnerdata/Apprenticeship.cs @@ -0,0 +1,5 @@ +namespace SFA.DAS.Earnings.Api.LearnerData; + +public class Apprenticeship { + public long Uln { get; set; } +} \ No newline at end of file diff --git a/src/Earnings/SFA.DAS.Earnings.Api/Learnerdata/PagedResult.cs b/src/Earnings/SFA.DAS.Earnings.Api/Learnerdata/PagedResult.cs new file mode 100644 index 0000000000..c1685f8061 --- /dev/null +++ b/src/Earnings/SFA.DAS.Earnings.Api/Learnerdata/PagedResult.cs @@ -0,0 +1,9 @@ +namespace SFA.DAS.Earnings.Api.LearnerData; + +public class PagedResult { + public List Apprenticeships { get; set; } = []; + public long TotalRecords { get; set; } + public int Page { get; set; } + public int PageSize { get; set; } + public int TotalPages { get; set; } +} \ No newline at end of file diff --git a/src/Earnings/SFA.DAS.Earnings.Api/Startup.cs b/src/Earnings/SFA.DAS.Earnings.Api/Startup.cs index 9bccebb547..9d81a96216 100644 --- a/src/Earnings/SFA.DAS.Earnings.Api/Startup.cs +++ b/src/Earnings/SFA.DAS.Earnings.Api/Startup.cs @@ -6,31 +6,27 @@ using SFA.DAS.Api.Common.AppStart; using SFA.DAS.Api.Common.Configuration; using SFA.DAS.Earnings.Api.AppStart; +using SFA.DAS.Earnings.Api.Controllers; +using SFA.DAS.Earnings.Api.Learnerdata; +using SFA.DAS.Earnings.Application.LearnerData; using SFA.DAS.SharedOuterApi.AppStart; using SFA.DAS.SharedOuterApi.Infrastructure.HealthCheck; namespace SFA.DAS.Earnings.Api; [ExcludeFromCodeCoverage] -public class Startup +public class Startup(IConfiguration configuration, IWebHostEnvironment env) { - private readonly IWebHostEnvironment _env; - private readonly IConfiguration _configuration; - - public Startup(IConfiguration configuration, IWebHostEnvironment env) - { - _env = env; - _configuration = configuration.BuildSharedConfiguration(); - } + private readonly IConfiguration _configuration = configuration.BuildSharedConfiguration(); public void ConfigureServices(IServiceCollection services) { services.AddNLog(); services.AddOptions(); - services.AddSingleton(_env); + services.AddSingleton(env); services.AddConfigurationOptions(_configuration); - + if (!_configuration.IsLocalOrDev()) { var azureAdConfiguration = _configuration @@ -45,6 +41,7 @@ public void ConfigureServices(IServiceCollection services) } services.AddMediatR(configuration => configuration.RegisterServicesFromAssembly(typeof(Earning).Assembly)); + services.AddSingleton(); services.AddServiceRegistration(_configuration); services diff --git a/src/Earnings/SFA.DAS.Earnings.UnitTests/Application/Earnings/GetAllEarningsQueryTestFixture.cs b/src/Earnings/SFA.DAS.Earnings.UnitTests/Application/Earnings/GetAllEarningsQueryTestFixture.cs index ff2811d559..cad3f6faed 100644 --- a/src/Earnings/SFA.DAS.Earnings.UnitTests/Application/Earnings/GetAllEarningsQueryTestFixture.cs +++ b/src/Earnings/SFA.DAS.Earnings.UnitTests/Application/Earnings/GetAllEarningsQueryTestFixture.cs @@ -64,13 +64,13 @@ public GetApprenticeshipsResponse BuildApprenticeshipsResponse(long ukprn) Key = Guid.NewGuid(), Episodes = new List { - new Episode + new() { Key = Guid.NewGuid(), TrainingCode = $"{Fixture.Create()} ", Prices = new List { - new EpisodePrice + new() { StartDate = new DateTime(2020, 1, 1), EndDate = new DateTime(2021, 1, 1), @@ -95,13 +95,13 @@ public GetApprenticeshipsResponse BuildApprenticeshipsResponse(long ukprn) Key = Guid.NewGuid(), Episodes = new List { - new Episode + new() { Key = Guid.NewGuid(), TrainingCode = $"{Fixture.Create()} ", Prices = new List { - new EpisodePrice + new() { StartDate = new DateTime(2020, 8, 1), EndDate = new DateTime(2021, 5, 2), @@ -110,7 +110,7 @@ public GetApprenticeshipsResponse BuildApprenticeshipsResponse(long ukprn) TotalPrice = 22500, FundingBandMaximum = 30000 }, - new EpisodePrice + new() { StartDate = new DateTime(2021, 5, 3), EndDate = new DateTime(2021, 7, 31), @@ -154,7 +154,7 @@ public GetFm36DataResponse BuildEarningsResponse(GetApprenticeshipsResponse appr FundingLineType = Fixture.Create(), Episodes = new List { - new SharedOuterApi.InnerApi.Responses.Earnings.Episode + new() { Key = apprenticeshipsResponse.Apprenticeships[0].Episodes[0].Key, NumberOfInstalments = 12, @@ -162,18 +162,18 @@ public GetFm36DataResponse BuildEarningsResponse(GetApprenticeshipsResponse appr OnProgramTotal = 12000, Instalments = new List { - new Instalment{ AcademicYear = 1920, DeliveryPeriod = 6, Amount = 1000 }, - new Instalment{ AcademicYear = 1920, DeliveryPeriod = 7, Amount = 1000 }, - new Instalment{ AcademicYear = 1920, DeliveryPeriod = 8, Amount = 1000 }, - new Instalment{ AcademicYear = 1920, DeliveryPeriod = 9, Amount = 1000 }, - new Instalment{ AcademicYear = 1920, DeliveryPeriod = 10, Amount = 1000 }, - new Instalment{ AcademicYear = 1920, DeliveryPeriod = 11, Amount = 1000 }, - new Instalment{ AcademicYear = 1920, DeliveryPeriod = 12, Amount = 1000 }, - new Instalment{ AcademicYear = 2021, DeliveryPeriod = 1, Amount = 1000 }, - new Instalment{ AcademicYear = 2021, DeliveryPeriod = 2, Amount = 1000 }, - new Instalment{ AcademicYear = 2021, DeliveryPeriod = 3, Amount = 1000 }, - new Instalment{ AcademicYear = 2021, DeliveryPeriod = 4, Amount = 1000 }, - new Instalment{ AcademicYear = 2021, DeliveryPeriod = 5, Amount = 1000 } + new() { AcademicYear = 1920, DeliveryPeriod = 6, Amount = 1000 }, + new() { AcademicYear = 1920, DeliveryPeriod = 7, Amount = 1000 }, + new() { AcademicYear = 1920, DeliveryPeriod = 8, Amount = 1000 }, + new() { AcademicYear = 1920, DeliveryPeriod = 9, Amount = 1000 }, + new() { AcademicYear = 1920, DeliveryPeriod = 10, Amount = 1000 }, + new() { AcademicYear = 1920, DeliveryPeriod = 11, Amount = 1000 }, + new() { AcademicYear = 1920, DeliveryPeriod = 12, Amount = 1000 }, + new() { AcademicYear = 2021, DeliveryPeriod = 1, Amount = 1000 }, + new() { AcademicYear = 2021, DeliveryPeriod = 2, Amount = 1000 }, + new() { AcademicYear = 2021, DeliveryPeriod = 3, Amount = 1000 }, + new() { AcademicYear = 2021, DeliveryPeriod = 4, Amount = 1000 }, + new() { AcademicYear = 2021, DeliveryPeriod = 5, Amount = 1000 } } } } @@ -185,7 +185,7 @@ public GetFm36DataResponse BuildEarningsResponse(GetApprenticeshipsResponse appr FundingLineType = Fixture.Create(), Episodes = new List { - new SharedOuterApi.InnerApi.Responses.Earnings.Episode + new() { Key = apprenticeshipsResponse.Apprenticeships[1].Episodes[0].Key, NumberOfInstalments = 12, @@ -193,18 +193,18 @@ public GetFm36DataResponse BuildEarningsResponse(GetApprenticeshipsResponse appr OnProgramTotal = 24000, Instalments = new List { - new Instalment{ AcademicYear = 2021, DeliveryPeriod = 1, Amount = 1875 }, - new Instalment{ AcademicYear = 2021, DeliveryPeriod = 2, Amount = 1875 }, - new Instalment{ AcademicYear = 2021, DeliveryPeriod = 3, Amount = 1875 }, - new Instalment{ AcademicYear = 2021, DeliveryPeriod = 4, Amount = 1875 }, - new Instalment{ AcademicYear = 2021, DeliveryPeriod = 5, Amount = 1875 }, - new Instalment{ AcademicYear = 2021, DeliveryPeriod = 6, Amount = 1875 }, - new Instalment{ AcademicYear = 2021, DeliveryPeriod = 7, Amount = 1875 }, - new Instalment{ AcademicYear = 2021, DeliveryPeriod = 8, Amount = 1875 }, - new Instalment{ AcademicYear = 2021, DeliveryPeriod = 9, Amount = 1875 }, - new Instalment{ AcademicYear = 2021, DeliveryPeriod = 10, Amount = 4375 }, - new Instalment{ AcademicYear = 2021, DeliveryPeriod = 11, Amount = 4375 }, - new Instalment{ AcademicYear = 2021, DeliveryPeriod = 12, Amount = 4375 } + new() { AcademicYear = 2021, DeliveryPeriod = 1, Amount = 1875 }, + new() { AcademicYear = 2021, DeliveryPeriod = 2, Amount = 1875 }, + new() { AcademicYear = 2021, DeliveryPeriod = 3, Amount = 1875 }, + new() { AcademicYear = 2021, DeliveryPeriod = 4, Amount = 1875 }, + new() { AcademicYear = 2021, DeliveryPeriod = 5, Amount = 1875 }, + new() { AcademicYear = 2021, DeliveryPeriod = 6, Amount = 1875 }, + new() { AcademicYear = 2021, DeliveryPeriod = 7, Amount = 1875 }, + new() { AcademicYear = 2021, DeliveryPeriod = 8, Amount = 1875 }, + new() { AcademicYear = 2021, DeliveryPeriod = 9, Amount = 1875 }, + new() { AcademicYear = 2021, DeliveryPeriod = 10, Amount = 4375 }, + new() { AcademicYear = 2021, DeliveryPeriod = 11, Amount = 4375 }, + new() { AcademicYear = 2021, DeliveryPeriod = 12, Amount = 4375 } } } } diff --git a/src/Earnings/SFA.DAS.Earnings.UnitTests/Application/LearnerData/GetLearnerDataHandlerTests.cs b/src/Earnings/SFA.DAS.Earnings.UnitTests/Application/LearnerData/GetLearnerDataHandlerTests.cs new file mode 100644 index 0000000000..775a47f8f4 --- /dev/null +++ b/src/Earnings/SFA.DAS.Earnings.UnitTests/Application/LearnerData/GetLearnerDataHandlerTests.cs @@ -0,0 +1,89 @@ +using AutoFixture.NUnit3; +using FluentAssertions; +using Moq; +using SFA.DAS.Earnings.Api.Learnerdata; +using SFA.DAS.Earnings.Application.LearnerData.GetLearnerData; +using SFA.DAS.Testing.AutoFixture; + +namespace SFA.DAS.Earnings.UnitTests.Application.LearnerData; + +public class GetLearnerDataHandlerTests +{ + [Test, MoqAutoData] + public async Task Handle_Should_Return_Correct_Learner_Data( + [Frozen] Mock datastoreMock, // Mock of the ILearnerDataStore + GetLearnerDataQueryHandler handler) + { + // Arrange + const long ukprn = 1000001; + const int academicYear = 2025; + const int page = 1; + const int pageSize = 10; + + var expectedLearnerData = new List { 100, 200, 300 }; + var totalRecords = 30; + + // Mock the datastore methods + datastoreMock.Setup(ds => ds.Search(ukprn, academicYear, page, pageSize)) + .Returns(expectedLearnerData); // Return pre-defined list of learner data + + datastoreMock.Setup(ds => ds.Count(ukprn, academicYear)) + .Returns(totalRecords); // Return total record count + + var query = new GetLearnerDataQuery(ukprn, academicYear, page, pageSize); + + // Act + var result = await handler.Handle(query, CancellationToken.None); + + // Assert + result.Should().NotBeNull(); + result.TotalRecords.Should().Be((uint)totalRecords); // Assert TotalRecords matches + result.Apprenticeships.Should().HaveCount(expectedLearnerData.Count); // Assert count of learner data + + // Check that each learner data matches expected Uln values + for (int i = 0; i < expectedLearnerData.Count; i++) + { + result.Apprenticeships[i].Uln.Should().Be(expectedLearnerData[i]); + } + + // Verify that the datastore methods were called + datastoreMock.Verify(ds => ds.Search(ukprn, academicYear, page, pageSize), Times.Once); + datastoreMock.Verify(ds => ds.Count(ukprn, academicYear), Times.Once); + } + + [Test, MoqAutoData] + public async Task Handle_Should_Return_Empty_Learner_Data_When_No_Results( + [Frozen] Mock datastoreMock, + GetLearnerDataQueryHandler handler) + { + // Arrange + const long ukprn = 1000001; + const int academicYear = 2025; + const int page = 1; + const int pageSize = 10; + + var expectedLearnerData = new List(); // No results + var totalRecords = 0; + + // Mock the datastore methods + datastoreMock.Setup(ds => ds.Search(ukprn, academicYear, page, pageSize)) + .Returns(expectedLearnerData); // Return an empty list + + datastoreMock.Setup(ds => ds.Count(ukprn, academicYear)) + .Returns(totalRecords); // Return 0 records + + var query = new GetLearnerDataQuery(ukprn, academicYear, page, pageSize); + + // Act + var result = await handler.Handle(query, CancellationToken.None); + + // Assert + result.Should().NotBeNull(); + result.TotalRecords.Should().Be((uint)totalRecords); // Assert TotalRecords is 0 + result.Apprenticeships.Should().BeEmpty(); // Assert no learner data returned + + // Verify that the datastore methods were called + datastoreMock.Verify(ds => ds.Search(ukprn, academicYear, page, pageSize), Times.Once); + datastoreMock.Verify(ds => ds.Count(ukprn, academicYear), Times.Once); + } +} \ No newline at end of file diff --git a/src/Earnings/SFA.DAS.Earnings.UnitTests/Application/LearnerData/LeanerDataStoreTests.cs b/src/Earnings/SFA.DAS.Earnings.UnitTests/Application/LearnerData/LeanerDataStoreTests.cs new file mode 100644 index 0000000000..52b90aff82 --- /dev/null +++ b/src/Earnings/SFA.DAS.Earnings.UnitTests/Application/LearnerData/LeanerDataStoreTests.cs @@ -0,0 +1,98 @@ +using FluentAssertions; +using SFA.DAS.Earnings.Application.LearnerData; + +namespace SFA.DAS.Earnings.UnitTests.Application.LearnerData; + +public class LeanerDataStoreTests +{ + [Test] + public void Pagination_happy_path_returns_correct_data() + { + var learnerData = new List + { + new(1, 21222, 2021, 100), + new(2, 21222, 2021, 200), + new(3, 21222, 2021, 300), + new(4, 21222, 2021, 400), + new(5, 21222, 2021, 500), + new(6, 21222, 2021, 600), + new(7, 21222, 2021, 700), + new(8, 21222, 2021, 800), + new(9, 21222, 2021, 900), + new(11, 21222, 2021, 1000), + new(12, 21222, 2021, 1100) + }; + + var store = new LearnerDataStore(learnerData); + + var results = store.Search(21222, 2021, 1, 3); + + results.Count.Should().Be(3); + results[0].Should().Be(100); + } + + [Test] + public void Sending_invalid_data_to_paging() + { + var learnerData = new List + { + new(1, 21222, 2021, 100), + new(2, 21222, 2021, 200), + new(3, 21222, 2021, 300), + new(4, 21222, 2021, 400), + new(5, 21222, 2021, 500), + new(6, 21222, 2021, 600), + new(7, 21222, 2021, 700), + new(8, 21222, 2021, 800), + new(9, 21222, 2021, 900), + new(10,21222, 2021, 1000), + new(11, 21222, 2021, 1100) + }; + + var store = new LearnerDataStore(learnerData); + + var results = store.Search(21222, 2021, 100, 3); + results.Should().BeEmpty(); + } + + [Test] + public void Count_returns_correct_data() + { + var learnerData = new List + { + new(1, 21222, 2021, 100), + new(2, 21222, 2021, 200), + new(3, 21222, 2021, 300), + new(4, 21222, 2021, 400), + new(5, 21222, 2021, 500), + new(6, 21222, 2021, 600), + new(7, 21222, 2021, 700), + new(8, 21222, 2021, 800), + new(9, 21222, 2021, 900), + new(10, 21222, 2021, 1000), + new(11, 21222, 2021, 1100), + new(12, 21223, 2021, 1001), + new(13, 21223, 2021, 2001), + new(14, 21223, 2021, 3001), + new(15, 21223, 2021, 4001), + new(16, 21224, 2021, 5001), + new(17, 21224, 2021, 6001), + new(18, 21224, 2021, 7001), + new(19, 21224, 2021, 8001), + new(20, 21224, 2021, 9001), + new(21, 21223, 2021, 10001), + new(22, 21223, 2223, 6001), + new(23, 21223, 2223, 70011), + new(24, 21223, 2223, 80011), + new(25, 21223, 2223, 90011), + new(26, 21223, 2223, 100011), + new(27, 21223, 2223, 1000112) + }; + + var store = new LearnerDataStore(learnerData); + + Assert.That(store.Count(21222, 2021), Is.EqualTo(11)); + Assert.That(store.Count(21223, 2021), Is.EqualTo(5)); + Assert.That(store.Count(21223, 2223), Is.EqualTo(6)); + } +} \ No newline at end of file diff --git a/src/Earnings/SFA.DAS.Earnings.UnitTests/SFA.DAS.Earnings.UnitTests.csproj b/src/Earnings/SFA.DAS.Earnings.UnitTests/SFA.DAS.Earnings.UnitTests.csproj index d2a330a2bb..6a81c8ec02 100644 --- a/src/Earnings/SFA.DAS.Earnings.UnitTests/SFA.DAS.Earnings.UnitTests.csproj +++ b/src/Earnings/SFA.DAS.Earnings.UnitTests/SFA.DAS.Earnings.UnitTests.csproj @@ -24,6 +24,7 @@ + @@ -36,6 +37,7 @@ + diff --git a/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/GetLearnerData/GetLearnerDataQuery.cs b/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/GetLearnerData/GetLearnerDataQuery.cs new file mode 100644 index 0000000000..5197245d84 --- /dev/null +++ b/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/GetLearnerData/GetLearnerDataQuery.cs @@ -0,0 +1,12 @@ +using MediatR; + +namespace SFA.DAS.Earnings.Application.LearnerData.GetLearnerData; + +public class GetLearnerDataQuery(long ukprn, int academicYear, int page, int pageSize) + : IRequest +{ + public long Ukprn { get; } = ukprn; + public int AcademicYear { get; } = academicYear; + public int Page { get; } = page; + public int PageSize { get; } = pageSize; +} \ No newline at end of file diff --git a/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/GetLearnerData/GetLearnerDataQueryHandler.cs b/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/GetLearnerData/GetLearnerDataQueryHandler.cs new file mode 100644 index 0000000000..46f797e323 --- /dev/null +++ b/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/GetLearnerData/GetLearnerDataQueryHandler.cs @@ -0,0 +1,24 @@ +using MediatR; +using SFA.DAS.Earnings.Api.Learnerdata; + +namespace SFA.DAS.Earnings.Application.LearnerData.GetLearnerData; + +public class GetLearnerDataQueryHandler(ILearnerDataStore datastore) + : IRequestHandler +{ + public Task Handle(GetLearnerDataQuery request, CancellationToken cancellationToken) + { + var results = datastore.Search(request.Ukprn, request.AcademicYear, request.Page, request.PageSize); + + var totalRecords = datastore.Count(request.Ukprn, request.AcademicYear); + + return Task.FromResult(new GetLearnerDataQueryResult + { + Apprenticeships = results.ConvertAll(uln => new ApprenticeshipResult + { + Uln = uln + }), + TotalRecords = totalRecords + }); + } +} \ No newline at end of file diff --git a/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/GetLearnerData/GetLearnerDataQueryResult.cs b/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/GetLearnerData/GetLearnerDataQueryResult.cs new file mode 100644 index 0000000000..baa665a993 --- /dev/null +++ b/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/GetLearnerData/GetLearnerDataQueryResult.cs @@ -0,0 +1,12 @@ +namespace SFA.DAS.Earnings.Application.LearnerData.GetLearnerData; + +public class GetLearnerDataQueryResult +{ + public long TotalRecords { get; init; } + public List Apprenticeships { get; set; } = []; +} + +public class ApprenticeshipResult +{ + public long Uln { get; set; } +} \ No newline at end of file diff --git a/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/ILearnerDataStore.cs b/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/ILearnerDataStore.cs new file mode 100644 index 0000000000..c6d6b155be --- /dev/null +++ b/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/ILearnerDataStore.cs @@ -0,0 +1,7 @@ +namespace SFA.DAS.Earnings.Api.Learnerdata; + +public interface ILearnerDataStore +{ + List Search(long ukprn, int academicYear, int page, int pageSize); + int Count(long ukprn, int academicYear); +} \ No newline at end of file diff --git a/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/LearnerDataCsvRecord.cs b/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/LearnerDataCsvRecord.cs new file mode 100644 index 0000000000..c5b5061e79 --- /dev/null +++ b/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/LearnerDataCsvRecord.cs @@ -0,0 +1,9 @@ +namespace SFA.DAS.Earnings.Application.LearnerData; + +public class LearnerDataCsvRecord(long id, long ukprn, int academicYear, long uln) +{ + public long Id { get; set; } = id; + public long Ukprn { get; set; } = ukprn; + public int AcademicYear { get; set; } = academicYear; + public long Uln { get; set; } = uln; +} \ No newline at end of file diff --git a/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/LearnerDataStore.cs b/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/LearnerDataStore.cs new file mode 100644 index 0000000000..569fc94775 --- /dev/null +++ b/src/Earnings/SFA.DAS.Earnings/Application/LearnerData/LearnerDataStore.cs @@ -0,0 +1,69 @@ +using System.Reflection; +using SFA.DAS.Earnings.Api.Learnerdata; + +namespace SFA.DAS.Earnings.Application.LearnerData; + +public class LearnerDataStore : ILearnerDataStore +{ + private readonly List _data = []; + + public LearnerDataStore() + { + try + { + var assembly = Assembly.GetExecutingAssembly(); + + var resourcePath = "SFA.DAS.Earnings.cannedLearnerData.csv"; + + using var stream = assembly.GetManifestResourceStream(resourcePath); + if (stream == null) + { + throw new FileNotFoundException("The embedded resource was not found.", resourcePath); + } + + using var reader = new StreamReader(stream); + string line; + long count = 0; + + while ((line = reader.ReadLine()) != null) + { + var entries = line.Split(','); + + if (entries.Length == 3) + { + _data.Add(new LearnerDataCsvRecord(++count, long.Parse(entries[0]), int.Parse(entries[1]), long.Parse(entries[2]) + )); + } + else + { + Console.WriteLine($"Skipping invalid line: {line}"); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"Error loading resource: {ex.Message}"); + } + } + + public LearnerDataStore(List data) + { + _data = data; + } + + public List Search(long ukprn, int academicYear, int page, int pageSize) + { + return _data + .Where(s => s.Ukprn == ukprn && s.AcademicYear == academicYear) + .OrderBy(l => l.Id) + .Skip(pageSize * (page - 1)) + .Take(pageSize) + .Select(s => s.Uln) + .ToList(); + } + + public int Count(long ukprn, int academicYear) + { + return _data.Count(s => s.Ukprn == ukprn && s.AcademicYear == academicYear); + } +} \ No newline at end of file diff --git a/src/Earnings/SFA.DAS.Earnings/SFA.DAS.Earnings.csproj b/src/Earnings/SFA.DAS.Earnings/SFA.DAS.Earnings.csproj index 3d5d8f7b48..924c0257f6 100644 --- a/src/Earnings/SFA.DAS.Earnings/SFA.DAS.Earnings.csproj +++ b/src/Earnings/SFA.DAS.Earnings/SFA.DAS.Earnings.csproj @@ -14,4 +14,10 @@ + + + PreserveNewest + + + diff --git a/src/Earnings/SFA.DAS.Earnings/cannedLearnerData.csv b/src/Earnings/SFA.DAS.Earnings/cannedLearnerData.csv new file mode 100644 index 0000000000..83aa9272d2 --- /dev/null +++ b/src/Earnings/SFA.DAS.Earnings/cannedLearnerData.csv @@ -0,0 +1,1000 @@ +12345678,2223,0000000001 +12345678,2223,0000000002 +12345678,2223,0000000003 +12345678,2223,0000000004 +12345678,2223,0000000005 +12345678,2223,0000000006 +12345678,2223,0000000007 +12345678,2223,0000000008 +12345678,2223,0000000009 +12345678,2223,0000000010 +12345678,2223,0000000011 +12345678,2223,0000000012 +12345678,2223,0000000013 +12345678,2223,0000000014 +12345678,2223,0000000015 +12345678,2223,0000000016 +12345678,2223,0000000017 +12345678,2223,0000000018 +12345678,2223,0000000019 +12345678,2223,0000000020 +12345678,2223,0000000021 +12345678,2223,0000000022 +12345678,2223,0000000023 +12345678,2223,0000000024 +12345678,2223,0000000025 +12345678,2223,0000000026 +12345678,2223,0000000027 +12345678,2223,0000000028 +12345678,2223,0000000029 +12345678,2223,0000000030 +12345678,2223,0000000031 +12345678,2223,0000000032 +12345678,2223,0000000033 +12345678,2223,0000000034 +12345678,2223,0000000035 +12345678,2223,0000000036 +12345678,2223,0000000037 +12345678,2223,0000000038 +12345678,2223,0000000039 +12345678,2223,0000000040 +12345678,2223,0000000041 +12345678,2223,0000000042 +12345678,2223,0000000043 +12345678,2223,0000000044 +12345678,2223,0000000045 +12345678,2223,0000000046 +12345678,2223,0000000047 +12345678,2223,0000000048 +12345678,2223,0000000049 +12345678,2223,0000000050 +12345678,2223,0000000051 +12345678,2223,0000000052 +12345678,2223,0000000053 +12345678,2223,0000000054 +12345678,2223,0000000055 +12345678,2223,0000000056 +12345678,2223,0000000057 +12345678,2223,0000000058 +12345678,2223,0000000059 +12345678,2223,0000000060 +12345678,2223,0000000061 +12345678,2223,0000000062 +12345678,2223,0000000063 +12345678,2223,0000000064 +12345678,2223,0000000065 +12345678,2223,0000000066 +12345678,2223,0000000067 +12345678,2223,0000000068 +12345678,2223,0000000069 +12345678,2223,0000000070 +12345678,2223,0000000071 +12345678,2223,0000000072 +12345678,2223,0000000073 +12345678,2223,0000000074 +12345678,2223,0000000075 +12345678,2223,0000000076 +12345678,2223,0000000077 +12345678,2223,0000000078 +12345678,2223,0000000079 +12345678,2223,0000000080 +12345678,2223,0000000081 +12345678,2223,0000000082 +12345678,2223,0000000083 +12345678,2223,0000000084 +12345678,2223,0000000085 +12345678,2223,0000000086 +12345678,2223,0000000087 +12345678,2223,0000000088 +12345678,2223,0000000089 +12345678,2223,0000000090 +12345678,2223,0000000091 +12345678,2223,0000000092 +12345678,2223,0000000093 +12345678,2223,0000000094 +12345678,2223,0000000095 +12345678,2223,0000000096 +12345678,2223,0000000097 +12345678,2223,0000000098 +12345678,2223,0000000099 +12345678,2223,0000000100 +12345678,2223,0000000101 +12345678,2223,0000000102 +12345678,2223,0000000103 +12345678,2223,0000000104 +12345678,2223,0000000105 +12345678,2223,0000000106 +12345678,2223,0000000107 +12345678,2223,0000000108 +12345678,2223,0000000109 +12345678,2223,0000000110 +12345678,2223,0000000111 +12345678,2223,0000000112 +12345678,2223,0000000113 +12345678,2223,0000000114 +12345678,2223,0000000115 +12345678,2223,0000000116 +12345678,2223,0000000117 +12345678,2223,0000000118 +12345678,2223,0000000119 +12345678,2223,0000000120 +12345678,2223,0000000121 +12345678,2223,0000000122 +12345678,2223,0000000123 +12345678,2223,0000000124 +12345678,2223,0000000125 +12345678,2223,0000000126 +12345678,2223,0000000127 +12345678,2223,0000000128 +12345678,2223,0000000129 +12345678,2223,0000000130 +12345678,2223,0000000131 +12345678,2223,0000000132 +12345678,2223,0000000133 +12345678,2223,0000000134 +12345678,2223,0000000135 +12345678,2223,0000000136 +12345678,2223,0000000137 +12345678,2223,0000000138 +12345678,2223,0000000139 +12345678,2223,0000000140 +12345678,2223,0000000141 +12345678,2223,0000000142 +12345678,2223,0000000143 +12345678,2223,0000000144 +12345678,2223,0000000145 +12345678,2223,0000000146 +12345678,2223,0000000147 +12345678,2223,0000000148 +12345678,2223,0000000149 +12345678,2223,0000000150 +12345678,2223,0000000151 +12345678,2223,0000000152 +12345678,2223,0000000153 +12345678,2223,0000000154 +12345678,2223,0000000155 +12345678,2223,0000000156 +12345678,2223,0000000157 +12345678,2223,0000000158 +12345678,2223,0000000159 +12345678,2223,0000000160 +12345678,2223,0000000161 +12345678,2223,0000000162 +12345678,2223,0000000163 +12345678,2223,0000000164 +12345678,2223,0000000165 +12345678,2223,0000000166 +12345678,2223,0000000167 +12345678,2223,0000000168 +12345678,2223,0000000169 +12345678,2223,0000000170 +12345678,2223,0000000171 +12345678,2223,0000000172 +12345678,2223,0000000173 +12345678,2223,0000000174 +12345678,2223,0000000175 +12345678,2223,0000000176 +12345678,2223,0000000177 +12345678,2223,0000000178 +12345678,2223,0000000179 +12345678,2223,0000000180 +12345678,2223,0000000181 +12345678,2223,0000000182 +12345678,2223,0000000183 +12345678,2223,0000000184 +12345678,2223,0000000185 +12345678,2223,0000000186 +12345678,2223,0000000187 +12345678,2223,0000000188 +12345678,2223,0000000189 +12345678,2223,0000000190 +12345678,2223,0000000191 +12345678,2223,0000000192 +12345678,2223,0000000193 +12345678,2223,0000000194 +12345678,2223,0000000195 +12345678,2223,0000000196 +12345678,2223,0000000197 +12345678,2223,0000000198 +12345678,2223,0000000199 +12345678,2223,0000000200 +12345678,2223,0000000201 +12345678,2223,0000000202 +12345678,2223,0000000203 +12345678,2223,0000000204 +12345678,2223,0000000205 +12345678,2223,0000000206 +12345678,2223,0000000207 +12345678,2223,0000000208 +12345678,2223,0000000209 +12345678,2223,0000000210 +12345678,2223,0000000211 +12345678,2223,0000000212 +12345678,2223,0000000213 +12345678,2223,0000000214 +12345678,2223,0000000215 +12345678,2223,0000000216 +12345678,2223,0000000217 +12345678,2223,0000000218 +12345678,2223,0000000219 +12345678,2223,0000000220 +12345678,2223,0000000221 +12345678,2223,0000000222 +12345678,2223,0000000223 +12345678,2223,0000000224 +12345678,2223,0000000225 +12345678,2223,0000000226 +12345678,2223,0000000227 +12345678,2223,0000000228 +12345678,2223,0000000229 +12345678,2223,0000000230 +12345678,2223,0000000231 +12345678,2223,0000000232 +12345678,2223,0000000233 +12345678,2223,0000000234 +12345678,2223,0000000235 +12345678,2223,0000000236 +12345678,2223,0000000237 +12345678,2223,0000000238 +12345678,2223,0000000239 +12345678,2223,0000000240 +12345678,2223,0000000241 +12345678,2223,0000000242 +12345678,2223,0000000243 +12345678,2223,0000000244 +12345678,2223,0000000245 +12345678,2223,0000000246 +12345678,2223,0000000247 +12345678,2223,0000000248 +12345678,2223,0000000249 +12345678,2223,0000000250 +12345678,2223,0000000251 +12345678,2223,0000000252 +12345678,2223,0000000253 +12345678,2223,0000000254 +12345678,2223,0000000255 +12345678,2223,0000000256 +12345678,2223,0000000257 +12345678,2223,0000000258 +12345678,2223,0000000259 +12345678,2223,0000000260 +12345678,2223,0000000261 +12345678,2223,0000000262 +12345678,2223,0000000263 +12345678,2223,0000000264 +12345678,2223,0000000265 +12345678,2223,0000000266 +12345678,2223,0000000267 +12345678,2223,0000000268 +12345678,2223,0000000269 +12345678,2223,0000000270 +12345678,2223,0000000271 +12345678,2223,0000000272 +12345678,2223,0000000273 +12345678,2223,0000000274 +12345678,2223,0000000275 +12345678,2223,0000000276 +12345678,2223,0000000277 +12345678,2223,0000000278 +12345678,2223,0000000279 +12345678,2223,0000000280 +12345678,2223,0000000281 +12345678,2223,0000000282 +12345678,2223,0000000283 +12345678,2223,0000000284 +12345678,2223,0000000285 +12345678,2223,0000000286 +12345678,2223,0000000287 +12345678,2223,0000000288 +12345678,2223,0000000289 +12345678,2223,0000000290 +12345678,2223,0000000291 +12345678,2223,0000000292 +12345678,2223,0000000293 +12345678,2223,0000000294 +12345678,2223,0000000295 +12345678,2223,0000000296 +12345678,2223,0000000297 +12345678,2223,0000000298 +12345678,2223,0000000299 +12345678,2223,0000000300 +12345678,2223,0000000301 +12345678,2223,0000000302 +12345678,2223,0000000303 +12345678,2223,0000000304 +12345678,2223,0000000305 +12345678,2223,0000000306 +12345678,2223,0000000307 +12345678,2223,0000000308 +12345678,2223,0000000309 +12345678,2223,0000000310 +12345678,2223,0000000311 +12345678,2223,0000000312 +12345678,2223,0000000313 +12345678,2223,0000000314 +12345678,2223,0000000315 +12345678,2223,0000000316 +12345678,2223,0000000317 +12345678,2223,0000000318 +12345678,2223,0000000319 +12345678,2223,0000000320 +12345678,2223,0000000321 +12345678,2223,0000000322 +12345678,2223,0000000323 +12345678,2223,0000000324 +12345678,2223,0000000325 +12345678,2223,0000000326 +12345678,2223,0000000327 +12345678,2223,0000000328 +12345678,2223,0000000329 +12345678,2223,0000000330 +12345678,2223,0000000331 +12345678,2223,0000000332 +12345678,2223,0000000333 +12345678,2223,0000000334 +12345678,2223,0000000335 +12345678,2223,0000000336 +12345678,2223,0000000337 +12345678,2223,0000000338 +12345678,2223,0000000339 +12345678,2223,0000000340 +12345678,2223,0000000341 +12345678,2223,0000000342 +12345678,2223,0000000343 +12345678,2223,0000000344 +12345678,2223,0000000345 +12345678,2223,0000000346 +12345678,2223,0000000347 +12345678,2223,0000000348 +12345678,2223,0000000349 +12345678,2223,0000000350 +12345678,2223,0000000351 +12345678,2223,0000000352 +12345678,2223,0000000353 +12345678,2223,0000000354 +12345678,2223,0000000355 +12345678,2223,0000000356 +12345678,2223,0000000357 +12345678,2223,0000000358 +12345678,2223,0000000359 +12345678,2223,0000000360 +12345678,2223,0000000361 +12345678,2223,0000000362 +12345678,2223,0000000363 +12345678,2223,0000000364 +12345678,2223,0000000365 +12345678,2223,0000000366 +12345678,2223,0000000367 +12345678,2223,0000000368 +12345678,2223,0000000369 +12345678,2223,0000000370 +12345678,2223,0000000371 +12345678,2223,0000000372 +12345678,2223,0000000373 +12345678,2223,0000000374 +12345678,2223,0000000375 +12345678,2223,0000000376 +12345678,2223,0000000377 +12345678,2223,0000000378 +12345678,2223,0000000379 +12345678,2223,0000000380 +12345678,2223,0000000381 +12345678,2223,0000000382 +12345678,2223,0000000383 +12345678,2223,0000000384 +12345678,2223,0000000385 +12345678,2223,0000000386 +12345678,2223,0000000387 +12345678,2223,0000000388 +12345678,2223,0000000389 +12345678,2223,0000000390 +12345678,2223,0000000391 +12345678,2223,0000000392 +12345678,2223,0000000393 +12345678,2223,0000000394 +12345678,2223,0000000395 +12345678,2223,0000000396 +12345678,2223,0000000397 +12345678,2223,0000000398 +12345678,2223,0000000399 +12345678,2223,0000000400 +12345678,2223,0000000401 +12345678,2223,0000000402 +12345678,2223,0000000403 +12345678,2223,0000000404 +12345678,2223,0000000405 +12345678,2223,0000000406 +12345678,2223,0000000407 +12345678,2223,0000000408 +12345678,2223,0000000409 +12345678,2223,0000000410 +12345678,2223,0000000411 +12345678,2223,0000000412 +12345678,2223,0000000413 +12345678,2223,0000000414 +12345678,2223,0000000415 +12345678,2223,0000000416 +12345678,2223,0000000417 +12345678,2223,0000000418 +12345678,2223,0000000419 +12345678,2223,0000000420 +12345678,2223,0000000421 +12345678,2223,0000000422 +12345678,2223,0000000423 +12345678,2223,0000000424 +12345678,2223,0000000425 +12345678,2223,0000000426 +12345678,2223,0000000427 +12345678,2223,0000000428 +12345678,2223,0000000429 +12345678,2223,0000000430 +12345678,2223,0000000431 +12345678,2223,0000000432 +12345678,2223,0000000433 +12345678,2223,0000000434 +12345678,2223,0000000435 +12345678,2223,0000000436 +12345678,2223,0000000437 +12345678,2223,0000000438 +12345678,2223,0000000439 +12345678,2223,0000000440 +12345678,2223,0000000441 +12345678,2223,0000000442 +12345678,2223,0000000443 +12345678,2223,0000000444 +12345678,2223,0000000445 +12345678,2223,0000000446 +12345678,2223,0000000447 +12345678,2223,0000000448 +12345678,2223,0000000449 +12345678,2223,0000000450 +12345678,2223,0000000451 +12345678,2223,0000000452 +12345678,2223,0000000453 +12345678,2223,0000000454 +12345678,2223,0000000455 +12345678,2223,0000000456 +12345678,2223,0000000457 +12345678,2223,0000000458 +12345678,2223,0000000459 +12345678,2223,0000000460 +12345678,2223,0000000461 +12345678,2223,0000000462 +12345678,2223,0000000463 +12345678,2223,0000000464 +12345678,2223,0000000465 +12345678,2223,0000000466 +12345678,2223,0000000467 +12345678,2223,0000000468 +12345678,2223,0000000469 +12345678,2223,0000000470 +12345678,2223,0000000471 +12345678,2223,0000000472 +12345678,2223,0000000473 +12345678,2223,0000000474 +12345678,2223,0000000475 +12345678,2223,0000000476 +12345678,2223,0000000477 +12345678,2223,0000000478 +12345678,2223,0000000479 +12345678,2223,0000000480 +12345678,2223,0000000481 +12345678,2223,0000000482 +12345678,2223,0000000483 +12345678,2223,0000000484 +12345678,2223,0000000485 +12345678,2223,0000000486 +12345678,2223,0000000487 +12345678,2223,0000000488 +12345678,2223,0000000489 +12345678,2223,0000000490 +12345678,2223,0000000491 +12345678,2223,0000000492 +12345678,2223,0000000493 +12345678,2223,0000000494 +12345678,2223,0000000495 +12345678,2223,0000000496 +12345678,2223,0000000497 +12345678,2223,0000000498 +12345678,2223,0000000499 +12345678,2223,0000000500 +12345678,2223,0000000501 +12345678,2223,0000000502 +12345678,2223,0000000503 +12345678,2223,0000000504 +12345678,2223,0000000505 +12345678,2223,0000000506 +12345678,2223,0000000507 +12345678,2223,0000000508 +12345678,2223,0000000509 +12345678,2223,0000000510 +12345678,2223,0000000511 +12345678,2223,0000000512 +12345678,2223,0000000513 +12345678,2223,0000000514 +12345678,2223,0000000515 +12345678,2223,0000000516 +12345678,2223,0000000517 +12345678,2223,0000000518 +12345678,2223,0000000519 +12345678,2223,0000000520 +12345678,2223,0000000521 +12345678,2223,0000000522 +12345678,2223,0000000523 +12345678,2223,0000000524 +12345678,2223,0000000525 +12345678,2223,0000000526 +12345678,2223,0000000527 +12345678,2223,0000000528 +12345678,2223,0000000529 +12345678,2223,0000000530 +12345678,2223,0000000531 +12345678,2223,0000000532 +12345678,2223,0000000533 +12345678,2223,0000000534 +12345678,2223,0000000535 +12345678,2223,0000000536 +12345678,2223,0000000537 +12345678,2223,0000000538 +12345678,2223,0000000539 +12345678,2223,0000000540 +12345678,2223,0000000541 +12345678,2223,0000000542 +12345678,2223,0000000543 +12345678,2223,0000000544 +12345678,2223,0000000545 +12345678,2223,0000000546 +12345678,2223,0000000547 +12345678,2223,0000000548 +12345678,2223,0000000549 +12345678,2223,0000000550 +12345678,2223,0000000551 +12345678,2223,0000000552 +12345678,2223,0000000553 +12345678,2223,0000000554 +12345678,2223,0000000555 +12345678,2223,0000000556 +12345678,2223,0000000557 +12345678,2223,0000000558 +12345678,2223,0000000559 +12345678,2223,0000000560 +12345678,2223,0000000561 +12345678,2223,0000000562 +12345678,2223,0000000563 +12345678,2223,0000000564 +12345678,2223,0000000565 +12345678,2223,0000000566 +12345678,2223,0000000567 +12345678,2223,0000000568 +12345678,2223,0000000569 +12345678,2223,0000000570 +12345678,2223,0000000571 +12345678,2223,0000000572 +12345678,2223,0000000573 +12345678,2223,0000000574 +12345678,2223,0000000575 +12345678,2223,0000000576 +12345678,2223,0000000577 +12345678,2223,0000000578 +12345678,2223,0000000579 +12345678,2223,0000000580 +12345678,2223,0000000581 +12345678,2223,0000000582 +12345678,2223,0000000583 +12345678,2223,0000000584 +12345678,2223,0000000585 +12345678,2223,0000000586 +12345678,2223,0000000587 +12345678,2223,0000000588 +12345678,2223,0000000589 +12345678,2223,0000000590 +12345678,2223,0000000591 +12345678,2223,0000000592 +12345678,2223,0000000593 +12345678,2223,0000000594 +12345678,2223,0000000595 +12345678,2223,0000000596 +12345678,2223,0000000597 +12345678,2223,0000000598 +12345678,2223,0000000599 +12345678,2223,0000000600 +12345678,2223,0000000601 +12345678,2223,0000000602 +12345678,2223,0000000603 +12345678,2223,0000000604 +12345678,2223,0000000605 +12345678,2223,0000000606 +12345678,2223,0000000607 +12345678,2223,0000000608 +12345678,2223,0000000609 +12345678,2223,0000000610 +12345678,2223,0000000611 +12345678,2223,0000000612 +12345678,2223,0000000613 +12345678,2223,0000000614 +12345678,2223,0000000615 +12345678,2223,0000000616 +12345678,2223,0000000617 +12345678,2223,0000000618 +12345678,2223,0000000619 +12345678,2223,0000000620 +12345678,2223,0000000621 +12345678,2223,0000000622 +12345678,2223,0000000623 +12345678,2223,0000000624 +12345678,2223,0000000625 +12345678,2223,0000000626 +12345678,2223,0000000627 +12345678,2223,0000000628 +12345678,2223,0000000629 +12345678,2223,0000000630 +12345678,2223,0000000631 +12345678,2223,0000000632 +12345678,2223,0000000633 +12345678,2223,0000000634 +12345678,2223,0000000635 +12345678,2223,0000000636 +12345678,2223,0000000637 +12345678,2223,0000000638 +12345678,2223,0000000639 +12345678,2223,0000000640 +12345678,2223,0000000641 +12345678,2223,0000000642 +12345678,2223,0000000643 +12345678,2223,0000000644 +12345678,2223,0000000645 +12345678,2223,0000000646 +12345678,2223,0000000647 +12345678,2223,0000000648 +12345678,2223,0000000649 +12345678,2223,0000000650 +12345678,2223,0000000651 +12345678,2223,0000000652 +12345678,2223,0000000653 +12345678,2223,0000000654 +12345678,2223,0000000655 +12345678,2223,0000000656 +12345678,2223,0000000657 +12345678,2223,0000000658 +12345678,2223,0000000659 +12345678,2223,0000000660 +12345678,2223,0000000661 +12345678,2223,0000000662 +12345678,2223,0000000663 +12345678,2223,0000000664 +12345678,2223,0000000665 +12345678,2223,0000000666 +12345678,2223,0000000667 +12345678,2223,0000000668 +12345678,2223,0000000669 +12345678,2223,0000000670 +12345678,2223,0000000671 +12345678,2223,0000000672 +12345678,2223,0000000673 +12345678,2223,0000000674 +12345678,2223,0000000675 +12345678,2223,0000000676 +12345678,2223,0000000677 +12345678,2223,0000000678 +12345678,2223,0000000679 +12345678,2223,0000000680 +12345678,2223,0000000681 +12345678,2223,0000000682 +12345678,2223,0000000683 +12345678,2223,0000000684 +12345678,2223,0000000685 +12345678,2223,0000000686 +12345678,2223,0000000687 +12345678,2223,0000000688 +12345678,2223,0000000689 +12345678,2223,0000000690 +12345678,2223,0000000691 +12345678,2223,0000000692 +12345678,2223,0000000693 +12345678,2223,0000000694 +12345678,2223,0000000695 +12345678,2223,0000000696 +12345678,2223,0000000697 +12345678,2223,0000000698 +12345678,2223,0000000699 +12345678,2223,0000000700 +12345678,2223,0000000701 +12345678,2223,0000000702 +12345678,2223,0000000703 +12345678,2223,0000000704 +12345678,2223,0000000705 +12345678,2223,0000000706 +12345678,2223,0000000707 +12345678,2223,0000000708 +12345678,2223,0000000709 +12345678,2223,0000000710 +12345678,2223,0000000711 +12345678,2223,0000000712 +12345678,2223,0000000713 +12345678,2223,0000000714 +12345678,2223,0000000715 +12345678,2223,0000000716 +12345678,2223,0000000717 +12345678,2223,0000000718 +12345678,2223,0000000719 +12345678,2223,0000000720 +12345678,2223,0000000721 +12345678,2223,0000000722 +12345678,2223,0000000723 +12345678,2223,0000000724 +12345678,2223,0000000725 +12345678,2223,0000000726 +12345678,2223,0000000727 +12345678,2223,0000000728 +12345678,2223,0000000729 +12345678,2223,0000000730 +12345678,2223,0000000731 +12345678,2223,0000000732 +12345678,2223,0000000733 +12345678,2223,0000000734 +12345678,2223,0000000735 +12345678,2223,0000000736 +12345678,2223,0000000737 +12345678,2223,0000000738 +12345678,2223,0000000739 +12345678,2223,0000000740 +12345678,2223,0000000741 +12345678,2223,0000000742 +12345678,2223,0000000743 +12345678,2223,0000000744 +12345678,2223,0000000745 +12345678,2223,0000000746 +12345678,2223,0000000747 +12345678,2223,0000000748 +12345678,2223,0000000749 +12345678,2223,0000000750 +12345678,2223,0000000751 +12345678,2223,0000000752 +12345678,2223,0000000753 +12345678,2223,0000000754 +12345678,2223,0000000755 +12345678,2223,0000000756 +12345678,2223,0000000757 +12345678,2223,0000000758 +12345678,2223,0000000759 +12345678,2223,0000000760 +12345678,2223,0000000761 +12345678,2223,0000000762 +12345678,2223,0000000763 +12345678,2223,0000000764 +12345678,2223,0000000765 +12345678,2223,0000000766 +12345678,2223,0000000767 +12345678,2223,0000000768 +12345678,2223,0000000769 +12345678,2223,0000000770 +12345678,2223,0000000771 +12345678,2223,0000000772 +12345678,2223,0000000773 +12345678,2223,0000000774 +12345678,2223,0000000775 +12345678,2223,0000000776 +12345678,2223,0000000777 +12345678,2223,0000000778 +12345678,2223,0000000779 +12345678,2223,0000000780 +12345678,2223,0000000781 +12345678,2223,0000000782 +12345678,2223,0000000783 +12345678,2223,0000000784 +12345678,2223,0000000785 +12345678,2223,0000000786 +12345678,2223,0000000787 +12345678,2223,0000000788 +12345678,2223,0000000789 +12345678,2223,0000000790 +12345678,2223,0000000791 +12345678,2223,0000000792 +12345678,2223,0000000793 +12345678,2223,0000000794 +12345678,2223,0000000795 +12345678,2223,0000000796 +12345678,2223,0000000797 +12345678,2223,0000000798 +12345678,2223,0000000799 +12345678,2223,0000000800 +12345678,2223,0000000801 +12345678,2223,0000000802 +12345678,2223,0000000803 +12345678,2223,0000000804 +12345678,2223,0000000805 +12345678,2223,0000000806 +12345678,2223,0000000807 +12345678,2223,0000000808 +12345678,2223,0000000809 +12345678,2223,0000000810 +12345678,2223,0000000811 +12345678,2223,0000000812 +12345678,2223,0000000813 +12345678,2223,0000000814 +12345678,2223,0000000815 +12345678,2223,0000000816 +12345678,2223,0000000817 +12345678,2223,0000000818 +12345678,2223,0000000819 +12345678,2223,0000000820 +12345678,2223,0000000821 +12345678,2223,0000000822 +12345678,2223,0000000823 +12345678,2223,0000000824 +12345678,2223,0000000825 +12345678,2223,0000000826 +12345678,2223,0000000827 +12345678,2223,0000000828 +12345678,2223,0000000829 +12345678,2223,0000000830 +12345678,2223,0000000831 +12345678,2223,0000000832 +12345678,2223,0000000833 +12345678,2223,0000000834 +12345678,2223,0000000835 +12345678,2223,0000000836 +12345678,2223,0000000837 +12345678,2223,0000000838 +12345678,2223,0000000839 +12345678,2223,0000000840 +12345678,2223,0000000841 +12345678,2223,0000000842 +12345678,2223,0000000843 +12345678,2223,0000000844 +12345678,2223,0000000845 +12345678,2223,0000000846 +12345678,2223,0000000847 +12345678,2223,0000000848 +12345678,2223,0000000849 +12345678,2223,0000000850 +12345678,2223,0000000851 +12345678,2223,0000000852 +12345678,2223,0000000853 +12345678,2223,0000000854 +12345678,2223,0000000855 +12345678,2223,0000000856 +12345678,2223,0000000857 +12345678,2223,0000000858 +12345678,2223,0000000859 +12345678,2223,0000000860 +12345678,2223,0000000861 +12345678,2223,0000000862 +12345678,2223,0000000863 +12345678,2223,0000000864 +12345678,2223,0000000865 +12345678,2223,0000000866 +12345678,2223,0000000867 +12345678,2223,0000000868 +12345678,2223,0000000869 +12345678,2223,0000000870 +12345678,2223,0000000871 +12345678,2223,0000000872 +12345678,2223,0000000873 +12345678,2223,0000000874 +12345678,2223,0000000875 +12345678,2223,0000000876 +12345678,2223,0000000877 +12345678,2223,0000000878 +12345678,2223,0000000879 +12345678,2223,0000000880 +12345678,2223,0000000881 +12345678,2223,0000000882 +12345678,2223,0000000883 +12345678,2223,0000000884 +12345678,2223,0000000885 +12345678,2223,0000000886 +12345678,2223,0000000887 +12345678,2223,0000000888 +12345678,2223,0000000889 +12345678,2223,0000000890 +12345678,2223,0000000891 +12345678,2223,0000000892 +12345678,2223,0000000893 +12345678,2223,0000000894 +12345678,2223,0000000895 +12345678,2223,0000000896 +12345678,2223,0000000897 +12345678,2223,0000000898 +12345678,2223,0000000899 +12345678,2223,0000000900 +12345678,2223,0000000901 +12345678,2223,0000000902 +12345678,2223,0000000903 +12345678,2223,0000000904 +12345678,2223,0000000905 +12345678,2223,0000000906 +12345678,2223,0000000907 +12345678,2223,0000000908 +12345678,2223,0000000909 +12345678,2223,0000000910 +12345678,2223,0000000911 +12345678,2223,0000000912 +12345678,2223,0000000913 +12345678,2223,0000000914 +12345678,2223,0000000915 +12345678,2223,0000000916 +12345678,2223,0000000917 +12345678,2223,0000000918 +12345678,2223,0000000919 +12345678,2223,0000000920 +12345678,2223,0000000921 +12345678,2223,0000000922 +12345678,2223,0000000923 +12345678,2223,0000000924 +12345678,2223,0000000925 +12345678,2223,0000000926 +12345678,2223,0000000927 +12345678,2223,0000000928 +12345678,2223,0000000929 +12345678,2223,0000000930 +12345678,2223,0000000931 +12345678,2223,0000000932 +12345678,2223,0000000933 +12345678,2223,0000000934 +12345678,2223,0000000935 +12345678,2223,0000000936 +12345678,2223,0000000937 +12345678,2223,0000000938 +12345678,2223,0000000939 +12345678,2223,0000000940 +12345678,2223,0000000941 +12345678,2223,0000000942 +12345678,2223,0000000943 +12345678,2223,0000000944 +12345678,2223,0000000945 +12345678,2223,0000000946 +12345678,2223,0000000947 +12345678,2223,0000000948 +12345678,2223,0000000949 +12345678,2223,0000000950 +12345678,2223,0000000951 +12345678,2223,0000000952 +12345678,2223,0000000953 +12345678,2223,0000000954 +12345678,2223,0000000955 +12345678,2223,0000000956 +12345678,2223,0000000957 +12345678,2223,0000000958 +12345678,2223,0000000959 +12345678,2223,0000000960 +12345678,2223,0000000961 +12345678,2223,0000000962 +12345678,2223,0000000963 +12345678,2223,0000000964 +12345678,2223,0000000965 +12345678,2223,0000000966 +12345678,2223,0000000967 +12345678,2223,0000000968 +12345678,2223,0000000969 +12345678,2223,0000000970 +12345678,2223,0000000971 +12345678,2223,0000000972 +12345678,2223,0000000973 +12345678,2223,0000000974 +12345678,2223,0000000975 +12345678,2223,0000000976 +12345678,2223,0000000977 +12345678,2223,0000000978 +12345678,2223,0000000979 +12345678,2223,0000000980 +12345678,2223,0000000981 +12345678,2223,0000000982 +12345678,2223,0000000983 +12345678,2223,0000000984 +12345678,2223,0000000985 +12345678,2223,0000000986 +12345678,2223,0000000987 +12345678,2223,0000000988 +12345678,2223,0000000989 +12345678,2223,0000000990 +12345678,2223,0000000991 +12345678,2223,0000000992 +12345678,2223,0000000993 +12345678,2223,0000000994 +12345678,2223,0000000995 +12345678,2223,0000000996 +12345678,2223,0000000997 +12345678,2223,0000000998 +12345678,2223,0000000999 +12345678,2223,0000001000 \ No newline at end of file diff --git a/src/LearnerData/swagger.yaml b/src/LearnerData/swagger.yaml new file mode 100644 index 0000000000..f81f7ae642 --- /dev/null +++ b/src/LearnerData/swagger.yaml @@ -0,0 +1,334 @@ +openapi: 3.0.0 +info: + version: '1.0' + title: Learner data external API for consumption from an authenticated consumer + description: 'This service will provide a contract to the Data Collections team for submission to the Apprenticeship service' +paths: + /info: + get: + description: information regarding this API. The version will be returned + security: + - ApiKeyAuth: [] # Require API Key authentication + responses: + '200': + $ref: '#/components/responses/infoResponse' + '429': + $ref: '#/components/responses/rateLimitExceededResponse' + /liveness: + get: + description: The system is responsive and accepting incoming requests; however, this endpoint does not perform a dependency check, so the system may not be fully ready to process requests correctly. + security: + - ApiKeyAuth: [] # Require API Key authentication + responses: + '200': + $ref: '#/components/responses/okResponse' + '429': + $ref: '#/components/responses/rateLimitExceededResponse' + /readiness: + get: + description: The system is fully ready to accept requests and all the dependencies are healthy + security: + - ApiKeyAuth: [] # Require API Key authentication + responses: + '200': + $ref: '#/components/responses/healthCheckResponse' + '503': + $ref: '#/components/responses/healthCheckResponse' + '429': + $ref: '#/components/responses/rateLimitExceededResponse' + /learners/{guid}: + put: + summary: External authenticated endpoint for creating or updating individual learners + parameters: + - name: guid + in: path + required: true + description: A unique record id from the ILR + schema: + type: string + format: guid + security: + - ApiKeyAuth: [] # Require API Key authentication + requestBody: + require: true + $ref: '#/components/requestBodies/learnerRequest' + responses: + '202': + description: The resource has been accepted. The resource will appear on the GET endpoint under eventual consistency + $ref: '#/components/responses/acceptedResponse' + '401': + $ref: '#/components/responses/unauthorized' + '429': + $ref: '#/components/responses/rateLimitExceededResponse' + /providers/{ukprn}/academicyears/{academicyear}/apprenticeships: + get: + summary: Returns a paged list of apprenticeships for this provider + security: + - ApiKeyAuth: [] # Require API Key authentication + parameters: + - name: ukprn + in: path + required: true + description: The UK Provider Reference Number for the provider entering this learner + schema: + type: string + - name: academicyear + in: path + required: true + example: 2425 + schema: + type: integer + - name: page + in: query + required: false + description: Default is 1 + schema: + type: integer + - name: pagesize + in: query + required: false + example: 20 + description: Default is 20, can be between 10 and 100 + schema: + type: integer + responses: + '200': + $ref: '#/components/responses/pagedApprenticeshipsResponse' + +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-API-Key + description: "Enter your API key in the `X-API-Key` header." + headers: + X-RateLimit-Limit: + description: The maximum number of requests allowed in a time window + schema: + type: integer + example: 100 + X-RateLimit-Remaining: + description: The number of requests remaining in the current time window + schema: + type: integer + example: 99 + X-RateLimit-Reset: + description: The time (in UNIX timestamp) when the rate limit resets + schema: + type: integer + example: 1712345678 + Retry-After: + description: The number of seconds to wait before making a new request + schema: + type: integer + example: 60 + + requestBodies: + learnerRequest: + description: The ilr record to be submitted to AS + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/learner' + + responses: + pagedApprenticeshipsResponse: + description: A paged apprenticeship response + headers: + Link: + description: Pagination links (next, prev, first, last) + schema: + type: string + format: url + example: '; rel="next", ; rel="last"' + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/pagedApprenticeships' + + acceptedResponse: + description: A resource has been created or updated through the service + headers: + X-RateLimit-Limit: + $ref: '#/components/headers/X-RateLimit-Limit' + X-RateLimit-Remaining: + $ref: '#/components/headers/X-RateLimit-Remaining' + X-RateLimit-Reset: + $ref: '#/components/headers/X-RateLimit-Reset' + + rateLimitExceededResponse: + description: Too many requests - rate limit exceeded + content: + application/json: + schema: + type: object + properties: + error: + type: string + example: "Rate limit exceeded. Try again later." + + learnerResponse: + description: A single response to a specific learner + content: + application/json: + schema: + $ref: '#/components/schemas/learner' + + notFound: + description: The specified resource was not found + content: + application/json: + schema: + $ref: '#/components/schemas/error' + + okResponse: + description: OK + content: + text/plain: + schema: + type: string + example: OK + + badRequest: + description: A bad request was detected + content: + application/json: + schema: + $ref: '#/components/schemas/error' + + unauthorized: + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/error' + + healthCheckResponse: + description: Results of a health check + content: + application/json: + schema: + $ref: '#/components/schemas/healthcheckResults' + + infoResponse: + description: Information response + content: + text/plain: + schema: + type: string + example: Leaner data APIM for the apprenticeship service - Version 3.0.2 + + schemas: + error: + type: object + properties: + code: + type: string + message: + type: string + required: + - code + - message + + pagedApprenticeships: + type: object + properties: + apprenticeships: + type: array + items: + $ref: '#/components/schemas/apprenticeship' + total: + type: integer + minimum: 0 + page: + type: integer + minimum: 1 + pageSize: + type: integer + minimum: 1 + maximum: 20 + totalPages: + type: integer + minimum: 1 + required: + - page + - total + - pageSize + - totalPages + + healthcheckResults: + type: array + items: + $ref: '#/components/schemas/healthcheck' + + healthcheck: + type: object + properties: + description: + type: string + result: + type: string + + apprenticeship: + type: object + properties: + uln: + type: string + + learner: + type: object + properties: + uln: + type: integer + firstName: + type: string + lastName: + type: string + dateOfBirth: + type: string + format: date + learnerEmail: + type: string + priorLearningPercent: + description: This is the percentage of learning already complete before this course starts + type: integer + example: 10 + ukprn: + type: integer + agreementId: + type: integer + trainingPrice: + type: integer + description: This is the earliest price + epaoPrice: + type: integer + description: This is the earliest price + standardCode: + type: integer + startDate: + type: string + format: date + plannedEndDate: + type: string + format: date + required: + - uln, + - firstName + - lastName + - dateOfBirth + - learnerEmail + - ukprn + - priorLearningPercent + - standardCode + - trainingPrice + - epaoPrice + - startDate + - plannedEndDate + - agreementId + +security: + - ApiKeyAuth: [] # Apply API Key security globally \ No newline at end of file