From 946e8ef1aeeffea276b13823ddbe444f05f1b032 Mon Sep 17 00:00:00 2001 From: James Gunn Date: Tue, 17 Dec 2024 14:40:21 +0000 Subject: [PATCH] Add endpoint for updating induction status from Welsh induction outcome (#1752) --- .../Operations/SetCpdInductionStatus.cs | 6 +- .../Operations/SetWelshInductionStatus.cs | 78 ++++++ .../V3/VNext/Controllers/PersonsController.cs | 28 ++- .../SetWelshInductionStatusRequest.cs | 8 + .../src/TeachingRecordSystem.Core/ApiRoles.cs | 10 +- .../DataStore/Postgres/Models/Person.cs | 89 +++++++ .../Models/InductionExemptionReason.cs | 8 - .../Models/InductionExemptionReasons.cs | 24 ++ .../V3/VNext/SetWelshInductionStatusTests.cs | 232 ++++++++++++++++++ .../DataStore/Postgres/Models/PersonTests.cs | 97 ++++++++ .../Persons/PersonDetail/InductionTests.cs | 4 +- 11 files changed, 568 insertions(+), 16 deletions(-) create mode 100644 TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Implementation/Operations/SetWelshInductionStatus.cs create mode 100644 TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Requests/SetWelshInductionStatusRequest.cs delete mode 100644 TeachingRecordSystem/src/TeachingRecordSystem.Core/Models/InductionExemptionReason.cs create mode 100644 TeachingRecordSystem/src/TeachingRecordSystem.Core/Models/InductionExemptionReasons.cs create mode 100644 TeachingRecordSystem/tests/TeachingRecordSystem.Api.Tests/V3/VNext/SetWelshInductionStatusTests.cs diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Implementation/Operations/SetCpdInductionStatus.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Implementation/Operations/SetCpdInductionStatus.cs index 3d0d5c140..41674b48f 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Implementation/Operations/SetCpdInductionStatus.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Implementation/Operations/SetCpdInductionStatus.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using Microsoft.Xrm.Sdk.Query; +using TeachingRecordSystem.Api.Infrastructure.Security; using TeachingRecordSystem.Core.DataStore.Postgres; using TeachingRecordSystem.Core.DataStore.Postgres.Models; using TeachingRecordSystem.Core.Dqt; @@ -22,6 +23,7 @@ public class SetCpdInductionStatusHandler( TrsDbContext dbContext, ICrmQueryDispatcher crmQueryDispatcher, TrsDataSyncHelper syncHelper, + ICurrentUserProvider currentUserProvider, IClock clock) { public async Task> HandleAsync(SetCpdInductionStatusCommand command) @@ -88,12 +90,14 @@ public async Task> HandleAsync(SetCpdIndu return ApiError.StaleRequest(cpdInductionCpdModifiedOn); } + var (currentUserId, _) = currentUserProvider.GetCurrentApplicationUser(); + person.SetCpdInductionStatus( command.Status, command.StartDate, command.CompletedDate, command.CpdModifiedOn, - PostgresModels.SystemUser.SystemUserId, + currentUserId, clock.UtcNow, out var updatedEvent); diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Implementation/Operations/SetWelshInductionStatus.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Implementation/Operations/SetWelshInductionStatus.cs new file mode 100644 index 000000000..5e0ee3b38 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/Implementation/Operations/SetWelshInductionStatus.cs @@ -0,0 +1,78 @@ +using System.Diagnostics; +using Microsoft.Xrm.Sdk.Query; +using TeachingRecordSystem.Api.Infrastructure.Security; +using TeachingRecordSystem.Core.DataStore.Postgres; +using TeachingRecordSystem.Core.DataStore.Postgres.Models; +using TeachingRecordSystem.Core.Dqt; +using TeachingRecordSystem.Core.Dqt.Models; +using TeachingRecordSystem.Core.Dqt.Queries; +using TeachingRecordSystem.Core.Services.TrsDataSync; + +namespace TeachingRecordSystem.Api.V3.Implementation.Operations; + +public record SetWelshInductionStatusCommand(string Trn, bool Passed, DateOnly StartDate, DateOnly CompletedDate); + +public record SetWelshInductionStatusResult; + +public class SetWelshInductionStatusHandler( + TrsDbContext dbContext, + ICrmQueryDispatcher crmQueryDispatcher, + TrsDataSyncHelper syncHelper, + ICurrentUserProvider currentUserProvider, + IClock clock) +{ + public async Task> HandleAsync(SetWelshInductionStatusCommand command) + { + var dqtContact = await crmQueryDispatcher.ExecuteQueryAsync( + new GetActiveContactByTrnQuery(command.Trn, new ColumnSet(Contact.Fields.dfeta_QTSDate))); + + if (dqtContact is null) + { + return ApiError.PersonNotFound(command.Trn); + } + + if (dqtContact.dfeta_QTSDate is null) + { + return ApiError.PersonDoesNotHaveQts(command.Trn); + } + + await using var txn = await dbContext.Database.BeginTransactionAsync(System.Data.IsolationLevel.ReadCommitted); + + var person = await GetPersonAsync(); + + if (person is null) + { + // The person record hasn't synced to TRS yet - force that to happen so we can assign induction status + var synced = await syncHelper.SyncPersonAsync(dqtContact.Id); + if (!synced) + { + throw new Exception($"Could not sync Person with contact ID: '{dqtContact.Id}'."); + } + + person = await GetPersonAsync(); + Debug.Assert(person is not null); + } + + var (currentUserId, _) = currentUserProvider.GetCurrentApplicationUser(); + + person.TrySetWelshInductionStatus( + command.Passed, + !command.Passed ? command.StartDate : null, + !command.Passed ? command.CompletedDate : null, + currentUserId, + clock.UtcNow, + out var updatedEvent); + + if (updatedEvent is not null) + { + dbContext.AddEvent(updatedEvent); + } + + await dbContext.SaveChangesAsync(); + await txn.CommitAsync(); + + return new SetWelshInductionStatusResult(); + + Task GetPersonAsync() => dbContext.Persons.SingleOrDefaultAsync(p => p.Trn == command.Trn); + } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Controllers/PersonsController.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Controllers/PersonsController.cs index 5a0d82db6..4d14eebc5 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Controllers/PersonsController.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Controllers/PersonsController.cs @@ -18,7 +18,7 @@ public class PersonsController(IMapper mapper) : ControllerBase { [HttpPut("{trn}/cpd-induction")] [SwaggerOperation( - OperationId = "SetPersonInductionStatus", + OperationId = "SetPersonCpdInductionStatus", Summary = "Set person induction status", Description = "Sets the induction details of the person with the given TRN.")] [ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)] @@ -44,6 +44,32 @@ public async Task SetCpdInductionStatusAsync( .MapErrorCode(ApiError.ErrorCodes.StaleRequest, StatusCodes.Status409Conflict); } + [HttpPut("{trn}/welsh-induction")] + [SwaggerOperation( + OperationId = "SetPersonWelshInductionStatus", + Summary = "Set person induction status", + Description = "Sets the induction details of the person with the given TRN.")] + [ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] + [Authorize(Policy = AuthorizationPolicies.ApiKey, Roles = ApiRoles.SetWelshInduction)] + public async Task SetWelshInductionStatusAsync( + [FromRoute] string trn, + [FromBody] SetWelshInductionStatusRequest request, + [FromServices] SetWelshInductionStatusHandler handler) + { + var command = new SetWelshInductionStatusCommand( + trn, + request.Passed, + request.StartDate, + request.CompletedDate); + + var result = await handler.HandleAsync(command); + + return result.ToActionResult(_ => NoContent()) + .MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status404NotFound); + } + [HttpGet("{trn}")] [SwaggerOperation( OperationId = "GetPersonByTrn", diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Requests/SetWelshInductionStatusRequest.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Requests/SetWelshInductionStatusRequest.cs new file mode 100644 index 000000000..779c6d50b --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Api/V3/VNext/Requests/SetWelshInductionStatusRequest.cs @@ -0,0 +1,8 @@ +namespace TeachingRecordSystem.Api.V3.VNext.Requests; + +public record SetWelshInductionStatusRequest +{ + public required bool Passed { get; init; } + public required DateOnly StartDate { get; init; } + public required DateOnly CompletedDate { get; init; } +} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/ApiRoles.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/ApiRoles.cs index b644820c6..a2530f325 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/ApiRoles.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/ApiRoles.cs @@ -11,9 +11,10 @@ public static class ApiRoles public const string AppropriateBody = "AppropriateBody"; public const string UpdateRole = "UpdateRole"; public const string SetCpdInduction = "SetCpdInduction"; + public const string SetWelshInduction = "SetWelshInduction"; - public static IReadOnlyCollection All { get; } = new[] - { + public static IReadOnlyCollection All { get; } = + [ GetPerson, UpdatePerson, UpdateNpq, @@ -22,6 +23,7 @@ public static class ApiRoles AssignQtls, AppropriateBody, UpdateRole, - SetCpdInduction - }; + SetCpdInduction, + SetWelshInduction + ]; } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Models/Person.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Models/Person.cs index 4baa4eb7d..5104b8141 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Models/Person.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/DataStore/Postgres/Models/Person.cs @@ -182,6 +182,95 @@ public void SetInductionStatus( }; } + public bool TrySetWelshInductionStatus( + bool passed, + DateOnly? startDate, + DateOnly? completedDate, + EventModels.RaisedByUserInfo updatedBy, + DateTime now, + [NotNullWhen(true)] out PersonInductionUpdatedEvent? @event) + { + var newStatus = GetInductionStatusFromWelshOutcome(passed, out var exemptionReasons); + + // FUTURE When we have QTS in TRS - assert person has QTS + AssertInductionChangeIsValid(newStatus, startDate, completedDate, exemptionReasons); + + if (InductionStatus is InductionStatus.RequiredToComplete) + { + if (newStatus == InductionStatus.Exempt) + { + InductionStatus = newStatus; + InductionExemptionReasons = exemptionReasons; + InductionModifiedOn = now; + + @event = new PersonInductionUpdatedEvent + { + PersonId = PersonId, + InductionStatus = newStatus, + InductionStartDate = startDate, + InductionCompletedDate = completedDate, + InductionExemptionReasons = InductionExemptionReasons, + CpdInductionStatus = default, + CpdInductionStartDate = default, + CpdInductionCompletedDate = default, + CpdInductionCpdModifiedOn = default, + ChangeReason = null, + ChangeReasonDetail = null, + EvidenceFile = null, + Changes = PersonInductionUpdatedEventChanges.InductionStatus | + PersonInductionUpdatedEventChanges.InductionStartDate | + PersonInductionUpdatedEventChanges.InductionCompletedDate, + EventId = Guid.NewGuid(), + CreatedUtc = now, + RaisedBy = updatedBy + }; + + return true; + } + else if (newStatus == InductionStatus.FailedInWales) + { + InductionStatus = newStatus; + InductionModifiedOn = now; + + @event = new PersonInductionUpdatedEvent + { + PersonId = PersonId, + InductionStatus = newStatus, + InductionStartDate = null, + InductionCompletedDate = null, + InductionExemptionReasons = exemptionReasons, + CpdInductionStatus = default, + CpdInductionStartDate = default, + CpdInductionCompletedDate = default, + CpdInductionCpdModifiedOn = default, + ChangeReason = null, + ChangeReasonDetail = null, + EvidenceFile = null, + Changes = PersonInductionUpdatedEventChanges.InductionStatus | PersonInductionUpdatedEventChanges.InductionExemptionReasons, + EventId = Guid.NewGuid(), + CreatedUtc = now, + RaisedBy = updatedBy + }; + + return true; + } + } + + @event = null; + return false; + } + + public static InductionStatus GetInductionStatusFromWelshOutcome(bool passed, out InductionExemptionReasons exemptionReasons) + { + var status = passed ? InductionStatus.Exempt : InductionStatus.FailedInWales; + + exemptionReasons = passed + ? InductionExemptionReasons.PassedInductionInWales + : InductionExemptionReasons.None; + + return status; + } + public static bool ValidateInductionData( InductionStatus status, DateOnly? startDate, diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Models/InductionExemptionReason.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Models/InductionExemptionReason.cs deleted file mode 100644 index 820a51dbc..000000000 --- a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Models/InductionExemptionReason.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace TeachingRecordSystem.Core.Models; - -[Flags] -public enum InductionExemptionReasons -{ - None = 0, - SomethingMadeUpForNow = 1 << 0 -} diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.Core/Models/InductionExemptionReasons.cs b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Models/InductionExemptionReasons.cs new file mode 100644 index 000000000..35f8d34c9 --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.Core/Models/InductionExemptionReasons.cs @@ -0,0 +1,24 @@ +namespace TeachingRecordSystem.Core.Models; + +[Flags] +public enum InductionExemptionReasons +{ + None = 0, + QualifiedBefore07052000 = 1 << 0, + QualifiedBetween07051999And01042003FirstPostWasInWalesAndLastedAMinimumOfTwoTerms = 1 << 1, + QualifiedThroughFurtherEducationRouteBetween01092001And01092004 = 1 << 2, + PassedInductionInGuernsey = 1 << 3, + PassedInductionInIsleOfMan = 1 << 4, + PassedInductionInJersey = 1 << 5, + PassedInductionInNorthernIreland = 1 << 6, + PassedInductionInServiceChildrensEducationSchoolsInGermanyOrCyprus = 1 << 7, + PassedInductionInWales = 1 << 8, + PassedProbationaryPeriodInGibraltar = 1 << 9, + Exempt = 1 << 10, + ExemptDataLossOrErrorCriteria = 1 << 11, + HasOrIsEligibleForFullRegistrationInScotland = 1 << 12, + OverseasTrainedTeacher = 1 << 13, + QualifiedThroughEeaMutualRecognitionRoute = 1 << 14, + RegisteredTeacherWithAtLeast2YearsFullTimeTeachingExperience = 1 << 15, + ExemptThroughQtlsProvidedTheyMaintainMembershipOfTheSocietyOfEducationAndTraining = 1 << 16, +} diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.Api.Tests/V3/VNext/SetWelshInductionStatusTests.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.Api.Tests/V3/VNext/SetWelshInductionStatusTests.cs new file mode 100644 index 000000000..8d405bb4d --- /dev/null +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.Api.Tests/V3/VNext/SetWelshInductionStatusTests.cs @@ -0,0 +1,232 @@ +namespace TeachingRecordSystem.Api.Tests.V3.VNext; + +public class SetWelshInductionStatusTests : TestBase +{ + public SetWelshInductionStatusTests(HostFixture hostFixture) : base(hostFixture) + { + SetCurrentApiClient([ApiRoles.SetWelshInduction]); + } + + [Fact] + public async Task Put_UserDoesNotHavePermission_ReturnsForbidden() + { + // Arrange + SetCurrentApiClient(roles: []); + + var person = await TestData.CreatePersonAsync(p => p + .WithTrn() + .WithQts()); + + var startDate = person.QtsDate!.Value.AddDays(6); + var completedDate = startDate.AddMonths(12); + + var request = new HttpRequestMessage(HttpMethod.Put, $"/v3/persons/{person.Trn}/welsh-induction") + { + Content = CreateJsonContent(new + { + passed = true, + startDate = startDate, + completedDate = completedDate + }) + }; + + // Act + var response = await GetHttpClientWithApiKey().SendAsync(request); + + // Act + Assert.Equal(StatusCodes.Status403Forbidden, (int)response.StatusCode); + } + + [Fact] + public async Task Put_PersonDoesNotExist_ReturnsNotFound() + { + // Arrange + var trn = await TestData.GenerateTrnAsync(); + + var startDate = new DateOnly(2022, 1, 1); + var completedDate = startDate.AddMonths(12); + + var request = new HttpRequestMessage(HttpMethod.Put, $"/v3/persons/{trn}/welsh-induction") + { + Content = CreateJsonContent(new + { + passed = true, + startDate = startDate, + completedDate = completedDate + }) + }; + + // Act + var response = await GetHttpClientWithApiKey().SendAsync(request); + + // Act + await AssertEx.JsonResponseIsErrorAsync(response, ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status404NotFound); + } + + [Fact] + public async Task Put_PersonDoesNotHaveQts_ReturnsError() + { + // Arrange + var person = await TestData.CreatePersonAsync(p => p + .WithTrn()); + + var startDate = new DateOnly(2022, 1, 1); + var completedDate = startDate.AddMonths(12); + + var request = new HttpRequestMessage(HttpMethod.Put, $"/v3/persons/{person.Trn}/welsh-induction") + { + Content = CreateJsonContent(new + { + passed = true, + startDate = startDate, + completedDate = completedDate + }) + }; + + // Act + var response = await GetHttpClientWithApiKey().SendAsync(request); + + // Act + await AssertEx.JsonResponseIsErrorAsync(response, ApiError.ErrorCodes.PersonDoesNotHaveQts, StatusCodes.Status400BadRequest); + } + + [Fact] + public async Task Put_ValidRequestWithPassedForPersonWithRequiredToCompleteStatus_UpdatesDbAndReturnsNoContent() + { + // Arrange + var person = await TestData.CreatePersonAsync(p => p + .WithTrn() + .WithQts() + .WithInductionStatus(InductionStatus.RequiredToComplete)); + + var startDate = person.QtsDate!.Value.AddDays(6); + var completedDate = startDate.AddMonths(12); + + var request = new HttpRequestMessage(HttpMethod.Put, $"/v3/persons/{person.Trn}/welsh-induction") + { + Content = CreateJsonContent(new + { + passed = true, + startDate = startDate, + completedDate = completedDate + }) + }; + + // Act + var response = await GetHttpClientWithApiKey().SendAsync(request); + + // Act + Assert.Equal(StatusCodes.Status204NoContent, (int)response.StatusCode); + + await WithDbContextAsync(async dbContext => + { + var dbPerson = await dbContext.Persons.SingleAsync(p => p.PersonId == person.PersonId); + Assert.Equal(InductionStatus.Exempt, dbPerson.InductionStatus); + }); + } + + [Fact] + public async Task Put_ValidRequestWithFailedForPersonWithRequiredToCompleteStatus_UpdatesDbAndReturnsNoContent() + { + // Arrange + var person = await TestData.CreatePersonAsync(p => p + .WithTrn() + .WithQts() + .WithInductionStatus(InductionStatus.RequiredToComplete)); + + var startDate = person.QtsDate!.Value.AddDays(6); + var completedDate = startDate.AddMonths(12); + + var request = new HttpRequestMessage(HttpMethod.Put, $"/v3/persons/{person.Trn}/welsh-induction") + { + Content = CreateJsonContent(new + { + passed = false, + startDate = startDate, + completedDate = completedDate + }) + }; + + // Act + var response = await GetHttpClientWithApiKey().SendAsync(request); + + // Act + Assert.Equal(StatusCodes.Status204NoContent, (int)response.StatusCode); + + await WithDbContextAsync(async dbContext => + { + var dbPerson = await dbContext.Persons.SingleAsync(p => p.PersonId == person.PersonId); + Assert.Equal(InductionStatus.FailedInWales, dbPerson.InductionStatus); + }); + } + + [Fact] + public async Task Put_ValidRequestWithPassedForPersonWithHighPriorityStatus_DoesNotUpdateStatusAndReturnsNoContent() + { + // Arrange + var person = await TestData.CreatePersonAsync(p => p + .WithTrn() + .WithQts() + .WithInductionStatus(InductionStatus.Passed)); + + var startDate = person.QtsDate!.Value.AddDays(6); + var completedDate = startDate.AddMonths(12); + + var request = new HttpRequestMessage(HttpMethod.Put, $"/v3/persons/{person.Trn}/welsh-induction") + { + Content = CreateJsonContent(new + { + passed = true, + startDate = startDate, + completedDate = completedDate + }) + }; + + // Act + var response = await GetHttpClientWithApiKey().SendAsync(request); + + // Act + Assert.Equal(StatusCodes.Status204NoContent, (int)response.StatusCode); + + await WithDbContextAsync(async dbContext => + { + var dbPerson = await dbContext.Persons.SingleAsync(p => p.PersonId == person.PersonId); + Assert.NotEqual(InductionStatus.Exempt, dbPerson.InductionStatus); + }); + } + + [Fact] + public async Task Put_ValidRequestWithFailedForPersonWithHighPriorityStatus_DoesNotUpdateStatusAndReturnsNoContent() + { + // Arrange + var person = await TestData.CreatePersonAsync(p => p + .WithTrn() + .WithQts() + .WithInductionStatus(InductionStatus.Passed)); + + var startDate = person.QtsDate!.Value.AddDays(6); + var completedDate = startDate.AddMonths(12); + + var request = new HttpRequestMessage(HttpMethod.Put, $"/v3/persons/{person.Trn}/welsh-induction") + { + Content = CreateJsonContent(new + { + passed = false, + startDate = startDate, + completedDate = completedDate + }) + }; + + // Act + var response = await GetHttpClientWithApiKey().SendAsync(request); + + // Act + Assert.Equal(StatusCodes.Status204NoContent, (int)response.StatusCode); + + await WithDbContextAsync(async dbContext => + { + var dbPerson = await dbContext.Persons.SingleAsync(p => p.PersonId == person.PersonId); + Assert.NotEqual(InductionStatus.Exempt, dbPerson.InductionStatus); + }); + } +} diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Tests/DataStore/Postgres/Models/PersonTests.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Tests/DataStore/Postgres/Models/PersonTests.cs index b1636a0b9..3ac8b9c31 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Tests/DataStore/Postgres/Models/PersonTests.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.Core.Tests/DataStore/Postgres/Models/PersonTests.cs @@ -131,4 +131,101 @@ public void SetCpdInductionStatus_PersonIsExemptAndNewStatusIsNotPassed_KeepsOve Assert.NotEqual(Clock.UtcNow, person.InductionModifiedOn); Assert.Equal(Clock.UtcNow, person.CpdInductionModifiedOn); } + + [Theory] + [InlineData(true, InductionStatus.Exempt)] + [InlineData(true, InductionStatus.InProgress)] + [InlineData(true, InductionStatus.Passed)] + [InlineData(true, InductionStatus.Failed)] + [InlineData(false, InductionStatus.Exempt)] + [InlineData(false, InductionStatus.InProgress)] + [InlineData(false, InductionStatus.Passed)] + [InlineData(false, InductionStatus.Failed)] + public void TrySetWelshInductionStatus_StatusIsAlreadySetToHigherPriorityStatus_ReturnsFalse(bool passed, InductionStatus currentStatus) + { + // Arrange + var person = new Person + { + PersonId = Guid.NewGuid(), + CreatedOn = Clock.UtcNow, + UpdatedOn = Clock.UtcNow, + Trn = "1234567", + FirstName = "Joe", + MiddleName = "", + LastName = "Bloggs", + DateOfBirth = new(1990, 1, 1), + }; + + person.SetInductionStatus( + currentStatus, + startDate: currentStatus.RequiresStartDate() ? new(2024, 1, 1) : null, + completedDate: currentStatus.RequiresCompletedDate() ? new(2024, 10, 1) : null, + exemptionReasons: currentStatus is InductionStatus.Exempt ? InductionExemptionReasons.QualifiedBefore07052000 : InductionExemptionReasons.None, + updatedBy: SystemUser.SystemUserId, + Clock.UtcNow, + out _); + + Clock.Advance(); + + // Act + var result = person.TrySetWelshInductionStatus( + passed, + startDate: !passed ? new(2024, 1, 1) : null, + completedDate: !passed ? new(2024, 10, 1) : null, + updatedBy: SystemUser.SystemUserId, + Clock.UtcNow, + out _); + + // Assert + Assert.False(result); + Assert.Equal(currentStatus, person.InductionStatus); + } + + [Theory] + [InlineData(true, InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionExemptionReasons.PassedInductionInWales)] + [InlineData(false, InductionStatus.RequiredToComplete, InductionStatus.FailedInWales, InductionExemptionReasons.None)] + public void TrySetWelshInductionStatus_StatusIsAtLowerPriorityStatus_UpdatesStatusAndReturnsTrue( + bool passed, + InductionStatus currentStatus, + InductionStatus expectedStatus, + InductionExemptionReasons expectedExemptionReasons) + { + // Arrange + var person = new Person + { + PersonId = Guid.NewGuid(), + CreatedOn = Clock.UtcNow, + UpdatedOn = Clock.UtcNow, + Trn = "1234567", + FirstName = "Joe", + MiddleName = "", + LastName = "Bloggs", + DateOfBirth = new(1990, 1, 1), + }; + + person.SetInductionStatus( + currentStatus, + startDate: currentStatus.RequiresStartDate() ? new(2024, 1, 1) : null, + completedDate: currentStatus.RequiresCompletedDate() ? new(2024, 10, 1) : null, + exemptionReasons: currentStatus is InductionStatus.Exempt ? InductionExemptionReasons.QualifiedBefore07052000 : InductionExemptionReasons.None, + updatedBy: SystemUser.SystemUserId, + Clock.UtcNow, + out _); + + Clock.Advance(); + + // Act + var result = person.TrySetWelshInductionStatus( + passed, + startDate: !passed ? new(2024, 1, 1) : null, + completedDate: !passed ? new(2024, 10, 1) : null, + updatedBy: SystemUser.SystemUserId, + Clock.UtcNow, + out _); + + // Assert + Assert.True(result); + Assert.Equal(expectedStatus, person.InductionStatus); + Assert.Equal(expectedExemptionReasons, person.InductionExemptionReasons); + } } diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/InductionTests.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/InductionTests.cs index 7348b3a65..cca14c0bc 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/InductionTests.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/InductionTests.cs @@ -118,7 +118,7 @@ public async Task Get_WithPersonIdForPersonWithInductionStatusExempt_DisplaysExp .WithQts() .WithInductionStatus(builder => builder .WithStatus(setInductionStatus) - .WithExemptionReasons(InductionExemptionReasons.SomethingMadeUpForNow) + .WithExemptionReasons(InductionExemptionReasons.QualifiedBefore07052000) ) ); @@ -132,7 +132,7 @@ public async Task Get_WithPersonIdForPersonWithInductionStatusExempt_DisplaysExp var inductionStatus = doc.GetElementByTestId("induction-status"); Assert.Contains(StatusStrings[setInductionStatus], inductionStatus!.TextContent); var exemptionReason = doc.GetElementByTestId("induction-exemption-reasons")!.Children[1].TextContent; - Assert.Contains("SomethingMadeUpForNow", exemptionReason); // CML TODO - needs proper mapping to string + Assert.Contains("QualifiedBefore07052000", exemptionReason); // CML TODO - needs proper mapping to string Assert.Null(doc.GetElementByTestId("induction-start-date")); Assert.Null(doc.GetElementByTestId("induction-end-date")); Assert.NotNull(doc.GetAllElementsByTestId("induction-backlink"));