From d538c3bdf817c15de680395062fe0071c8b82388 Mon Sep 17 00:00:00 2001 From: CLAWLOR Date: Wed, 18 Dec 2024 15:17:00 +0000 Subject: [PATCH] show applicable radio buttons --- .../EditInduction/EditInductionState.cs | 7 +- .../PersonDetail/EditInduction/Status.cshtml | 8 +- .../EditInduction/Status.cshtml.cs | 36 ++++- .../ValidationAttributes/NotEqualAttribute.cs | 23 +++ .../EditInduction/EditInductionStatusTests.cs | 140 +++++++++++++++++- 5 files changed, 200 insertions(+), 14 deletions(-) create mode 100644 TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/ValidationAttributes/NotEqualAttribute.cs diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/EditInductionState.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/EditInductionState.cs index 7b229f556..f56c4943d 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/EditInductionState.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/EditInductionState.cs @@ -10,13 +10,14 @@ public class EditInductionState : IRegisterJourney requestDataKeys: ["personId"], appendUniqueKey: true); - public string? PersonName { get; set; } public InductionStatus InductionStatus { get; set; } + public InductionStatus InitialInductionStatus{ get; set; } public DateOnly? StartDate { get; set; } public DateOnly? CompletedDate { get; set; } public InductionExemptionReasons? ExemptionReasons { get; set; } public string? ChangeReason { get; set; } public InductionJourneyPage? JourneyStartPage { get; set; } + public bool RecordManagedInCpd { get; set; } public bool Initialized { get; set; } @@ -28,8 +29,8 @@ public async Task EnsureInitializedAsync(TrsDbContext dbContext, Guid personId, } var person = await dbContext.Persons .SingleAsync(q => q.PersonId == personId); - InductionStatus = person!.InductionStatus; - PersonName = person.LastName; + + InitialInductionStatus = person!.InductionStatus; if (JourneyStartPage == null) { JourneyStartPage = startPage; diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/Status.cshtml b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/Status.cshtml index 4a53c109f..c1e1c39c8 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/Status.cshtml +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/Status.cshtml @@ -12,14 +12,14 @@
- Induction - @Model.PersonName + Induction - @Model.PersonName
- - @foreach (var inductionStatus in InductionStatusRegistry.All) + + @foreach (var inductionStatus in Model.StatusChoices) { - @inductionStatus.Name + @inductionStatus.Title } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/Status.cshtml.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/Status.cshtml.cs index cf433cc18..875b78f2d 100644 --- a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/Status.cshtml.cs +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/Pages/Persons/PersonDetail/EditInduction/Status.cshtml.cs @@ -2,19 +2,34 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using TeachingRecordSystem.Core.DataStore.Postgres; +using TeachingRecordSystem.SupportUi.ValidationAttributes; namespace TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail.EditInduction; [Journey(JourneyNames.EditInduction), ActivatesJourney, RequireJourneyInstance] public class StatusModel : CommonJourneyPage { + private static List ValidStatusesWhenManagedByCpd = new() { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.FailedInWales }; + protected TrsDbContext _dbContext; + protected IClock _clock; + protected bool InductionStatusManagedByCpd; [BindProperty] [Display(Name = "Select a status")] - [Required(ErrorMessage = "Select a status")] + [NotEqual(InductionStatus.None, ErrorMessage = "Select a status")] public InductionStatus InductionStatus { get; set; } + public InductionStatus InitialInductionStatus { get; set; } public string? PersonName { get; set; } + public IEnumerable StatusChoices + { + get + { + return InductionStatusManagedByCpd ? + InductionStatusRegistry.All.Where(i => ValidStatusesWhenManagedByCpd.Contains(i.Value) && i.Value != InitialInductionStatus) + : InductionStatusRegistry.All.ToArray()[1..].Where(i => i.Value != InitialInductionStatus); + } + } public InductionJourneyPage NextPage { @@ -30,15 +45,25 @@ _ when InductionStatus.RequiresStartDate() => InductionJourneyPage.StartDate, } public string BackLink => LinkGenerator.PersonInduction(PersonId); - public StatusModel(TrsLinkGenerator linkGenerator, TrsDbContext dbContext) : base(linkGenerator) + public StatusModel(TrsLinkGenerator linkGenerator, TrsDbContext dbContext, IClock clock) : base(linkGenerator) { _dbContext = dbContext; + _clock = clock; } - public void OnGet() + public async Task OnGetAsync() { + var person = await _dbContext.Persons.SingleAsync(q => q.PersonId == PersonId); + InductionStatusManagedByCpd = person.InductionStatusManagedByCpd(_clock.UtcNow.ToDateOnlyWithDqtBstFix(true)); // CML TODO understand date-time stuff InductionStatus = JourneyInstance!.State.InductionStatus; - PersonName = JourneyInstance!.State.PersonName; + InitialInductionStatus = JourneyInstance!.State.InitialInductionStatus; + await JourneyInstance!.UpdateStateAsync(state => + { + if (state.InitialInductionStatus == InductionStatus.None) + { + state.InitialInductionStatus = InitialInductionStatus; + } + }); } public async Task OnPostAsync() @@ -58,11 +83,10 @@ public async Task OnPostAsync() public override async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) { await JourneyInstance!.State.EnsureInitializedAsync(_dbContext, PersonId, InductionJourneyPage.Status); + var personInfo = context.HttpContext.GetCurrentPersonFeature(); - PersonId = personInfo.PersonId; PersonName = personInfo.Name; - await next(); } } diff --git a/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/ValidationAttributes/NotEqualAttribute.cs b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/ValidationAttributes/NotEqualAttribute.cs new file mode 100644 index 000000000..fb47d8b5f --- /dev/null +++ b/TeachingRecordSystem/src/TeachingRecordSystem.SupportUi/ValidationAttributes/NotEqualAttribute.cs @@ -0,0 +1,23 @@ +namespace TeachingRecordSystem.SupportUi.ValidationAttributes; + +using System.ComponentModel.DataAnnotations; + +public class NotEqualAttribute : ValidationAttribute +{ + private readonly object _notAllowedValue; + + public NotEqualAttribute(object notAllowedValue) + { + _notAllowedValue = notAllowedValue; + } + + protected override ValidationResult? IsValid(object value, ValidationContext validationContext) + { + if (value != null && value.Equals(_notAllowedValue)) + { + return new ValidationResult(ErrorMessage ?? $"The value cannot be {_notAllowedValue}."); + } + + return ValidationResult.Success; + } +} diff --git a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/EditInduction/EditInductionStatusTests.cs b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/EditInduction/EditInductionStatusTests.cs index 2ee303e71..23cedf04f 100644 --- a/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/EditInduction/EditInductionStatusTests.cs +++ b/TeachingRecordSystem/tests/TeachingRecordSystem.SupportUi.Tests/PageTests/Persons/PersonDetail/EditInduction/EditInductionStatusTests.cs @@ -1,4 +1,6 @@ +using AngleSharp.Dom; using AngleSharp.Html.Dom; +using TeachingRecordSystem.Core.DataStore.Postgres.Models; using TeachingRecordSystem.SupportUi.Pages.Persons.PersonDetail.EditInduction; namespace TeachingRecordSystem.SupportUi.Tests.PageTests.Persons.PersonDetail.EditInduction; @@ -6,7 +8,36 @@ namespace TeachingRecordSystem.SupportUi.Tests.PageTests.Persons.PersonDetail.Ed public class EditInductionStatusTests(HostFixture hostFixture) : TestBase(hostFixture) { [Fact] - public async Task ContinueAndCancelButtons_ExistOnPage() + public async Task Get_PageLegend_Expected() + { + // Arrange + InductionStatus inductionStatus = InductionStatus.Passed; + var person = await TestData.CreatePersonAsync(p => p + .WithQts() + .WithFirstName("Alfred") + .WithMiddleName("The") + .WithLastName("Great")); + var expectedCaption = "Induction - Alfred The Great"; + var journeyInstance = await CreateJourneyInstanceAsync( + person.PersonId, + new EditInductionState() + { + Initialized = true, + InductionStatus = inductionStatus + }); + var request = new HttpRequestMessage(HttpMethod.Get, $"/persons/{person.PersonId}/edit-induction/status?{journeyInstance.GetUniqueIdQueryParameter()}"); + + // Act + var response = await HttpClient.SendAsync(request); + + // Assert + var doc = await AssertEx.HtmlResponseAsync(response); + var caption = doc.GetElementByTestId("induction-status-caption"); + Assert.Equal(expectedCaption, caption!.TextContent); + } + + [Fact] + public async Task Get_ContinueAndCancelButtons_ExistOnPage() { // Arrange InductionStatus inductionStatus = InductionStatus.Passed; @@ -35,6 +66,113 @@ public async Task ContinueAndCancelButtons_ExistOnPage() Assert.Equal("Cancel and return to record", buttons.ElementAt(1)!.TextContent); } + [Theory] + [InlineData(InductionStatus.RequiredToComplete, new InductionStatus[] {InductionStatus.Exempt, InductionStatus.InProgress, InductionStatus.Passed, InductionStatus.Failed, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.Exempt, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.InProgress, InductionStatus.Passed, InductionStatus.Failed, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.InProgress, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.Passed, InductionStatus.Failed, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.Passed, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.InProgress, InductionStatus.Failed, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.Failed, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.InProgress, InductionStatus.Passed, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.FailedInWales, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.InProgress, InductionStatus.Passed, InductionStatus.Failed })] + public async Task Get_InductionNotManagedByCpd_ExpectedRadioButtonsExistOnPage(InductionStatus inductionStatus, InductionStatus[] expectedStatuses) + { + // Arrange + var expectedChoices = expectedStatuses.Select(s => s.ToString()); + + var person = await TestData.CreatePersonAsync(p => p.WithQts()); + + var journeyInstance = await CreateJourneyInstanceAsync( + person.PersonId, + new EditInductionState() + { + Initialized = true, + InitialInductionStatus = inductionStatus + }); + var request = new HttpRequestMessage(HttpMethod.Get, $"/persons/{person.PersonId}/edit-induction/status?{journeyInstance.GetUniqueIdQueryParameter()}"); + + // Act + var response = await HttpClient.SendAsync(request); + + // Assert + var doc = await AssertEx.HtmlResponseAsync(response); + var statusChoices = doc.QuerySelectorAll("[type=radio]").Select(r => r.Value); + var statusChoicesLegend = doc.GetElementByTestId("status-choices-legend"); + Assert.Equal("Select a status", statusChoicesLegend!.TextContent); + Assert.Equal(expectedChoices, statusChoices); + } + + [Theory] + [InlineData(InductionStatus.Passed, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.Failed, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt, InductionStatus.FailedInWales })] + [InlineData(InductionStatus.FailedInWales, new InductionStatus[] { InductionStatus.RequiredToComplete, InductionStatus.Exempt })] + public async Task Get_InductionManagedByCpd_ExpectedRadioButtonsExistOnPage(InductionStatus inductionStatus, InductionStatus[] expectedStatuses) + { + // Arrange + var expectedChoices = expectedStatuses.Select(s => s.ToString()); + var overSevenYearsAgo = Clock.Today.AddYears(-7).AddDays(-1); + var person = await TestData.CreatePersonAsync(p => p.WithQts()); + await WithDbContext(async dbContext => + { + dbContext.Attach(person.Person); + person.Person.SetCpdInductionStatus( + InductionStatus.Passed, + startDate: Clock.Today.AddYears(-7).AddMonths(-6), + completedDate: overSevenYearsAgo, + cpdModifiedOn: Clock.UtcNow, + updatedBy: SystemUser.SystemUserId, + now: Clock.UtcNow, + out _); + await dbContext.SaveChangesAsync(); + }); + + var journeyInstance = await CreateJourneyInstanceAsync( + person.PersonId, + new EditInductionState() + { + Initialized = true, + InitialInductionStatus = inductionStatus + }); + var request = new HttpRequestMessage(HttpMethod.Get, $"/persons/{person.PersonId}/edit-induction/status?{journeyInstance.GetUniqueIdQueryParameter()}"); + + // Act + var response = await HttpClient.SendAsync(request); + + // Assert + var doc = await AssertEx.HtmlResponseAsync(response); + var statusChoices = doc.QuerySelectorAll("[type=radio]").Select(r => r.Value); + var statusChoicesLegend = doc.GetElementByTestId("status-choices-legend"); + Assert.Equal("Select a status", statusChoicesLegend!.TextContent); + Assert.Equal(expectedChoices, statusChoices); + } + + [Fact] + public async Task Post_SelectedStatus_PersistsStatus() + { + // Arrange + var person = await TestData.CreatePersonAsync(p => p.WithQts()); + + var journeyInstance = await CreateJourneyInstanceAsync( + person.PersonId, + new EditInductionState() + { + Initialized = true, + InductionStatus = InductionStatus.Passed + }); + var postRequest = new HttpRequestMessage(HttpMethod.Post, $"/persons/{person.PersonId}/edit-induction/status?{journeyInstance.GetUniqueIdQueryParameter()}") + { + Content = new FormUrlEncodedContent(new Dictionary + { + ["InductionStatus"] = "Exempt" + }) + }; + + // Act + var response = await HttpClient.SendAsync(postRequest); + + // Assert + journeyInstance = await ReloadJourneyInstance(journeyInstance); + Assert.Equal("Exempt", journeyInstance.State.InductionStatus.GetTitle()); + } + private Task> CreateJourneyInstanceAsync(Guid personId, EditInductionState? state = null) => CreateJourneyInstance( JourneyNames.EditInduction,