From 80e8b6dd3af8f0628b5fc2b93637fa4a23c544e9 Mon Sep 17 00:00:00 2001 From: Roger Howell Date: Sat, 10 Feb 2024 19:50:11 +0000 Subject: [PATCH] Add delivery manager question --- .../Database/Entities/AssessmentRequest.cs | 4 + .../UseCases/AssessmentRequestRepository.cs | 74 ++++- ...RequestDeliveryManagerCompletenessTests.cs | 92 ++++++ ...ntRequestDeliveryManagerValidationTests.cs | 261 +++++++++++++++++ ...entRequestProductManagerValidationTests.cs | 46 +-- ...stProductOwnerManagerCompletenessTests.cs} | 30 +- .../Model/AssessmentRequest.cs | 275 ++++++++++++++---- .../Request/Question/DeliveryManager.cshtml | 103 +++++++ .../Question/DeliveryManager.cshtml.cs | 177 +++++++++++ .../Question/ProductOwnerManager.cshtml.cs | 4 +- .../Pages/Book/Request/TaskList.cshtml | 248 ++++++++-------- 11 files changed, 1092 insertions(+), 222 deletions(-) create mode 100644 src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestDeliveryManagerCompletenessTests.cs create mode 100644 src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestDeliveryManagerValidationTests.cs rename src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/{AssessmentRequestProductManagerCompletenessTests.cs => AssessmentRequestProductOwnerManagerCompletenessTests.cs} (64%) create mode 100644 src/ServiceAssessmentService/ServiceAssessmentService.WebApp/Pages/Book/Request/Question/DeliveryManager.cshtml create mode 100644 src/ServiceAssessmentService/ServiceAssessmentService.WebApp/Pages/Book/Request/Question/DeliveryManager.cshtml.cs diff --git a/src/ServiceAssessmentService/ServiceAssessmentService.Application/Database/Entities/AssessmentRequest.cs b/src/ServiceAssessmentService/ServiceAssessmentService.Application/Database/Entities/AssessmentRequest.cs index 60d3489c..327afa30 100644 --- a/src/ServiceAssessmentService/ServiceAssessmentService.Application/Database/Entities/AssessmentRequest.cs +++ b/src/ServiceAssessmentService/ServiceAssessmentService.Application/Database/Entities/AssessmentRequest.cs @@ -67,6 +67,8 @@ public ServiceAssessmentService.Domain.Model.AssessmentRequest ToDomainModel() SeniorResponsibleOfficer = SeniorResponsibleOfficer?.ToDomainModel(), HasProductOwnerManager = HasProductOwnerManager, ProductOwnerManager = ProductOwnerManager?.ToDomainModel(), + HasDeliveryManager = HasDeliveryManager, + DeliveryManager = DeliveryManager?.ToDomainModel(), CreatedAt = CreatedUtc, UpdatedAt = UpdatedUtc, }; @@ -95,6 +97,8 @@ public static Database.Entities.AssessmentRequest FromDomain(Domain.Model.Assess SeniorResponsibleOfficerId = domainModel.SeniorResponsibleOfficer?.Id, HasProductOwnerManager = domainModel.HasProductOwnerManager, ProductOwnerManagerId = domainModel.ProductOwnerManager?.Id, + HasDeliveryManager = domainModel.HasDeliveryManager, + DeliveryManagerId = domainModel.DeliveryManager?.Id, CreatedUtc = domainModel.CreatedAt.UtcDateTime, UpdatedUtc = domainModel.UpdatedAt.UtcDateTime, }; diff --git a/src/ServiceAssessmentService/ServiceAssessmentService.Application/UseCases/AssessmentRequestRepository.cs b/src/ServiceAssessmentService/ServiceAssessmentService.Application/UseCases/AssessmentRequestRepository.cs index d3c1f70a..79d2ed8f 100644 --- a/src/ServiceAssessmentService/ServiceAssessmentService.Application/UseCases/AssessmentRequestRepository.cs +++ b/src/ServiceAssessmentService/ServiceAssessmentService.Application/UseCases/AssessmentRequestRepository.cs @@ -28,6 +28,7 @@ public AssessmentRequestRepository(DataContext dbContext, ILogger e.DeputyDirector) .Include(e => e.SeniorResponsibleOfficer) .Include(e => e.ProductOwnerManager) + .Include(e => e.DeliveryManager) .Select(e => e.ToDomainModel()) .ToListAsync(); @@ -43,6 +44,7 @@ public AssessmentRequestRepository(DataContext dbContext, ILogger e.DeputyDirector) .Include(e => e.SeniorResponsibleOfficer) .Include(e => e.ProductOwnerManager) + .Include(e => e.DeliveryManager) .Where(e => e.Id == id) .Select(e => e.ToDomainModel()) .SingleOrDefaultAsync(); @@ -743,7 +745,7 @@ public async Task> Upda return validateDescriptionResult; } - public async Task> UpdateProductOwnerManagerAsync(Guid id, bool? hasProductManager, string newPersonalName, string newFamilyName, string newEmail) + public async Task> UpdateProductOwnerManagerAsync(Guid id, bool? hasProductOwnerManager, string newPersonalName, string newFamilyName, string newEmail) { // Validate the assessment request being edited, exists var assessmentRequest = await _dbContext.AssessmentRequests @@ -769,7 +771,7 @@ public async Task> Upda } // Do specific update - assessmentRequest.HasProductOwnerManager = hasProductManager; + assessmentRequest.HasProductOwnerManager = hasProductOwnerManager; if ( string.IsNullOrWhiteSpace(newPersonalName) @@ -792,7 +794,73 @@ public async Task> Upda // Validate the new value var domainModel = assessmentRequest.ToDomainModel(); - var validateDescriptionResult = domainModel.ValidateProductManager(); + var validateDescriptionResult = domainModel.ValidateProductOwnerManager(); + + // If valid, save it to the database + if (validateDescriptionResult.IsValid) + { + _dbContext.AssessmentRequests.Update(assessmentRequest); + await _dbContext.SaveChangesAsync(); + } + else + { + _logger.LogInformation("Attempted to update assessment request with ID {Id}, but it was not valid", id); + } + + // Return the result + return validateDescriptionResult; + } + + public async Task> UpdateDeliveryManagerAsync(Guid id, bool? hasProductOwnerManager, string newPersonalName, string newFamilyName, string newEmail) + { + // Validate the assessment request being edited, exists + var assessmentRequest = await _dbContext.AssessmentRequests + .Include(e => e.DeliveryManager) + .SingleOrDefaultAsync(e => e.Id == id); + if (assessmentRequest is null) + { + var validationResult = new RadioConditionalValidationResult + { + NestedValidationResult = new() + { + IsValid = true, + }, + }; + validationResult.RadioQuestionValidationErrors.Add(new() + { + FieldName = nameof(assessmentRequest.Id), + ErrorMessage = $"Assessment request with ID {id} not found", + }); + validationResult.IsValid = false; + + return validationResult; + } + + // Do specific update + assessmentRequest.HasDeliveryManager = hasProductOwnerManager; + + if ( + string.IsNullOrWhiteSpace(newPersonalName) + && string.IsNullOrWhiteSpace(newFamilyName) + && string.IsNullOrWhiteSpace(newEmail) + ) + { + // No person details provided, therefore declaring that the SRO is null. + assessmentRequest.DeliveryManager = null; + } + else + { + // At least one detail about the SRO has been provided, therefore... + // Either use the existing SRO (fetched via query above) and overwrite details, or create a new one and supply details for the first time. + assessmentRequest.DeliveryManager ??= new Database.Entities.Person(); + assessmentRequest.DeliveryManager.PersonalName = newPersonalName; + assessmentRequest.DeliveryManager.FamilyName = newFamilyName; + assessmentRequest.DeliveryManager.Email = newEmail; + } + + // Validate the new value + var domainModel = assessmentRequest.ToDomainModel(); + var validateDescriptionResult = domainModel.ValidateProductOwnerManager(); // If valid, save it to the database if (validateDescriptionResult.IsValid) diff --git a/src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestDeliveryManagerCompletenessTests.cs b/src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestDeliveryManagerCompletenessTests.cs new file mode 100644 index 00000000..d6e96467 --- /dev/null +++ b/src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestDeliveryManagerCompletenessTests.cs @@ -0,0 +1,92 @@ +using ServiceAssessmentService.Domain.Model; + +namespace ServiceAssessmentService.Domain.Test; + +public class AssessmentRequestDeliveryManagerCompletenessTests +{ + private static Person ArbitraryValidPerson => new Person + { + PersonalName = "Arbitrary Personal Name", + FamilyName = "Arbitrary Family Name", + Email = "arbitrary@example.com", + }; + private static Person ArbitraryIncompletePerson => new Person + { + PersonalName = null, + FamilyName = null, + Email = null, + }; + + public static TheoryData CompleteCombinations => new() + { + // Complete scenarios + // - Declares Delivery Manager is known, Delivery Manager details provided + {true, ArbitraryValidPerson}, + // - Declares Delivery Manager is not known, no Delivery Manager details provided + {false, null}, + + }; + + public static TheoryData IncompleteCombinations => new() + { + // All other combinations are incomplete/invalid + {null, null}, + {null, ArbitraryValidPerson}, + {null, ArbitraryIncompletePerson}, + + {true, null}, + // {true, ArbitraryValidPerson}, // ignore - valid combination + {true, ArbitraryIncompletePerson}, + + // {false, null}, // ignore - valid combination + {false, ArbitraryValidPerson}, + {false, ArbitraryIncompletePerson}, + + }; + + + [Theory] + [MemberData(nameof(CompleteCombinations))] + public void IsDeliveryManagerComplete_WHEN_CompleteCombinationOfValues_THEN_IsComplete( + bool? hasDeliveryManager, + Person? deliveryManager + ) + { + // Arrange + var assessmentRequest = new AssessmentRequest + { + HasDeliveryManager = hasDeliveryManager, + DeliveryManager = deliveryManager, + }; + + // Act + var result = assessmentRequest.IsDeliveryManagerComplete(); + + // Assert + Assert.True(result); + } + + [Theory] + [MemberData(nameof(IncompleteCombinations))] + public void IsDeliveryManagerComplete_WHEN_IncompleteCombinationOfValues_THEN_IsComplete( + bool? hasDeliveryManager, + Person? deliveryManager + ) + { + // Arrange + var assessmentRequest = new AssessmentRequest + { + HasDeliveryManager = hasDeliveryManager, + DeliveryManager = deliveryManager, + }; + + // Act + var result = assessmentRequest.IsDeliveryManagerComplete(); + + // Assert + string deliveryManagerString = deliveryManager?.ToString() ?? "null"; + + Assert.False(result, $"Expected to be incomplete: " + + $"\n- DeliveryManager: {deliveryManagerString}"); + } +} diff --git a/src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestDeliveryManagerValidationTests.cs b/src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestDeliveryManagerValidationTests.cs new file mode 100644 index 00000000..aa6bcd0a --- /dev/null +++ b/src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestDeliveryManagerValidationTests.cs @@ -0,0 +1,261 @@ +using ServiceAssessmentService.Domain.Model; + + +namespace ServiceAssessmentService.Domain.Test; + +/// +/// - Must declare if there is a Delivery Manager +/// - When yes, must provide Delivery Manager details +/// - When no, Delivery Manager details must be absent +/// +public class AssessmentRequestDeliveryManagerValidationTests +{ + + private static Person ArbitraryValidPerson => new Person + { + PersonalName = "Arbitrary Personal Name", + FamilyName = "Arbitrary Family Name", + Email = "arbitrary@example.com", + }; + + + + + [Fact] + public void HappyPath_WHEN_HasDeliveryManager_False_AND_DeliveryManager_Null_THEN_RadioIsValid_AND_NestedIsValid() + { + // Arrange + var assessmentRequest = new AssessmentRequest + { + HasDeliveryManager = false, + DeliveryManager = null, + }; + + // Act + var result = assessmentRequest.ValidateDeliveryManager(); + + // Assert + Assert.Multiple( + () => { Assert.True(result.IsValid); }, + () => { Assert.Empty(result.RadioQuestionValidationErrors); }, + () => { Assert.Empty(result.RadioQuestionValidationWarnings); }, + + () => { Assert.True(result.NestedValidationResult.IsValid); }, + () => { Assert.Empty(result.NestedValidationResult.ValidationErrors); }, + () => { Assert.Empty(result.NestedValidationResult.ValidationWarnings); } + ); + } + + [Fact] + public void HappyPath_WHEN_HasDeliveryManager_True_AND_DeliveryManager_Valid_THEN_RadioIsValid_AND_NestedIsValid() + { + // Arrange + var assessmentRequest = new AssessmentRequest + { + HasDeliveryManager = true, + DeliveryManager = ArbitraryValidPerson, + }; + + // Act + var result = assessmentRequest.ValidateDeliveryManager(); + + // Assert + Assert.Multiple( + () => { Assert.True(result.IsValid); }, + () => { Assert.Empty(result.RadioQuestionValidationErrors); }, + () => { Assert.Empty(result.RadioQuestionValidationWarnings); }, + + () => { Assert.True(result.NestedValidationResult.IsValid); }, + () => { Assert.Empty(result.NestedValidationResult.ValidationErrors); }, + () => { Assert.Empty(result.NestedValidationResult.ValidationWarnings); } + ); + } + + + [Fact] + public void WHEN_HasDeliveryManager_Null_AND_DeliveryManager_Null_THEN_RadioIsInvalid_AND_NestedIsValid() + { + // Arrange + var assessmentRequest = new AssessmentRequest + { + HasDeliveryManager = null, + DeliveryManager = null, + }; + + // Act + var result = assessmentRequest.ValidateDeliveryManager(); + + // Assert + Assert.Multiple( + () => { Assert.False(result.IsValid); }, + () => { Assert.Contains(result.RadioQuestionValidationErrors, v => v.FieldName == nameof(assessmentRequest.HasDeliveryManager)); }, + () => { Assert.Empty(result.RadioQuestionValidationWarnings); }, + + () => { Assert.True(result.NestedValidationResult.IsValid); }, + () => { Assert.Empty(result.NestedValidationResult.ValidationErrors); }, + () => { Assert.Empty(result.NestedValidationResult.ValidationWarnings); } + ); + } + + [Fact] + public void WHEN_HasDeliveryManager_True_AND_DeliveryManager_Null_THEN_RadioIsInvalid_AND_NestedIsValid() + { + // Arrange + var assessmentRequest = new AssessmentRequest + { + HasDeliveryManager = true, + DeliveryManager = null, + }; + + // Act + var result = assessmentRequest.ValidateDeliveryManager(); + + // Assert + Assert.Multiple( + () => { Assert.False(result.IsValid); }, + () => { Assert.Contains(result.RadioQuestionValidationErrors, v => v.FieldName == nameof(assessmentRequest.HasDeliveryManager)); }, + () => { Assert.Empty(result.RadioQuestionValidationWarnings); }, + + () => { Assert.False(result.NestedValidationResult.IsValid); }, + () => { Assert.Contains(result.NestedValidationResult.ValidationErrors, v => v.FieldName == nameof(assessmentRequest.DeliveryManager)); }, + () => { Assert.Empty(result.NestedValidationResult.ValidationWarnings); } + ); + } + + // has delivery manager true, delivery manager invalid personal name, radio valid, nested invalid + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + [InlineData("\t")] + public void WHEN_HasDeliveryManager_True_AND_DeliveryManager_InvalidPersonalName_THEN_RadioIsValid_AND_NestedIsInvalid(string? personalName) + { + // Arrange + var assessmentRequest = new AssessmentRequest + { + HasDeliveryManager = true, + DeliveryManager = new Person + { + PersonalName = personalName, + FamilyName = "Arbitrary Family Name", + Email = "arbitrary@example.com", + }, + }; + + // Act + var result = assessmentRequest.ValidateDeliveryManager(); + + // Assert + Assert.Multiple( + () => { Assert.True(result.IsValid); }, + () => { Assert.Empty(result.RadioQuestionValidationErrors); }, + () => { Assert.Empty(result.RadioQuestionValidationWarnings); }, + + () => { Assert.False(result.NestedValidationResult.IsValid); }, + () => { Assert.Contains(result.NestedValidationResult.PersonalNameErrors, v => v.FieldName == nameof(assessmentRequest.DeliveryManager.PersonalName)); }, + () => { Assert.Empty(result.NestedValidationResult.ValidationWarnings); } + ); + } + + // has delivery manager true, delivery manager invalid family name, radio valid, nested invalid + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + [InlineData("\t")] + public void WHEN_HasDeliveryManager_True_AND_DeliveryManager_InvalidFamilyName_THEN_RadioIsValid_AND_NestedIsInvalid(string? familyName) + { + // Arrange + var assessmentRequest = new AssessmentRequest + { + HasDeliveryManager = true, + DeliveryManager = new Person + { + PersonalName = "Arbitrary Personal Name", + FamilyName = familyName, + Email = "arbitrary@example.com", + }, + }; + + // Act + var result = assessmentRequest.ValidateDeliveryManager(); + + // Assert + Assert.Multiple( + () => { Assert.True(result.IsValid); }, + () => { Assert.Empty(result.RadioQuestionValidationErrors); }, + () => { Assert.Empty(result.RadioQuestionValidationWarnings); }, + + () => { Assert.False(result.NestedValidationResult.IsValid); }, + () => { Assert.Contains(result.NestedValidationResult.FamilyNameErrors, v => v.FieldName == nameof(assessmentRequest.DeliveryManager.FamilyName)); }, + () => { Assert.Empty(result.NestedValidationResult.ValidationWarnings); } + ); + } + + // has delivery manager true, delivery manager invalid email, radio valid, nested invalid + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + [InlineData("\t")] + [InlineData("invalid-email")] + [InlineData("invalid-email@")] + public void WHEN_HasDeliveryManager_True_AND_DeliveryManager_InvalidEmail_THEN_RadioIsValid_AND_NestedIsInvalid(string? email) + { + // Arrange + var assessmentRequest = new AssessmentRequest + { + HasDeliveryManager = true, + DeliveryManager = new Person + { + PersonalName = "Arbitrary Personal Name", + FamilyName = "Arbitrary Family Name", + Email = email, + }, + }; + + // Act + var result = assessmentRequest.ValidateDeliveryManager(); + + // Assert + Assert.Multiple( + () => { Assert.True(result.IsValid); }, + () => { Assert.Empty(result.RadioQuestionValidationErrors); }, + () => { Assert.Empty(result.RadioQuestionValidationWarnings); }, + + () => { Assert.False(result.NestedValidationResult.IsValid); }, + () => { Assert.Contains(result.NestedValidationResult.EmailErrors, v => v.FieldName == nameof(assessmentRequest.DeliveryManager.Email)); }, + () => { Assert.Empty(result.NestedValidationResult.ValidationWarnings); } + ); + } + + // has delivery manager false, delivery manager valid, radio invalid, nested invalid + [Fact] + public void WHEN_HasDeliveryManager_False_AND_DeliveryManager_Valid_THEN_RadioIsInvalid_AND_NestedIsValid() + { + // Arrange + var assessmentRequest = new AssessmentRequest + { + HasDeliveryManager = false, + DeliveryManager = ArbitraryValidPerson, + }; + + // Act + var result = assessmentRequest.ValidateDeliveryManager(); + + // Assert + Assert.Multiple( + () => { Assert.False(result.IsValid); }, + () => { Assert.Contains(result.RadioQuestionValidationErrors, v => v.FieldName == nameof(assessmentRequest.HasDeliveryManager)); }, + () => { Assert.Empty(result.RadioQuestionValidationWarnings); }, + + () => { Assert.False(result.NestedValidationResult.IsValid); }, + () => { Assert.Contains(result.NestedValidationResult.ValidationErrors, v => v.FieldName == nameof(assessmentRequest.DeliveryManager)); }, + () => { Assert.Empty(result.NestedValidationResult.ValidationWarnings); } + ); + } + + + + +} diff --git a/src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestProductManagerValidationTests.cs b/src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestProductManagerValidationTests.cs index 5c70d953..f7a383a3 100644 --- a/src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestProductManagerValidationTests.cs +++ b/src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestProductManagerValidationTests.cs @@ -8,7 +8,7 @@ namespace ServiceAssessmentService.Domain.Test; /// - When yes, must provide Product Manager details /// - When no, Product Manager details must be absent /// -public class AssessmentRequestProductManagerValidationTests +public class AssessmentRequestProductOwnerManagerValidationTests { private static Person ArbitraryValidPerson => new Person @@ -22,7 +22,7 @@ public class AssessmentRequestProductManagerValidationTests [Fact] - public void HappyPath_WHEN_HasProductManager_False_AND_ProductManager_Null_THEN_RadioIsValid_AND_NestedIsValid() + public void HappyPath_WHEN_HasProductOwnerManager_False_AND_ProductOwnerManager_Null_THEN_RadioIsValid_AND_NestedIsValid() { // Arrange var assessmentRequest = new AssessmentRequest @@ -32,7 +32,7 @@ public void HappyPath_WHEN_HasProductManager_False_AND_ProductManager_Null_THEN_ }; // Act - var result = assessmentRequest.ValidateProductManager(); + var result = assessmentRequest.ValidateProductOwnerManager(); // Assert Assert.Multiple( @@ -47,7 +47,7 @@ public void HappyPath_WHEN_HasProductManager_False_AND_ProductManager_Null_THEN_ } [Fact] - public void HappyPath_WHEN_HasProductManager_True_AND_ProductManager_Valid_THEN_RadioIsValid_AND_NestedIsValid() + public void HappyPath_WHEN_HasProductOwnerManager_True_AND_ProductOwnerManager_Valid_THEN_RadioIsValid_AND_NestedIsValid() { // Arrange var assessmentRequest = new AssessmentRequest @@ -57,7 +57,7 @@ public void HappyPath_WHEN_HasProductManager_True_AND_ProductManager_Valid_THEN_ }; // Act - var result = assessmentRequest.ValidateProductManager(); + var result = assessmentRequest.ValidateProductOwnerManager(); // Assert Assert.Multiple( @@ -73,7 +73,7 @@ public void HappyPath_WHEN_HasProductManager_True_AND_ProductManager_Valid_THEN_ [Fact] - public void WHEN_HasProductManager_Null_AND_ProductManager_Null_THEN_RadioIsInvalid_AND_NestedIsValid() + public void WHEN_HasProductOwnerManager_Null_AND_ProductOwnerManager_Null_THEN_RadioIsInvalid_AND_NestedIsValid() { // Arrange var assessmentRequest = new AssessmentRequest @@ -83,7 +83,7 @@ public void WHEN_HasProductManager_Null_AND_ProductManager_Null_THEN_RadioIsInva }; // Act - var result = assessmentRequest.ValidateProductManager(); + var result = assessmentRequest.ValidateProductOwnerManager(); // Assert Assert.Multiple( @@ -98,7 +98,7 @@ public void WHEN_HasProductManager_Null_AND_ProductManager_Null_THEN_RadioIsInva } [Fact] - public void WHEN_HasProductManager_True_AND_ProductManager_Null_THEN_RadioIsInvalid_AND_NestedIsValid() + public void WHEN_HasProductOwnerManager_True_AND_ProductOwnerManager_Null_THEN_RadioIsInvalid_AND_NestedIsValid() { // Arrange var assessmentRequest = new AssessmentRequest @@ -108,7 +108,7 @@ public void WHEN_HasProductManager_True_AND_ProductManager_Null_THEN_RadioIsInva }; // Act - var result = assessmentRequest.ValidateProductManager(); + var result = assessmentRequest.ValidateProductOwnerManager(); // Assert Assert.Multiple( @@ -132,8 +132,8 @@ public void WHEN_HasProductManager_True_AND_ProductManager_Null_THEN_RadioIsInva // [InlineData(true, "Arbitrary Personal Name",null, "arbitrary@example.com")] // [InlineData(true, "Arbitrary Personal Name","Arbitrary Family Name", null)] // // [InlineData(true, "Arbitrary Personal Name","Arbitrary Family Name", "arbitrary@example.com")] // ignore - valid - // public void WHEN_HasProductManager_True_AND_ProductManager_Invalid_THEN_RadioIsValid_AND_NestedIsInvalid( - // bool hasProductManager, + // public void WHEN_HasProductOwnerManager_True_AND_ProductOwnerManager_Invalid_THEN_RadioIsValid_AND_NestedIsInvalid( + // bool hasProductOwnerManager, // string? personalName, // string? familyName, // string? email @@ -142,8 +142,8 @@ public void WHEN_HasProductManager_True_AND_ProductManager_Null_THEN_RadioIsInva // // Arrange // var assessmentRequest = new AssessmentRequest // { - // HasProductManager = hasProductManager, - // ProductManager = new Person + // HasProductOwnerManager = hasProductOwnerManager, + // ProductOwnerManager = new Person // { // PersonalName = personalName, // FamilyName = familyName, @@ -152,7 +152,7 @@ public void WHEN_HasProductManager_True_AND_ProductManager_Null_THEN_RadioIsInva // }; // // // Act - // var result = assessmentRequest.ValidateProductManager(); + // var result = assessmentRequest.ValidateProductOwnerManager(); // // // Assert // Assert.Multiple( @@ -161,7 +161,7 @@ public void WHEN_HasProductManager_True_AND_ProductManager_Null_THEN_RadioIsInva // () => { Assert.Empty(result.RadioQuestionValidationWarnings); }, // // () => { Assert.False(result.NestedValidationResult.IsValid); }, - // () => { Assert.Contains(result.NestedValidationResult.ValidationErrors, v => v.FieldName == nameof(assessmentRequest.ProductManager)); }, + // () => { Assert.Contains(result.NestedValidationResult.ValidationErrors, v => v.FieldName == nameof(assessmentRequest.ProductOwnerManager)); }, // () => { Assert.Empty(result.NestedValidationResult.ValidationWarnings); } // ); // } @@ -172,7 +172,7 @@ public void WHEN_HasProductManager_True_AND_ProductManager_Null_THEN_RadioIsInva [InlineData("")] [InlineData(" ")] [InlineData("\t")] - public void WHEN_HasProductManager_True_AND_ProductManager_InvalidPersonalName_THEN_RadioIsValid_AND_NestedIsInvalid(string? personalName) + public void WHEN_HasProductOwnerManager_True_AND_ProductOwnerManager_InvalidPersonalName_THEN_RadioIsValid_AND_NestedIsInvalid(string? personalName) { // Arrange var assessmentRequest = new AssessmentRequest @@ -187,7 +187,7 @@ public void WHEN_HasProductManager_True_AND_ProductManager_InvalidPersonalName_T }; // Act - var result = assessmentRequest.ValidateProductManager(); + var result = assessmentRequest.ValidateProductOwnerManager(); // Assert Assert.Multiple( @@ -207,7 +207,7 @@ public void WHEN_HasProductManager_True_AND_ProductManager_InvalidPersonalName_T [InlineData("")] [InlineData(" ")] [InlineData("\t")] - public void WHEN_HasProductManager_True_AND_ProductManager_InvalidFamilyName_THEN_RadioIsValid_AND_NestedIsInvalid(string? familyName) + public void WHEN_HasProductOwnerManager_True_AND_ProductOwnerManager_InvalidFamilyName_THEN_RadioIsValid_AND_NestedIsInvalid(string? familyName) { // Arrange var assessmentRequest = new AssessmentRequest @@ -222,7 +222,7 @@ public void WHEN_HasProductManager_True_AND_ProductManager_InvalidFamilyName_THE }; // Act - var result = assessmentRequest.ValidateProductManager(); + var result = assessmentRequest.ValidateProductOwnerManager(); // Assert Assert.Multiple( @@ -244,7 +244,7 @@ public void WHEN_HasProductManager_True_AND_ProductManager_InvalidFamilyName_THE [InlineData("\t")] [InlineData("invalid-email")] [InlineData("invalid-email@")] - public void WHEN_HasProductManager_True_AND_ProductManager_InvalidEmail_THEN_RadioIsValid_AND_NestedIsInvalid(string? email) + public void WHEN_HasProductOwnerManager_True_AND_ProductOwnerManager_InvalidEmail_THEN_RadioIsValid_AND_NestedIsInvalid(string? email) { // Arrange var assessmentRequest = new AssessmentRequest @@ -259,7 +259,7 @@ public void WHEN_HasProductManager_True_AND_ProductManager_InvalidEmail_THEN_Rad }; // Act - var result = assessmentRequest.ValidateProductManager(); + var result = assessmentRequest.ValidateProductOwnerManager(); // Assert Assert.Multiple( @@ -275,7 +275,7 @@ public void WHEN_HasProductManager_True_AND_ProductManager_InvalidEmail_THEN_Rad // has product manager false, product manager valid, radio invalid, nested invalid [Fact] - public void WHEN_HasProductManager_False_AND_ProductManager_Valid_THEN_RadioIsInvalid_AND_NestedIsValid() + public void WHEN_HasProductOwnerManager_False_AND_ProductOwnerManager_Valid_THEN_RadioIsInvalid_AND_NestedIsValid() { // Arrange var assessmentRequest = new AssessmentRequest @@ -285,7 +285,7 @@ public void WHEN_HasProductManager_False_AND_ProductManager_Valid_THEN_RadioIsIn }; // Act - var result = assessmentRequest.ValidateProductManager(); + var result = assessmentRequest.ValidateProductOwnerManager(); // Assert Assert.Multiple( diff --git a/src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestProductManagerCompletenessTests.cs b/src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestProductOwnerManagerCompletenessTests.cs similarity index 64% rename from src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestProductManagerCompletenessTests.cs rename to src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestProductOwnerManagerCompletenessTests.cs index 5b657b48..e927190d 100644 --- a/src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestProductManagerCompletenessTests.cs +++ b/src/ServiceAssessmentService/ServiceAssessmentService.Domain.Test/AssessmentRequestProductOwnerManagerCompletenessTests.cs @@ -2,7 +2,7 @@ namespace ServiceAssessmentService.Domain.Test; -public class AssessmentRequestProductManagerCompletenessTests +public class AssessmentRequestProductOwnerManagerCompletenessTests { private static Person ArbitraryValidPerson => new Person { @@ -20,9 +20,9 @@ public class AssessmentRequestProductManagerCompletenessTests public static TheoryData CompleteCombinations => new() { // Complete scenarios - // - Declares Product Manager is known, Product Manager details provided + // - Declares Product Owner/Manager is known, Product Owner/Manager details provided {true, ArbitraryValidPerson}, - // - Declares Product Manager is not known, no Product Manager details provided + // - Declares Product Owner/Manager is not known, no Product Owner/Manager details provided {false, null}, }; @@ -47,16 +47,16 @@ public class AssessmentRequestProductManagerCompletenessTests [Theory] [MemberData(nameof(CompleteCombinations))] - public void IsProductManagerComplete_WHEN_CompleteCombinationOfValues_THEN_IsComplete( - bool? hasProductManager, - Person? productManager + public void IsProductOwnerManagerComplete_WHEN_CompleteCombinationOfValues_THEN_IsComplete( + bool? hasProductOwnerManager, + Person? productOwnerManager ) { // Arrange var assessmentRequest = new AssessmentRequest { - HasProductOwnerManager = hasProductManager, - ProductOwnerManager = productManager, + HasProductOwnerManager = hasProductOwnerManager, + ProductOwnerManager = productOwnerManager, }; // Act @@ -68,25 +68,25 @@ public void IsProductManagerComplete_WHEN_CompleteCombinationOfValues_THEN_IsCom [Theory] [MemberData(nameof(IncompleteCombinations))] - public void IsProductManagerComplete_WHEN_IncompleteCombinationOfValues_THEN_IsComplete( - bool? hasProductManager, - Person? productManager + public void IsProductOwnerManagerComplete_WHEN_IncompleteCombinationOfValues_THEN_IsComplete( + bool? hasProductOwnerManager, + Person? productOwnerManager ) { // Arrange var assessmentRequest = new AssessmentRequest { - HasProductOwnerManager = hasProductManager, - ProductOwnerManager = productManager, + HasProductOwnerManager = hasProductOwnerManager, + ProductOwnerManager = productOwnerManager, }; // Act var result = assessmentRequest.IsProductOwnerManagerComplete(); // Assert - string productManagerString = productManager?.ToString() ?? "null"; + string productOwnerManagerString = productOwnerManager?.ToString() ?? "null"; Assert.False(result, $"Expected to be incomplete: " + - $"\n- ProductManager: {productManagerString}"); + $"\n- ProductOwnerManager: {productOwnerManagerString}"); } } diff --git a/src/ServiceAssessmentService/ServiceAssessmentService.Domain/Model/AssessmentRequest.cs b/src/ServiceAssessmentService/ServiceAssessmentService.Domain/Model/AssessmentRequest.cs index 9ea8fb82..e8eb0826 100644 --- a/src/ServiceAssessmentService/ServiceAssessmentService.Domain/Model/AssessmentRequest.cs +++ b/src/ServiceAssessmentService/ServiceAssessmentService.Domain/Model/AssessmentRequest.cs @@ -19,7 +19,7 @@ public class AssessmentRequest {nameof(DeputyDirector), x => x.IsDeputyDirectorComplete()}, { nameof(SeniorResponsibleOfficer), x => x.IsSeniorResponsibleOfficerComplete() }, { nameof(ProductOwnerManager), x => x.IsProductOwnerManagerComplete() }, - // { nameof(DeliveryManager), x => x.IsDeliveryManagerComplete() }, + { nameof(DeliveryManager), x => x.IsDeliveryManagerComplete() }, }; public Guid Id { get; set; } @@ -300,23 +300,6 @@ public RadioConditionalValidationResult ValidateProjectCod return radioValidationResult; } - // public bool IsProjectCodeComplete() - // { - // return IsProjectCodeKnown switch - // { - // null => - // // Not yet declared if project code is known, thus is incomplete. - // false, - // false => - // // Project code declared as not known, the actual project code is not required. - // true, - // true when !string.IsNullOrWhiteSpace(ProjectCode) => - // // Project code declared as known, and the actual project code is provided -- all information provided, thus is complete. - // true, - // _ => throw new Exception(), - // }; - // } - public bool IsProjectCodeComplete() { if (IsProjectCodeKnown is true && !string.IsNullOrWhiteSpace(ProjectCode)) @@ -947,13 +930,13 @@ public bool IsSeniorResponsibleOfficerComplete() - #region ProductManager + #region ProductOwnerManager public bool? HasProductOwnerManager { get; set; } = null; public Person? ProductOwnerManager { get; set; } - public RadioConditionalValidationResult ValidateProductManager() + public RadioConditionalValidationResult ValidateProductOwnerManager() { var radioValidationResult = new RadioConditionalValidationResult() @@ -1106,7 +1089,7 @@ public RadioConditionalValidationResult ValidateProductM // TODO: Consider max length (probably 100 chars?) - // TODO: Consider rejecting newlines with an error, as productManager should normally be a short phrase only without newlines. + // TODO: Consider rejecting newlines with an error, as product owner/manager should normally be a short phrase only without newlines. // TODO: Consider handling of accented characters and multi-byte characters (e.g., emoji) } @@ -1159,6 +1142,218 @@ public bool IsProductOwnerManagerComplete() #endregion + #region DeliveryManager + + public bool? HasDeliveryManager { get; set; } = null; + + public Person? DeliveryManager { get; set; } + + public RadioConditionalValidationResult ValidateDeliveryManager() + { + + var radioValidationResult = new RadioConditionalValidationResult() + { + IsValid = true, + NestedValidationResult = new PersonValidationResult() + { + IsValid = true, + } + }; + + + if (HasDeliveryManager == null) + { + // Not yet declared if has a Delivery Manager - incomplete + radioValidationResult.IsValid = false; + radioValidationResult.RadioQuestionValidationErrors.Add(new ValidationError + { + FieldName = nameof(HasDeliveryManager), + ErrorMessage = "Please select whether the team has a delivery manager", + }); + } + else if (HasDeliveryManager == true) + { + if (DeliveryManager is null) + { + radioValidationResult.IsValid = false; + radioValidationResult.RadioQuestionValidationErrors.Add(new ValidationError + { + FieldName = nameof(HasDeliveryManager), + ErrorMessage = "When declaring the project has a delivery manager, please provide their details.", + }); + radioValidationResult.NestedValidationResult.IsValid = false; + radioValidationResult.NestedValidationResult.ValidationErrors.Add(new ValidationError + { + FieldName = nameof(DeliveryManager), + ErrorMessage = "When declaring the project has a delivery manager, please provide their details.", + }); + } + else + { + if (string.IsNullOrWhiteSpace(DeliveryManager.PersonalName)) + { + radioValidationResult.NestedValidationResult.IsValid = false; + radioValidationResult.NestedValidationResult.PersonalNameErrors.Add(new ValidationError + { + FieldName = nameof(DeliveryManager.PersonalName), + ErrorMessage = "Please enter the delivery manager's personal name.", + }); + } + else + { + if (DeliveryManager.PersonalName.Any(c => c < 32 || 126 < c)) + { + /* + * ASCII char codes 32-126 are standard printable characters (upper and lower case letters, numbers, typical punctuation, etc) + * Add a warning if any characters fall outside this range + * Note not an error as it may be desirable to use non-standard characters (e.g., accented characters or emoji) + */ + radioValidationResult.NestedValidationResult.IsValid = false; + radioValidationResult.NestedValidationResult.PersonalNameWarnings.Add(new ValidationWarning + { + FieldName = nameof(DeliveryManager.PersonalName), + WarningMessage = + "The delivery manager's personal name contains non-standard ASCII characters -- non-standard characters (e.g., \"smart quotes\" copy/pasted from MS Word) may not be intentional and may cause errors with values not be displayed correctly", + }); + } + } + + if (string.IsNullOrWhiteSpace(DeliveryManager.FamilyName)) + { + radioValidationResult.NestedValidationResult.IsValid = false; + radioValidationResult.NestedValidationResult.FamilyNameErrors.Add(new ValidationError + { + FieldName = nameof(DeliveryManager.FamilyName), + ErrorMessage = "Please enter the delivery manager's family name.", + }); + } + else + { + if (DeliveryManager.FamilyName.Any(c => c < 32 || 126 < c)) + { + /* + * ASCII char codes 32-126 are standard printable characters (upper and lower case letters, numbers, typical punctuation, etc) + * Add a warning if any characters fall outside this range + * Note not an error as it may be desirable to use non-standard characters (e.g., accented characters or emoji) + */ + radioValidationResult.NestedValidationResult.IsValid = false; + radioValidationResult.NestedValidationResult.FamilyNameWarnings.Add(new ValidationWarning + { + FieldName = nameof(DeliveryManager.FamilyName), + WarningMessage = + "The delivery manager's family name contains non-standard ASCII characters -- non-standard characters (e.g., \"smart quotes\" copy/pasted from MS Word) may not be intentional and may cause errors with values not be displayed correctly", + }); + } + } + + if (string.IsNullOrWhiteSpace(DeliveryManager.Email)) + { + radioValidationResult.NestedValidationResult.IsValid = false; + radioValidationResult.NestedValidationResult.EmailErrors.Add(new ValidationError + { + FieldName = nameof(DeliveryManager.Email), + ErrorMessage = "Please enter the delivery manager's email address.", + }); + } + else + { + if (DeliveryManager.Email.Any(c => c < 32 || 126 < c)) + { + /* + * ASCII char codes 32-126 are standard printable characters (upper and lower case letters, numbers, typical punctuation, etc) + * Add a warning if any characters fall outside this range + * Note not an error as it may be desirable to use non-standard characters (e.g., accented characters or emoji) + */ + radioValidationResult.NestedValidationResult.IsValid = false; + radioValidationResult.NestedValidationResult.EmailWarnings.Add(new ValidationWarning + { + FieldName = nameof(DeliveryManager.Email), + WarningMessage = + "The delivery manager's email address contains non-standard ASCII characters -- non-standard characters (e.g., \"smart quotes\" copy/pasted from MS Word) may not be intentional and may cause errors with values not be displayed correctly", + }); + } + + if (!EmailValidationUtilities.IsValidEmail(DeliveryManager.Email)) + { + radioValidationResult.NestedValidationResult.IsValid = false; + radioValidationResult.NestedValidationResult.EmailErrors.Add(new ValidationError + { + FieldName = nameof(DeliveryManager.Email), + ErrorMessage = + "The delivery manager's email address is not recognised as being in a valid email format.", + }); + } + + if (!EmailValidationUtilities.IsValidDomain(DeliveryManager.Email)) + { + radioValidationResult.NestedValidationResult.IsValid = false; + radioValidationResult.NestedValidationResult.EmailErrors.Add(new ValidationError + { + FieldName = nameof(DeliveryManager.Email), + ErrorMessage = + "The delivery manager's email address is not recognised as having a recognised/acceptable domain.", + }); + } + + // TODO: Validate email format + // TODO: Validate email domain is DfE + } + + + // TODO: Consider max length (probably 100 chars?) + // TODO: Consider rejecting newlines with an error, as deliveryManager should normally be a short phrase only without newlines. + // TODO: Consider handling of accented characters and multi-byte characters (e.g., emoji) + + } + } + else if (HasDeliveryManager == false) + { + + if (DeliveryManager is not null) + { + radioValidationResult.IsValid = false; + radioValidationResult.RadioQuestionValidationErrors.Add(new ValidationError + { + FieldName = nameof(HasDeliveryManager), + ErrorMessage = "When declaring the project has no delivery manager, please do not provide the delivery manager's details.", + }); + radioValidationResult.NestedValidationResult.IsValid = false; + radioValidationResult.NestedValidationResult.ValidationErrors.Add(new ValidationError + { + FieldName = nameof(DeliveryManager), + ErrorMessage = "When declaring the project has no delivery manager, please do not provide the delivery manager's details.", + }); + } + else + { + // No delivery manager declared, and no details provided -- valid. + } + } + + return radioValidationResult; + } + + + + public bool IsDeliveryManagerComplete() + { + return HasDeliveryManager switch + { + null => + // Not yet declared if project code is known, thus is incomplete. + false, + true => + // Declared as known, and present - complete. + (DeliveryManager is not null && DeliveryManager.IsComplete()), + false => + // Declared as not known, and no details provided - complete. + (DeliveryManager is null), + }; + } + + #endregion + + public DateTimeOffset CreatedAt { get; set; } public DateTimeOffset UpdatedAt { get; set; } @@ -1176,38 +1371,6 @@ public bool IsProductOwnerManagerComplete() // return ReviewDates.Any(); // } - // - // public bool IsProductOwnerManagerComplete() - // { - // return HasProductOwnerManager switch - // { - // null => - // // Not yet declared if product owner / project manager is known, thus is incomplete. - // false, - // false => - // // Declared as having nil product owner manager, thus name/email not required. - // true, - // true => - // // Declared as having a product owner manager, thus name/email required. - // (!string.IsNullOrWhiteSpace(ProductOwnerManager?.Name) && !string.IsNullOrWhiteSpace(ProductOwnerManager?.Email)), - // }; - // } - // - // public bool IsDeliveryManagerComplete() - // { - // return HasDeliveryManager switch - // { - // null => - // // Not yet declared if delivery manager is known, thus is incomplete. - // false, - // false => - // // Declared as having nil delivery manager, thus name/email not required. - // true, - // true => - // // Declared as having a delivery manager, thus name/email required. - // (!string.IsNullOrWhiteSpace(DeliveryManager?.Name) && !string.IsNullOrWhiteSpace(DeliveryManager?.Email)), - // }; - // } public string GetOverallCompletionStatusDescription() { @@ -1216,15 +1379,15 @@ public string GetOverallCompletionStatusDescription() if (countCompleted == 0) { - return "Not started"; + return "Request not started"; } else if (countCompleted == count) { - return "Complete"; + return "Request complete"; } else { - return $"In progress"; + return $"Request in progress"; } } diff --git a/src/ServiceAssessmentService/ServiceAssessmentService.WebApp/Pages/Book/Request/Question/DeliveryManager.cshtml b/src/ServiceAssessmentService/ServiceAssessmentService.WebApp/Pages/Book/Request/Question/DeliveryManager.cshtml new file mode 100644 index 00000000..9687f2cf --- /dev/null +++ b/src/ServiceAssessmentService/ServiceAssessmentService.WebApp/Pages/Book/Request/Question/DeliveryManager.cshtml @@ -0,0 +1,103 @@ +@page +@model ServiceAssessmentService.WebApp.Pages.Book.Request.Question.DeliveryManagerPageModel +@{ + ViewData["Title"] = Model.RadioQuestionText; + ViewData["Section"] = SiteSection.BookingRequest; +} + +
+
+ +
+
+ +
+ @* Omitting the form action/asp-path results in the form being submitted to the same URL (desirable behaviour) *@ +
+ + + + + + @Model.RadioQuestionText + + + @Model.RadioQuestionHint + + + @if (Model.RadioErrors.Any()) + { + @(string.Join(", ", Model.RadioErrors)) + } + + + Yes + + + + @Model.PersonalNameQuestionText + @if (!string.IsNullOrWhiteSpace(Model.PersonalNameQuestionHint)) + { + @Model.PersonalNameQuestionHint + } + + @if (Model.PersonalNameErrors.Any()) + { + @* string.Join here is a workaround, to allow for there to be multiple potential error messages, but the govuk-frontend-taghelpers library permits only one error message entry *@ + + @string.Join(", ", Model.PersonalNameErrors) + + } + + + + @Model.FamilyNameQuestionText + @if (!string.IsNullOrWhiteSpace(Model.FamilyNameQuestionHint)) + { + @Model.FamilyNameQuestionHint + } + + @if (Model.FamilyNameErrors.Any()) + { + @* string.Join here is a workaround, to allow for there to be multiple potential error messages, but the govuk-frontend-taghelpers library permits only one error message entry *@ + + @string.Join(", ", Model.FamilyNameErrors) + + } + + + + @Model.EmailQuestionText + @if (!string.IsNullOrWhiteSpace(Model.EmailQuestionHint)) + { + @Model.EmailQuestionHint + } + + @if (Model.EmailErrors.Any()) + { + @* string.Join here is a workaround, to allow for there to be multiple potential error messages, but the govuk-frontend-taghelpers library permits only one error message entry *@ + + @string.Join(", ", Model.EmailErrors) + + } + + + + + + No + + + + + +
+ Save and continue + Skip and return to later +
+
+
+ +
+
+
diff --git a/src/ServiceAssessmentService/ServiceAssessmentService.WebApp/Pages/Book/Request/Question/DeliveryManager.cshtml.cs b/src/ServiceAssessmentService/ServiceAssessmentService.WebApp/Pages/Book/Request/Question/DeliveryManager.cshtml.cs new file mode 100644 index 00000000..8e5539ea --- /dev/null +++ b/src/ServiceAssessmentService/ServiceAssessmentService.WebApp/Pages/Book/Request/Question/DeliveryManager.cshtml.cs @@ -0,0 +1,177 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using ServiceAssessmentService.Application.UseCases; +using ServiceAssessmentService.Domain.Model; + +namespace ServiceAssessmentService.WebApp.Pages.Book.Request.Question; + +public class DeliveryManagerPageModel : PageModel +{ + private readonly AssessmentRequestRepository _assessmentRequestRepository; + private readonly AssessmentTypeRepository _assessmentTypeRepository; + private readonly PhaseRepository _phaseRepository; + private readonly PortfolioRepository _portfolioRepository; + + private readonly ILogger _logger; + + public DeliveryManagerPageModel( + AssessmentRequestRepository assessmentRequestRepository + , AssessmentTypeRepository assessmentTypeRepository + , PhaseRepository phaseRepository + , PortfolioRepository portfolioRepository + , ILogger logger + ) + { + _assessmentRequestRepository = assessmentRequestRepository; + _assessmentTypeRepository = assessmentTypeRepository; + _phaseRepository = phaseRepository; + _portfolioRepository = portfolioRepository; + _logger = logger; + } + + + public Guid? Id { get; set; } = Guid.Empty; + + [BindProperty] + public string? HasDeliveryManagerValue { get; set; } + + [BindProperty] + public string? DeliveryManagerPersonalName { get; set; } + [BindProperty] + public string? DeliveryManagerFamilyName { get; set; } + [BindProperty] + public string? DeliveryManagerEmail { get; set; } + + + public List RadioErrors { get; set; } = new(); + public List RadioWarnings { get; set; } = new(); + + public List ValidationErrors { get; set; } = new(); + public List ValidationWarnings { get; set; } = new(); + + public List PersonalNameErrors { get; set; } = new(); + public List PersonalNameWarnings { get; set; } = new(); + + public List FamilyNameErrors { get; set; } = new(); + public List FamilyNameWarnings { get; set; } = new(); + + public List EmailErrors { get; set; } = new(); + public List EmailWarnings { get; set; } = new(); + + + + public const string _hasDeliveryManagerValueYes = "yes"; + public String HasDeliveryManagerValueYes => _hasDeliveryManagerValueYes; + + public const string _hasDeliveryManagerValueNo = "no"; + public String HasDeliveryManagerValueNo => _hasDeliveryManagerValueNo; + + private const string _hasDeliveryManagerFormElementName = "service-has-delivery-manager"; + public string HasDeliveryManagerFormElementName => _hasDeliveryManagerFormElementName; + + private const string _hasDeliveryManagerRadioPrefix = "has-delivery-manager"; + public string HasDeliveryManagerRadioPrefix => _hasDeliveryManagerRadioPrefix; + + private const string _formElementNameDeliveryManagerPersonalName = "service-delivery-manager-personal-name"; + public string FormElementNameDeliveryManagerPersonalName => _formElementNameDeliveryManagerPersonalName; + + private const string _formElementNameDeliveryManagerFamilyName = "service-delivery-manager-family-name"; + public string FormElementNameDeliveryManagerFamilyName => _formElementNameDeliveryManagerFamilyName; + + private const string _formElementNameDeliveryManagerEmail = "service-delivery-manager-email"; + public string FormElementNameDeliveryManagerEmail => _formElementNameDeliveryManagerEmail; + + + + + public string RadioQuestionText => $"Does the team have a delivery manager?"; + public string? RadioQuestionHint => "Select one option."; + + public string PersonalNameQuestionText => $"Personal name"; + public string? PersonalNameQuestionHint => null; + + public string FamilyNameQuestionText => $"Family name"; + public string? FamilyNameQuestionHint => null; + + public string EmailQuestionText => $"Email address"; + public string? EmailQuestionHint => null; + + + public async Task OnGet(Guid id) + { + var req = await _assessmentRequestRepository.GetByIdAsync(id); + if (req == null) + { + _logger.LogInformation("Attempted to edit assessment request with ID {Id}, but it was not found", id); + return NotFound(); + } + + Id = id; + + HasDeliveryManagerValue = req.HasDeliveryManager switch + { + true => _hasDeliveryManagerValueYes, + false => _hasDeliveryManagerValueNo, + _ => null, + }; + + DeliveryManagerPersonalName = req.DeliveryManager?.PersonalName ?? string.Empty; + DeliveryManagerFamilyName = req.DeliveryManager?.FamilyName ?? string.Empty; + DeliveryManagerEmail = req.DeliveryManager?.Email ?? string.Empty; + + return Page(); + } + + public async Task OnPost( + Guid id, + [FromForm(Name = _hasDeliveryManagerFormElementName), AllowEmpty] string? newHasDeliveryManager, + [FromForm(Name = _formElementNameDeliveryManagerPersonalName), AllowEmpty] string newPersonalName, + [FromForm(Name = _formElementNameDeliveryManagerFamilyName), AllowEmpty] string newFamilyName, + [FromForm(Name = _formElementNameDeliveryManagerEmail), AllowEmpty] string newEmail + ) + { + var req = await _assessmentRequestRepository.GetByIdAsync(id); + if (req == null) + { + _logger.LogInformation("Attempted to edit assessment request with ID {Id}, but it was not found", id); + return NotFound(); + } + + + bool? hasDeliveryManager = newHasDeliveryManager switch + { + _hasDeliveryManagerValueYes => true, + _hasDeliveryManagerValueNo => false, + _ => null, + }; + + var changeResult = await _assessmentRequestRepository.UpdateDeliveryManagerAsync(id, hasDeliveryManager, newPersonalName, newFamilyName, newEmail); + if (!changeResult.IsValid) + { + Id = id; + + HasDeliveryManagerValue = newHasDeliveryManager; // Update the page model to use the user-supplied value, allowing them to edit it on the next page rather than losing their input. + + DeliveryManagerPersonalName = newPersonalName; // Update the page model to use the user-supplied value, allowing them to edit it on the next page rather than losing their input. + DeliveryManagerFamilyName = newFamilyName; // Update the page model to use the user-supplied value, allowing them to edit it on the next page rather than losing their input. + DeliveryManagerEmail = newEmail; // Update the page model to use the user-supplied value, allowing them to edit it on the next page rather than losing their input. + + RadioWarnings = changeResult.RadioQuestionValidationWarnings.Select(e => e.WarningMessage).ToList(); + RadioErrors = changeResult.RadioQuestionValidationErrors.Select(e => e.ErrorMessage).ToList(); + + ValidationWarnings = changeResult.NestedValidationResult.ValidationWarnings.Select(e => e.WarningMessage).ToList(); + ValidationErrors = changeResult.NestedValidationResult.ValidationErrors.Select(e => e.ErrorMessage).ToList(); + PersonalNameWarnings = changeResult.NestedValidationResult.PersonalNameWarnings.Select(e => e.WarningMessage).ToList(); + PersonalNameErrors = changeResult.NestedValidationResult.PersonalNameErrors.Select(e => e.ErrorMessage).ToList(); + FamilyNameWarnings = changeResult.NestedValidationResult.FamilyNameWarnings.Select(e => e.WarningMessage).ToList(); + FamilyNameErrors = changeResult.NestedValidationResult.FamilyNameErrors.Select(e => e.ErrorMessage).ToList(); + EmailWarnings = changeResult.NestedValidationResult.EmailWarnings.Select(e => e.WarningMessage).ToList(); + EmailErrors = changeResult.NestedValidationResult.EmailErrors.Select(e => e.ErrorMessage).ToList(); + + return Page(); + } + + return RedirectToPage("/Book/Request/TaskList", new { id }); + // return RedirectToPage("/Book/Request/Question/DeliveryManager", new { id }); + } +} diff --git a/src/ServiceAssessmentService/ServiceAssessmentService.WebApp/Pages/Book/Request/Question/ProductOwnerManager.cshtml.cs b/src/ServiceAssessmentService/ServiceAssessmentService.WebApp/Pages/Book/Request/Question/ProductOwnerManager.cshtml.cs index 1cca5746..1ddf9726 100644 --- a/src/ServiceAssessmentService/ServiceAssessmentService.WebApp/Pages/Book/Request/Question/ProductOwnerManager.cshtml.cs +++ b/src/ServiceAssessmentService/ServiceAssessmentService.WebApp/Pages/Book/Request/Question/ProductOwnerManager.cshtml.cs @@ -171,7 +171,7 @@ public async Task OnPost( return Page(); } - return RedirectToPage("/Book/Request/TaskList", new { id }); - // return RedirectToPage("/Book/Request/Question/DeliveryManager", new { id }); + // return RedirectToPage("/Book/Request/TaskList", new { id }); + return RedirectToPage("/Book/Request/Question/DeliveryManager", new { id }); } } diff --git a/src/ServiceAssessmentService/ServiceAssessmentService.WebApp/Pages/Book/Request/TaskList.cshtml b/src/ServiceAssessmentService/ServiceAssessmentService.WebApp/Pages/Book/Request/TaskList.cshtml index 015c4257..d1a2a958 100644 --- a/src/ServiceAssessmentService/ServiceAssessmentService.WebApp/Pages/Book/Request/TaskList.cshtml +++ b/src/ServiceAssessmentService/ServiceAssessmentService.WebApp/Pages/Book/Request/TaskList.cshtml @@ -26,7 +26,7 @@

@(Model.AssessmentRequest.Name ?? "Untitled") (@(Model.AssessmentRequest.PhaseConcluding?.DisplayNameMidSentenceCase ?? "unknown phase") @(Model.AssessmentRequest.AssessmentType?.DisplayNameMidSentenceCase ?? "unknown assessment type"))

-

Request incomplete

+

@Model.AssessmentRequest.GetOverallCompletionStatusDescription()

You have completed @Model.AssessmentRequest.CountOfCompleted() @@ -150,7 +150,7 @@ } - +

  • Project code @@ -182,7 +182,7 @@ }
  • - +
  • Start date @@ -201,7 +201,7 @@ }
  • - +
  • End date @@ -220,51 +220,52 @@ }
  • - - @*
  • *@ - @* *@ - @* Requested review weeks *@ - @* *@ - @* *@ - @* @* *@ - @* TODO: Consider scenario where user might: *@ - @* 1. select an end date, *@ - @* 2. then select review dates, *@ - @* 3. then time passes and the end date is now within the next five weeks, and previously selected values are now invalid *@ - @* *@ - @* *@ - @* @if (Model.AssessmentRequest.IsEndDateWithinNextFiveWeeks) *@ - @* { *@ - @* None available as end date is within next 5 weeks *@ - @* } *@ - @* else if ( /*Model.AssessmentRequest.IsEndDateKnown == true &&*/ Model.AssessmentRequest.ReviewDates.Any()) *@ - @* { *@ - @*
      *@ - @* @foreach (var date in Model.AssessmentRequest.ReviewDates) *@ - @* { *@ - @*
    • @date
    • *@ - @* } *@ - @*
    *@ - @* } *@ - @* else *@ - @* { *@ - @* @notYetSupplied *@ - @* } *@ - @*
    *@ - @* *@ - @* @if (Model.AssessmentRequest.IsReviewDatesComplete()) *@ - @* { *@ - @* Completed *@ - @* } *@ - @* else *@ - @* { *@ - @* Not started *@ - @* } *@ - @* *@ - @* *@ - @*
  • *@ + + @*
  • *@ + @* *@ + @* Requested review weeks *@ + @* *@ + @* *@ + @* @* *@ + @* TODO: Consider scenario where user might: *@ + @* 1. select an end date, *@ + @* 2. then select review dates, *@ + @* 3. then time passes and the end date is now within the next five weeks, and previously selected values are now invalid *@ + @* *@ + @* *@ + @* @if (Model.AssessmentRequest.IsEndDateWithinNextFiveWeeks) *@ + @* { *@ + @* None available as end date is within next 5 weeks *@ + @* } *@ + @* else if ( /*Model.AssessmentRequest.IsEndDateKnown == true &&*/ Model.AssessmentRequest.ReviewDates.Any()) *@ + @* { *@ + @*
      *@ + @* @foreach (var date in Model.AssessmentRequest.ReviewDates) *@ + @* { *@ + @*
    • @date
    • *@ + @* } *@ + @*
    *@ + @* } *@ + @* else *@ + @* { *@ + @* @notYetSupplied *@ + @* } *@ + @*
    *@ + @* *@ + @* @if (Model.AssessmentRequest.IsReviewDatesComplete()) *@ + @* { *@ + @* Completed *@ + @* } *@ + @* else *@ + @* { *@ + @* Not started *@ + @* } *@ + @* *@ + @* *@ + @*
  • *@ +
  • 2. Product and delivery @@ -359,83 +360,85 @@ }

  • -
  • - - Product manager - - - @if (Model.AssessmentRequest.HasProductOwnerManager == true) - { - if (Model.AssessmentRequest.ProductOwnerManager is null) - { - @notYetSupplied - ; - } - else - { - @Model.AssessmentRequest.ProductOwnerManager.FullName() (@Model.AssessmentRequest.ProductOwnerManager.Email) - } - } - else if (Model.AssessmentRequest.HasProductOwnerManager == false) - { - Not known - } - else - { - @notYetSupplied - } - - - @if (Model.AssessmentRequest.IsProductOwnerManagerComplete()) - { - Completed - } - else - { - Not started - } - -
  • - @*
  • *@ - @* *@ - @* Delivery manager *@ - @* *@ - @* *@ - @* @if (Model.AssessmentRequest.HasDeliveryManager == true) *@ - @* { *@ - @* if (Model.AssessmentRequest.DeliveryManager is null) *@ - @* { *@ - @* @notYetSupplied *@ - @* ; *@ - @* } *@ - @* else *@ - @* { *@ - @* @Model.AssessmentRequest.DeliveryManager.Name (@Model.AssessmentRequest.DeliveryManager.Email) *@ - @* } *@ - @* } *@ - @* else if (Model.AssessmentRequest.HasDeliveryManager == false) *@ - @* { *@ - @* Not known *@ - @* } *@ - @* else *@ - @* { *@ - @* @notYetSupplied *@ - @* } *@ - @* *@ - @* *@ - @* @if (Model.AssessmentRequest.IsDeliveryManagerComplete()) *@ - @* { *@ - @* Completed *@ - @* } *@ - @* else *@ - @* { *@ - @* Not started *@ - @* } *@ - @* *@ - @*
  • *@ + +
  • + + Product owner/manager + + + @if (Model.AssessmentRequest.HasProductOwnerManager == true) + { + if (Model.AssessmentRequest.ProductOwnerManager is null) + { + @notYetSupplied + ; + } + else + { + @Model.AssessmentRequest.ProductOwnerManager.FullName() (@Model.AssessmentRequest.ProductOwnerManager.Email) + } + } + else if (Model.AssessmentRequest.HasProductOwnerManager == false) + { + Not known + } + else + { + @notYetSupplied + } + + + @if (Model.AssessmentRequest.IsProductOwnerManagerComplete()) + { + Completed + } + else + { + Not started + } + +
  • + +
  • + + Delivery manager + + + @if (Model.AssessmentRequest.HasDeliveryManager == true) + { + if (Model.AssessmentRequest.DeliveryManager is null) + { + @notYetSupplied + ; + } + else + { + @Model.AssessmentRequest.DeliveryManager.FullName() (@Model.AssessmentRequest.DeliveryManager.Email) + } + } + else if (Model.AssessmentRequest.HasDeliveryManager == false) + { + Not known + } + else + { + @notYetSupplied + } + + + @if (Model.AssessmentRequest.IsDeliveryManagerComplete()) + { + Completed + } + else + { + Not started + } + +
  • - +
  • 3. Submit @@ -455,7 +458,6 @@

  • -