diff --git a/src/SFA.DAS.Reservations.Application.UnitTests/Reservations/Commands/CacheReservationCourse/WhenCachingReservationCourse.cs b/src/SFA.DAS.Reservations.Application.UnitTests/Reservations/Commands/CacheReservationCourse/WhenCachingReservationCourse.cs index 481ec566a..a3db5d67b 100644 --- a/src/SFA.DAS.Reservations.Application.UnitTests/Reservations/Commands/CacheReservationCourse/WhenCachingReservationCourse.cs +++ b/src/SFA.DAS.Reservations.Application.UnitTests/Reservations/Commands/CacheReservationCourse/WhenCachingReservationCourse.cs @@ -168,14 +168,14 @@ public async Task Then_Gets_Selected_Course(CacheReservationCourseCommand comman await _commandHandler.Handle(command, CancellationToken.None); //Assert - _mockCourseService.Verify(cs => cs.GetCourse(command.CourseId), Times.Once); + _mockCourseService.Verify(cs => cs.GetCourse(command.SelectedCourseId), Times.Once); } [Test, AutoData] public async Task Then_Calls_Cache_Service_To_Save_Reservation(CacheReservationCourseCommand command) { //Assign - command.CourseId = _expectedCourse.Id; + command.SelectedCourseId = _expectedCourse.Id; //Act await _commandHandler.Handle(command, CancellationToken.None); @@ -192,7 +192,7 @@ public async Task Then_Calls_Cache_Service_To_Save_Reservation(CacheReservationC public async Task Then_Caches_Course_Choice_If_Not_Selected(CacheReservationCourseCommand command) { //Assign - command.CourseId = null; + command.SelectedCourseId = null; //Act await _commandHandler.Handle(command, CancellationToken.None); diff --git a/src/SFA.DAS.Reservations.Application.UnitTests/Reservations/Commands/CacheReservationCourse/WhenValidatingCommand.cs b/src/SFA.DAS.Reservations.Application.UnitTests/Reservations/Commands/CacheReservationCourse/WhenValidatingCommand.cs index ac7c2d6b8..adc462dd1 100644 --- a/src/SFA.DAS.Reservations.Application.UnitTests/Reservations/Commands/CacheReservationCourse/WhenValidatingCommand.cs +++ b/src/SFA.DAS.Reservations.Application.UnitTests/Reservations/Commands/CacheReservationCourse/WhenValidatingCommand.cs @@ -29,7 +29,7 @@ public async Task Then_If_ReservationId_Is_Invalid_Then_Fail() var command = new CacheReservationCourseCommand { Id = Guid.Empty, - CourseId = "1" + SelectedCourseId = "1" }; var result = await _validator.ValidateAsync(command); @@ -49,7 +49,7 @@ public async Task Then_If_CourseId_Not_Set_Then_Fail() var command = new CacheReservationCourseCommand { Id = Guid.NewGuid(), - CourseId = "" + SelectedCourseId = "" }; var result = await _validator.ValidateAsync(command); @@ -57,8 +57,8 @@ public async Task Then_If_CourseId_Not_Set_Then_Fail() result.IsValid().Should().BeFalse(); result.ValidationDictionary.Count.Should().Be(1); result.ValidationDictionary - .Should().ContainKey(nameof( CacheReservationCourseCommand.CourseId)) - .WhichValue.Should().Be("Select a course"); + .Should().ContainKey(nameof(CacheReservationCourseCommand.SelectedCourseId)) + .WhichValue.Should().Be("Select which apprenticeship training your apprentice will take"); } @@ -70,7 +70,7 @@ public async Task Then_If_CourseId_Is_Invalid_Then_Fail() var command = new CacheReservationCourseCommand { Id = Guid.NewGuid(), - CourseId = "123" + SelectedCourseId = "123" }; var result = await _validator.ValidateAsync(command); @@ -78,8 +78,8 @@ public async Task Then_If_CourseId_Is_Invalid_Then_Fail() result.IsValid().Should().BeFalse(); result.ValidationDictionary.Count.Should().Be(1); result.ValidationDictionary - .Should().ContainKey(nameof( CacheReservationCourseCommand.CourseId)) - .WhichValue.Should().Be("Course is invalid"); + .Should().ContainKey(nameof( CacheReservationCourseCommand.SelectedCourseId)) + .WhichValue.Should().Be("Selected course does not exist"); } [Test] @@ -87,7 +87,7 @@ public async Task And_All_Fields_Invalid_Then_Returns_All_Errors() { _courseService.Setup(s => s.CourseExists(It.IsAny())).ReturnsAsync(false); - var command = new CacheReservationCourseCommand{ CourseId = "INVALID" }; + var command = new CacheReservationCourseCommand{ SelectedCourseId = "INVALID" }; var result = await _validator.ValidateAsync(command); @@ -95,7 +95,7 @@ public async Task And_All_Fields_Invalid_Then_Returns_All_Errors() result.ValidationDictionary.Count.Should().Be(2); result.ValidationDictionary .Should().ContainKey(nameof( CacheReservationCourseCommand.Id)) - .And.ContainKey(nameof( CacheReservationCourseCommand.CourseId)); + .And.ContainKey(nameof( CacheReservationCourseCommand.SelectedCourseId)); } [Test] @@ -104,7 +104,7 @@ public async Task And_All_Fields_Valid_Then_Valid() var command = new CacheReservationCourseCommand { Id = Guid.NewGuid(), - CourseId = "1" + SelectedCourseId = "1" }; var result = await _validator.ValidateAsync(command); diff --git a/src/SFA.DAS.Reservations.Application/Reservations/Commands/CacheReservationCourse/CacheReservationCourseCommand.cs b/src/SFA.DAS.Reservations.Application/Reservations/Commands/CacheReservationCourse/CacheReservationCourseCommand.cs index 30aa6e046..cd20ac273 100644 --- a/src/SFA.DAS.Reservations.Application/Reservations/Commands/CacheReservationCourse/CacheReservationCourseCommand.cs +++ b/src/SFA.DAS.Reservations.Application/Reservations/Commands/CacheReservationCourse/CacheReservationCourseCommand.cs @@ -7,7 +7,7 @@ public class CacheReservationCourseCommand: IRequest { public Guid Id { get; set; } - public string CourseId { get; set; } + public string SelectedCourseId { get; set; } public uint? UkPrn { get; set; } } } \ No newline at end of file diff --git a/src/SFA.DAS.Reservations.Application/Reservations/Commands/CacheReservationCourse/CacheReservationCourseCommandHandler.cs b/src/SFA.DAS.Reservations.Application/Reservations/Commands/CacheReservationCourse/CacheReservationCourseCommandHandler.cs index fb476db4b..2e0aea0ae 100644 --- a/src/SFA.DAS.Reservations.Application/Reservations/Commands/CacheReservationCourse/CacheReservationCourseCommandHandler.cs +++ b/src/SFA.DAS.Reservations.Application/Reservations/Commands/CacheReservationCourse/CacheReservationCourseCommandHandler.cs @@ -57,7 +57,7 @@ public async Task Handle(CacheReservationCourseCommand command, Cancellati throw new CachedReservationNotFoundException(command.Id); } - if (string.IsNullOrEmpty(command.CourseId)) + if (string.IsNullOrEmpty(command.SelectedCourseId)) { var course = new Course(null,null,0); cachedReservation.CourseId = course.Id; @@ -65,7 +65,7 @@ public async Task Handle(CacheReservationCourseCommand command, Cancellati } else { - var course = await _courseService.GetCourse(command.CourseId); + var course = await _courseService.GetCourse(command.SelectedCourseId); cachedReservation.CourseId = course.Id; cachedReservation.CourseDescription = course.CourseDescription; diff --git a/src/SFA.DAS.Reservations.Application/Reservations/Commands/CacheReservationCourse/CacheReservationCourseCommandValidator.cs b/src/SFA.DAS.Reservations.Application/Reservations/Commands/CacheReservationCourse/CacheReservationCourseCommandValidator.cs index 0c508d418..b09f15de3 100644 --- a/src/SFA.DAS.Reservations.Application/Reservations/Commands/CacheReservationCourse/CacheReservationCourseCommandValidator.cs +++ b/src/SFA.DAS.Reservations.Application/Reservations/Commands/CacheReservationCourse/CacheReservationCourseCommandValidator.cs @@ -23,13 +23,13 @@ public async Task ValidateAsync(CacheReservationCourseCommand result.AddError(nameof(command.Id), $"{nameof( CacheReservationCourseCommand.Id)} has not been supplied"); } - if(string.IsNullOrEmpty(command.CourseId)) + if(string.IsNullOrEmpty(command.SelectedCourseId)) { - result.AddError(nameof(command.CourseId), "Select a course"); + result.AddError(nameof(command.SelectedCourseId), "Select which apprenticeship training your apprentice will take"); } - else if(!await _courseService.CourseExists(command.CourseId)) + else if(!await _courseService.CourseExists(command.SelectedCourseId)) { - result.AddError(nameof(command.CourseId), "Course is invalid"); + result.AddError(nameof(command.SelectedCourseId), "Selected course does not exist"); } return result; diff --git a/src/SFA.DAS.Reservations.Web.AcceptanceTests/Steps/ReservationSteps.cs b/src/SFA.DAS.Reservations.Web.AcceptanceTests/Steps/ReservationSteps.cs index dad1174e5..46da3e60f 100644 --- a/src/SFA.DAS.Reservations.Web.AcceptanceTests/Steps/ReservationSteps.cs +++ b/src/SFA.DAS.Reservations.Web.AcceptanceTests/Steps/ReservationSteps.cs @@ -42,8 +42,13 @@ public void GivenIAmANonLevyEmployer() public void GivenIHaveChosenACourse() { var controller = Services.GetService(); + var postSelectCourseViewModel = new PostSelectCourseViewModel + { + SelectedCourseId = TestData.Course.Id, + ApprenticeTrainingKnown = true + }; - var result = controller.PostSelectCourse(TestData.ReservationRouteModel, TestData.Course.Id) + var result = controller.PostSelectCourse(TestData.ReservationRouteModel, postSelectCourseViewModel) .Result as RedirectToRouteResult; Assert.IsNotNull(result); diff --git a/src/SFA.DAS.Reservations.Web.UnitTests/Employers/WhenCallingPostSelectCourse.cs b/src/SFA.DAS.Reservations.Web.UnitTests/Employers/WhenCallingPostSelectCourse.cs index 40ec55c9f..80d0de6fb 100644 --- a/src/SFA.DAS.Reservations.Web.UnitTests/Employers/WhenCallingPostSelectCourse.cs +++ b/src/SFA.DAS.Reservations.Web.UnitTests/Employers/WhenCallingPostSelectCourse.cs @@ -64,29 +64,35 @@ public void Arrange() } [Test, MoqAutoData] - public async Task Then_Caches_Draft_Reservation(ReservationsRouteModel routeModel) + public async Task Then_Caches_Draft_Reservation( + ReservationsRouteModel routeModel, + PostSelectCourseViewModel postSelectCourseViewModel) { //Assign - var selectedCourse = _course.Id; + postSelectCourseViewModel.SelectedCourseId = _course.Id; + postSelectCourseViewModel.ApprenticeTrainingKnown = true; //Act - await _controller.PostSelectCourse(routeModel, selectedCourse); + await _controller.PostSelectCourse(routeModel, postSelectCourseViewModel); //Assert _mediator.Verify(mediator => mediator.Send(It.Is(command => - command.CourseId.Equals(_course.Id)), It.IsAny())); + command.SelectedCourseId.Equals(_course.Id)), It.IsAny())); } [Test, MoqAutoData] - public async Task And_No_Course_Then_Caches_Draft_Reservation(ReservationsRouteModel routeModel) + public async Task And_No_Course_Then_Caches_Draft_Reservation( + ReservationsRouteModel routeModel, + PostSelectCourseViewModel postSelectCourseViewModel) { _mediator.Setup(mediator => mediator.Send( It.IsAny(), It.IsAny())) .ReturnsAsync(() => _cachedReservationResult); - - await _controller.PostSelectCourse(routeModel, null); + postSelectCourseViewModel.SelectedCourseId = null; + postSelectCourseViewModel.ApprenticeTrainingKnown = true; + await _controller.PostSelectCourse(routeModel, postSelectCourseViewModel); _mediator.Verify(mediator => mediator.Send(It.IsAny(), It.IsAny()), @@ -94,13 +100,15 @@ public async Task And_No_Course_Then_Caches_Draft_Reservation(ReservationsRouteM } [Test, MoqAutoData] - public async Task Then_Adds_Guid_To_RouteModel(ReservationsRouteModel routeModel) + public async Task Then_Adds_Guid_To_RouteModel( + ReservationsRouteModel routeModel, + PostSelectCourseViewModel postSelectCourseViewModel) { //Assign - var selectedCourse = _course.Id; + postSelectCourseViewModel.SelectedCourseId = _course.Id; //Act - var result = await _controller.PostSelectCourse(routeModel, selectedCourse) as RedirectToRouteResult; + var result = await _controller.PostSelectCourse(routeModel, postSelectCourseViewModel) as RedirectToRouteResult; //Assert Assert.IsNotNull(result); @@ -110,16 +118,19 @@ public async Task Then_Adds_Guid_To_RouteModel(ReservationsRouteModel routeModel } [Test, AutoData]//note cannot use moqautodata to construct controller here due to modelmetadata usage. - public async Task And_Validation_Error_Then_Returns_Validation_Error_Details(ReservationsRouteModel routeModel) + public async Task And_Validation_Error_Then_Returns_Validation_Error_Details( + ReservationsRouteModel routeModel, + PostSelectCourseViewModel postSelectCourseViewModel) { //Assign - var selectedCourse = _course.Id; + postSelectCourseViewModel.SelectedCourseId = _course.Id; + postSelectCourseViewModel.ApprenticeTrainingKnown = true; _mediator.Setup(mediator => mediator.Send(It.IsAny(), It.IsAny())) .ThrowsAsync(new ValidationException(new ValidationResult("Failed", new List { "Course|The Course field is not valid." }), null, null)); //Act - var result = await _controller.PostSelectCourse(routeModel, selectedCourse); + var result = await _controller.PostSelectCourse(routeModel, postSelectCourseViewModel); //Assert Assert.IsNotNull(result); @@ -132,25 +143,27 @@ public async Task And_Validation_Error_Then_Returns_Validation_Error_Details(Res [Test, MoqAutoData] public async Task Then_The_BackLink_Is_Set_To_Return_To_CohortDetails_From_ValidationError_If_There_Is_A_Cohort_Ref( ReservationsRouteModel routeModel, - string cohortUrl + string cohortUrl, + PostSelectCourseViewModel postSelectCourseViewModel ) { //Arrange - routeModel.CohortReference = "ABC123"; + _cachedReservationResult.CohortRef = "ABC123"; _mediator.Setup(mediator => mediator.Send(It.IsAny(), It.IsAny())) .ThrowsAsync(new ValidationException(new ValidationResult("Failed", new List { "Course|The Course field is not valid." }), null, null)); - var selectedCourse = _course.Id; + postSelectCourseViewModel.SelectedCourseId = string.Empty; + postSelectCourseViewModel.ApprenticeTrainingKnown = true; _externalUrlHelper - .Setup(x => x.GenerateCohortDetailsUrl(null, routeModel.EmployerAccountId,routeModel.CohortReference)) + .Setup(x => x.GenerateCohortDetailsUrl(null, routeModel.EmployerAccountId, _cachedReservationResult.CohortRef)) .Returns(cohortUrl); //Act - var result = await _controller.PostSelectCourse(routeModel, selectedCourse) as ViewResult; + var result = await _controller.PostSelectCourse(routeModel, postSelectCourseViewModel) as ViewResult; var viewModel = result?.Model as EmployerSelectCourseViewModel; Assert.IsNotNull(viewModel); Assert.AreEqual(cohortUrl, viewModel.BackLink); - Assert.AreEqual(routeModel.CohortReference, viewModel.CohortReference); + Assert.AreEqual(_cachedReservationResult.CohortRef, viewModel.CohortReference); } [Test, MoqAutoData] @@ -158,18 +171,20 @@ public async Task Then_The_BackLink_Is_Set_To_Return_To_ReviewPage_If_There_Is_F ICollection courses, [Frozen] Mock mockMediator, ReservationsRouteModel routeModel, - EmployerReservationsController controller) + EmployerReservationsController controller, + PostSelectCourseViewModel postSelectCourseViewModel) { //Arrange + _cachedReservationResult.CohortRef = ""; routeModel.CohortReference = ""; routeModel.FromReview = true; _mediator.Setup(mediator => mediator.Send(It.IsAny(), It.IsAny())) .ThrowsAsync(new ValidationException(new ValidationResult("Failed", new List { "Course|The Course field is not valid." }), null, null)); - var selectedCourse = _course.Id; + postSelectCourseViewModel.SelectedCourseId = _course.Id; //Act - var result = await _controller.PostSelectCourse(routeModel, selectedCourse) as ViewResult; + var result = await _controller.PostSelectCourse(routeModel, postSelectCourseViewModel) as ViewResult; var viewModel = result?.Model as EmployerSelectCourseViewModel; Assert.IsNotNull(viewModel); @@ -183,23 +198,67 @@ public async Task Then_The_BackLink_Is_Set_To_Return_To_SelectLegalEntityView( ICollection courses, [Frozen] Mock mockMediator, ReservationsRouteModel routeModel, - EmployerReservationsController controller) + EmployerReservationsController controller, + PostSelectCourseViewModel postSelectCourseViewModel) { //Arrange + _cachedReservationResult.CohortRef = ""; + _cachedReservationResult.EmployerHasSingleLegalEntity = false; routeModel.CohortReference = ""; routeModel.FromReview = false; _mediator.Setup(mediator => mediator.Send(It.IsAny(), It.IsAny())) .ThrowsAsync(new ValidationException(new ValidationResult("Failed", new List { "Course|The Course field is not valid." }), null, null)); - var selectedCourse = _course.Id; + postSelectCourseViewModel.SelectedCourseId= _course.Id; //Act - var result = await _controller.PostSelectCourse(routeModel, selectedCourse) as ViewResult; + var result = await _controller.PostSelectCourse(routeModel, postSelectCourseViewModel) as ViewResult; var viewModel = result?.Model as EmployerSelectCourseViewModel; Assert.IsNotNull(viewModel); Assert.AreEqual(RouteNames.EmployerSelectLegalEntity, viewModel.BackLink); Assert.AreEqual(routeModel.CohortReference, viewModel.CohortReference); } + + [Test, MoqAutoData] + public async Task WhenApprenticeshipTrainingNotKnown_ThenRedirectsToGuidancePage( + ReservationsRouteModel routeModel, + EmployerReservationsController controller, + PostSelectCourseViewModel postSelectCourseViewModel) + { + //Arrange + postSelectCourseViewModel.ApprenticeTrainingKnown = false; + var expectedRouteName = RouteNames.EmployerCourseGuidance; + + //Act + var result = await controller.PostSelectCourse(routeModel, postSelectCourseViewModel) as RedirectToRouteResult; + + //Assert + Assert.NotNull(result); + Assert.AreEqual(expectedRouteName, result.RouteName); + } + + [Test, MoqAutoData] + public async Task WhenApprenticeshipTrainingIsNull_ThenRedirectsToSelectCourse( + ReservationsRouteModel routeModel, + PostSelectCourseViewModel postSelectCourseViewModel, + [Frozen] Mock mockMediator, + EmployerReservationsController controller) + { + //Arrange + postSelectCourseViewModel.ApprenticeTrainingKnown = null; + var expectedViewName = "SelectCourse"; + _mediator.Setup(mediator => mediator.Send(It.IsAny(), It.IsAny())) + .ThrowsAsync(new ValidationException(new ValidationResult("Failed", new List { "Course|The Course field is not valid." }), null, null)); + + //Act + var result = await _controller.PostSelectCourse(routeModel, postSelectCourseViewModel) as ViewResult; + + //Assert + Assert.NotNull(result); + Assert.AreEqual(expectedViewName, result.ViewName); + + + } } } diff --git a/src/SFA.DAS.Reservations.Web.UnitTests/Reservations/WhenCallingPostApprenticeshipTraining.cs b/src/SFA.DAS.Reservations.Web.UnitTests/Reservations/WhenCallingPostApprenticeshipTraining.cs index 9ae46fbad..e544dfcb9 100644 --- a/src/SFA.DAS.Reservations.Web.UnitTests/Reservations/WhenCallingPostApprenticeshipTraining.cs +++ b/src/SFA.DAS.Reservations.Web.UnitTests/Reservations/WhenCallingPostApprenticeshipTraining.cs @@ -144,7 +144,7 @@ public async Task And_Has_Ukprn_Then_Caches_Course_And_StartDate( _mediator.Verify(mediator => mediator.Send( It.Is( c => - c.CourseId.Equals(_course.Id) && + c.SelectedCourseId.Equals(_course.Id) && c.UkPrn.Equals(routeModel.UkPrn)), It.IsAny())); @@ -169,7 +169,7 @@ public async Task And_No_Ukprn_Then_Caches_StartDate_Only( _mediator.Verify(mediator => mediator.Send( It.Is( c => - c.CourseId.Equals(_course.Id)), + c.SelectedCourseId.Equals(_course.Id)), It.IsAny()), Times.Never); _mediator.Verify(mediator => mediator.Send( @@ -191,7 +191,7 @@ public async Task And_No_Course_Then_Caches_Draft_Reservation( _mediator.Verify(mediator => mediator.Send( It.Is( c => - c.CourseId == null), + c.SelectedCourseId == null), It.IsAny())); _mediator.Verify(mediator => mediator.Send( diff --git a/src/SFA.DAS.Reservations.Web/Controllers/EmployerReservationsController.cs b/src/SFA.DAS.Reservations.Web/Controllers/EmployerReservationsController.cs index f05ddf646..cfd8db21c 100644 --- a/src/SFA.DAS.Reservations.Web/Controllers/EmployerReservationsController.cs +++ b/src/SFA.DAS.Reservations.Web/Controllers/EmployerReservationsController.cs @@ -239,64 +239,45 @@ public async Task PostSelectLegalEntity(ReservationsRouteModel ro [Route("{id}/select-course",Name = RouteNames.EmployerSelectCourse)] public async Task SelectCourse(ReservationsRouteModel routeModel) { - var cachedReservation = await _mediator.Send(new GetCachedReservationQuery {Id = routeModel.Id.Value}); - if (cachedReservation == null) + var viewModel = await BuildEmployerSelectCourseViewModel(routeModel, routeModel.FromReview); + + if (viewModel == null) { return View("Index"); } - var getCoursesResponse = await _mediator.Send(new GetCoursesQuery()); - - var courseViewModels = getCoursesResponse.Courses.Select(course => new CourseViewModel(course, cachedReservation.CourseId)); - - var viewModel = new EmployerSelectCourseViewModel - { - ReservationId = routeModel.Id.Value, - Courses = courseViewModels, - BackLink = GenerateBackLink(routeModel, cachedReservation.CohortRef, cachedReservation.EmployerHasSingleLegalEntity), - CohortReference = cachedReservation.CohortRef - }; - - return View(viewModel); + return View("SelectCourse",viewModel); } - private string GenerateBackLink(ReservationsRouteModel routeModel, string cohortRef, bool employerHasSingleLegalEntity = false) + [HttpPost] + [ValidateAntiForgeryToken] + [Route("{id}/select-course", Name = RouteNames.EmployerSelectCourse)] + public async Task PostSelectCourse(ReservationsRouteModel routeModel, PostSelectCourseViewModel postViewModel) { - if (!string.IsNullOrEmpty(routeModel.CohortReference)) + if (!ModelState.IsValid) { - return _urlHelper.GenerateCohortDetailsUrl(null, routeModel.EmployerAccountId, cohortRef); - } - if (routeModel.FromReview.HasValue && routeModel.FromReview.Value) - return RouteNames.EmployerReview; - - if (employerHasSingleLegalEntity) - return RouteNames.EmployerStart; + var viewModel = await BuildEmployerSelectCourseViewModel(routeModel, postViewModel.ApprenticeTrainingKnown); - return RouteNames.EmployerSelectLegalEntity; - } + if (viewModel == null) + { + return View("Index"); + } - private string GenerateLimitReachedBackLink(ReservationsRouteModel routeModel) - { - if (!string.IsNullOrEmpty(routeModel.CohortReference)) + return View("SelectCourse",viewModel); + } + + if (postViewModel.ApprenticeTrainingKnown == false) { - return _urlHelper.GenerateCohortDetailsUrl(null, routeModel.EmployerAccountId, routeModel.CohortReference); + return RedirectToRoute(RouteNames.EmployerCourseGuidance, routeModel); } - return Url.RouteUrl(RouteNames.EmployerManage, routeModel); - } - - [HttpPost] - [ValidateAntiForgeryToken] - [Route("{id}/select-course", Name = RouteNames.EmployerSelectCourse)] - public async Task PostSelectCourse(ReservationsRouteModel routeModel, string selectedCourseId) - { try { await _mediator.Send(new CacheReservationCourseCommand { Id = routeModel.Id.Value, - CourseId = selectedCourseId + SelectedCourseId = postViewModel.SelectedCourseId }); @@ -314,22 +295,8 @@ await _mediator.Send(new CacheReservationCourseCommand ModelState.AddModelError(member.Split('|')[0], member.Split('|')[1]); } - var getCoursesResponse = await _mediator.Send(new GetCoursesQuery()); + var viewModel = await BuildEmployerSelectCourseViewModel(routeModel, postViewModel.ApprenticeTrainingKnown, true); - if (getCoursesResponse?.Courses == null) - { - return RedirectToRoute(RouteNames.Error500); - } - - var courseViewModels = getCoursesResponse.Courses.Select(c => new CourseViewModel(c)); - - var viewModel = new EmployerSelectCourseViewModel - { - ReservationId = routeModel.Id.Value, - Courses = courseViewModels, - BackLink = GenerateBackLink(routeModel, routeModel.CohortReference), - CohortReference = routeModel.CohortReference - }; return View("SelectCourse", viewModel); } @@ -360,5 +327,58 @@ public IActionResult CourseGuidance(ReservationsRouteModel routeModel) return View("CourseGuidance", model); } + + private async Task BuildEmployerSelectCourseViewModel( + ReservationsRouteModel routeModel, + bool? apprenticeTrainingKnownOrFromReview, + bool failedValidation = false) + { + var cachedReservation = await _mediator.Send(new GetCachedReservationQuery { Id = routeModel.Id.Value }); + if (cachedReservation == null) + { + return null; + } + + var getCoursesResponse = await _mediator.Send(new GetCoursesQuery()); + + var courseViewModels = getCoursesResponse.Courses.Select(course => new CourseViewModel(course, failedValidation? null : cachedReservation.CourseId)); + + var viewModel = new EmployerSelectCourseViewModel + { + ReservationId = routeModel.Id.Value, + Courses = courseViewModels, + BackLink = GenerateBackLink(routeModel, cachedReservation.CohortRef, cachedReservation.EmployerHasSingleLegalEntity), + CohortReference = cachedReservation.CohortRef, + ApprenticeTrainingKnown = !string.IsNullOrEmpty(cachedReservation.CourseId) ? true : apprenticeTrainingKnownOrFromReview + }; + + return viewModel; + } + + private string GenerateBackLink(ReservationsRouteModel routeModel, string cohortRef, bool employerHasSingleLegalEntity = false) + { + if (!string.IsNullOrEmpty(routeModel.CohortReference)) + { + return _urlHelper.GenerateCohortDetailsUrl(null, routeModel.EmployerAccountId, cohortRef); + } + + if (routeModel.FromReview.HasValue && routeModel.FromReview.Value) + return RouteNames.EmployerReview; + + if (employerHasSingleLegalEntity) + return RouteNames.EmployerStart; + + return RouteNames.EmployerSelectLegalEntity; + } + + private string GenerateLimitReachedBackLink(ReservationsRouteModel routeModel) + { + if (!string.IsNullOrEmpty(routeModel.CohortReference)) + { + return _urlHelper.GenerateCohortDetailsUrl(null, routeModel.EmployerAccountId, routeModel.CohortReference); + } + + return Url.RouteUrl(RouteNames.EmployerManage, routeModel); + } } } diff --git a/src/SFA.DAS.Reservations.Web/Controllers/ReservationsController.cs b/src/SFA.DAS.Reservations.Web/Controllers/ReservationsController.cs index 5884de24f..5f9284352 100644 --- a/src/SFA.DAS.Reservations.Web/Controllers/ReservationsController.cs +++ b/src/SFA.DAS.Reservations.Web/Controllers/ReservationsController.cs @@ -90,8 +90,7 @@ public async Task PostApprenticeshipTraining(ReservationsRouteMod { var isProvider = routeModel.UkPrn != null; TrainingDateModel trainingDateModel = null; - Course course = null; - + try { if (!string.IsNullOrWhiteSpace(formModel.StartDate)) @@ -111,26 +110,15 @@ public async Task PostApprenticeshipTraining(ReservationsRouteMod return View("ApprenticeshipTraining", model); } - - if (!string.IsNullOrEmpty(formModel.SelectedCourseId)) - { - var getCoursesResult = await _mediator.Send(new GetCoursesQuery()); - - var selectedCourse = - getCoursesResult.Courses.SingleOrDefault(c => c.Id.Equals(formModel.SelectedCourseId)); - - course = selectedCourse ?? throw new ArgumentException("Selected course does not exist", nameof(formModel.SelectedCourseId)); - //todo: should be a validation exception, also this throw is not unit tested - } - - var cachedReservation = await _mediator.Send(new GetCachedReservationQuery {Id = routeModel.Id.GetValueOrDefault()}); + + var cachedReservation = await _mediator.Send(new GetCachedReservationQuery {Id = routeModel.Id.GetValueOrDefault()}); if(isProvider) { var courseCommand = new CacheReservationCourseCommand { Id = cachedReservation.Id, - CourseId = course?.Id, + SelectedCourseId = formModel.SelectedCourseId, UkPrn = routeModel.UkPrn }; diff --git a/src/SFA.DAS.Reservations.Web/Models/ApprenticeshipTrainingFormModel.cs b/src/SFA.DAS.Reservations.Web/Models/ApprenticeshipTrainingFormModel.cs index eb9febca6..3a6d68db6 100644 --- a/src/SFA.DAS.Reservations.Web/Models/ApprenticeshipTrainingFormModel.cs +++ b/src/SFA.DAS.Reservations.Web/Models/ApprenticeshipTrainingFormModel.cs @@ -7,6 +7,7 @@ public class ApprenticeshipTrainingFormModel { [Required(ErrorMessage = "You must select a start date")] public string StartDate { get; set; } + [Required(ErrorMessage = "Select which apprenticeship training your apprentice will take")] public string SelectedCourseId { get; set; } public string AccountLegalEntityPublicHashedId { get; set; } public string CohortRef { get; set; } diff --git a/src/SFA.DAS.Reservations.Web/Models/EmployerSelectCourseViewModel.cs b/src/SFA.DAS.Reservations.Web/Models/EmployerSelectCourseViewModel.cs index 1787f48f2..2cc5212bd 100644 --- a/src/SFA.DAS.Reservations.Web/Models/EmployerSelectCourseViewModel.cs +++ b/src/SFA.DAS.Reservations.Web/Models/EmployerSelectCourseViewModel.cs @@ -8,5 +8,6 @@ public class EmployerSelectCourseViewModel : SelectCourseViewModel public string RouteName { get; set; } public string BackLink { get; set; } public string CohortReference { get; set; } + public bool? ApprenticeTrainingKnown { get; set; } } } diff --git a/src/SFA.DAS.Reservations.Web/Models/PostSelectCourseViewModel.cs b/src/SFA.DAS.Reservations.Web/Models/PostSelectCourseViewModel.cs new file mode 100644 index 000000000..e27634aa4 --- /dev/null +++ b/src/SFA.DAS.Reservations.Web/Models/PostSelectCourseViewModel.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; + +namespace SFA.DAS.Reservations.Web.Models +{ + public class PostSelectCourseViewModel + { + public string SelectedCourseId { get; set; } + [Required(ErrorMessage = "Select whether you know which apprenticeship training your apprentice will take")] + public bool? ApprenticeTrainingKnown { get; set; } + } +} diff --git a/src/SFA.DAS.Reservations.Web/Views/EmployerReservations/SelectCourse.cshtml b/src/SFA.DAS.Reservations.Web/Views/EmployerReservations/SelectCourse.cshtml index a4497e732..898898042 100644 --- a/src/SFA.DAS.Reservations.Web/Views/EmployerReservations/SelectCourse.cshtml +++ b/src/SFA.DAS.Reservations.Web/Views/EmployerReservations/SelectCourse.cshtml @@ -1,5 +1,6 @@ @using System.Text @using Microsoft.AspNetCore.Mvc.ModelBinding +@using Microsoft.AspNetCore.Mvc.ModelBinding.Validation @using SFA.DAS.Reservations.Web.Infrastructure @model EmployerSelectCourseViewModel @{ @@ -20,35 +21,67 @@ else
+
+ + + + -

Which apprenticeship training will the apprentice take?

- -
+
+ +

Do you know which apprenticeship training your apprentice will take?

+
+ + @if (ViewData.ModelState.ContainsKey(nameof(Model.ApprenticeTrainingKnown)) && ViewData.ModelState[nameof(Model.ApprenticeTrainingKnown)].Errors.Any()) + { + + @ViewData.ModelState[nameof(Model.ApprenticeTrainingKnown)].Errors.First().ErrorMessage + + } - @if (!ViewData.ModelState.IsValid) - { - - Select a course from the list - - } - - - - This information can be changed later. If you want to find the correct training, - you can check Find apprenticeship training or ask your training provider. - - + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + Can I change the course? + + +
+ You can change the apprenticeship training course when you add an apprentice's details. +
+
+
+
\ No newline at end of file diff --git a/src/SFA.DAS.Reservations.Web/Views/Reservations/ApprenticeshipTraining.cshtml b/src/SFA.DAS.Reservations.Web/Views/Reservations/ApprenticeshipTraining.cshtml index 793f09b8e..75a953e39 100644 --- a/src/SFA.DAS.Reservations.Web/Views/Reservations/ApprenticeshipTraining.cshtml +++ b/src/SFA.DAS.Reservations.Web/Views/Reservations/ApprenticeshipTraining.cshtml @@ -6,14 +6,17 @@ var firstStartDate = Model.PossibleStartDates.FirstOrDefault()?.Id; - var customErrorKeys = new Dictionary { { "StartDate", $"StartDate-{firstStartDate}" } }; + var customErrorKeys = new Dictionary + { + { "StartDate", $"StartDate-{firstStartDate}" } + }; ViewData["CustomErrorKeys"] = customErrorKeys; var courseInvalid = !ViewData.ModelState.IsValid && - ViewData.ModelState.ContainsKey("CourseId") && - ViewData.ModelState["CourseId"].Errors != null && - ViewData.ModelState["CourseId"].Errors.Any(); + ViewData.ModelState.ContainsKey("SelectedCourseId") && + ViewData.ModelState["SelectedCourseId"].Errors != null && + ViewData.ModelState["SelectedCourseId"].Errors.Any(); var startDateInvalid = !ViewData.ModelState.IsValid && ViewData.ModelState.ContainsKey("StartDate") && @@ -71,13 +74,6 @@

Which apprenticeship training will the apprentice take?

- @if (courseInvalid) - { - - Select a course from the list. - - } - @@ -87,6 +83,10 @@
} + else + { + + }
@if (Model.IsProvider) diff --git a/src/SFA.DAS.Reservations.Web/Views/Shared/_CourseSearch.cshtml b/src/SFA.DAS.Reservations.Web/Views/Shared/_CourseSearch.cshtml index e9ce8783c..aa0598db1 100644 --- a/src/SFA.DAS.Reservations.Web/Views/Shared/_CourseSearch.cshtml +++ b/src/SFA.DAS.Reservations.Web/Views/Shared/_CourseSearch.cshtml @@ -1,7 +1,12 @@ @model SelectCourseViewModel - - @foreach (var course in Model.Courses) { diff --git a/src/SFA.DAS.Reservations.Web/Views/Shared/_PageErrorsOverview.cshtml b/src/SFA.DAS.Reservations.Web/Views/Shared/_PageErrorsOverview.cshtml index 3d7f07166..074df47b6 100644 --- a/src/SFA.DAS.Reservations.Web/Views/Shared/_PageErrorsOverview.cshtml +++ b/src/SFA.DAS.Reservations.Web/Views/Shared/_PageErrorsOverview.cshtml @@ -12,12 +12,10 @@
    @{ var errorStringBuilder = new StringBuilder(); - var errorCount = ViewData.ModelState.ErrorCount; - var loopCount = 1; - + var customErrorKeys = ViewData["CustomErrorKeys"] as Dictionary; - foreach (var error in ViewData.ModelState) + foreach (var error in ViewData.ModelState.OrderBy(c=>c.Key)) { if (error.Value.ValidationState == ModelValidationState.Invalid) { @@ -31,16 +29,7 @@ var errorMessage = error.Value.Errors.FirstOrDefault()?.ErrorMessage; errorStringBuilder.Append($"
  • {errorMessage}
  • "); - if (loopCount == errorCount - 1) - { - errorStringBuilder.Append(" and "); - } - else if (loopCount != errorCount) - { - errorStringBuilder.Append(", "); - } - - loopCount++; + } } } diff --git a/src/SFA.DAS.Reservations.Web/wwwroot/js/app.js b/src/SFA.DAS.Reservations.Web/wwwroot/js/app.js index 3a3a7a9ac..cafd22e65 100644 --- a/src/SFA.DAS.Reservations.Web/wwwroot/js/app.js +++ b/src/SFA.DAS.Reservations.Web/wwwroot/js/app.js @@ -1,6 +1,6 @@ var forms = $('.validate-auto-complete'); var radioInputs = forms.find('.govuk-radios__input'); -var idSelectField = 'course-search'; +var idSelectField = 'SelectedCourseId'; var selectEl = document.querySelector('#' + idSelectField); if (selectEl) { @@ -25,159 +25,27 @@ if (selectEl) { this.selectElement.selectedIndex = 0; } } + }); -} - -forms.attr('novalidate', 'novalidate'); - -forms.on('submit', function (e) { - - var canSubmit = this.checkValidity(), - form = this, - validationMessages = []; - - if (!canSubmit) { - enableFormSubmitButton(); - } - - $('.autocomplete__input').each(function () { - var that = $(this); - setTimeout(function () { - if (!checkField(that)) { - var fieldId = that.attr('id'), - errorMessage = $('#' + fieldId + '-select').data('validation-message'), - errorSummaryMessage = $('#' + fieldId + '-select').data('validation-summary-message') || $('#' + fieldId + '-select').data('validation-message'); - validationMessages.unshift({ id: fieldId, message: errorMessage, summaryMessage: errorSummaryMessage }); - canSubmit = false; - enableFormSubmitButton(); - } - }, 100); - }); - - radioInputs.each(function () { - hideValidationMessage($(this)); - }); - - radioInputs.each(function () { - var result = this.checkValidity(); - if (!result) { - var errorMessage = showRadioValidationMessage($(this)); - if (errorMessage !== undefined) - validationMessages.unshift(errorMessage); - } else { - hideValidationMessage($(this)); - } - }); - - setTimeout(function () { - if (canSubmit) { - hideErrorSummary(); - form.submit(); - } else { - hideErrorSummary(); - showErrorSummary(validationMessages); - } - }, 300); - - e.preventDefault(); + forms.on('submit', + function(e) { + + $('.autocomplete__input').each(function() { + var that = $(this); + if (that.val().length === 0) { + var fieldId = that.attr('id'), + selectField = $('#' + fieldId + '-select'); + selectField[0].selectedIndex = 0; + } + }); -}).keydown(function (e) { - if (e.which === 13) { - e.preventDefault(); - return false; - } -}); - -var enableFormSubmitButton = function() { - forms.each(function () { - var button = $(this).find(".govuk-button"); - button.removeAttr("disabled"); - }); -} - -var checkField = function ($field) { - var textInput = $field, - selectField = $('#' + textInput.attr('id') + '-select'); - - if (selectField[0].selectedIndex === 0) { - showSelectValidationMessage(selectField); - return false; - - } else { - hideValidationMessage(selectField); - } - return true; -}; - -var showSelectValidationMessage = function ($field) { - var fieldGroup = $field.parent(), - fieldLabel = fieldGroup.find('label'), - validationMessageText = $field.data('validation-message'), - validationMessage = $('').addClass('govuk-error-message').text(validationMessageText), - alreadyShowingError = fieldGroup.hasClass('govuk-form-group--error'); - - if (!alreadyShowingError) { - fieldGroup.addClass('govuk-form-group--error'); - fieldLabel.after(validationMessage); - } -}; - -var showRadioValidationMessage = function ($field) { - var $parent = $field.closest('.govuk-form-group'), - $radioGroup = $field.closest('.govuk-radios'), - validationMessageText = $radioGroup.data('validation-message'), - validationSummaryMessageText = $radioGroup.data('validation-summary-message') || $radioGroup.data('validation-message'); - - if (!$parent.hasClass('govuk-form-group--error')) { - var validationMessage = $('') - .text(validationMessageText) - .prop('class', 'govuk-error-message') - .prop('id', 'validation-' + slugify(validationMessageText)); - $radioGroup.before(validationMessage); - $parent.addClass('govuk-form-group--error'); - $field.attr({ - 'aria-describedby': 'validation-' + slugify(validationMessage), - 'aria-invalid': 'true' }); - return { id: $field.attr('id'), message: validationMessageText, summaryMessage: validationSummaryMessageText } - } -}; - -var hideValidationMessage = function ($field) { - var $parent = $field.closest('.govuk-form-group'); - - $parent.removeClass('govuk-form-group--error'); - $parent.find('.govuk-error-message').remove(); - $field.removeAttr('aria-describedby'); - $field.removeAttr('aria-invalid'); -}; -var showErrorSummary = function (validationMessages) { - var errorSummary = $('.govuk-error-summary'), - errorList = $('.govuk-list.govuk-error-summary__list'); - if (errorSummary.length === 0) { - errorSummary = $('
    ').addClass('govuk-error-summary').attr('tabindex', -1).data('module', 'error-summary'); - var errorTitle = $('

    ').addClass('govuk-error-summary__title').text('There is a problem'); - var errorBody = $('
    ').addClass('govuk-error-summary__body'); - errorList = $('