diff --git a/app/controllers/DirectDebitSummaryController.scala b/app/controllers/DirectDebitSummaryController.scala index e35858c4..fb797ca7 100644 --- a/app/controllers/DirectDebitSummaryController.scala +++ b/app/controllers/DirectDebitSummaryController.scala @@ -18,10 +18,11 @@ package controllers import controllers.actions.* import models.UserAnswers +import pages.{AmendPaymentAmountPage, AmendPlanEndDatePage, AmendPlanStartDatePage, ManagePaymentPlanTypePage, SuspensionPeriodRangeDatePage} import play.api.i18n.{I18nSupport, MessagesApi} import play.api.mvc.{Action, AnyContent, MessagesControllerComponents} import services.NationalDirectDebitService -import queries.{DirectDebitReferenceQuery, PaymentPlanReferenceQuery, PaymentPlansCountQuery} +import queries.{DirectDebitReferenceQuery, PaymentPlanDetailsQuery, PaymentPlanReferenceQuery, PaymentPlansCountQuery} import repositories.SessionRepository import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController import views.html.DirectDebitSummaryView @@ -45,10 +46,10 @@ class DirectDebitSummaryController @Inject() ( val userAnswers = request.userAnswers.getOrElse(UserAnswers(request.userId)) userAnswers.get(DirectDebitReferenceQuery) match { case Some(reference) => - cleansePaymentReference(userAnswers).flatMap { _ => + cleanseUserData(userAnswers).flatMap { cleansedUserAnswers => nddService.retrieveDirectDebitPaymentPlans(reference).flatMap { ddPaymentPlans => for { - updatedAnswers <- Future.fromTry(userAnswers.set(PaymentPlansCountQuery, ddPaymentPlans.paymentPlanCount)) + updatedAnswers <- Future.fromTry(cleansedUserAnswers.set(PaymentPlansCountQuery, ddPaymentPlans.paymentPlanCount)) updatedAnswers <- Future.fromTry(updatedAnswers.set(DirectDebitReferenceQuery, reference)) _ <- sessionRepository.set(updatedAnswers) } yield { @@ -74,9 +75,15 @@ class DirectDebitSummaryController @Inject() ( } yield Redirect(routes.DirectDebitSummaryController.onPageLoad()) } - private def cleansePaymentReference(userAnswers: UserAnswers): Future[UserAnswers] = + private def cleanseUserData(userAnswers: UserAnswers): Future[UserAnswers] = for { updatedUserAnswers <- Future.fromTry(userAnswers.remove(PaymentPlanReferenceQuery)) + updatedUserAnswers <- Future.fromTry(updatedUserAnswers.remove(PaymentPlanDetailsQuery)) + updatedUserAnswers <- Future.fromTry(updatedUserAnswers.remove(ManagePaymentPlanTypePage)) + updatedUserAnswers <- Future.fromTry(updatedUserAnswers.remove(AmendPaymentAmountPage)) + updatedUserAnswers <- Future.fromTry(updatedUserAnswers.remove(AmendPlanStartDatePage)) + updatedUserAnswers <- Future.fromTry(updatedUserAnswers.remove(AmendPlanEndDatePage)) + updatedUserAnswers <- Future.fromTry(updatedUserAnswers.remove(SuspensionPeriodRangeDatePage)) _ <- sessionRepository.set(updatedUserAnswers) } yield updatedUserAnswers } diff --git a/app/controllers/PaymentPlanDetailsController.scala b/app/controllers/PaymentPlanDetailsController.scala index fe8f6439..672624f6 100644 --- a/app/controllers/PaymentPlanDetailsController.scala +++ b/app/controllers/PaymentPlanDetailsController.scala @@ -32,6 +32,8 @@ import utils.Constants import viewmodels.checkAnswers.* import views.html.PaymentPlanDetailsView +import java.time.LocalDate +import java.time.format.DateTimeFormatter import javax.inject.Inject import scala.concurrent.duration.* import scala.concurrent.{Await, ExecutionContext, Future} @@ -59,28 +61,52 @@ class PaymentPlanDetailsController @Inject() ( updatedAnswers <- Future.fromTry(updatedAnswers.set(ManagePaymentPlanTypePage, planDetail.planType)) updatedAnswers <- planDetail.scheduledPaymentAmount match { case Some(amount) => Future.fromTry(updatedAnswers.set(AmendPaymentAmountPage, amount)) - case None => Future.successful(updatedAnswers) + case _ => Future.successful(updatedAnswers) } updatedAnswers <- (planDetail.suspensionStartDate, planDetail.suspensionEndDate) match { case (Some(startDate), Some(endDate)) => Future.fromTry(updatedAnswers.set(SuspensionPeriodRangeDatePage, SuspensionPeriodRange(startDate, endDate))) case _ => Future.successful(updatedAnswers) } - updatedAnswers <- planDetail.scheduledPaymentEndDate match { - case Some(endDate) => Future.fromTry(updatedAnswers.set(AmendPlanEndDatePage, endDate)) - case None => Future.successful(updatedAnswers) + updatedAnswers <- planDetail.scheduledPaymentStartDate match { + case Some(paymentStartDate) => Future.fromTry(updatedAnswers.set(AmendPlanEndDatePage, paymentStartDate)) + case _ => Future.successful(updatedAnswers) } updatedAnswers <- planDetail.scheduledPaymentEndDate match { - case Some(endDate) => Future.fromTry(updatedAnswers.set(AmendPlanEndDatePage, endDate)) - case None => Future.successful(updatedAnswers) + case Some(paymentEndDate) => Future.fromTry(updatedAnswers.set(AmendPlanEndDatePage, paymentEndDate)) + case _ => Future.successful(updatedAnswers) } - updatedAnswers <- cleanseCancelPaymentPlanPage(updatedAnswers) - _ <- sessionRepository.set(updatedAnswers) + updatedAnswers <- cleanseSessionPages(updatedAnswers) + showAllActionsFlag <- calculateShowAction(nddService, planDetail) + _ <- sessionRepository.set(updatedAnswers) } yield { - val flag: Future[Boolean] = calculateShowAction(nddService, planDetail) - val showActions = Await.result(flag, 5.seconds) + val showAmendLink = isAmendLinkVisible(showAllActionsFlag, planDetail) + val showCancelLink = isCancelLinkVisible(showAllActionsFlag, planDetail) + val showSuspendLink = isSuspendLinkVisible(showAllActionsFlag, planDetail) val summaryRows: Seq[SummaryListRow] = buildSummaryRows(planDetail) - Ok(view(planDetail.planType, paymentPlanReference, showActions, summaryRows)) + val isSuspensionActive = isSuspendPeriodActive(planDetail) + + val formattedSuspensionStartDate = planDetail.suspensionStartDate + .map(_.format(DateTimeFormatter.ofPattern(Constants.longDateTimeFormatPattern))) + .getOrElse("") + + val formattedSuspensionEndDate = planDetail.suspensionEndDate + .map(_.format(DateTimeFormatter.ofPattern(Constants.longDateTimeFormatPattern))) + .getOrElse("") + + Ok( + view( + planDetail.planType, + paymentPlanReference, + showAmendLink, + showCancelLink, + showSuspendLink, + isSuspensionActive, + formattedSuspensionStartDate, + formattedSuspensionEndDate, + summaryRows + ) + ) } } case _ => @@ -117,10 +143,14 @@ class PaymentPlanDetailsController @Inject() ( AmendPlanStartDateSummary.row(planDetail.planType, planDetail.scheduledPaymentStartDate, Constants.shortDateTimeFormatPattern), AmendPlanEndDateSummary.row(planDetail.scheduledPaymentEndDate, Constants.shortDateTimeFormatPattern), PaymentsFrequencySummary.row(planDetail.scheduledPaymentFrequency), - AmendPaymentAmountSummary.row(planDetail.planType, planDetail.scheduledPaymentAmount), - AmendSuspendDateSummary.row(planDetail.suspensionStartDate, true), // true for start - AmendSuspendDateSummary.row(planDetail.suspensionEndDate, false) // false for end - ) + AmendPaymentAmountSummary.row(planDetail.planType, planDetail.scheduledPaymentAmount) + ) ++ + (if (isSuspendPeriodActive(planDetail)) { + Seq(SuspensionPeriodRangeDateSummary.row(planDetail.suspensionStartDate, planDetail.suspensionEndDate)) + } else { + Seq.empty + }) + case _ => // For Variable and Tax repayment plan Seq( AmendPaymentPlanTypeSummary.row(planDetail.planType), @@ -145,20 +175,15 @@ class PaymentPlanDetailsController @Inject() ( case Some(startDate) => nddService.isTwoDaysPriorPaymentDate(startDate) case None => Future.successful(true) } - case PaymentPlanType.BudgetPaymentPlan.toString => - planDetail.scheduledPaymentEndDate match { - case Some(startDate) => nddService.isThreeDaysPriorPlanEndDate(startDate) - case None => Future.successful(true) - } - case PaymentPlanType.VariablePaymentPlan.toString => + case PaymentPlanType.BudgetPaymentPlan.toString | PaymentPlanType.VariablePaymentPlan.toString => for { isTwoDaysBeforeStart <- planDetail.scheduledPaymentStartDate match { case Some(startDate) => nddService.isTwoDaysPriorPaymentDate(startDate) - case None => Future.successful(true) + case _ => Future.successful(true) } isThreeDaysBeforeEnd <- planDetail.scheduledPaymentEndDate match { case Some(endDate) => nddService.isThreeDaysPriorPlanEndDate(endDate) - case None => Future.successful(true) + case _ => Future.successful(true) } } yield isTwoDaysBeforeStart && isThreeDaysBeforeEnd @@ -166,10 +191,32 @@ class PaymentPlanDetailsController @Inject() ( } } - private def cleanseCancelPaymentPlanPage(userAnswers: UserAnswers): Future[UserAnswers] = + private def cleanseSessionPages(userAnswers: UserAnswers): Future[UserAnswers] = for { updatedUserAnswers <- Future.fromTry(userAnswers.remove(CancelPaymentPlanPage)) + updatedUserAnswers <- Future.fromTry(updatedUserAnswers.remove(DuplicateWarningPage)) _ <- sessionRepository.set(updatedUserAnswers) } yield updatedUserAnswers + private def isAmendLinkVisible(showAllActionsFlag: Boolean, planDetail: PaymentPlanDetails): Boolean = { + showAllActionsFlag && (planDetail.planType == PaymentPlanType.SinglePaymentPlan.toString || planDetail.planType == PaymentPlanType.BudgetPaymentPlan.toString) + } + + private def isCancelLinkVisible(showAllActionsFlag: Boolean, planDetail: PaymentPlanDetails): Boolean = { + showAllActionsFlag && planDetail.planType != PaymentPlanType.TaxCreditRepaymentPlan.toString + } + + private def isSuspendLinkVisible(showAllActionsFlag: Boolean, planDetail: PaymentPlanDetails): Boolean = { + planDetail.planType match { + case planType if planType == PaymentPlanType.BudgetPaymentPlan.toString => + showAllActionsFlag && !isSuspendPeriodActive(planDetail) + case _ => false + } + } + + private def isSuspendPeriodActive(planDetail: PaymentPlanDetails): Boolean = { + (for { + suspensionEndDate <- planDetail.suspensionEndDate + } yield !LocalDate.now().isAfter(suspensionEndDate)).getOrElse(false) + } } diff --git a/app/controllers/YourDirectDebitInstructionsController.scala b/app/controllers/YourDirectDebitInstructionsController.scala index 9d643591..18e21805 100644 --- a/app/controllers/YourDirectDebitInstructionsController.scala +++ b/app/controllers/YourDirectDebitInstructionsController.scala @@ -21,7 +21,7 @@ import controllers.actions.* import models.UserAnswers import play.api.i18n.{I18nSupport, MessagesApi} import play.api.mvc.{Action, AnyContent, MessagesControllerComponents} -import queries.{DirectDebitReferenceQuery, PaymentPlanReferenceQuery} +import queries.{DirectDebitReferenceQuery, PaymentPlansCountQuery} import repositories.SessionRepository import services.NationalDirectDebitService import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController @@ -55,8 +55,8 @@ class YourDirectDebitInstructionsController @Inject() ( private def cleanseDirectDebitReference(userAnswers: UserAnswers): Future[UserAnswers] = for { - userAnswersWithoutDirectDebitReference <- Future.fromTry(userAnswers.remove(DirectDebitReferenceQuery)) - userAnswersPaymentReference <- Future.fromTry(userAnswersWithoutDirectDebitReference.remove(PaymentPlanReferenceQuery)) - _ <- sessionRepository.set(userAnswersPaymentReference) - } yield userAnswersPaymentReference + updatedUserAnswers <- Future.fromTry(userAnswers.remove(DirectDebitReferenceQuery)) + updatedUserAnswers <- Future.fromTry(updatedUserAnswers.remove(PaymentPlansCountQuery)) + _ <- sessionRepository.set(updatedUserAnswers) + } yield updatedUserAnswers } diff --git a/app/viewmodels/checkAnswers/AmendSuspendDateSummary.scala b/app/viewmodels/checkAnswers/AmendSuspendDateSummary.scala deleted file mode 100644 index 06998fce..00000000 --- a/app/viewmodels/checkAnswers/AmendSuspendDateSummary.scala +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2025 HM Revenue & Customs - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package viewmodels.checkAnswers - -import play.api.i18n.Messages -import uk.gov.hmrc.govukfrontend.views.viewmodels.summarylist.SummaryListRow -import viewmodels.govuk.summarylist.* -import viewmodels.implicits.* - -import java.time.LocalDate -import java.time.format.DateTimeFormatter - -object AmendSuspendDateSummary { - - def row(suspendDate: Option[LocalDate], isStartDate: Boolean)(implicit messages: Messages): SummaryListRow = - val label = if (isStartDate) { - "paymentPlanDetails.details.suspendStartDate" - } else { - "paymentPlanDetails.details.suspendEndDate" - } - val formattedDate = suspendDate - .map(_.format(DateTimeFormatter.ofPattern("d MMM yyyy"))) - .getOrElse("") - SummaryListRowViewModel( - key = label, - value = ValueViewModel(formattedDate), - actions = Seq.empty - ) - -} diff --git a/app/viewmodels/checkAnswers/SuspensionPeriodRangeDateSummary.scala b/app/viewmodels/checkAnswers/SuspensionPeriodRangeDateSummary.scala index f6461fc3..1dc25fb3 100644 --- a/app/viewmodels/checkAnswers/SuspensionPeriodRangeDateSummary.scala +++ b/app/viewmodels/checkAnswers/SuspensionPeriodRangeDateSummary.scala @@ -33,14 +33,18 @@ package viewmodels.checkAnswers import controllers.routes -import models.{CheckMode, SuspensionPeriodRange, UserAnswers} +import models.{CheckMode, NormalMode, SuspensionPeriodRange, UserAnswers} import pages.SuspensionPeriodRangeDatePage import play.api.i18n.{Lang, Messages} import uk.gov.hmrc.govukfrontend.views.viewmodels.summarylist.SummaryListRow +import utils.Constants import utils.DateTimeFormats.formattedDateTimeShort import viewmodels.govuk.summarylist.* import viewmodels.implicits.* +import java.time.LocalDate +import java.time.format.DateTimeFormatter + object SuspensionPeriodRangeDateSummary { def row(answers: UserAnswers, showChange: Boolean = false)(implicit messages: Messages): Option[SummaryListRow] = answers.get(SuspensionPeriodRangeDatePage).map { answer => @@ -62,4 +66,29 @@ object SuspensionPeriodRangeDateSummary { } ) } + + def row(suspendStartDate: Option[LocalDate], suspendEndDate: Option[LocalDate])(implicit messages: Messages): SummaryListRow = { + val formattedStartDate = suspendStartDate + .map(_.format(DateTimeFormatter.ofPattern(Constants.shortDateTimeFormatPattern))) + .getOrElse("") + + val formattedEndDate = suspendEndDate + .map(_.format(DateTimeFormatter.ofPattern(Constants.shortDateTimeFormatPattern))) + .getOrElse("") + + val formattedValue = + s"$formattedStartDate ${messages("suspensionPeriodRangeDate.to")} $formattedEndDate" + + SummaryListRowViewModel( + key = "suspensionPeriodRangeDate.checkYourAnswersLabel", + value = ValueViewModel(formattedValue), + actions = Seq( + ActionItemViewModel("site.change", routes.SuspensionPeriodRangeDateController.onPageLoad(NormalMode).url) + .withVisuallyHiddenText(messages("suspensionPeriodRangeDate.change.hidden")), + ActionItemViewModel("site.remove", routes.JourneyRecoveryController.onPageLoad().url) // TODO Updated after RM1 + .withVisuallyHiddenText(messages("suspensionPeriodRangeDate.change.hidden")) + ) + ) + } + } diff --git a/app/views/PaymentPlanDetailsView.scala.html b/app/views/PaymentPlanDetailsView.scala.html index 58b03e68..b59a3779 100644 --- a/app/views/PaymentPlanDetailsView.scala.html +++ b/app/views/PaymentPlanDetailsView.scala.html @@ -15,6 +15,7 @@ *@ @import models.responses.* +@import java.time.LocalDate @import java.time.format.DateTimeFormatter @import views.html.components.* @import utils.MaskAndFormatUtils.* @@ -29,14 +30,17 @@ govukSummaryList: GovukSummaryList ) -@(planType: String, paymentReference: String, showActions: Boolean, rows: Seq[SummaryListRow])(implicit request: Request[_], messages: Messages) +@(planType: String, paymentReference: String, showAmendLink: Boolean, showCancelLink: Boolean, showSuspendLink: Boolean, isSuspensionActive: Boolean, suspensionStartDate: String, suspensionEndDate: String, rows: Seq[SummaryListRow])(implicit request: Request[_], messages: Messages) @layout(pageTitle = titleForManageJourneyNoForm(messages("paymentPlanDetails.title")), backLink = Some(routes.DirectDebitSummaryController.onPageLoad().url)) { @header1("paymentPlanDetails.heading") @paragraph("paymentPlanDetails.p1") - @paragraph("paymentPlanDetails.p2") -

@messages("paymentPlanDetails.inset.text")

- @header2("paymentPlanDetails.heading") + + @if(isSuspensionActive) { +

+ @messages("paymentPlanDetails.inset.text", suspensionStartDate, suspensionEndDate) +

+ } @govukSummaryList(SummaryList( card = Some(Card( @@ -46,17 +50,17 @@ actions = Some(Actions( items = Seq( - if (showActions && (planType == PaymentPlanType.SinglePaymentPlan.toString || planType == PaymentPlanType.BudgetPaymentPlan.toString)) ActionItem( + if (showAmendLink) ActionItem( href = routes.AmendPaymentAmountController.onPageLoad(NormalMode).url, content = messages("paymentPlanDetails.details.amend"), visuallyHiddenText = Some(messages("Amend")) ) else null, - if (showActions && planType != PaymentPlanType.TaxCreditRepaymentPlan.toString) ActionItem( + if (showCancelLink) ActionItem( href = routes.CancelPaymentPlanController.onPageLoad().url, content = messages("paymentPlanDetails.details.cancel"), visuallyHiddenText = Some(messages("Cancel")) ) else null, - if (showActions && planType == PaymentPlanType.BudgetPaymentPlan.toString) ActionItem( + if (showSuspendLink) ActionItem( href = routes.SuspendPaymentPlanController.onPageLoad().url, content = messages("paymentPlanDetails.details.suspend"), visuallyHiddenText = Some(messages("Suspend")) diff --git a/conf/messages.en b/conf/messages.en index 6df58c0f..a2fc2600 100644 --- a/conf/messages.en +++ b/conf/messages.en @@ -404,9 +404,8 @@ directDebitPaymentSummary.addPaymentButton = Add payment plan paymentPlanDetails.title = Your payment plan details paymentPlanDetails.heading = Your payment plan details -paymentPlanDetails.p1 = You can amend the details of your payment plan, or cancel it if it is no longer required, by following either the ''Amend payment plan'' or ''Cancel payment plan'' links below. -paymentPlanDetails.p2 = If you wish to suspend your payment plan please follow the ''Suspend payment plan'' link below. -paymentPlanDetails.inset.text = Payments due within the next 3 working days will not be affected by any changes made. +paymentPlanDetails.p1 = If a payment is due within the next 3 working days, you will not be able to make any changes. +paymentPlanDetails.inset.text = This payment plan is suspended from {0} to {1}. paymentPlanDetails.details.paymentReference = Payment reference: {0} paymentPlanDetails.details.amend = Amend paymentPlanDetails.details.cancel = Cancel diff --git a/test/base/SpecBase.scala b/test/base/SpecBase.scala index 7a8c99f2..c4fd36a9 100644 --- a/test/base/SpecBase.scala +++ b/test/base/SpecBase.scala @@ -61,7 +61,7 @@ trait SpecBase extends AnyFreeSpec with Matchers with TryValues with OptionValue initialPaymentAmount = Some(BigDecimal(25.00)), scheduledPaymentEndDate = Some(currentDate.plusMonths(6)), scheduledPaymentFrequency = Some("Monthly"), - suspensionStartDate = Some(currentDate.plusDays(5)), + suspensionStartDate = None, suspensionEndDate = None, balancingPaymentAmount = Some(60.00), balancingPaymentDate = Some(currentDate.plusMonths(6).plusDays(10)), diff --git a/test/controllers/AmendPaymentPlanConfirmationControllerSpec.scala b/test/controllers/AmendPaymentPlanConfirmationControllerSpec.scala index c0567253..94f7238a 100644 --- a/test/controllers/AmendPaymentPlanConfirmationControllerSpec.scala +++ b/test/controllers/AmendPaymentPlanConfirmationControllerSpec.scala @@ -35,7 +35,6 @@ import uk.gov.hmrc.http.HeaderCarrier import utils.{Constants, DirectDebitDetailsData} import viewmodels.checkAnswers.* import views.html.AmendPaymentPlanConfirmationView -import uk.gov.hmrc.http.HeaderCarrier implicit val hc: HeaderCarrier = HeaderCarrier() import java.time.LocalDate diff --git a/test/controllers/AmendPlanEndDateControllerSpec.scala b/test/controllers/AmendPlanEndDateControllerSpec.scala index 2d9ae9f6..df73b990 100644 --- a/test/controllers/AmendPlanEndDateControllerSpec.scala +++ b/test/controllers/AmendPlanEndDateControllerSpec.scala @@ -18,12 +18,8 @@ package controllers import base.SpecBase import forms.AmendPlanEndDateFormProvider -import models.responses.{DirectDebitDetails, PaymentPlanDetails, PaymentPlanResponse} -import models.{NextPaymentValidationResult, NormalMode, PaymentPlanType} import models.responses.{DirectDebitDetails, DuplicateCheckResponse, PaymentPlanDetails, PaymentPlanResponse} import models.{NextPaymentValidationResult, NormalMode, PaymentPlanType} -import models.{NextPaymentValidationResult, NormalMode} -import models.PaymentPlanType import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.when import org.scalatestplus.mockito.MockitoSugar diff --git a/test/controllers/DirectDebitSummaryControllerSpec.scala b/test/controllers/DirectDebitSummaryControllerSpec.scala index 658fa7bb..33735a00 100644 --- a/test/controllers/DirectDebitSummaryControllerSpec.scala +++ b/test/controllers/DirectDebitSummaryControllerSpec.scala @@ -17,13 +17,15 @@ package controllers import base.SpecBase +import org.mockito.{ArgumentCaptor, Mockito} import org.mockito.ArgumentMatchers.{any, eq as eqTo} -import org.mockito.Mockito.{verify, when} +import org.mockito.Mockito.{times, verify, when} import org.scalatestplus.mockito.MockitoSugar.mock import play.api.inject.bind import play.api.test.FakeRequest import play.api.test.Helpers.* -import queries.DirectDebitReferenceQuery +import queries.* +import pages.* import repositories.SessionRepository import services.NationalDirectDebitService import utils.DirectDebitDetailsData @@ -168,5 +170,60 @@ class DirectDebitSummaryControllerSpec extends SpecBase with DirectDebitDetailsD redirectLocation(result).value mustEqual routes.DirectDebitSummaryController.onPageLoad().url } } + + "must call cleansePaymentReference and return OK with the correct view" in { + val directDebitReference = "ref number 1" + val userAnswersWithDirectDebitReference = + emptyUserAnswers + .set( + DirectDebitReferenceQuery, + directDebitReference + ) + .success + .value + .set( + PaymentPlansCountQuery, + 2 + ) + .success + .value + + val application = applicationBuilder(userAnswers = Some(userAnswersWithDirectDebitReference)) + .overrides( + bind[SessionRepository].toInstance(mockSessionRepository), + bind[NationalDirectDebitService].toInstance(mockService) + ) + .build() + + running(application) { + when(mockSessionRepository.set(any())) + .thenReturn(Future.successful(true)) + when(mockSessionRepository.get(any())) + .thenReturn(Future.successful(Some(userAnswersWithDirectDebitReference))) + when(mockService.retrieveDirectDebitPaymentPlans(any())(any(), any())) + .thenReturn(Future.successful(mockDDPaymentPlansResponse)) + + val request = FakeRequest(GET, routes.DirectDebitSummaryController.onPageLoad().url) + + val result = route(application, request).value + + status(result) mustEqual OK + + val captor = ArgumentCaptor.forClass(classOf[models.UserAnswers]) + verify(mockSessionRepository, times(6)).set(captor.capture()) + + val capturedList = captor.getAllValues + + val firstCaptured = capturedList.get(0) + + firstCaptured.get(PaymentPlanReferenceQuery) mustBe None + firstCaptured.get(PaymentPlanDetailsQuery) mustBe None + firstCaptured.get(ManagePaymentPlanTypePage) mustBe None + firstCaptured.get(AmendPaymentAmountPage) mustBe None + firstCaptured.get(AmendPlanStartDatePage) mustBe None + firstCaptured.get(AmendPlanEndDatePage) mustBe None + firstCaptured.get(SuspensionPeriodRangeDatePage) mustBe None + } + } } } diff --git a/test/controllers/PaymentPlanDetailsControllerSpec.scala b/test/controllers/PaymentPlanDetailsControllerSpec.scala index 03dff674..f61b7f74 100644 --- a/test/controllers/PaymentPlanDetailsControllerSpec.scala +++ b/test/controllers/PaymentPlanDetailsControllerSpec.scala @@ -31,9 +31,11 @@ import repositories.SessionRepository import services.NationalDirectDebitService import uk.gov.hmrc.govukfrontend.views.viewmodels.summarylist.SummaryListRow import utils.Constants -import viewmodels.checkAnswers.* +import viewmodels.checkAnswers.{SuspensionPeriodRangeDateSummary, *} import views.html.PaymentPlanDetailsView +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter import scala.concurrent.Future class PaymentPlanDetailsControllerSpec extends SpecBase { @@ -56,286 +58,925 @@ class PaymentPlanDetailsControllerSpec extends SpecBase { ) } - "must return OK and the correct view for a GET with a SinglePayment Plan" in { - def summaryList(paymentPlanData: PaymentPlanResponse, app: Application): Seq[SummaryListRow] = { - val planDetail = paymentPlanData.paymentPlanDetails - Seq( - AmendPaymentPlanTypeSummary.row(planDetail.planType)(messages(app)), - AmendPaymentPlanSourceSummary.row(planDetail.hodService)(messages(app)), - DateSetupSummary.row(planDetail.submissionDateTime)(messages(app)), - AmendPaymentAmountSummary.row(planDetail.planType, planDetail.scheduledPaymentAmount)(messages(app)), - AmendPlanStartDateSummary.row(planDetail.planType, planDetail.scheduledPaymentStartDate, Constants.shortDateTimeFormatPattern)( - messages(app) - ) - ) + "onPageLoad" - { + ".SinglePayment Plan" - { + "must return OK and the correct view for a GET" in { + def summaryList(paymentPlanData: PaymentPlanResponse, app: Application): Seq[SummaryListRow] = { + val planDetail = paymentPlanData.paymentPlanDetails + Seq( + AmendPaymentPlanTypeSummary.row(planDetail.planType)(messages(app)), + AmendPaymentPlanSourceSummary.row(planDetail.hodService)(messages(app)), + DateSetupSummary.row(planDetail.submissionDateTime)(messages(app)), + AmendPaymentAmountSummary.row(planDetail.planType, planDetail.scheduledPaymentAmount)(messages(app)), + AmendPlanStartDateSummary.row(planDetail.planType, planDetail.scheduledPaymentStartDate, Constants.shortDateTimeFormatPattern)( + messages(app) + ) + ) + } + + val mockSinglePaymentPlanDetailResponse = + dummyPlanDetailResponse.copy(paymentPlanDetails = + dummyPlanDetailResponse.paymentPlanDetails.copy(planType = PaymentPlanType.SinglePaymentPlan.toString) + ) + + val paymentPlanReference = "ppReference" + val directDebitReference = "ddReference" + + val userAnswersWithPaymentReference = + emptyUserAnswers + .set( + PaymentPlanReferenceQuery, + paymentPlanReference + ) + .success + .value + .set( + DirectDebitReferenceQuery, + directDebitReference + ) + .success + .value + + val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) + .overrides( + bind[SessionRepository].toInstance(mockSessionRepository), + bind[NationalDirectDebitService].toInstance(mockService) + ) + .build() + + running(application) { + when(mockSessionRepository.set(any())) + .thenReturn(Future.successful(true)) + when(mockSessionRepository.get(any())) + .thenReturn(Future.successful(Some(userAnswersWithPaymentReference))) + when(mockService.getPaymentPlanDetails(any(), any())(any(), any())) + .thenReturn(Future.successful(mockSinglePaymentPlanDetailResponse)) + when(mockService.isTwoDaysPriorPaymentDate(any())(any())) + .thenReturn(Future.successful(true)) + + val summaryListRows = summaryList(mockSinglePaymentPlanDetailResponse, application) + val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) + val result = route(application, request).value + val view = application.injector.instanceOf[PaymentPlanDetailsView] + status(result) mustEqual OK + contentAsString(result) mustEqual view("singlePaymentPlan", paymentPlanReference, true, true, false, false, "", "", summaryListRows)( + request, + messages(application) + ).toString + } + } } - val mockSinglePaymentPlanDetailResponse = - dummyPlanDetailResponse.copy(paymentPlanDetails = - dummyPlanDetailResponse.paymentPlanDetails.copy(planType = PaymentPlanType.SinglePaymentPlan.toString) - ) - - val paymentPlanReference = "ppReference" - val directDebitReference = "ddReference" - - val userAnswersWithPaymentReference = - emptyUserAnswers - .set( - PaymentPlanReferenceQuery, - paymentPlanReference + ".BudgetPayment Plan" - { + + def summaryListWithoutSuspendPeriod(paymentPlanData: PaymentPlanResponse, app: Application): Seq[SummaryListRow] = { + val planDetail = paymentPlanData.paymentPlanDetails + Seq( + AmendPaymentPlanTypeSummary.row(planDetail.planType)(messages(app)), + AmendPaymentPlanSourceSummary.row(planDetail.hodService)(messages(app)), + DateSetupSummary.row(planDetail.submissionDateTime)(messages(app)), + TotalAmountDueSummary.row(planDetail.totalLiability)(messages(app)), + MonthlyPaymentAmountSummary.row(planDetail.scheduledPaymentAmount, planDetail.totalLiability)(messages(app)), + FinalPaymentAmountSummary.row(planDetail.balancingPaymentAmount, planDetail.totalLiability)(messages(app)), + AmendPlanStartDateSummary.row(planDetail.planType, planDetail.scheduledPaymentStartDate, Constants.shortDateTimeFormatPattern)( + messages(app) + ), + AmendPlanEndDateSummary.row(planDetail.scheduledPaymentEndDate, Constants.shortDateTimeFormatPattern)(messages(app)), + PaymentsFrequencySummary.row(planDetail.scheduledPaymentFrequency)(messages(app)), + AmendPaymentAmountSummary.row(planDetail.planType, planDetail.scheduledPaymentAmount)(messages(app)) ) - .success - .value - .set( - DirectDebitReferenceQuery, - directDebitReference + } + + def summaryListWithSuspendPeriod(paymentPlanData: PaymentPlanResponse, app: Application): Seq[SummaryListRow] = { + val planDetail = paymentPlanData.paymentPlanDetails + Seq( + AmendPaymentPlanTypeSummary.row(planDetail.planType)(messages(app)), + AmendPaymentPlanSourceSummary.row(planDetail.hodService)(messages(app)), + DateSetupSummary.row(planDetail.submissionDateTime)(messages(app)), + TotalAmountDueSummary.row(planDetail.totalLiability)(messages(app)), + MonthlyPaymentAmountSummary.row(planDetail.scheduledPaymentAmount, planDetail.totalLiability)(messages(app)), + FinalPaymentAmountSummary.row(planDetail.balancingPaymentAmount, planDetail.totalLiability)(messages(app)), + AmendPlanStartDateSummary.row(planDetail.planType, planDetail.scheduledPaymentStartDate, Constants.shortDateTimeFormatPattern)( + messages(app) + ), + AmendPlanEndDateSummary.row(planDetail.scheduledPaymentEndDate, Constants.shortDateTimeFormatPattern)(messages(app)), + PaymentsFrequencySummary.row(planDetail.scheduledPaymentFrequency)(messages(app)), + AmendPaymentAmountSummary.row(planDetail.planType, planDetail.scheduledPaymentAmount)(messages(app)), + SuspensionPeriodRangeDateSummary.row(planDetail.suspensionStartDate, planDetail.suspensionEndDate)(messages(app)) ) - .success - .value - - val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) - .overrides( - bind[SessionRepository].toInstance(mockSessionRepository), - bind[NationalDirectDebitService].toInstance(mockService) - ) - .build() - - running(application) { - when(mockSessionRepository.set(any())) - .thenReturn(Future.successful(true)) - when(mockSessionRepository.get(any())) - .thenReturn(Future.successful(Some(userAnswersWithPaymentReference))) - when(mockService.getPaymentPlanDetails(any(), any())(any(), any())) - .thenReturn(Future.successful(mockSinglePaymentPlanDetailResponse)) - when(mockService.isTwoDaysPriorPaymentDate(any())(any())) - .thenReturn(Future.successful(true)) - - val summaryListRows = summaryList(mockSinglePaymentPlanDetailResponse, application) - val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) - val result = route(application, request).value - val view = application.injector.instanceOf[PaymentPlanDetailsView] - status(result) mustEqual OK - contentAsString(result) mustEqual view("singlePaymentPlan", paymentPlanReference, true, summaryListRows)(request, - messages(application) - ).toString + } + + "must return OK and the correct view for a GET" - { + + "when Suspension is inactive" - { + "should show Amend, Cancel and Suspend actions" in { + val mockBudgetPaymentPlanDetailResponse = + dummyPlanDetailResponse.copy(paymentPlanDetails = + dummyPlanDetailResponse.paymentPlanDetails.copy( + planType = PaymentPlanType.BudgetPaymentPlan.toString, + scheduledPaymentStartDate = Some(LocalDateTime.now().plusDays(5).toLocalDate), + scheduledPaymentEndDate = Some(LocalDateTime.now().plusDays(20).toLocalDate), + suspensionStartDate = None, + suspensionEndDate = None + ) + ) + + val paymentPlanReference = "ppReference" + val directDebitReference = "ddReference" + + val userAnswersWithPaymentReference = + emptyUserAnswers + .set( + PaymentPlanReferenceQuery, + paymentPlanReference + ) + .success + .value + .set( + DirectDebitReferenceQuery, + directDebitReference + ) + .success + .value + + val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) + .overrides( + bind[SessionRepository].toInstance(mockSessionRepository), + bind[NationalDirectDebitService].toInstance(mockService) + ) + .build() + + running(application) { + when(mockSessionRepository.set(any())) + .thenReturn(Future.successful(true)) + when(mockSessionRepository.get(any())) + .thenReturn(Future.successful(Some(userAnswersWithPaymentReference))) + when(mockService.getPaymentPlanDetails(any(), any())(any(), any())) + .thenReturn(Future.successful(mockBudgetPaymentPlanDetailResponse)) + when(mockService.isTwoDaysPriorPaymentDate(any())(any())) + .thenReturn(Future.successful(true)) + when(mockService.isThreeDaysPriorPlanEndDate(any())(any())) + .thenReturn(Future.successful(true)) + + val summaryListRows = summaryListWithoutSuspendPeriod(mockBudgetPaymentPlanDetailResponse, application) + val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) + val result = route(application, request).value + val view = application.injector.instanceOf[PaymentPlanDetailsView] + status(result) mustEqual OK + contentAsString(result) mustEqual view("budgetPaymentPlan", paymentPlanReference, true, true, true, false, "", "", summaryListRows)( + request, + messages(application) + ).toString + } + } + + "should show Amend, Cancel and Suspend actions when scheduledPaymentEndDate is None" in { + val mockBudgetPaymentPlanDetailResponse = + dummyPlanDetailResponse.copy(paymentPlanDetails = + dummyPlanDetailResponse.paymentPlanDetails.copy( + planType = PaymentPlanType.BudgetPaymentPlan.toString, + scheduledPaymentStartDate = Some(LocalDateTime.now().plusDays(5).toLocalDate), + scheduledPaymentEndDate = None, + suspensionStartDate = None, + suspensionEndDate = None + ) + ) + + val paymentPlanReference = "ppReference" + val directDebitReference = "ddReference" + + val userAnswersWithPaymentReference = + emptyUserAnswers + .set( + PaymentPlanReferenceQuery, + paymentPlanReference + ) + .success + .value + .set( + DirectDebitReferenceQuery, + directDebitReference + ) + .success + .value + + val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) + .overrides( + bind[SessionRepository].toInstance(mockSessionRepository), + bind[NationalDirectDebitService].toInstance(mockService) + ) + .build() + + running(application) { + when(mockSessionRepository.set(any())) + .thenReturn(Future.successful(true)) + when(mockSessionRepository.get(any())) + .thenReturn(Future.successful(Some(userAnswersWithPaymentReference))) + when(mockService.getPaymentPlanDetails(any(), any())(any(), any())) + .thenReturn(Future.successful(mockBudgetPaymentPlanDetailResponse)) + when(mockService.isTwoDaysPriorPaymentDate(any())(any())) + .thenReturn(Future.successful(true)) + + val summaryListRows = summaryListWithoutSuspendPeriod(mockBudgetPaymentPlanDetailResponse, application) + val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) + val result = route(application, request).value + val view = application.injector.instanceOf[PaymentPlanDetailsView] + status(result) mustEqual OK + contentAsString(result) mustEqual view("budgetPaymentPlan", paymentPlanReference, true, true, true, false, "", "", summaryListRows)( + request, + messages(application) + ).toString + } + } + + "should not show Amend, Cancel and Suspend actions when scheduledPaymentStartDate is two day prior start date" in { + val mockBudgetPaymentPlanDetailResponse = + dummyPlanDetailResponse.copy(paymentPlanDetails = + dummyPlanDetailResponse.paymentPlanDetails.copy( + planType = PaymentPlanType.BudgetPaymentPlan.toString, + scheduledPaymentStartDate = Some(LocalDateTime.now().plusDays(2).toLocalDate), + scheduledPaymentEndDate = Some(LocalDateTime.now().plusDays(10).toLocalDate), + suspensionStartDate = None, + suspensionEndDate = None + ) + ) + + val paymentPlanReference = "ppReference" + val directDebitReference = "ddReference" + + val userAnswersWithPaymentReference = + emptyUserAnswers + .set( + PaymentPlanReferenceQuery, + paymentPlanReference + ) + .success + .value + .set( + DirectDebitReferenceQuery, + directDebitReference + ) + .success + .value + + val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) + .overrides( + bind[SessionRepository].toInstance(mockSessionRepository), + bind[NationalDirectDebitService].toInstance(mockService) + ) + .build() + + running(application) { + when(mockSessionRepository.set(any())) + .thenReturn(Future.successful(true)) + when(mockSessionRepository.get(any())) + .thenReturn(Future.successful(Some(userAnswersWithPaymentReference))) + when(mockService.getPaymentPlanDetails(any(), any())(any(), any())) + .thenReturn(Future.successful(mockBudgetPaymentPlanDetailResponse)) + when(mockService.isTwoDaysPriorPaymentDate(any())(any())) + .thenReturn(Future.successful(false)) + when(mockService.isThreeDaysPriorPlanEndDate(any())(any())) + .thenReturn(Future.successful(true)) + + val summaryListRows = summaryListWithoutSuspendPeriod(mockBudgetPaymentPlanDetailResponse, application) + val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) + val result = route(application, request).value + val view = application.injector.instanceOf[PaymentPlanDetailsView] + status(result) mustEqual OK + contentAsString(result) mustEqual view("budgetPaymentPlan", + paymentPlanReference, + false, + false, + false, + false, + "", + "", + summaryListRows + )( + request, + messages(application) + ).toString + } + } + + "should not show Amend, Cancel and Suspend actions when scheduledPaymentEndDate is three day prior end date" in { + val mockBudgetPaymentPlanDetailResponse = + dummyPlanDetailResponse.copy(paymentPlanDetails = + dummyPlanDetailResponse.paymentPlanDetails.copy( + planType = PaymentPlanType.BudgetPaymentPlan.toString, + scheduledPaymentStartDate = Some(LocalDateTime.now().minusDays(3).toLocalDate), + scheduledPaymentEndDate = Some(LocalDateTime.now().plusDays(3).toLocalDate), + suspensionStartDate = None, + suspensionEndDate = None + ) + ) + + val paymentPlanReference = "ppReference" + val directDebitReference = "ddReference" + + val userAnswersWithPaymentReference = + emptyUserAnswers + .set( + PaymentPlanReferenceQuery, + paymentPlanReference + ) + .success + .value + .set( + DirectDebitReferenceQuery, + directDebitReference + ) + .success + .value + + val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) + .overrides( + bind[SessionRepository].toInstance(mockSessionRepository), + bind[NationalDirectDebitService].toInstance(mockService) + ) + .build() + + running(application) { + when(mockSessionRepository.set(any())) + .thenReturn(Future.successful(true)) + when(mockSessionRepository.get(any())) + .thenReturn(Future.successful(Some(userAnswersWithPaymentReference))) + when(mockService.getPaymentPlanDetails(any(), any())(any(), any())) + .thenReturn(Future.successful(mockBudgetPaymentPlanDetailResponse)) + when(mockService.isTwoDaysPriorPaymentDate(any())(any())) + .thenReturn(Future.successful(true)) + when(mockService.isThreeDaysPriorPlanEndDate(any())(any())) + .thenReturn(Future.successful(false)) + + val summaryListRows = summaryListWithoutSuspendPeriod(mockBudgetPaymentPlanDetailResponse, application) + val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) + val result = route(application, request).value + val view = application.injector.instanceOf[PaymentPlanDetailsView] + status(result) mustEqual OK + contentAsString(result) mustEqual view("budgetPaymentPlan", + paymentPlanReference, + false, + false, + false, + false, + "", + "", + summaryListRows + )( + request, + messages(application) + ).toString + } + } + + // Defensive test: user should not be able to alter data if they somehow reach this screen + "should not show Amend, Cancel and Suspend actions when payment plan is inactive" in { + val mockBudgetPaymentPlanDetailResponse = + dummyPlanDetailResponse.copy(paymentPlanDetails = + dummyPlanDetailResponse.paymentPlanDetails.copy( + planType = PaymentPlanType.BudgetPaymentPlan.toString, + scheduledPaymentStartDate = Some(LocalDateTime.now().minusDays(30).toLocalDate), + scheduledPaymentEndDate = Some(LocalDateTime.now().plusDays(5).toLocalDate), + suspensionStartDate = None, + suspensionEndDate = None + ) + ) + + val paymentPlanReference = "ppReference" + val directDebitReference = "ddReference" + + val userAnswersWithPaymentReference = + emptyUserAnswers + .set( + PaymentPlanReferenceQuery, + paymentPlanReference + ) + .success + .value + .set( + DirectDebitReferenceQuery, + directDebitReference + ) + .success + .value + + val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) + .overrides( + bind[SessionRepository].toInstance(mockSessionRepository), + bind[NationalDirectDebitService].toInstance(mockService) + ) + .build() + + running(application) { + when(mockSessionRepository.set(any())) + .thenReturn(Future.successful(true)) + when(mockSessionRepository.get(any())) + .thenReturn(Future.successful(Some(userAnswersWithPaymentReference))) + when(mockService.getPaymentPlanDetails(any(), any())(any(), any())) + .thenReturn(Future.successful(mockBudgetPaymentPlanDetailResponse)) + when(mockService.isTwoDaysPriorPaymentDate(any())(any())) + .thenReturn(Future.successful(false)) + when(mockService.isThreeDaysPriorPlanEndDate(any())(any())) + .thenReturn(Future.successful(false)) + + val summaryListRows = summaryListWithoutSuspendPeriod(mockBudgetPaymentPlanDetailResponse, application) + val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) + val result = route(application, request).value + val view = application.injector.instanceOf[PaymentPlanDetailsView] + status(result) mustEqual OK + contentAsString(result) mustEqual view("budgetPaymentPlan", + paymentPlanReference, + false, + false, + false, + false, + "", + "", + summaryListRows + )( + request, + messages(application) + ).toString + } + } + } + + "when Suspension is active" - { + "should show Amend and Cancel action but not Suspend action" in { + val mockBudgetPaymentPlanDetailResponse = + dummyPlanDetailResponse.copy(paymentPlanDetails = + dummyPlanDetailResponse.paymentPlanDetails.copy( + planType = PaymentPlanType.BudgetPaymentPlan.toString, + scheduledPaymentStartDate = Some(LocalDateTime.now().plusDays(5).toLocalDate), + scheduledPaymentEndDate = Some(LocalDateTime.now().plusDays(30).toLocalDate), + suspensionStartDate = Some(LocalDateTime.now().plusDays(10).toLocalDate), + suspensionEndDate = Some(LocalDateTime.now().plusDays(15).toLocalDate) + ) + ) + + val formattedSuspensionStartDate = mockBudgetPaymentPlanDetailResponse.paymentPlanDetails.suspensionStartDate + .map(_.format(DateTimeFormatter.ofPattern(Constants.longDateTimeFormatPattern))) + .getOrElse("") + + val formattedSuspensionEndDate = mockBudgetPaymentPlanDetailResponse.paymentPlanDetails.suspensionEndDate + .map(_.format(DateTimeFormatter.ofPattern(Constants.longDateTimeFormatPattern))) + .getOrElse("") + + val paymentPlanReference = "ppReference" + val directDebitReference = "ddReference" + + val userAnswersWithPaymentReference = + emptyUserAnswers + .set( + PaymentPlanReferenceQuery, + paymentPlanReference + ) + .success + .value + .set( + DirectDebitReferenceQuery, + directDebitReference + ) + .success + .value + + val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) + .overrides( + bind[SessionRepository].toInstance(mockSessionRepository), + bind[NationalDirectDebitService].toInstance(mockService) + ) + .build() + + running(application) { + when(mockSessionRepository.set(any())) + .thenReturn(Future.successful(true)) + when(mockSessionRepository.get(any())) + .thenReturn(Future.successful(Some(userAnswersWithPaymentReference))) + when(mockService.getPaymentPlanDetails(any(), any())(any(), any())) + .thenReturn(Future.successful(mockBudgetPaymentPlanDetailResponse)) + when(mockService.isTwoDaysPriorPaymentDate(any())(any())) + .thenReturn(Future.successful(true)) + when(mockService.isThreeDaysPriorPlanEndDate(any())(any())) + .thenReturn(Future.successful(true)) + + val summaryListRows = summaryListWithSuspendPeriod(mockBudgetPaymentPlanDetailResponse, application) + val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) + val result = route(application, request).value + val view = application.injector.instanceOf[PaymentPlanDetailsView] + status(result) mustEqual OK + contentAsString(result) mustEqual view("budgetPaymentPlan", + paymentPlanReference, + true, + true, + false, + true, + formattedSuspensionStartDate, + formattedSuspensionEndDate, + summaryListRows + )( + request, + messages(application) + ).toString + } + } + + "should show Amend and Cancel action but not Suspend action when scheduledPaymentEndDate is None" in { + val mockBudgetPaymentPlanDetailResponse = + dummyPlanDetailResponse.copy(paymentPlanDetails = + dummyPlanDetailResponse.paymentPlanDetails.copy( + planType = PaymentPlanType.BudgetPaymentPlan.toString, + scheduledPaymentStartDate = Some(LocalDateTime.now().plusDays(5).toLocalDate), + scheduledPaymentEndDate = None, + suspensionStartDate = Some(LocalDateTime.now().plusDays(10).toLocalDate), + suspensionEndDate = Some(LocalDateTime.now().plusDays(15).toLocalDate) + ) + ) + + val formattedSuspensionStartDate = mockBudgetPaymentPlanDetailResponse.paymentPlanDetails.suspensionStartDate + .map(_.format(DateTimeFormatter.ofPattern(Constants.longDateTimeFormatPattern))) + .getOrElse("") + + val formattedSuspensionEndDate = mockBudgetPaymentPlanDetailResponse.paymentPlanDetails.suspensionEndDate + .map(_.format(DateTimeFormatter.ofPattern(Constants.longDateTimeFormatPattern))) + .getOrElse("") + + val paymentPlanReference = "ppReference" + val directDebitReference = "ddReference" + + val userAnswersWithPaymentReference = + emptyUserAnswers + .set( + PaymentPlanReferenceQuery, + paymentPlanReference + ) + .success + .value + .set( + DirectDebitReferenceQuery, + directDebitReference + ) + .success + .value + + val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) + .overrides( + bind[SessionRepository].toInstance(mockSessionRepository), + bind[NationalDirectDebitService].toInstance(mockService) + ) + .build() + + running(application) { + when(mockSessionRepository.set(any())) + .thenReturn(Future.successful(true)) + when(mockSessionRepository.get(any())) + .thenReturn(Future.successful(Some(userAnswersWithPaymentReference))) + when(mockService.getPaymentPlanDetails(any(), any())(any(), any())) + .thenReturn(Future.successful(mockBudgetPaymentPlanDetailResponse)) + when(mockService.isTwoDaysPriorPaymentDate(any())(any())) + .thenReturn(Future.successful(true)) + + val summaryListRows = summaryListWithSuspendPeriod(mockBudgetPaymentPlanDetailResponse, application) + val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) + val result = route(application, request).value + val view = application.injector.instanceOf[PaymentPlanDetailsView] + status(result) mustEqual OK + contentAsString(result) mustEqual view("budgetPaymentPlan", + paymentPlanReference, + true, + true, + false, + true, + formattedSuspensionStartDate, + formattedSuspensionEndDate, + summaryListRows + )( + request, + messages(application) + ).toString + } + } + + "should not show Amend, Cancel and Suspend actions when scheduledPaymentStartDate is two day prior start date but should show the suspend period" in { + val mockBudgetPaymentPlanDetailResponse = + dummyPlanDetailResponse.copy(paymentPlanDetails = + dummyPlanDetailResponse.paymentPlanDetails.copy( + planType = PaymentPlanType.BudgetPaymentPlan.toString, + scheduledPaymentStartDate = Some(LocalDateTime.now().plusDays(2).toLocalDate), + scheduledPaymentEndDate = Some(LocalDateTime.now().plusDays(30).toLocalDate), + suspensionStartDate = Some(LocalDateTime.now().plusDays(5).toLocalDate), + suspensionEndDate = Some(LocalDateTime.now().plusDays(10).toLocalDate) + ) + ) + + val formattedSuspensionStartDate = mockBudgetPaymentPlanDetailResponse.paymentPlanDetails.suspensionStartDate + .map(_.format(DateTimeFormatter.ofPattern(Constants.longDateTimeFormatPattern))) + .getOrElse("") + + val formattedSuspensionEndDate = mockBudgetPaymentPlanDetailResponse.paymentPlanDetails.suspensionEndDate + .map(_.format(DateTimeFormatter.ofPattern(Constants.longDateTimeFormatPattern))) + .getOrElse("") + + val paymentPlanReference = "ppReference" + val directDebitReference = "ddReference" + + val userAnswersWithPaymentReference = + emptyUserAnswers + .set( + PaymentPlanReferenceQuery, + paymentPlanReference + ) + .success + .value + .set( + DirectDebitReferenceQuery, + directDebitReference + ) + .success + .value + + val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) + .overrides( + bind[SessionRepository].toInstance(mockSessionRepository), + bind[NationalDirectDebitService].toInstance(mockService) + ) + .build() + + running(application) { + when(mockSessionRepository.set(any())) + .thenReturn(Future.successful(true)) + when(mockSessionRepository.get(any())) + .thenReturn(Future.successful(Some(userAnswersWithPaymentReference))) + when(mockService.getPaymentPlanDetails(any(), any())(any(), any())) + .thenReturn(Future.successful(mockBudgetPaymentPlanDetailResponse)) + when(mockService.isTwoDaysPriorPaymentDate(any())(any())) + .thenReturn(Future.successful(false)) + when(mockService.isThreeDaysPriorPlanEndDate(any())(any())) + .thenReturn(Future.successful(true)) + + val summaryListRows = summaryListWithSuspendPeriod(mockBudgetPaymentPlanDetailResponse, application) + val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) + val result = route(application, request).value + val view = application.injector.instanceOf[PaymentPlanDetailsView] + status(result) mustEqual OK + contentAsString(result) mustEqual view("budgetPaymentPlan", + paymentPlanReference, + false, + false, + false, + true, + formattedSuspensionStartDate, + formattedSuspensionEndDate, + summaryListRows + )( + request, + messages(application) + ).toString + } + } + + // Defensive test: user should not be able to set suspend date if two day prior start and three day prior end date + "should not show Amend, Cancel and Suspend actions when scheduledPaymentEndDate is two day prior start date and three day prior end date but should show the suspend period" in { + val mockBudgetPaymentPlanDetailResponse = + dummyPlanDetailResponse.copy(paymentPlanDetails = + dummyPlanDetailResponse.paymentPlanDetails.copy( + planType = PaymentPlanType.BudgetPaymentPlan.toString, + scheduledPaymentStartDate = Some(LocalDateTime.now().plusDays(1).toLocalDate), + scheduledPaymentEndDate = Some(LocalDateTime.now().plusDays(3).toLocalDate), + suspensionStartDate = Some(LocalDateTime.now().plusDays(2).toLocalDate), + suspensionEndDate = Some(LocalDateTime.now().plusDays(2).toLocalDate) + ) + ) + + val formattedSuspensionStartDate = mockBudgetPaymentPlanDetailResponse.paymentPlanDetails.suspensionStartDate + .map(_.format(DateTimeFormatter.ofPattern(Constants.longDateTimeFormatPattern))) + .getOrElse("") + + val formattedSuspensionEndDate = mockBudgetPaymentPlanDetailResponse.paymentPlanDetails.suspensionEndDate + .map(_.format(DateTimeFormatter.ofPattern(Constants.longDateTimeFormatPattern))) + .getOrElse("") + + val paymentPlanReference = "ppReference" + val directDebitReference = "ddReference" + + val userAnswersWithPaymentReference = + emptyUserAnswers + .set( + PaymentPlanReferenceQuery, + paymentPlanReference + ) + .success + .value + .set( + DirectDebitReferenceQuery, + directDebitReference + ) + .success + .value + + val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) + .overrides( + bind[SessionRepository].toInstance(mockSessionRepository), + bind[NationalDirectDebitService].toInstance(mockService) + ) + .build() + + running(application) { + when(mockSessionRepository.set(any())) + .thenReturn(Future.successful(true)) + when(mockSessionRepository.get(any())) + .thenReturn(Future.successful(Some(userAnswersWithPaymentReference))) + when(mockService.getPaymentPlanDetails(any(), any())(any(), any())) + .thenReturn(Future.successful(mockBudgetPaymentPlanDetailResponse)) + when(mockService.isTwoDaysPriorPaymentDate(any())(any())) + .thenReturn(Future.successful(false)) + when(mockService.isThreeDaysPriorPlanEndDate(any())(any())) + .thenReturn(Future.successful(false)) + + val summaryListRows = summaryListWithSuspendPeriod(mockBudgetPaymentPlanDetailResponse, application) + val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) + val result = route(application, request).value + val view = application.injector.instanceOf[PaymentPlanDetailsView] + status(result) mustEqual OK + contentAsString(result) mustEqual view("budgetPaymentPlan", + paymentPlanReference, + false, + false, + false, + true, + formattedSuspensionStartDate, + formattedSuspensionEndDate, + summaryListRows + )( + request, + messages(application) + ).toString + } + } + } + } } - } - "must return OK and the correct view for a GET with a BudgetPayment Plan" in { - def summaryList(paymentPlanData: PaymentPlanResponse, app: Application): Seq[SummaryListRow] = { - val planDetail = paymentPlanData.paymentPlanDetails - Seq( - AmendPaymentPlanTypeSummary.row(planDetail.planType)(messages(app)), - AmendPaymentPlanSourceSummary.row(planDetail.hodService)(messages(app)), - DateSetupSummary.row(planDetail.submissionDateTime)(messages(app)), - TotalAmountDueSummary.row(planDetail.totalLiability)(messages(app)), - MonthlyPaymentAmountSummary.row(planDetail.scheduledPaymentAmount, planDetail.totalLiability)(messages(app)), - FinalPaymentAmountSummary.row(planDetail.balancingPaymentAmount, planDetail.totalLiability)(messages(app)), - AmendPlanStartDateSummary.row(planDetail.planType, planDetail.scheduledPaymentStartDate, Constants.shortDateTimeFormatPattern)( - messages(app) - ), - AmendPlanEndDateSummary.row(planDetail.scheduledPaymentEndDate, Constants.shortDateTimeFormatPattern)(messages(app)), - PaymentsFrequencySummary.row(planDetail.scheduledPaymentFrequency)(messages(app)), - AmendPaymentAmountSummary.row(planDetail.planType, planDetail.scheduledPaymentAmount)(messages(app)), - AmendSuspendDateSummary.row(planDetail.suspensionStartDate, true)(messages(app)), - AmendSuspendDateSummary.row(planDetail.suspensionEndDate, false)(messages(app)) - ) + ".VariablePayment Plan" - { + "must return OK and the correct view for a GET" in { + val mockVariablePaymentPlanDetailResponse = + dummyPlanDetailResponse.copy(paymentPlanDetails = + dummyPlanDetailResponse.paymentPlanDetails.copy(planType = PaymentPlanType.VariablePaymentPlan.toString) + ) + + val paymentPlanReference = "ppReference" + val directDebitReference = "ddReference" + + val userAnswersWithPaymentReference = + emptyUserAnswers + .set( + PaymentPlanReferenceQuery, + paymentPlanReference + ) + .success + .value + .set( + DirectDebitReferenceQuery, + directDebitReference + ) + .success + .value + + val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) + .overrides( + bind[SessionRepository].toInstance(mockSessionRepository), + bind[NationalDirectDebitService].toInstance(mockService) + ) + .build() + + running(application) { + when(mockSessionRepository.set(any())) + .thenReturn(Future.successful(true)) + when(mockSessionRepository.get(any())) + .thenReturn(Future.successful(Some(userAnswersWithPaymentReference))) + when(mockService.getPaymentPlanDetails(any(), any())(any(), any())) + .thenReturn(Future.successful(mockVariablePaymentPlanDetailResponse)) + when(mockService.isThreeDaysPriorPlanEndDate(any())(any())) + .thenReturn(Future.successful(true)) + when(mockService.isTwoDaysPriorPaymentDate(any())(any())) + .thenReturn(Future.successful(true)) + + val summaryListRows = varRepaySummaryList(mockVariablePaymentPlanDetailResponse, application) + val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) + val result = route(application, request).value + val view = application.injector.instanceOf[PaymentPlanDetailsView] + status(result) mustEqual OK + contentAsString(result) mustEqual view("variablePaymentPlan", paymentPlanReference, false, true, false, false, "", "", summaryListRows)( + request, + messages(application) + ).toString + } + } } - val mockBudgetPaymentPlanDetailResponse = - dummyPlanDetailResponse.copy(paymentPlanDetails = - dummyPlanDetailResponse.paymentPlanDetails.copy(planType = PaymentPlanType.BudgetPaymentPlan.toString) - ) - - val paymentPlanReference = "ppReference" - val directDebitReference = "ddReference" - - val userAnswersWithPaymentReference = - emptyUserAnswers - .set( - PaymentPlanReferenceQuery, - paymentPlanReference - ) - .success - .value - .set( - DirectDebitReferenceQuery, - directDebitReference - ) - .success - .value - - val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) - .overrides( - bind[SessionRepository].toInstance(mockSessionRepository), - bind[NationalDirectDebitService].toInstance(mockService) - ) - .build() - - running(application) { - when(mockSessionRepository.set(any())) - .thenReturn(Future.successful(true)) - when(mockSessionRepository.get(any())) - .thenReturn(Future.successful(Some(userAnswersWithPaymentReference))) - when(mockService.getPaymentPlanDetails(any(), any())(any(), any())) - .thenReturn(Future.successful(mockBudgetPaymentPlanDetailResponse)) - when(mockService.isThreeDaysPriorPlanEndDate(any())(any())) - .thenReturn(Future.successful(true)) - - val summaryListRows = summaryList(mockBudgetPaymentPlanDetailResponse, application) - val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) - val result = route(application, request).value - val view = application.injector.instanceOf[PaymentPlanDetailsView] - status(result) mustEqual OK - contentAsString(result) mustEqual view("budgetPaymentPlan", paymentPlanReference, true, summaryListRows)(request, - messages(application) - ).toString + ".TaxCreditRepayment Plan" - { + "must return OK and the correct view for a GET" in { + val mockTaxCreditRepaymentPlanDetailResponse = + dummyPlanDetailResponse.copy(paymentPlanDetails = + dummyPlanDetailResponse.paymentPlanDetails.copy(planType = PaymentPlanType.TaxCreditRepaymentPlan.toString) + ) + + val paymentPlanReference = "ppReference" + val directDebitReference = "ddReference" + + val userAnswersWithPaymentReference = + emptyUserAnswers + .set( + PaymentPlanReferenceQuery, + paymentPlanReference + ) + .success + .value + .set( + DirectDebitReferenceQuery, + directDebitReference + ) + .success + .value + + val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) + .overrides( + bind[SessionRepository].toInstance(mockSessionRepository), + bind[NationalDirectDebitService].toInstance(mockService) + ) + .build() + + running(application) { + when(mockSessionRepository.set(any())) + .thenReturn(Future.successful(true)) + when(mockSessionRepository.get(any())) + .thenReturn(Future.successful(Some(userAnswersWithPaymentReference))) + when(mockService.getPaymentPlanDetails(any(), any())(any(), any())) + .thenReturn(Future.successful(mockTaxCreditRepaymentPlanDetailResponse)) + + val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) + val summaryListRows = varRepaySummaryList(mockTaxCreditRepaymentPlanDetailResponse, application) + val result = route(application, request).value + val view = application.injector.instanceOf[PaymentPlanDetailsView] + status(result) mustEqual OK + contentAsString(result) mustEqual view("taxCreditRepaymentPlan", + paymentPlanReference, + false, + false, + false, + false, + "", + "", + summaryListRows + )( + request, + messages(application) + ).toString + } + } } - } - "must return OK and the correct view for a GET with a Variable Plan" in { - val mockVariablePaymentPlanDetailResponse = - dummyPlanDetailResponse.copy(paymentPlanDetails = - dummyPlanDetailResponse.paymentPlanDetails.copy(planType = PaymentPlanType.VariablePaymentPlan.toString) - ) + "must redirect to Journey Recover page when DirectDebitReferenceQuery is not set" in { + val application = applicationBuilder(userAnswers = Some(emptyUserAnswers)) + .overrides() + .build() - val paymentPlanReference = "ppReference" - val directDebitReference = "ddReference" + running(application) { + val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) + val result = route(application, request).value - val userAnswersWithPaymentReference = - emptyUserAnswers - .set( - PaymentPlanReferenceQuery, - paymentPlanReference - ) - .success - .value - .set( - DirectDebitReferenceQuery, - directDebitReference - ) - .success - .value - - val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) - .overrides( - bind[SessionRepository].toInstance(mockSessionRepository), - bind[NationalDirectDebitService].toInstance(mockService) - ) - .build() - - running(application) { - when(mockSessionRepository.set(any())) - .thenReturn(Future.successful(true)) - when(mockSessionRepository.get(any())) - .thenReturn(Future.successful(Some(userAnswersWithPaymentReference))) - when(mockService.getPaymentPlanDetails(any(), any())(any(), any())) - .thenReturn(Future.successful(mockVariablePaymentPlanDetailResponse)) - when(mockService.isThreeDaysPriorPlanEndDate(any())(any())) - .thenReturn(Future.successful(true)) - when(mockService.isTwoDaysPriorPaymentDate(any())(any())) - .thenReturn(Future.successful(true)) - - val summaryListRows = varRepaySummaryList(mockVariablePaymentPlanDetailResponse, application) - val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) - val result = route(application, request).value - val view = application.injector.instanceOf[PaymentPlanDetailsView] - status(result) mustEqual OK - contentAsString(result) mustEqual view("variablePaymentPlan", paymentPlanReference, true, summaryListRows)(request, - messages(application) - ).toString + status(result) mustEqual SEE_OTHER + redirectLocation(result).value mustEqual routes.JourneyRecoveryController.onPageLoad().url + } } } - "must return OK and the correct view for a GET with a Tax Credit Repayment Plan" in { - val mockTaxCreditRepaymentPlanDetailResponse = - dummyPlanDetailResponse.copy(paymentPlanDetails = - dummyPlanDetailResponse.paymentPlanDetails.copy(planType = PaymentPlanType.TaxCreditRepaymentPlan.toString) - ) - - val paymentPlanReference = "ppReference" - val directDebitReference = "ddReference" - - val userAnswersWithPaymentReference = - emptyUserAnswers - .set( - PaymentPlanReferenceQuery, - paymentPlanReference - ) - .success - .value - .set( - DirectDebitReferenceQuery, - directDebitReference + "onRedirect" - { + "must redirect to Payment Plan Details page when a PaymentReferenceQuery is provided" in { + val paymentReference = "paymentReference" + val userAnswersWithPaymentReference = + emptyUserAnswers + .set( + PaymentPlanReferenceQuery, + paymentReference + ) + .success + .value + + val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) + .overrides( + bind[SessionRepository].toInstance(mockSessionRepository) ) - .success - .value - - val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) - .overrides( - bind[SessionRepository].toInstance(mockSessionRepository), - bind[NationalDirectDebitService].toInstance(mockService) - ) - .build() - - running(application) { - when(mockSessionRepository.set(any())) - .thenReturn(Future.successful(true)) - when(mockSessionRepository.get(any())) - .thenReturn(Future.successful(Some(userAnswersWithPaymentReference))) - when(mockService.getPaymentPlanDetails(any(), any())(any(), any())) - .thenReturn(Future.successful(mockTaxCreditRepaymentPlanDetailResponse)) - - val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) - val summaryListRows = varRepaySummaryList(mockTaxCreditRepaymentPlanDetailResponse, application) - val result = route(application, request).value - val view = application.injector.instanceOf[PaymentPlanDetailsView] - status(result) mustEqual OK - contentAsString(result) mustEqual view("taxCreditRepaymentPlan", paymentPlanReference, false, summaryListRows)(request, - messages(application) - ).toString - } - } + .build() - "must redirect to Journey Recover page when DirectDebitReferenceQuery is not set" in { - val application = applicationBuilder(userAnswers = Some(emptyUserAnswers)) - .overrides() - .build() + running(application) { + when(mockSessionRepository.set(any())).thenReturn(Future.successful(true)) + val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onRedirect(paymentReference).url) + val result = route(application, request).value + status(result) mustEqual SEE_OTHER + redirectLocation(result).value mustEqual routes.PaymentPlanDetailsController.onPageLoad().url - running(application) { - val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onPageLoad().url) - val result = route(application, request).value - - status(result) mustEqual SEE_OTHER - redirectLocation(result).value mustEqual routes.JourneyRecoveryController.onPageLoad().url - } - } - - "must redirect to Payment Plan Details page when a PaymentReferenceQuery is provided" in { - val paymentReference = "paymentReference" - val userAnswersWithPaymentReference = - emptyUserAnswers - .set( - PaymentPlanReferenceQuery, - paymentReference - ) - .success - .value - - val application = applicationBuilder(userAnswers = Some(userAnswersWithPaymentReference)) - .overrides( - bind[SessionRepository].toInstance(mockSessionRepository) - ) - .build() - - running(application) { - when(mockSessionRepository.set(any())).thenReturn(Future.successful(true)) - val request = FakeRequest(GET, routes.PaymentPlanDetailsController.onRedirect(paymentReference).url) - val result = route(application, request).value - status(result) mustEqual SEE_OTHER - redirectLocation(result).value mustEqual routes.PaymentPlanDetailsController.onPageLoad().url - - verify(mockSessionRepository).set(eqTo(userAnswersWithPaymentReference)) + verify(mockSessionRepository).set(eqTo(userAnswersWithPaymentReference)) + } } } }