From a0060439180fb9035582bbf957432e27dfc60889 Mon Sep 17 00:00:00 2001 From: Warren Smithson <36073378+warrenskiwork1979@users.noreply.github.com> Date: Thu, 23 Nov 2023 14:35:14 +0000 Subject: [PATCH] VEIOSS-350 | Allow Trader to Select Countries for Return (#4) * VEIOSS-350 | Add dropdown select for country. Create NoPeriodsAvailable page. Add missing captions to initial pages * VEIOSS-250 | Create AddToListPage for SoldToCountry. Refactor Action to exclude peroid param and refactor all associated controllers. WIP * VEIOSS-350 | Use Button group in view. --- .../controllers/$className$Controller.scala | 4 +- .../controllers/$className$Controller.scala | 4 +- .../controllers/$className$Controller.scala | 4 +- .../controllers/$className$Controller.scala | 4 +- .../controllers/$className$Controller.scala | 4 +- app/controllers/GetCountry.scala | 37 +++ app/controllers/IndexController.scala | 3 +- .../NoOtherPeriodsAvailableController.scala | 40 +++ .../SalesToCountryController.scala | 34 +-- app/controllers/SoldGoodsController.scala | 33 +-- app/controllers/SoldToCountryController.scala | 31 ++- .../SoldToCountryListController.scala | 87 +++++++ app/controllers/StartReturnController.scala | 29 +-- app/controllers/VatOnSalesController.scala | 34 +-- .../VatRatesFromCountryController.scala | 60 +++-- app/controllers/YourAccountController.scala | 8 +- .../AuthenticatedControllerComponents.scala | 9 +- .../actions/DataRetrievalAction.scala | 11 +- .../actions/IdentifierAction.scala | 11 +- app/controllers/auth/AuthController.scala | 3 +- app/forms/SoldToCountryListFormProvider.scala | 30 +++ app/models/Country.scala | 14 ++ app/models/UserAnswers.scala | 9 +- app/pages/NoOtherPeriodsAvailablePage.scala | 25 ++ app/pages/SalesToCountryPage.scala | 10 +- app/pages/SoldGoodsPage.scala | 8 +- app/pages/SoldToCountryListPage.scala | 71 ++++++ app/pages/SoldToCountryPage.scala | 12 +- app/pages/StartReturnPage.scala | 7 +- app/pages/VatOnSalesPage.scala | 10 +- app/pages/VatRatesFromCountryPage.scala | 10 +- app/pages/Waypoint.scala | 4 +- app/pages/YourAccountPage.scala | 25 ++ app/pages/package.scala | 24 ++ app/queries/DeriveNumberOfSales.scala | 27 ++ app/repositories/SessionRepository.scala | 18 +- app/utils/ItemsHelper.scala | 59 +++++ app/viewmodels/ListItemWrapper.scala | 21 ++ .../checkAnswers/SalesToCountrySummary.scala | 8 +- .../checkAnswers/SoldGoodsSummary.scala | 8 +- .../SoldToCountryListSummary.scala | 38 +++ .../checkAnswers/SoldToCountrySummary.scala | 8 +- .../checkAnswers/VatOnSalesSummary.scala | 8 +- .../VatRatesFromCountrySummary.scala | 8 +- app/viewmodels/govuk/CheckboxFluency.scala | 3 + app/viewmodels/govuk/FieldsetFluency.scala | 5 +- app/viewmodels/govuk/SelectFluency.scala | 81 ++++++ app/viewmodels/govuk/package.scala | 1 + .../NoOtherPeriodsAvailableView.scala.html | 33 +++ app/views/SalesToCountryView.scala.html | 2 +- app/views/SoldGoodsView.scala.html | 35 +-- app/views/SoldToCountryListView.scala.html | 75 ++++++ app/views/SoldToCountryView.scala.html | 29 ++- app/views/VatOnSalesView.scala.html | 2 +- app/views/VatRatesFromCountryView.scala.html | 11 +- app/views/components/addToList.scala.html | 46 ++++ conf/app.routes | 56 +++-- conf/messages.en | 22 +- test/controllers/IndexControllerSpec.scala | 2 +- ...oOtherPeriodsAvailableControllerSpec.scala | 44 ++++ .../SalesToCountryControllerSpec.scala | 9 +- .../controllers/SoldGoodsControllerSpec.scala | 9 +- .../SoldToCountryControllerSpec.scala | 6 +- .../SoldToCountryListControllerSpec.scala | 236 ++++++++++++++++++ .../StartReturnControllerSpec.scala | 60 +---- .../VatOnSalesControllerSpec.scala | 11 +- .../VatRatesFromCountryControllerSpec.scala | 35 ++- .../YourAccountControllerSpec.scala | 2 +- .../actions/DataRetrievalActionSpec.scala | 14 +- .../actions/FakeDataRetrievalAction.scala | 5 +- .../actions/GetRegistrationActionSpec.scala | 2 +- .../SoldToCountryListFormProviderSpec.scala | 45 ++++ 72 files changed, 1450 insertions(+), 343 deletions(-) create mode 100644 app/controllers/GetCountry.scala create mode 100644 app/controllers/NoOtherPeriodsAvailableController.scala create mode 100644 app/controllers/SoldToCountryListController.scala create mode 100644 app/forms/SoldToCountryListFormProvider.scala create mode 100644 app/pages/NoOtherPeriodsAvailablePage.scala create mode 100644 app/pages/SoldToCountryListPage.scala create mode 100644 app/pages/YourAccountPage.scala create mode 100644 app/pages/package.scala create mode 100644 app/queries/DeriveNumberOfSales.scala create mode 100644 app/utils/ItemsHelper.scala create mode 100644 app/viewmodels/ListItemWrapper.scala create mode 100644 app/viewmodels/checkAnswers/SoldToCountryListSummary.scala create mode 100644 app/viewmodels/govuk/SelectFluency.scala create mode 100644 app/views/NoOtherPeriodsAvailableView.scala.html create mode 100644 app/views/SoldToCountryListView.scala.html create mode 100644 app/views/components/addToList.scala.html create mode 100644 test/controllers/NoOtherPeriodsAvailableControllerSpec.scala create mode 100644 test/controllers/SoldToCountryListControllerSpec.scala create mode 100644 test/forms/SoldToCountryListFormProviderSpec.scala diff --git a/.g8/checkboxPage/app/controllers/$className$Controller.scala b/.g8/checkboxPage/app/controllers/$className$Controller.scala index d2d6f524..9a0763c7 100644 --- a/.g8/checkboxPage/app/controllers/$className$Controller.scala +++ b/.g8/checkboxPage/app/controllers/$className$Controller.scala @@ -25,7 +25,7 @@ class $className$Controller @Inject()( val form = formProvider() - def onPageLoad(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData(period) { + def onPageLoad(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData() { implicit request => val preparedForm = request.userAnswers.get($className$Page) match { @@ -36,7 +36,7 @@ class $className$Controller @Inject()( Ok(view(preparedForm, mode, period)) } - def onSubmit(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData(period).async { + def onSubmit(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData().async { implicit request => form.bindFromRequest().fold( diff --git a/.g8/intPage/app/controllers/$className$Controller.scala b/.g8/intPage/app/controllers/$className$Controller.scala index d2d6f524..9a0763c7 100644 --- a/.g8/intPage/app/controllers/$className$Controller.scala +++ b/.g8/intPage/app/controllers/$className$Controller.scala @@ -25,7 +25,7 @@ class $className$Controller @Inject()( val form = formProvider() - def onPageLoad(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData(period) { + def onPageLoad(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData() { implicit request => val preparedForm = request.userAnswers.get($className$Page) match { @@ -36,7 +36,7 @@ class $className$Controller @Inject()( Ok(view(preparedForm, mode, period)) } - def onSubmit(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData(period).async { + def onSubmit(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData().async { implicit request => form.bindFromRequest().fold( diff --git a/.g8/radioButtonPage/app/controllers/$className$Controller.scala b/.g8/radioButtonPage/app/controllers/$className$Controller.scala index dadd0d31..fcb2c1c5 100644 --- a/.g8/radioButtonPage/app/controllers/$className$Controller.scala +++ b/.g8/radioButtonPage/app/controllers/$className$Controller.scala @@ -25,7 +25,7 @@ class $className$Controller @Inject()( val form = formProvider() - def onPageLoad(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData(period) { + def onPageLoad(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData() { implicit request => val preparedForm = request.userAnswers.get($className$Page) match { @@ -36,7 +36,7 @@ class $className$Controller @Inject()( Ok(view(preparedForm, mode, period)) } - def onSubmit(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData(period).async { + def onSubmit(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData().async { implicit request => form.bindFromRequest().fold( diff --git a/.g8/stringPage/app/controllers/$className$Controller.scala b/.g8/stringPage/app/controllers/$className$Controller.scala index 1ab61500..e724ee49 100644 --- a/.g8/stringPage/app/controllers/$className$Controller.scala +++ b/.g8/stringPage/app/controllers/$className$Controller.scala @@ -25,7 +25,7 @@ class $className$Controller @Inject()( val form = formProvider() - def onPageLoad(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData(period) { + def onPageLoad(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData() { implicit request => val preparedForm = request.userAnswers.get($className$Page) match { @@ -36,7 +36,7 @@ class $className$Controller @Inject()( Ok(view(preparedForm, mode, period)) } - def onSubmit(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData(period).async { + def onSubmit(mode: Mode): Action[AnyContent] = cc.authAndRequireData().async { implicit request => form.bindFromRequest().fold( diff --git a/.g8/yesNoPage/app/controllers/$className$Controller.scala b/.g8/yesNoPage/app/controllers/$className$Controller.scala index 8092ede3..6b6bcc66 100644 --- a/.g8/yesNoPage/app/controllers/$className$Controller.scala +++ b/.g8/yesNoPage/app/controllers/$className$Controller.scala @@ -25,7 +25,7 @@ class $className;format="cap"$Controller @Inject()( val form = formProvider() - def onPageLoad(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData(period) { + def onPageLoad(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData() { implicit request => val preparedForm = request.userAnswers.get($className$Page) match { @@ -36,7 +36,7 @@ class $className;format="cap"$Controller @Inject()( Ok(view(preparedForm, mode, period)) } - def onSubmit(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData(period).async { + def onSubmit(mode: Mode, period: Period): Action[AnyContent] = cc.authAndRequireData().async { implicit request => form.bindFromRequest().fold( diff --git a/app/controllers/GetCountry.scala b/app/controllers/GetCountry.scala new file mode 100644 index 00000000..7ce805bd --- /dev/null +++ b/app/controllers/GetCountry.scala @@ -0,0 +1,37 @@ +/* + * Copyright 2023 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 controllers + +import models.requests.DataRequest +import models.{Country, Index} +import pages.{JourneyRecoveryPage, SoldToCountryPage, Waypoints} +import play.api.mvc.Results.Redirect +import play.api.mvc.{AnyContent, Result} +import utils.FutureSyntax.FutureOps + +import scala.concurrent.Future + +trait GetCountry { + + protected def getCountry(waypoints: Waypoints, index: Index) + (block: Country => Future[Result]) + (implicit request: DataRequest[AnyContent]): Future[Result] = + request.userAnswers + .get(SoldToCountryPage(index)) + .map(block(_)) + .getOrElse(Redirect(JourneyRecoveryPage.route(waypoints)).toFuture) +} diff --git a/app/controllers/IndexController.scala b/app/controllers/IndexController.scala index 519d962d..74967587 100644 --- a/app/controllers/IndexController.scala +++ b/app/controllers/IndexController.scala @@ -17,6 +17,7 @@ package controllers import controllers.actions.AuthenticatedControllerComponents +import pages.EmptyWaypoints import play.api.i18n.I18nSupport import play.api.mvc.{Action, AnyContent, MessagesControllerComponents} import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController @@ -30,6 +31,6 @@ class IndexController @Inject()( protected val controllerComponents: MessagesControllerComponents = cc def onPageLoad: Action[AnyContent] = cc.authAndGetRegistration { _ => - Redirect(routes.YourAccountController.onPageLoad) + Redirect(routes.YourAccountController.onPageLoad(waypoints = EmptyWaypoints)) } } diff --git a/app/controllers/NoOtherPeriodsAvailableController.scala b/app/controllers/NoOtherPeriodsAvailableController.scala new file mode 100644 index 00000000..1db9cab2 --- /dev/null +++ b/app/controllers/NoOtherPeriodsAvailableController.scala @@ -0,0 +1,40 @@ +/* + * Copyright 2023 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 controllers + +import controllers.actions._ +import pages.Waypoints +import play.api.i18n.{I18nSupport, MessagesApi} +import play.api.mvc.{Action, AnyContent, MessagesControllerComponents} +import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController +import views.html.NoOtherPeriodsAvailableView + +import javax.inject.Inject + +class NoOtherPeriodsAvailableController @Inject()( + override val messagesApi: MessagesApi, + cc: AuthenticatedControllerComponents, + view: NoOtherPeriodsAvailableView + ) extends FrontendBaseController with I18nSupport { + + protected val controllerComponents: MessagesControllerComponents = cc + + def onPageLoad(waypoints: Waypoints): Action[AnyContent] = (cc.actionBuilder andThen cc.identify) { + implicit request => + Ok(view(waypoints)) + } +} diff --git a/app/controllers/SalesToCountryController.scala b/app/controllers/SalesToCountryController.scala index 3578b1b6..a3a3ab10 100644 --- a/app/controllers/SalesToCountryController.scala +++ b/app/controllers/SalesToCountryController.scala @@ -18,31 +18,35 @@ package controllers import controllers.actions._ import forms.SalesToCountryFormProvider -import models.{Index, Period} +import models.Index import pages.{SalesToCountryPage, Waypoints} +import play.api.data.Form import play.api.i18n.{I18nSupport, MessagesApi} import play.api.mvc.{Action, AnyContent, MessagesControllerComponents} import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController +import utils.FutureSyntax.FutureOps import views.html.SalesToCountryView import javax.inject.Inject import scala.concurrent.{ExecutionContext, Future} class SalesToCountryController @Inject()( - override val messagesApi: MessagesApi, - cc: AuthenticatedControllerComponents, - formProvider: SalesToCountryFormProvider, - view: SalesToCountryView - )(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport { + override val messagesApi: MessagesApi, + cc: AuthenticatedControllerComponents, + formProvider: SalesToCountryFormProvider, + view: SalesToCountryView + )(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport { protected val controllerComponents: MessagesControllerComponents = cc - val form = formProvider() + val form: Form[Int] = formProvider() - def onPageLoad(waypoints: Waypoints, period: Period, index: Index): Action[AnyContent] = cc.authAndRequireData(period) { + def onPageLoad(waypoints: Waypoints, index: Index): Action[AnyContent] = cc.authAndRequireData() { implicit request => - val preparedForm = request.userAnswers.get(SalesToCountryPage(period, index)) match { + val period = request.userAnswers.period + + val preparedForm = request.userAnswers.get(SalesToCountryPage(index)) match { case None => form case Some(value) => form.fill(value) } @@ -50,18 +54,20 @@ class SalesToCountryController @Inject()( Ok(view(preparedForm, waypoints, period, index)) } - def onSubmit(waypoints: Waypoints, period: Period, index: Index): Action[AnyContent] = cc.authAndRequireData(period).async { + def onSubmit(waypoints: Waypoints, index: Index): Action[AnyContent] = cc.authAndRequireData().async { implicit request => + val period = request.userAnswers.period + form.bindFromRequest().fold( formWithErrors => - Future.successful(BadRequest(view(formWithErrors, waypoints, period, index))), + BadRequest(view(formWithErrors, waypoints, period, index)).toFuture, value => for { - updatedAnswers <- Future.fromTry(request.userAnswers.set(SalesToCountryPage(period, index), value)) - _ <- cc.sessionRepository.set(updatedAnswers) - } yield Redirect(SalesToCountryPage(period, index).navigate(waypoints, request.userAnswers, updatedAnswers).route) + updatedAnswers <- Future.fromTry(request.userAnswers.set(SalesToCountryPage(index), value)) + _ <- cc.sessionRepository.set(updatedAnswers) + } yield Redirect(SalesToCountryPage(index).navigate(waypoints, request.userAnswers, updatedAnswers).route) ) } } diff --git a/app/controllers/SoldGoodsController.scala b/app/controllers/SoldGoodsController.scala index 921ddb22..81d3c126 100644 --- a/app/controllers/SoldGoodsController.scala +++ b/app/controllers/SoldGoodsController.scala @@ -18,31 +18,34 @@ package controllers import controllers.actions._ import forms.SoldGoodsFormProvider -import models.Period import pages.{SoldGoodsPage, Waypoints} +import play.api.data.Form import play.api.i18n.{I18nSupport, MessagesApi} import play.api.mvc.{Action, AnyContent, MessagesControllerComponents} import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController +import utils.FutureSyntax.FutureOps import views.html.SoldGoodsView import javax.inject.Inject import scala.concurrent.{ExecutionContext, Future} class SoldGoodsController @Inject()( - override val messagesApi: MessagesApi, - cc: AuthenticatedControllerComponents, - formProvider: SoldGoodsFormProvider, - view: SoldGoodsView - )(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport { + override val messagesApi: MessagesApi, + cc: AuthenticatedControllerComponents, + formProvider: SoldGoodsFormProvider, + view: SoldGoodsView + )(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport { protected val controllerComponents: MessagesControllerComponents = cc - val form = formProvider() + val form: Form[Boolean] = formProvider() - def onPageLoad(waypoints: Waypoints, period: Period): Action[AnyContent] = cc.authAndRequireData(period) { + def onPageLoad(waypoints: Waypoints): Action[AnyContent] = cc.authAndRequireData() { implicit request => - val preparedForm = request.userAnswers.get(SoldGoodsPage(period)) match { + val period = request.userAnswers.period + + val preparedForm = request.userAnswers.get(SoldGoodsPage) match { case None => form case Some(value) => form.fill(value) } @@ -50,18 +53,20 @@ class SoldGoodsController @Inject()( Ok(view(preparedForm, waypoints, period)) } - def onSubmit(waypoints: Waypoints, period: Period): Action[AnyContent] = cc.authAndRequireData(period).async { + def onSubmit(waypoints: Waypoints): Action[AnyContent] = cc.authAndRequireData().async { implicit request => + val period = request.userAnswers.period + form.bindFromRequest().fold( formWithErrors => - Future.successful(BadRequest(view(formWithErrors, waypoints, period))), + BadRequest(view(formWithErrors, waypoints, period)).toFuture, value => for { - updatedAnswers <- Future.fromTry(request.userAnswers.set(SoldGoodsPage(period), value)) - _ <- cc.sessionRepository.set(updatedAnswers) - } yield Redirect(SoldGoodsPage(period).navigate(waypoints, request.userAnswers, updatedAnswers).route) + updatedAnswers <- Future.fromTry(request.userAnswers.set(SoldGoodsPage, value)) + _ <- cc.sessionRepository.set(updatedAnswers) + } yield Redirect(SoldGoodsPage.navigate(waypoints, request.userAnswers, updatedAnswers).route) ) } } diff --git a/app/controllers/SoldToCountryController.scala b/app/controllers/SoldToCountryController.scala index d49bb8df..c697b381 100644 --- a/app/controllers/SoldToCountryController.scala +++ b/app/controllers/SoldToCountryController.scala @@ -18,29 +18,32 @@ package controllers import controllers.actions._ import forms.SoldToCountryFormProvider -import models.{Index, Period} +import models.Index import pages.{SoldToCountryPage, Waypoints} import play.api.i18n.{I18nSupport, MessagesApi} import play.api.mvc.{Action, AnyContent, MessagesControllerComponents} import queries.AllSalesQuery import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController +import utils.FutureSyntax.FutureOps import views.html.SoldToCountryView import javax.inject.Inject import scala.concurrent.{ExecutionContext, Future} class SoldToCountryController @Inject()( - override val messagesApi: MessagesApi, - cc: AuthenticatedControllerComponents, - formProvider: SoldToCountryFormProvider, - view: SoldToCountryView - )(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport { + override val messagesApi: MessagesApi, + cc: AuthenticatedControllerComponents, + formProvider: SoldToCountryFormProvider, + view: SoldToCountryView + )(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport { protected val controllerComponents: MessagesControllerComponents = cc - def onPageLoad(waypoints: Waypoints, period: Period, index: Index): Action[AnyContent] = cc.authAndRequireData(period) { + def onPageLoad(waypoints: Waypoints, index: Index): Action[AnyContent] = cc.authAndRequireData() { implicit request => + val period = request.userAnswers.period + val form = formProvider( index, request.userAnswers.get(AllSalesQuery) @@ -48,7 +51,7 @@ class SoldToCountryController @Inject()( .map(_.country) ) - val preparedForm = request.userAnswers.get(SoldToCountryPage(period, index)) match { + val preparedForm = request.userAnswers.get(SoldToCountryPage(index)) match { case None => form case Some(value) => form.fill(value) } @@ -56,9 +59,11 @@ class SoldToCountryController @Inject()( Ok(view(preparedForm, waypoints, period, index)) } - def onSubmit(waypoints: Waypoints, period: Period, index: Index): Action[AnyContent] = cc.authAndRequireData(period).async { + def onSubmit(waypoints: Waypoints, index: Index): Action[AnyContent] = cc.authAndRequireData().async { implicit request => + val period = request.userAnswers.period + val form = formProvider( index, request.userAnswers.get(AllSalesQuery) @@ -68,13 +73,13 @@ class SoldToCountryController @Inject()( form.bindFromRequest().fold( formWithErrors => - Future.successful(BadRequest(view(formWithErrors, waypoints, period, index))), + BadRequest(view(formWithErrors, waypoints, period, index)).toFuture, value => for { - updatedAnswers <- Future.fromTry(request.userAnswers.set(SoldToCountryPage(period, index), value)) - _ <- cc.sessionRepository.set(updatedAnswers) - } yield Redirect(SoldToCountryPage(period, index).navigate(waypoints, request.userAnswers, updatedAnswers).route) + updatedAnswers <- Future.fromTry(request.userAnswers.set(SoldToCountryPage(index), value)) + _ <- cc.sessionRepository.set(updatedAnswers) + } yield Redirect(SoldToCountryPage(index).navigate(waypoints, request.userAnswers, updatedAnswers).route) ) } } diff --git a/app/controllers/SoldToCountryListController.scala b/app/controllers/SoldToCountryListController.scala new file mode 100644 index 00000000..43e7bc78 --- /dev/null +++ b/app/controllers/SoldToCountryListController.scala @@ -0,0 +1,87 @@ +/* + * Copyright 2023 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 controllers + +import controllers.actions._ +import forms.SoldToCountryListFormProvider +import models.Country +import pages.{SoldToCountryListPage, Waypoints} +import play.api.data.Form +import play.api.i18n.{I18nSupport, MessagesApi} +import play.api.mvc.{Action, AnyContent, MessagesControllerComponents} +import queries.DeriveNumberOfSales +import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController +import utils.FutureSyntax.FutureOps +import utils.ItemsHelper.getDerivedItems +import viewmodels.checkAnswers.SoldToCountryListSummary +import views.html.SoldToCountryListView + +import javax.inject.Inject +import scala.concurrent.{ExecutionContext, Future} + +class SoldToCountryListController @Inject()( + override val messagesApi: MessagesApi, + cc: AuthenticatedControllerComponents, + formProvider: SoldToCountryListFormProvider, + view: SoldToCountryListView + )(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport { + + protected val controllerComponents: MessagesControllerComponents = cc + + val form: Form[Boolean] = formProvider() + + def onPageLoad(waypoints: Waypoints): Action[AnyContent] = cc.authAndRequireData().async { + implicit request => + + val period = request.userAnswers.period + + getDerivedItems(waypoints, DeriveNumberOfSales) { + number => + + val canAddSales = number < Country.euCountriesWithNI.size + val salesSummary = SoldToCountryListSummary + .addToListRows(request.userAnswers, waypoints, SoldToCountryListPage()) + + Ok(view(form, waypoints, period, salesSummary, canAddSales)).toFuture + } + } + + def onSubmit(waypoints: Waypoints): Action[AnyContent] = cc.authAndRequireData().async { + implicit request => + + val period = request.userAnswers.period + + getDerivedItems(waypoints, DeriveNumberOfSales) { + number => + + val canAddSales = number < Country.euCountriesWithNI.size + val salesSummary = SoldToCountryListSummary + .addToListRows(request.userAnswers, waypoints, SoldToCountryListPage()) + + form.bindFromRequest().fold( + formWithErrors => + BadRequest(view(formWithErrors, waypoints, period, salesSummary, canAddSales)).toFuture, + + value => + for { + updatedAnswers <- Future.fromTry(request.userAnswers.set(SoldToCountryListPage(), value)) + _ <- cc.sessionRepository.set(updatedAnswers) + } yield Redirect(SoldToCountryListPage().navigate(waypoints, request.userAnswers, updatedAnswers).route) + ) + } + } +} diff --git a/app/controllers/StartReturnController.scala b/app/controllers/StartReturnController.scala index df0b89fe..3cc36eec 100644 --- a/app/controllers/StartReturnController.scala +++ b/app/controllers/StartReturnController.scala @@ -20,6 +20,7 @@ import controllers.actions._ import forms.StartReturnFormProvider import models.{Period, UserAnswers} import pages.{StartReturnPage, Waypoints} +import play.api.data.Form import play.api.i18n.{I18nSupport, MessagesApi} import play.api.mvc.{Action, AnyContent, MessagesControllerComponents} import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController @@ -40,15 +41,17 @@ class StartReturnController @Inject()( protected val controllerComponents: MessagesControllerComponents = cc - val form = formProvider() + val form: Form[Boolean] = formProvider() - def onPageLoad(waypoints: Waypoints, period: Period): Action[AnyContent] = cc.authAndGetOptionalData(period) { + def onPageLoad(waypoints: Waypoints, period: Period): Action[AnyContent] = cc.authAndGetOptionalData() { implicit request => + // TODO check for starting correct period + Ok(view(form, waypoints, period)) } - def onSubmit(waypoints: Waypoints, period: Period): Action[AnyContent] = cc.authAndGetOptionalData(period).async { + def onSubmit(waypoints: Waypoints, period: Period): Action[AnyContent] = cc.authAndGetOptionalData().async { implicit request => form.bindFromRequest().fold( @@ -57,23 +60,13 @@ class StartReturnController @Inject()( value => { - val answers: UserAnswers = request.userAnswers.getOrElse(UserAnswers( - request.userId, - period = period, - lastUpdated = Instant.now(clock) - )) - - val dbCall = if (!value) { - cc.sessionRepository.clear(request.userId) - } else { - Future.fromTry(answers.set(StartReturnPage(period), value)).flatMap { updatedAnswers => - cc.sessionRepository.set(updatedAnswers) - } - } + val initialisedAnswers = UserAnswers(id = request.userId, period = period, lastUpdated = Instant.now(clock)) for { - _ <- dbCall - } yield Redirect(StartReturnPage(period).navigate(waypoints, answers, answers).route) + answers <- request.userAnswers.getOrElse(initialisedAnswers).toFuture + updatedAnswers <- Future.fromTry(answers.set(StartReturnPage(period), value)) + _ <- cc.sessionRepository.set(updatedAnswers) + } yield Redirect(StartReturnPage(period).navigate(waypoints, answers, updatedAnswers).route) } ) } diff --git a/app/controllers/VatOnSalesController.scala b/app/controllers/VatOnSalesController.scala index daceaae6..8d6e2298 100644 --- a/app/controllers/VatOnSalesController.scala +++ b/app/controllers/VatOnSalesController.scala @@ -18,31 +18,35 @@ package controllers import controllers.actions._ import forms.VatOnSalesFormProvider -import models.{Index, Period} +import models.{Index, VatOnSales} import pages.{VatOnSalesPage, Waypoints} +import play.api.data.Form import play.api.i18n.{I18nSupport, MessagesApi} import play.api.mvc.{Action, AnyContent, MessagesControllerComponents} import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController +import utils.FutureSyntax.FutureOps import views.html.VatOnSalesView import javax.inject.Inject import scala.concurrent.{ExecutionContext, Future} class VatOnSalesController @Inject()( - override val messagesApi: MessagesApi, - cc: AuthenticatedControllerComponents, - formProvider: VatOnSalesFormProvider, - view: VatOnSalesView - )(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport { + override val messagesApi: MessagesApi, + cc: AuthenticatedControllerComponents, + formProvider: VatOnSalesFormProvider, + view: VatOnSalesView + )(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport { protected val controllerComponents: MessagesControllerComponents = cc - val form = formProvider() + val form: Form[VatOnSales] = formProvider() - def onPageLoad(waypoints: Waypoints, period: Period, index: Index): Action[AnyContent] = cc.authAndRequireData(period) { + def onPageLoad(waypoints: Waypoints, index: Index): Action[AnyContent] = cc.authAndRequireData() { implicit request => - val preparedForm = request.userAnswers.get(VatOnSalesPage(period, index)) match { + val period = request.userAnswers.period + + val preparedForm = request.userAnswers.get(VatOnSalesPage(index)) match { case None => form case Some(value) => form.fill(value) } @@ -50,18 +54,20 @@ class VatOnSalesController @Inject()( Ok(view(preparedForm, waypoints, period, index)) } - def onSubmit(waypoints: Waypoints, period: Period, index: Index): Action[AnyContent] = cc.authAndRequireData(period).async { + def onSubmit(waypoints: Waypoints, index: Index): Action[AnyContent] = cc.authAndRequireData().async { implicit request => + val period = request.userAnswers.period + form.bindFromRequest().fold( formWithErrors => - Future.successful(BadRequest(view(formWithErrors, waypoints, period, index))), + BadRequest(view(formWithErrors, waypoints, period, index)).toFuture, value => for { - updatedAnswers <- Future.fromTry(request.userAnswers.set(VatOnSalesPage(period, index), value)) - _ <- cc.sessionRepository.set(updatedAnswers) - } yield Redirect(VatOnSalesPage(period, index).navigate(waypoints, request.userAnswers, updatedAnswers).route) + updatedAnswers <- Future.fromTry(request.userAnswers.set(VatOnSalesPage(index), value)) + _ <- cc.sessionRepository.set(updatedAnswers) + } yield Redirect(VatOnSalesPage(index).navigate(waypoints, request.userAnswers, updatedAnswers).route) ) } } diff --git a/app/controllers/VatRatesFromCountryController.scala b/app/controllers/VatRatesFromCountryController.scala index 3f340a84..a07ec44e 100644 --- a/app/controllers/VatRatesFromCountryController.scala +++ b/app/controllers/VatRatesFromCountryController.scala @@ -18,50 +18,64 @@ package controllers import controllers.actions._ import forms.VatRatesFromCountryFormProvider -import models.{Index, Period} +import models.{Index, VatRatesFromCountry} import pages.{VatRatesFromCountryPage, Waypoints} +import play.api.data.Form import play.api.i18n.{I18nSupport, MessagesApi} import play.api.mvc.{Action, AnyContent, MessagesControllerComponents} import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController +import utils.FutureSyntax.FutureOps import views.html.VatRatesFromCountryView import javax.inject.Inject import scala.concurrent.{ExecutionContext, Future} class VatRatesFromCountryController @Inject()( - override val messagesApi: MessagesApi, - cc: AuthenticatedControllerComponents, - formProvider: VatRatesFromCountryFormProvider, - view: VatRatesFromCountryView - )(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport { + override val messagesApi: MessagesApi, + cc: AuthenticatedControllerComponents, + formProvider: VatRatesFromCountryFormProvider, + view: VatRatesFromCountryView + )(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport with GetCountry { protected val controllerComponents: MessagesControllerComponents = cc - val form = formProvider() + val form: Form[Set[VatRatesFromCountry]] = formProvider() - def onPageLoad(waypoints: Waypoints, period: Period, index: Index): Action[AnyContent] = cc.authAndRequireData(period) { + def onPageLoad(waypoints: Waypoints, index: Index): Action[AnyContent] = cc.authAndRequireData().async { implicit request => - val preparedForm = request.userAnswers.get(VatRatesFromCountryPage(period, index)) match { - case None => form - case Some(value) => form.fill(value) - } + val period = request.userAnswers.period + + getCountry(waypoints, index) { + country => - Ok(view(preparedForm, waypoints, period, index)) + val preparedForm = request.userAnswers.get(VatRatesFromCountryPage(index)) match { + case None => form + case Some(value) => form.fill(value) + } + + Ok(view(preparedForm, waypoints, period, index, country)).toFuture + } } - def onSubmit(waypoints: Waypoints, period: Period, index: Index): Action[AnyContent] = cc.authAndRequireData(period).async { + def onSubmit(waypoints: Waypoints, index: Index): Action[AnyContent] = cc.authAndRequireData().async { implicit request => - form.bindFromRequest().fold( - formWithErrors => - Future.successful(BadRequest(view(formWithErrors, waypoints, period, index))), + val period = request.userAnswers.period - value => - for { - updatedAnswers <- Future.fromTry(request.userAnswers.set(VatRatesFromCountryPage(period, index), value)) - _ <- cc.sessionRepository.set(updatedAnswers) - } yield Redirect(VatRatesFromCountryPage(period, index).navigate(waypoints, request.userAnswers, updatedAnswers).route) - ) + getCountry(waypoints, index) { + country => + + form.bindFromRequest().fold( + formWithErrors => + BadRequest(view(formWithErrors, waypoints, period, index, country)).toFuture, + + value => + for { + updatedAnswers <- Future.fromTry(request.userAnswers.set(VatRatesFromCountryPage(index), value)) + _ <- cc.sessionRepository.set(updatedAnswers) + } yield Redirect(VatRatesFromCountryPage(index).navigate(waypoints, request.userAnswers, updatedAnswers).route) + ) + } } } diff --git a/app/controllers/YourAccountController.scala b/app/controllers/YourAccountController.scala index f753ee41..9bc89310 100644 --- a/app/controllers/YourAccountController.scala +++ b/app/controllers/YourAccountController.scala @@ -19,29 +19,27 @@ package controllers import config.FrontendAppConfig import controllers.actions.AuthenticatedControllerComponents import logging.Logging +import pages.Waypoints import play.api.i18n.I18nSupport import play.api.mvc.{Action, AnyContent, MessagesControllerComponents} import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController import views.html.YourAccountView import javax.inject.Inject -import scala.concurrent.ExecutionContext class YourAccountController @Inject()( cc: AuthenticatedControllerComponents, view: YourAccountView, appConfig: FrontendAppConfig - )(implicit ec: ExecutionContext) + ) extends FrontendBaseController with I18nSupport with Logging { protected val controllerComponents: MessagesControllerComponents = cc - def onPageLoad: Action[AnyContent] = cc.authAndGetRegistration { + def onPageLoad(waypoints: Waypoints): Action[AnyContent] = cc.authAndGetRegistration { implicit request => Ok(view(request.registrationWrapper.vatInfo.getName, request.iossNumber, appConfig.amendRegistrationUrl)) } - - } diff --git a/app/controllers/actions/AuthenticatedControllerComponents.scala b/app/controllers/actions/AuthenticatedControllerComponents.scala index ed79745d..0d69b516 100644 --- a/app/controllers/actions/AuthenticatedControllerComponents.scala +++ b/app/controllers/actions/AuthenticatedControllerComponents.scala @@ -17,7 +17,6 @@ package controllers.actions import models.requests.{DataRequest, IdentifierRequest, OptionalDataRequest, RegistrationRequest} -import models.Period import play.api.http.FileMimeTypes import play.api.i18n.{Langs, MessagesApi} import play.api.mvc._ @@ -48,13 +47,13 @@ trait AuthenticatedControllerComponents extends MessagesControllerComponents { getRegistration } - def authAndGetOptionalData(period: Period): ActionBuilder[OptionalDataRequest, AnyContent] = { + def authAndGetOptionalData(): ActionBuilder[OptionalDataRequest, AnyContent] = { authAndGetRegistration andThen - getData(period) + getData() } - def authAndRequireData(period: Period): ActionBuilder[DataRequest, AnyContent] = { - authAndGetOptionalData(period) andThen + def authAndRequireData(): ActionBuilder[DataRequest, AnyContent] = { + authAndGetOptionalData() andThen requireData } diff --git a/app/controllers/actions/DataRetrievalAction.scala b/app/controllers/actions/DataRetrievalAction.scala index 04084bab..39b73ca0 100644 --- a/app/controllers/actions/DataRetrievalAction.scala +++ b/app/controllers/actions/DataRetrievalAction.scala @@ -17,26 +17,25 @@ package controllers.actions import models.requests.{OptionalDataRequest, RegistrationRequest} -import models.Period import play.api.mvc.ActionTransformer import repositories.SessionRepository import javax.inject.Inject import scala.concurrent.{ExecutionContext, Future} -class DataRetrievalAction(period: Period, repository: SessionRepository) +class DataRetrievalAction(sessionRepository: SessionRepository) (implicit val executionContext: ExecutionContext) extends ActionTransformer[RegistrationRequest, OptionalDataRequest] { override protected def transform[A](request: RegistrationRequest[A]): Future[OptionalDataRequest[A]] = - repository.get(request.userId, period).map { x => - OptionalDataRequest(request.request, request.credentials, request.vrn, request.iossNumber, request.registrationWrapper, x) + sessionRepository.get(request.userId).map { maybeUserAnswers => + OptionalDataRequest(request.request, request.credentials, request.vrn, request.iossNumber, request.registrationWrapper, maybeUserAnswers) } } class DataRetrievalActionProvider @Inject()(repository: SessionRepository) (implicit ec: ExecutionContext) { - def apply(period: Period): DataRetrievalAction = - new DataRetrievalAction(period, repository) + def apply(): DataRetrievalAction = + new DataRetrievalAction(repository) } \ No newline at end of file diff --git a/app/controllers/actions/IdentifierAction.scala b/app/controllers/actions/IdentifierAction.scala index b5578d9a..313ce3eb 100644 --- a/app/controllers/actions/IdentifierAction.scala +++ b/app/controllers/actions/IdentifierAction.scala @@ -88,8 +88,13 @@ class IdentifierAction @Inject()( } } - private def getSuccessfulResponse[A](request: Request[A], credentials: Credentials, vrn: Vrn, iossNumber: String, groupId: String) - (implicit hc: HeaderCarrier): Future[Either[Result, IdentifierRequest[A]]] = { + private def getSuccessfulResponse[A]( + request: Request[A], + credentials: Credentials, + vrn: Vrn, + iossNumber: String, + groupId: String + ): Future[Either[Result, IdentifierRequest[A]]] = { val identifierRequest = IdentifierRequest(request, credentials, vrn, iossNumber) Right(identifierRequest).toFuture } @@ -101,7 +106,7 @@ class IdentifierAction @Inject()( iossNumber: String, groupId: String, confidence: ConfidenceLevel - )(implicit hc: HeaderCarrier): Future[Either[Result, IdentifierRequest[A]]] = { + ): Future[Either[Result, IdentifierRequest[A]]] = { if (confidence >= ConfidenceLevel.L200) { getSuccessfulResponse(request, credentials, vrn, iossNumber, groupId) } else { diff --git a/app/controllers/auth/AuthController.scala b/app/controllers/auth/AuthController.scala index 2f118c71..ef0eee6e 100644 --- a/app/controllers/auth/AuthController.scala +++ b/app/controllers/auth/AuthController.scala @@ -23,13 +23,12 @@ import play.api.mvc.{Action, AnyContent, MessagesControllerComponents} import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController import javax.inject.Inject -import scala.concurrent.ExecutionContext class AuthController @Inject()( cc: AuthenticatedControllerComponents, config: FrontendAppConfig, - )(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport { + ) extends FrontendBaseController with I18nSupport { protected val controllerComponents: MessagesControllerComponents = cc diff --git a/app/forms/SoldToCountryListFormProvider.scala b/app/forms/SoldToCountryListFormProvider.scala new file mode 100644 index 00000000..09e129af --- /dev/null +++ b/app/forms/SoldToCountryListFormProvider.scala @@ -0,0 +1,30 @@ +/* + * Copyright 2023 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 forms + +import javax.inject.Inject + +import forms.mappings.Mappings +import play.api.data.Form + +class SoldToCountryListFormProvider @Inject() extends Mappings { + + def apply(): Form[Boolean] = + Form( + "value" -> boolean("soldToCountryList.error.required") + ) +} diff --git a/app/models/Country.scala b/app/models/Country.scala index d6fa858a..158cbd7b 100644 --- a/app/models/Country.scala +++ b/app/models/Country.scala @@ -17,6 +17,8 @@ package models import play.api.libs.json.{Json, OFormat} +import uk.gov.hmrc.govukfrontend.views.viewmodels.select.SelectItem +import viewmodels.govuk.select._ case class Country(code: String, name: String) @@ -260,5 +262,17 @@ object Country { euCountries.take(positionOfNI) ++ Seq(northernIreland) ++ euCountries.drop(positionOfNI) } + val euCountrySelectItems: Seq[SelectItem] = + selectItems(euCountries) + + def selectItems(countries: Seq[Country]): Seq[SelectItem] = + SelectItem(value = None, text = "Select a country") +: + countries.map { + country => + SelectItemViewModel( + value = country.code, + text = country.name + ) + } } diff --git a/app/models/UserAnswers.scala b/app/models/UserAnswers.scala index e2041581..7f0ced92 100644 --- a/app/models/UserAnswers.scala +++ b/app/models/UserAnswers.scala @@ -17,7 +17,7 @@ package models import play.api.libs.json._ -import queries.{Gettable, Settable} +import queries.{Derivable, Gettable, Settable} import uk.gov.hmrc.mongo.play.json.formats.MongoJavatimeFormats import java.time.Instant @@ -33,6 +33,13 @@ final case class UserAnswers( def get[A](page: Gettable[A])(implicit rds: Reads[A]): Option[A] = Reads.optionNoError(Reads.at(page.path)).reads(data).getOrElse(None) + def get[A, B](derivable: Derivable[A, B])(implicit rds: Reads[A]): Option[B] = { + Reads.optionNoError(Reads.at(derivable.path)) + .reads(data) + .getOrElse(None) + .map(derivable.derive) + } + def set[A](page: Settable[A], value: A)(implicit writes: Writes[A]): Try[UserAnswers] = { val updatedData = data.setObject(page.path, Json.toJson(value)) match { diff --git a/app/pages/NoOtherPeriodsAvailablePage.scala b/app/pages/NoOtherPeriodsAvailablePage.scala new file mode 100644 index 00000000..79aa7605 --- /dev/null +++ b/app/pages/NoOtherPeriodsAvailablePage.scala @@ -0,0 +1,25 @@ +/* + * Copyright 2023 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 pages + +import controllers.routes +import play.api.mvc.Call + +case object NoOtherPeriodsAvailablePage extends Page { + override def route(waypoints: Waypoints): Call = + routes.NoOtherPeriodsAvailableController.onPageLoad(waypoints) +} diff --git a/app/pages/SalesToCountryPage.scala b/app/pages/SalesToCountryPage.scala index 882b1e52..763ee07b 100644 --- a/app/pages/SalesToCountryPage.scala +++ b/app/pages/SalesToCountryPage.scala @@ -17,18 +17,18 @@ package pages import controllers.routes -import models.{Index, Period, UserAnswers} +import models.{Index, UserAnswers} import play.api.libs.json.JsPath import play.api.mvc.Call -case class SalesToCountryPage(period: Period, index: Index) extends QuestionPage[Int] { +case class SalesToCountryPage(index: Index) extends QuestionPage[Int] { - override def path: JsPath = JsPath \ toString + override def path: JsPath = JsPath \ PageConstants.sales \ index.position \ toString override def toString: String = "salesToCountry" - override def route(waypoints: Waypoints): Call = routes.SalesToCountryController.onPageLoad(waypoints, period, index) + override def route(waypoints: Waypoints): Call = routes.SalesToCountryController.onPageLoad(waypoints, index) override protected def nextPageNormalMode(waypoints: Waypoints, answers: UserAnswers): Page = - VatOnSalesPage(period, index) + VatOnSalesPage(index) } diff --git a/app/pages/SoldGoodsPage.scala b/app/pages/SoldGoodsPage.scala index f5320589..2bb78dd2 100644 --- a/app/pages/SoldGoodsPage.scala +++ b/app/pages/SoldGoodsPage.scala @@ -17,18 +17,18 @@ package pages import controllers.routes -import models.{Index, Period, UserAnswers} +import models.{Index, UserAnswers} import play.api.libs.json.JsPath import play.api.mvc.Call -case class SoldGoodsPage(period: Period) extends QuestionPage[Boolean] { +case object SoldGoodsPage extends QuestionPage[Boolean] { override def path: JsPath = JsPath \ toString override def toString: String = "soldGoods" - override def route(waypoints: Waypoints): Call = routes.SoldGoodsController.onPageLoad(waypoints, period) + override def route(waypoints: Waypoints): Call = routes.SoldGoodsController.onPageLoad(waypoints) override protected def nextPageNormalMode(waypoints: Waypoints, answers: UserAnswers): Page = - SoldToCountryPage(period, Index(0)) // TODO should it always be Index(0)? + SoldToCountryPage(Index(0)) // TODO should it always be Index(0)? } diff --git a/app/pages/SoldToCountryListPage.scala b/app/pages/SoldToCountryListPage.scala new file mode 100644 index 00000000..fb49e7e9 --- /dev/null +++ b/app/pages/SoldToCountryListPage.scala @@ -0,0 +1,71 @@ +/* + * Copyright 2023 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 pages + +import controllers.routes +import models.{Country, Index, UserAnswers} +import play.api.libs.json.{JsObject, JsPath} +import play.api.mvc.Call +import queries.{Derivable, DeriveNumberOfSales} + +object SoldToCountryListPage { + + val normalModeUrlFragment: String = "add-sales-country-list" + val checkModeUrlFragment: String = "change-add-sales-country-list" +} + +final case class SoldToCountryListPage(override val index: Option[Index] = None) extends AddItemPage(index) with QuestionPage[Boolean] { + + override def isTheSamePage(other: Page): Boolean = other match { + case _: SoldToCountryListPage => true + case _ => false + } + + override val normalModeUrlFragment: String = SoldToCountryListPage.normalModeUrlFragment + override val checkModeUrlFragment: String = SoldToCountryListPage.checkModeUrlFragment + + override def path: JsPath = JsPath \ toString + + override def toString: String = "soldToCountryList" + + override def route(waypoints: Waypoints): Call = + routes.SoldToCountryListController.onPageLoad(waypoints) + + override protected def nextPageNormalMode(waypoints: Waypoints, answers: UserAnswers): Page = + answers.get(this).map { + case true => + index + .map { i => + if (i.position +1 < Country.euCountriesWithNI.size) { + SoldToCountryPage(Index(i.position + 1)) + } else { + CheckYourAnswersPage + } + } + .getOrElse { + answers + .get(deriveNumberOfItems) + .map(n => SoldToCountryPage(Index(n))) + .orRecover + } + case false => + CheckYourAnswersPage + }.orRecover + + + override def deriveNumberOfItems: Derivable[Seq[JsObject], Int] = DeriveNumberOfSales +} diff --git a/app/pages/SoldToCountryPage.scala b/app/pages/SoldToCountryPage.scala index 8ca29924..a9129942 100644 --- a/app/pages/SoldToCountryPage.scala +++ b/app/pages/SoldToCountryPage.scala @@ -17,18 +17,18 @@ package pages import controllers.routes -import models.{Country, Index, Period, UserAnswers} +import models.{Country, Index, UserAnswers} import play.api.libs.json.JsPath import play.api.mvc.Call -case class SoldToCountryPage(period: Period, index: Index) extends QuestionPage[Country] { +case class SoldToCountryPage(index: Index) extends QuestionPage[Country] { - override def path: JsPath = JsPath \ toString + override def path: JsPath = JsPath \ PageConstants.sales \ index.position \ toString - override def toString: String = "soldToCountry" + override def toString: String = "country" - override def route(waypoints: Waypoints): Call = routes.SoldToCountryController.onPageLoad(waypoints, period, index) + override def route(waypoints: Waypoints): Call = routes.SoldToCountryController.onPageLoad(waypoints, index) override protected def nextPageNormalMode(waypoints: Waypoints, answers: UserAnswers): Page = - VatRatesFromCountryPage(period, index) + VatRatesFromCountryPage(index) } diff --git a/app/pages/StartReturnPage.scala b/app/pages/StartReturnPage.scala index b3a71b6d..715bed49 100644 --- a/app/pages/StartReturnPage.scala +++ b/app/pages/StartReturnPage.scala @@ -30,5 +30,10 @@ case class StartReturnPage(period: Period) extends QuestionPage[Boolean] { override def route(waypoints: Waypoints): Call = routes.StartReturnController.onPageLoad(waypoints, period) override protected def nextPageNormalMode(waypoints: Waypoints, answers: UserAnswers): Page = - SoldGoodsPage(period) + answers.get(this).map { + case true => + SoldGoodsPage + case false => + NoOtherPeriodsAvailablePage + }.orRecover } diff --git a/app/pages/VatOnSalesPage.scala b/app/pages/VatOnSalesPage.scala index af04c1ab..d2bba9dc 100644 --- a/app/pages/VatOnSalesPage.scala +++ b/app/pages/VatOnSalesPage.scala @@ -17,18 +17,18 @@ package pages import controllers.routes -import models.{Index, Period, UserAnswers, VatOnSales} +import models.{Index, UserAnswers, VatOnSales} import play.api.libs.json.JsPath import play.api.mvc.Call -case class VatOnSalesPage(period: Period, index: Index) extends QuestionPage[VatOnSales] { +case class VatOnSalesPage(index: Index) extends QuestionPage[VatOnSales] { - override def path: JsPath = JsPath \ toString + override def path: JsPath = JsPath \ PageConstants.sales \ index.position \ toString override def toString: String = "vatOnSales" - override def route(waypoints: Waypoints): Call = routes.VatOnSalesController.onPageLoad(waypoints, period, index) + override def route(waypoints: Waypoints): Call = routes.VatOnSalesController.onPageLoad(waypoints, index) override protected def nextPageNormalMode(waypoints: Waypoints, answers: UserAnswers): Page = - CheckYourAnswersPage + SoldToCountryListPage(Some(index)) } diff --git a/app/pages/VatRatesFromCountryPage.scala b/app/pages/VatRatesFromCountryPage.scala index f40d4717..3b2561d0 100644 --- a/app/pages/VatRatesFromCountryPage.scala +++ b/app/pages/VatRatesFromCountryPage.scala @@ -17,18 +17,18 @@ package pages import controllers.routes -import models.{Index, Period, UserAnswers, VatRatesFromCountry} +import models.{Index, UserAnswers, VatRatesFromCountry} import play.api.libs.json.JsPath import play.api.mvc.Call -case class VatRatesFromCountryPage(period: Period, index: Index) extends QuestionPage[Set[VatRatesFromCountry]] { +case class VatRatesFromCountryPage(index: Index) extends QuestionPage[Set[VatRatesFromCountry]] { - override def path: JsPath = JsPath \ toString + override def path: JsPath = JsPath \ PageConstants.sales \ index.position \ toString override def toString: String = "vatRatesFromCountry" - override def route(waypoints: Waypoints): Call = routes.VatRatesFromCountryController.onPageLoad(waypoints, period, index) + override def route(waypoints: Waypoints): Call = routes.VatRatesFromCountryController.onPageLoad(waypoints, index) override protected def nextPageNormalMode(waypoints: Waypoints, answers: UserAnswers): Page = - SalesToCountryPage(period, index) + SalesToCountryPage(index) } diff --git a/app/pages/Waypoint.scala b/app/pages/Waypoint.scala index 203ef966..a1112025 100644 --- a/app/pages/Waypoint.scala +++ b/app/pages/Waypoint.scala @@ -16,7 +16,7 @@ package pages -import models.Mode +import models.{CheckMode, Mode, NormalMode} case class Waypoint( page: WaypointPage, @@ -28,6 +28,8 @@ object Waypoint { private val fragments: Map[String, Waypoint] = Map( + SoldToCountryListPage.normalModeUrlFragment -> SoldToCountryListPage().waypoint(NormalMode), + SoldToCountryListPage.checkModeUrlFragment -> SoldToCountryListPage().waypoint(CheckMode), CheckYourAnswersPage.urlFragment -> CheckYourAnswersPage.waypoint ) diff --git a/app/pages/YourAccountPage.scala b/app/pages/YourAccountPage.scala new file mode 100644 index 00000000..50838b8b --- /dev/null +++ b/app/pages/YourAccountPage.scala @@ -0,0 +1,25 @@ +/* + * Copyright 2023 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 pages + +import controllers.routes +import play.api.mvc.Call + +object YourAccountPage extends Page { + override def route(waypoints: Waypoints): Call = + routes.YourAccountController.onPageLoad(waypoints) +} diff --git a/app/pages/package.scala b/app/pages/package.scala new file mode 100644 index 00000000..fcccd40b --- /dev/null +++ b/app/pages/package.scala @@ -0,0 +1,24 @@ +/* + * Copyright 2023 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 object pages { + + implicit class RecoveryOps(val a: Option[Page]) { + + def orRecover: Page = + a.getOrElse(JourneyRecoveryPage) + } +} diff --git a/app/queries/DeriveNumberOfSales.scala b/app/queries/DeriveNumberOfSales.scala new file mode 100644 index 00000000..40c41cc1 --- /dev/null +++ b/app/queries/DeriveNumberOfSales.scala @@ -0,0 +1,27 @@ +/* + * Copyright 2023 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 queries + +import pages.PageConstants +import play.api.libs.json.{JsObject, JsPath} + +case object DeriveNumberOfSales extends Derivable[Seq[JsObject], Int] { + + override val derive: Seq[JsObject] => Int = _.size + + override def path: JsPath = JsPath \ PageConstants.sales +} diff --git a/app/repositories/SessionRepository.scala b/app/repositories/SessionRepository.scala index aca5904d..e8b2a8e6 100644 --- a/app/repositories/SessionRepository.scala +++ b/app/repositories/SessionRepository.scala @@ -17,12 +17,11 @@ package repositories import config.FrontendAppConfig -import models.{Period, UserAnswers} +import models.UserAnswers import org.mongodb.scala.bson.conversions.Bson import org.mongodb.scala.model._ import play.api.libs.json.Format import uk.gov.hmrc.mongo.MongoComponent -import uk.gov.hmrc.mongo.play.json.Codecs.JsonOps import uk.gov.hmrc.mongo.play.json.PlayMongoRepository import uk.gov.hmrc.mongo.play.json.formats.MongoJavatimeFormats @@ -55,13 +54,6 @@ class SessionRepository @Inject()( private def byId(id: String): Bson = Filters.equal("_id", id) - private def byUserIdAndPeriod(userId: String, period: Period): Bson = { - Filters.and( - Filters.equal("_id", userId), - Filters.equal("period", period.toBson(legacyNumbers = false)) - ) - } - def keepAlive(id: String): Future[Boolean] = collection .updateOne( @@ -79,14 +71,6 @@ class SessionRepository @Inject()( .headOption() } - def get(userId: String, period: Period): Future[Option[UserAnswers]] = - keepAlive(userId).flatMap { - _ => - collection - .find(byUserIdAndPeriod(userId, period)) - .headOption() - } - def set(answers: UserAnswers): Future[Boolean] = { val updatedAnswers = answers copy (lastUpdated = Instant.now(clock)) diff --git a/app/utils/ItemsHelper.scala b/app/utils/ItemsHelper.scala new file mode 100644 index 00000000..3e19ce4d --- /dev/null +++ b/app/utils/ItemsHelper.scala @@ -0,0 +1,59 @@ +/* + * Copyright 2023 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 utils + +import controllers.actions.AuthenticatedControllerComponents +import models.requests.DataRequest +import pages.{JourneyRecoveryPage, QuestionPage, Waypoints} +import play.api.libs.json.JsObject +import play.api.mvc.{AnyContent, Result} +import play.api.mvc.Results.Redirect +import queries.{Derivable, Settable} +import utils.FutureSyntax.FutureOps + +import scala.concurrent.{ExecutionContext, Future} + +object ItemsHelper { + + def getDerivedItems(waypoints: Waypoints, derivable: Derivable[Seq[JsObject], Int])(block: Int => Future[Result]) + (implicit request: DataRequest[AnyContent]): Future[Result] = { + request.userAnswers.get(derivable).map { + number => + block(number) + }.getOrElse(Redirect(JourneyRecoveryPage.route(waypoints).url).toFuture) + } + + def determineRemoveAllItemsAndRedirect[A]( + waypoints: Waypoints, + doRemoveItems: Boolean, + cc: AuthenticatedControllerComponents, + query: Settable[A], + hasItems: QuestionPage[Boolean], + deleteAllItemsPage: QuestionPage[Boolean] + )(implicit ec: ExecutionContext, request: DataRequest[AnyContent]): Future[Result] = { + val removeItems = if (doRemoveItems) { + request.userAnswers.remove(query) + } else { + request.userAnswers.set(hasItems, true) + } + for { + updatedAnswers <- Future.fromTry(removeItems) + calculatedAnswers <- Future.fromTry(updatedAnswers.set(deleteAllItemsPage, doRemoveItems)) + _ <- cc.sessionRepository.set(calculatedAnswers) + } yield Redirect(deleteAllItemsPage.navigate(waypoints, request.userAnswers, calculatedAnswers).route) + } +} diff --git a/app/viewmodels/ListItemWrapper.scala b/app/viewmodels/ListItemWrapper.scala new file mode 100644 index 00000000..2ab0c36a --- /dev/null +++ b/app/viewmodels/ListItemWrapper.scala @@ -0,0 +1,21 @@ +/* + * Copyright 2023 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 + +import uk.gov.hmrc.hmrcfrontend.views.viewmodels.addtoalist.ListItem + +case class ListItemWrapper(listItem: ListItem, removeButtonEnabled: Boolean) diff --git a/app/viewmodels/checkAnswers/SalesToCountrySummary.scala b/app/viewmodels/checkAnswers/SalesToCountrySummary.scala index c6bdf395..10ac2cfd 100644 --- a/app/viewmodels/checkAnswers/SalesToCountrySummary.scala +++ b/app/viewmodels/checkAnswers/SalesToCountrySummary.scala @@ -17,7 +17,7 @@ package viewmodels.checkAnswers import controllers.routes -import models.{Index, Period, UserAnswers} +import models.{Index, UserAnswers} import pages.{SalesToCountryPage, Waypoints} import play.api.i18n.Messages import uk.gov.hmrc.govukfrontend.views.viewmodels.summarylist.SummaryListRow @@ -26,15 +26,15 @@ import viewmodels.implicits._ object SalesToCountrySummary { - def row(answers: UserAnswers, waypoints: Waypoints, period: Period, index: Index)(implicit messages: Messages): Option[SummaryListRow] = - answers.get(SalesToCountryPage(period, index)).map { + def row(answers: UserAnswers, waypoints: Waypoints, index: Index)(implicit messages: Messages): Option[SummaryListRow] = + answers.get(SalesToCountryPage(index)).map { answer => SummaryListRowViewModel( key = "salesToCountry.checkYourAnswersLabel", value = ValueViewModel(answer.toString), actions = Seq( - ActionItemViewModel("site.change", routes.SalesToCountryController.onPageLoad(waypoints, period, index).url) + ActionItemViewModel("site.change", routes.SalesToCountryController.onPageLoad(waypoints, index).url) .withVisuallyHiddenText(messages("salesToCountry.change.hidden")) ) ) diff --git a/app/viewmodels/checkAnswers/SoldGoodsSummary.scala b/app/viewmodels/checkAnswers/SoldGoodsSummary.scala index 65e9872a..70a45116 100644 --- a/app/viewmodels/checkAnswers/SoldGoodsSummary.scala +++ b/app/viewmodels/checkAnswers/SoldGoodsSummary.scala @@ -17,7 +17,7 @@ package viewmodels.checkAnswers import controllers.routes -import models.{Period, UserAnswers} +import models.UserAnswers import pages.{SoldGoodsPage, Waypoints} import play.api.i18n.Messages import uk.gov.hmrc.govukfrontend.views.viewmodels.summarylist.SummaryListRow @@ -26,8 +26,8 @@ import viewmodels.implicits._ object SoldGoodsSummary { - def row(answers: UserAnswers, waypoints: Waypoints, period: Period)(implicit messages: Messages): Option[SummaryListRow] = - answers.get(SoldGoodsPage(period)).map { + def row(answers: UserAnswers, waypoints: Waypoints)(implicit messages: Messages): Option[SummaryListRow] = + answers.get(SoldGoodsPage).map { answer => val value = if (answer) "site.yes" else "site.no" @@ -36,7 +36,7 @@ object SoldGoodsSummary { key = "soldGoods.checkYourAnswersLabel", value = ValueViewModel(value), actions = Seq( - ActionItemViewModel("site.change", routes.SoldGoodsController.onPageLoad(waypoints, period).url) + ActionItemViewModel("site.change", routes.SoldGoodsController.onPageLoad(waypoints).url) .withVisuallyHiddenText(messages("soldGoods.change.hidden")) ) ) diff --git a/app/viewmodels/checkAnswers/SoldToCountryListSummary.scala b/app/viewmodels/checkAnswers/SoldToCountryListSummary.scala new file mode 100644 index 00000000..5e540dcc --- /dev/null +++ b/app/viewmodels/checkAnswers/SoldToCountryListSummary.scala @@ -0,0 +1,38 @@ +/* + * Copyright 2023 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 models.UserAnswers +import pages.{AddItemPage, JourneyRecoveryPage, Waypoints} +import play.twirl.api.HtmlFormat +import queries.AllSalesQuery +import uk.gov.hmrc.hmrcfrontend.views.viewmodels.addtoalist.ListItem + +object SoldToCountryListSummary { + + def addToListRows(answers: UserAnswers, waypoints: Waypoints, sourcePage: AddItemPage): Seq[ListItem] = { + answers.get(AllSalesQuery).getOrElse(List.empty).zipWithIndex.map { + case (salesToCountryWithOptionalVat, countryIndex) => + + ListItem( + name = HtmlFormat.escape(salesToCountryWithOptionalVat.country.name).toString, + changeUrl = JourneyRecoveryPage.changeLink(waypoints, sourcePage).url, // TODO -> to Mini CYA when created + removeUrl = JourneyRecoveryPage.changeLink(waypoints, sourcePage).url // TODO -> to DeleteSoldToCountrySalesPage when created + ) + } + } +} diff --git a/app/viewmodels/checkAnswers/SoldToCountrySummary.scala b/app/viewmodels/checkAnswers/SoldToCountrySummary.scala index 3586c0a5..566756ce 100644 --- a/app/viewmodels/checkAnswers/SoldToCountrySummary.scala +++ b/app/viewmodels/checkAnswers/SoldToCountrySummary.scala @@ -17,7 +17,7 @@ package viewmodels.checkAnswers import controllers.routes -import models.{Index, Period, UserAnswers} +import models.{Index, UserAnswers} import pages.{SoldToCountryPage, Waypoints} import play.api.i18n.Messages import play.twirl.api.HtmlFormat @@ -27,15 +27,15 @@ import viewmodels.implicits._ object SoldToCountrySummary { - def row(answers: UserAnswers, waypoints: Waypoints, period: Period, index: Index)(implicit messages: Messages): Option[SummaryListRow] = - answers.get(SoldToCountryPage(period, index)).map { + def row(answers: UserAnswers, waypoints: Waypoints, index: Index)(implicit messages: Messages): Option[SummaryListRow] = + answers.get(SoldToCountryPage(index)).map { answer => SummaryListRowViewModel( key = "soldToCountry.checkYourAnswersLabel", value = ValueViewModel(HtmlFormat.escape(answer.name).toString), actions = Seq( - ActionItemViewModel("site.change", routes.SoldToCountryController.onPageLoad(waypoints, period, index).url) + ActionItemViewModel("site.change", routes.SoldToCountryController.onPageLoad(waypoints, index).url) .withVisuallyHiddenText(messages("soldToCountry.change.hidden")) ) ) diff --git a/app/viewmodels/checkAnswers/VatOnSalesSummary.scala b/app/viewmodels/checkAnswers/VatOnSalesSummary.scala index ce96884b..1378e8a6 100644 --- a/app/viewmodels/checkAnswers/VatOnSalesSummary.scala +++ b/app/viewmodels/checkAnswers/VatOnSalesSummary.scala @@ -17,7 +17,7 @@ package viewmodels.checkAnswers import controllers.routes -import models.{Index, Period, UserAnswers} +import models.{Index, UserAnswers} import pages.{VatOnSalesPage, Waypoints} import play.api.i18n.Messages import play.twirl.api.HtmlFormat @@ -28,8 +28,8 @@ import viewmodels.implicits._ object VatOnSalesSummary { - def row(answers: UserAnswers, waypoints: Waypoints, period: Period, index: Index)(implicit messages: Messages): Option[SummaryListRow] = - answers.get(VatOnSalesPage(period, index)).map { + def row(answers: UserAnswers, waypoints: Waypoints, index: Index)(implicit messages: Messages): Option[SummaryListRow] = + answers.get(VatOnSalesPage(index)).map { answer => val value = ValueViewModel( @@ -42,7 +42,7 @@ object VatOnSalesSummary { key = "vatOnSales.checkYourAnswersLabel", value = value, actions = Seq( - ActionItemViewModel("site.change", routes.VatOnSalesController.onPageLoad(waypoints, period, index).url) + ActionItemViewModel("site.change", routes.VatOnSalesController.onPageLoad(waypoints, index).url) .withVisuallyHiddenText(messages("vatOnSales.change.hidden")) ) ) diff --git a/app/viewmodels/checkAnswers/VatRatesFromCountrySummary.scala b/app/viewmodels/checkAnswers/VatRatesFromCountrySummary.scala index 29f195f6..11da626d 100644 --- a/app/viewmodels/checkAnswers/VatRatesFromCountrySummary.scala +++ b/app/viewmodels/checkAnswers/VatRatesFromCountrySummary.scala @@ -17,7 +17,7 @@ package viewmodels.checkAnswers import controllers.routes -import models.{Index, Period, UserAnswers} +import models.{Index, UserAnswers} import pages.{VatRatesFromCountryPage, Waypoints} import play.api.i18n.Messages import play.twirl.api.HtmlFormat @@ -28,8 +28,8 @@ import viewmodels.implicits._ object VatRatesFromCountrySummary { - def row(answers: UserAnswers, waypoints: Waypoints, period: Period, index: Index)(implicit messages: Messages): Option[SummaryListRow] = - answers.get(VatRatesFromCountryPage(period, index)).map { + def row(answers: UserAnswers, waypoints: Waypoints, index: Index)(implicit messages: Messages): Option[SummaryListRow] = + answers.get(VatRatesFromCountryPage(index)).map { answers => val value = ValueViewModel( @@ -45,7 +45,7 @@ object VatRatesFromCountrySummary { key = "vatRatesFromCountry.checkYourAnswersLabel", value = value, actions = Seq( - ActionItemViewModel("site.change", routes.VatRatesFromCountryController.onPageLoad(waypoints, period, index).url) + ActionItemViewModel("site.change", routes.VatRatesFromCountryController.onPageLoad(waypoints, index).url) .withVisuallyHiddenText(messages("vatRatesFromCountry.change.hidden")) ) ) diff --git a/app/viewmodels/govuk/CheckboxFluency.scala b/app/viewmodels/govuk/CheckboxFluency.scala index ce8d5b4d..be1ffa8d 100644 --- a/app/viewmodels/govuk/CheckboxFluency.scala +++ b/app/viewmodels/govuk/CheckboxFluency.scala @@ -66,6 +66,9 @@ trait CheckboxFluency { def describedBy(value: String): Checkboxes = checkboxes.copy(describedBy = Some(value)) + + def withHint(hint: Hint): Checkboxes = + checkboxes copy (hint = Some(hint)) } object CheckboxItemViewModel { diff --git a/app/viewmodels/govuk/FieldsetFluency.scala b/app/viewmodels/govuk/FieldsetFluency.scala index d04273af..73205553 100644 --- a/app/viewmodels/govuk/FieldsetFluency.scala +++ b/app/viewmodels/govuk/FieldsetFluency.scala @@ -57,12 +57,15 @@ trait FieldsetFluency { implicit class FluentLegend(legend: Legend) { - def asPageHeading(size: LegendSize = LegendSize.ExtraLarge): Legend = + def asPageHeading(size: LegendSize = LegendSize.Large): Legend = legend .copy(isPageHeading = true) .withCssClass(size.toString) def withCssClass(newClass: String): Legend = legend.copy(classes = s"${legend.classes} $newClass") + + def withSize(size: LegendSize): Legend = + legend.withCssClass(size.toString) } } diff --git a/app/viewmodels/govuk/SelectFluency.scala b/app/viewmodels/govuk/SelectFluency.scala new file mode 100644 index 00000000..43650171 --- /dev/null +++ b/app/viewmodels/govuk/SelectFluency.scala @@ -0,0 +1,81 @@ +/* + * Copyright 2023 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.govuk + +import play.api.data.Field +import play.api.i18n.Messages +import uk.gov.hmrc.govukfrontend.views.viewmodels.hint.Hint +import uk.gov.hmrc.govukfrontend.views.viewmodels.label.Label +import uk.gov.hmrc.govukfrontend.views.viewmodels.select.{Select, SelectItem} +import viewmodels.ErrorMessageAwareness + +object select extends SelectFluency + +trait SelectFluency { + + object SelectViewModel extends ErrorMessageAwareness { + + def apply( + field: Field, + items: Seq[SelectItem], + label: Label + )(implicit messages: Messages): Select = + Select( + id = field.id, + name = field.name, + items = items map (item => item copy (selected = field.value.isDefined && field.value == item.value)), + label = label, + errorMessage = errorMessage(field) + ) + } + + implicit class FluentSelect(select: Select) { + + def withHint(hint: Hint): Select = + select copy (hint = Some(hint)) + + def describedBy(value: String): Select = + select copy (describedBy = Some(value)) + + def withFormGroupClasses(classes: String): Select = + select copy (formGroupClasses = classes) + + def withCssClass(newClass: String): Select = + select copy (classes = s"${select.classes} $newClass") + + def withAttribute(attribute: (String, String)): Select = + select copy (attributes = select.attributes + attribute) + } + + object SelectItemViewModel { + + def apply( + value: String, + text: String + ): SelectItem = + SelectItem(value = Some(value), text = text) + } + + implicit class FluentSelectItem(item: SelectItem) { + + def disabled: SelectItem = + item copy (disabled = true) + + def withAttribute(attribute: (String, String)): SelectItem = + item copy (attributes = item.attributes + attribute) + } +} diff --git a/app/viewmodels/govuk/package.scala b/app/viewmodels/govuk/package.scala index 6e87720b..afc238fa 100644 --- a/app/viewmodels/govuk/package.scala +++ b/app/viewmodels/govuk/package.scala @@ -30,6 +30,7 @@ package object govuk { with InputFluency with LabelFluency with RadiosFluency + with SelectFluency with SummaryListFluency with TagFluency } diff --git a/app/views/NoOtherPeriodsAvailableView.scala.html b/app/views/NoOtherPeriodsAvailableView.scala.html new file mode 100644 index 00000000..71ef8f38 --- /dev/null +++ b/app/views/NoOtherPeriodsAvailableView.scala.html @@ -0,0 +1,33 @@ +@* + * Copyright 2023 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. + *@ + +@import pages.YourAccountPage + +@this( + layout: templates.Layout, + govukButton: GovukButton +) + +@(waypoints: Waypoints)(implicit request: Request[_], messages: Messages) + + @layout(pageTitle = titleNoForm(messages("noOtherPeriodsAvailable.title"))) { + +

@messages("noOtherPeriodsAvailable.heading")

+ +

@messages("noOtherPeriodsAvailable.p1")

+ +

@Html(messages("noOtherPeriodsAvailable.backToYourAccount", YourAccountPage.route(waypoints).url))

+ } diff --git a/app/views/SalesToCountryView.scala.html b/app/views/SalesToCountryView.scala.html index eef96673..808b9059 100644 --- a/app/views/SalesToCountryView.scala.html +++ b/app/views/SalesToCountryView.scala.html @@ -28,7 +28,7 @@ @layout(pageTitle = title(form, messages("salesToCountry.title"))) { - @formHelper(action = routes.SalesToCountryController.onSubmit(waypoints, period, index), Symbol("autoComplete") -> "off") { + @formHelper(action = routes.SalesToCountryController.onSubmit(waypoints, index), Symbol("autoComplete") -> "off") { @if(form.errors.nonEmpty) { @govukErrorSummary(ErrorSummaryViewModel(form)) diff --git a/app/views/SoldGoodsView.scala.html b/app/views/SoldGoodsView.scala.html index 763b27de..343c96db 100644 --- a/app/views/SoldGoodsView.scala.html +++ b/app/views/SoldGoodsView.scala.html @@ -14,30 +14,35 @@ * limitations under the License. *@ +@import viewmodels.LegendSize + @this( - layout: templates.Layout, - formHelper: FormWithCSRF, - govukErrorSummary: GovukErrorSummary, - govukRadios: GovukRadios, - button: ButtonGroup + layout: templates.Layout, + formHelper: FormWithCSRF, + govukErrorSummary: GovukErrorSummary, + govukRadios: GovukRadios, + button: ButtonGroup ) @(form: Form[_], waypoints: Waypoints, period: Period)(implicit request: Request[_], messages: Messages) -@layout(pageTitle = title(form, messages("soldGoods.title", period.displayText))) { + @layout(pageTitle = title(form, messages("soldGoods.title", period.displayText))) { - @formHelper(action = routes.SoldGoodsController.onSubmit(waypoints, period), Symbol("autoComplete") -> "off") { + @formHelper(action = routes.SoldGoodsController.onSubmit(waypoints), Symbol("autoComplete") -> "off") { - @if(form.errors.nonEmpty) { - @govukErrorSummary(ErrorSummaryViewModel(form)) - } + @if(form.errors.nonEmpty) { + @govukErrorSummary(ErrorSummaryViewModel(form)) + } - @govukRadios( - RadiosViewModel.yesNo( - field = form("value"), - legend = LegendViewModel(messages("soldGoods.heading", period.displayText)).asPageHeading() + @govukRadios( + RadiosViewModel.yesNo( + field = form("value"), + legend = HmrcPageHeadingLegend( + content = messages("soldGoods.heading", period.displayText), + caption = period.displayText + ).asPageHeading(size = LegendSize.Large) + ) ) - ) @button( messages("site.continue") diff --git a/app/views/SoldToCountryListView.scala.html b/app/views/SoldToCountryListView.scala.html new file mode 100644 index 00000000..4052240d --- /dev/null +++ b/app/views/SoldToCountryListView.scala.html @@ -0,0 +1,75 @@ +@* + * Copyright 2023 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. + *@ + +@import uk.gov.hmrc.hmrcfrontend.views.viewmodels.addtoalist.{ListItem, Long} +@import viewmodels.LegendSize + +@this( + layout: templates.Layout, + formHelper: FormWithCSRF, + govukErrorSummary: GovukErrorSummary, + govukRadios: GovukRadios, + addToList: components.addToList, + button: ButtonGroup +) + +@(form: Form[_], waypoints: Waypoints, period: Period, salesList: Seq[ListItem], canAddSales: Boolean)(implicit request: Request[_], messages: Messages) + +@titleText = @{ + salesList.size match { + case 1 => messages("soldToCountryList.title.singular") + case i => messages("soldToCountryList.title.plural", i) + } +} + +@headingText = @{ + salesList.size match { + case 1 => messages("soldToCountryList.heading.singular") + case i => messages("soldToCountryList.heading.plural", i) + } +} + +@layout(pageTitle = title(form, titleText)) { + + @formHelper(action = routes.SoldToCountryListController.onSubmit(waypoints), Symbol("autoComplete") -> "off") { + + @if(form.errors.nonEmpty) { + @govukErrorSummary(ErrorSummaryViewModel(form)) + } + +

@headingText

+ + @addToList(salesList, itemSize = Long, "soldToCountryList.change.hidden", "soldToCountryList.remove.hidden") + + @if(canAddSales) { + @govukRadios( + RadiosViewModel.yesNo( + field = form("value"), + legend = LegendViewModel( + HtmlContent(Html("""

""" + messages("soldToCountryList.addAnother") + "

")) + ).withSize(LegendSize.Medium) + ).withHint(HintViewModel(messages("soldToCountryList.addAnother.hint"))) + ) + } else { +

@messages("soldToCountryList.maximumReached")

+ + } + + @button( + messages("site.continue") + ) + } +} diff --git a/app/views/SoldToCountryView.scala.html b/app/views/SoldToCountryView.scala.html index b45b103b..d843870e 100644 --- a/app/views/SoldToCountryView.scala.html +++ b/app/views/SoldToCountryView.scala.html @@ -14,32 +14,39 @@ * limitations under the License. *@ -@import viewmodels.InputWidth._ +@import viewmodels.InputWidth.Fixed20 +@import viewmodels.LabelSize +@import views.html.components.ButtonGroup @this( - layout: templates.Layout, - formHelper: FormWithCSRF, - govukErrorSummary: GovukErrorSummary, - govukInput: GovukInput, - button: ButtonGroup + layout: templates.Layout, + formHelper: FormWithCSRF, + govukErrorSummary: GovukErrorSummary, + govukSelect: GovukSelect, + button: ButtonGroup ) @(form: Form[_], waypoints: Waypoints, period: Period, index: Index)(implicit request: Request[_], messages: Messages) @layout(pageTitle = title(form, messages("soldToCountry.title"))) { - @formHelper(action = routes.SoldToCountryController.onSubmit(waypoints, period, index)) { + @formHelper(action = routes.SoldToCountryController.onSubmit(waypoints, index)) { @if(form.errors.nonEmpty) { @govukErrorSummary(ErrorSummaryViewModel(form)) } - @govukInput( - InputViewModel( + @govukSelect( + SelectViewModel( field = form("value"), - label = LabelViewModel(messages("soldToCountry.heading")).asPageHeading() + items = Country.euCountrySelectItems, + label = HmrcPageHeadingLabel( + content = messages("soldToCountry.heading"), + caption = period.displayText + ).asPageHeading(size = LabelSize.Large) ) - .withWidth(Full) + .withCssClass(Fixed20.toString) + .withCssClass("autocomplete") ) @button( diff --git a/app/views/VatOnSalesView.scala.html b/app/views/VatOnSalesView.scala.html index d8477d0b..b646f352 100644 --- a/app/views/VatOnSalesView.scala.html +++ b/app/views/VatOnSalesView.scala.html @@ -26,7 +26,7 @@ @layout(pageTitle = title(form, messages("vatOnSales.title"))) { - @formHelper(action = routes.VatOnSalesController.onSubmit(waypoints, period, index), Symbol("autoComplete") -> "off") { + @formHelper(action = routes.VatOnSalesController.onSubmit(waypoints, index), Symbol("autoComplete") -> "off") { @if(form.errors.nonEmpty) { @govukErrorSummary(ErrorSummaryViewModel(form, errorLinkOverrides = Map("value" -> "value_0"))) diff --git a/app/views/VatRatesFromCountryView.scala.html b/app/views/VatRatesFromCountryView.scala.html index 60a67da1..43b7acee 100644 --- a/app/views/VatRatesFromCountryView.scala.html +++ b/app/views/VatRatesFromCountryView.scala.html @@ -22,11 +22,11 @@ button: ButtonGroup ) -@(form: Form[_], waypoints: Waypoints, period: Period, index: Index)(implicit request: Request[_], messages: Messages) +@(form: Form[_], waypoints: Waypoints, period: Period, index: Index, country: Country)(implicit request: Request[_], messages: Messages) @layout(pageTitle = title(form, messages("vatRatesFromCountry.title"))) { - @formHelper(action = routes.VatRatesFromCountryController.onSubmit(waypoints, period, index), Symbol("autoComplete") -> "off") { + @formHelper(action = routes.VatRatesFromCountryController.onSubmit(waypoints, index), Symbol("autoComplete") -> "off") { @if(form.errors.nonEmpty) { @govukErrorSummary(ErrorSummaryViewModel(form, errorLinkOverrides = Map("value" -> "value_0"))) @@ -36,9 +36,12 @@ CheckboxesViewModel( form = form, name = "value", - legend = LegendViewModel(messages("vatRatesFromCountry.heading")).asPageHeading(), + legend = HmrcPageHeadingLegend( + content = messages("vatRatesFromCountry.heading"), + caption = messages("vatRatesFromCountry.caption", period.displayText, country.name) + ), items = VatRatesFromCountry.checkboxItems - ) + ).withHint(HintViewModel(messages("vatRatesFromCountry.hint"))) ) @button( diff --git a/app/views/components/addToList.scala.html b/app/views/components/addToList.scala.html new file mode 100644 index 00000000..bd54da1f --- /dev/null +++ b/app/views/components/addToList.scala.html @@ -0,0 +1,46 @@ +@* + * Copyright 2023 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. + *@ + +@import uk.gov.hmrc.hmrcfrontend.views.viewmodels.addtoalist +@import uk.gov.hmrc.hmrcfrontend.views.viewmodels.addtoalist.{ItemSize, ListItem} + +@this() + +@(itemList: Seq[ListItem], itemSize: ItemSize = addtoalist.Short, changePrefix: String, removePrefix: String)(implicit messages: Messages) + +
+
+ @for(item <- itemList) { + + } +
+
\ No newline at end of file diff --git a/conf/app.routes b/conf/app.routes index c46c6f7b..da293a18 100644 --- a/conf/app.routes +++ b/conf/app.routes @@ -1,43 +1,49 @@ # microservice specific routes --> /hmrc-frontend hmrcfrontend.Routes +-> /hmrc-frontend hmrcfrontend.Routes -GET / controllers.IndexController.onPageLoad +GET / controllers.IndexController.onPageLoad -GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) +GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) -GET /language/:lang controllers.LanguageSwitchController.switchToLanguage(lang: String) +GET /language/:lang controllers.LanguageSwitchController.switchToLanguage(lang: String) -GET /refresh-session controllers.KeepAliveController.keepAlive +GET /refresh-session controllers.KeepAliveController.keepAlive -GET /there-is-a-problem controllers.JourneyRecoveryController.onPageLoad(continueUrl: Option[RedirectUrl] ?= None) +GET /there-is-a-problem controllers.JourneyRecoveryController.onPageLoad(continueUrl: Option[RedirectUrl] ?= None) -GET /check-your-answers controllers.CheckYourAnswersController.onPageLoad() +GET /check-your-answers controllers.CheckYourAnswersController.onPageLoad() -GET /account/sign-out-survey controllers.auth.AuthController.signOut -GET /account/sign-out controllers.auth.AuthController.signOutNoSurvey -GET /account/signed-out controllers.auth.SignedOutController.onPageLoad +GET /account/sign-out-survey controllers.auth.AuthController.signOut +GET /account/sign-out controllers.auth.AuthController.signOutNoSurvey +GET /account/signed-out controllers.auth.SignedOutController.onPageLoad -GET /unauthorised controllers.UnauthorisedController.onPageLoad +GET /unauthorised controllers.UnauthorisedController.onPageLoad -GET /cannot-use-not-registered controllers.NotRegisteredController.onPageLoad() +GET /cannot-use-not-registered controllers.NotRegisteredController.onPageLoad() -GET /your-account controllers.YourAccountController.onPageLoad +GET /your-account controllers.YourAccountController.onPageLoad(waypoints: Waypoints ?= EmptyWaypoints) -GET /startReturn/:period controllers.StartReturnController.onPageLoad(waypoints: Waypoints ?= EmptyWaypoints, period: Period) -POST /startReturn/:period controllers.StartReturnController.onSubmit(waypoints: Waypoints ?= EmptyWaypoints, period: Period) +GET /:period/start controllers.StartReturnController.onPageLoad(waypoints: Waypoints ?= EmptyWaypoints, period: Period) +POST /:period/start controllers.StartReturnController.onSubmit(waypoints: Waypoints ?= EmptyWaypoints, period: Period) -GET /soldGoods/:period controllers.SoldGoodsController.onPageLoad(waypoints: Waypoints ?= EmptyWaypoints, period: Period) -POST /soldGoods/:period controllers.SoldGoodsController.onSubmit(waypoints: Waypoints ?= EmptyWaypoints, period: Period) +GET /soldGoods controllers.SoldGoodsController.onPageLoad(waypoints: Waypoints ?= EmptyWaypoints) +POST /soldGoods controllers.SoldGoodsController.onSubmit(waypoints: Waypoints ?= EmptyWaypoints) -GET /soldToCountry/:period/:index controllers.SoldToCountryController.onPageLoad(waypoints: Waypoints ?= EmptyWaypoints, period: Period, index: Index) -POST /soldToCountry/:period/:index controllers.SoldToCountryController.onSubmit(waypoints: Waypoints ?= EmptyWaypoints, period: Period, index: Index) +GET /soldToCountry/:index controllers.SoldToCountryController.onPageLoad(waypoints: Waypoints ?= EmptyWaypoints, index: Index) +POST /soldToCountry/:index controllers.SoldToCountryController.onSubmit(waypoints: Waypoints ?= EmptyWaypoints, index: Index) -GET /vatRatesFromCountry/:period/:index controllers.VatRatesFromCountryController.onPageLoad(waypoints: Waypoints ?= EmptyWaypoints, period: Period, index: Index) -POST /vatRatesFromCountry/:period/:index controllers.VatRatesFromCountryController.onSubmit(waypoints: Waypoints ?= EmptyWaypoints, period: Period, index: Index) +GET /vatRatesFromCountry/:index controllers.VatRatesFromCountryController.onPageLoad(waypoints: Waypoints ?= EmptyWaypoints, index: Index) +POST /vatRatesFromCountry/:index controllers.VatRatesFromCountryController.onSubmit(waypoints: Waypoints ?= EmptyWaypoints, index: Index) -GET /salesToCountry/:period/:index controllers.SalesToCountryController.onPageLoad(waypoints: Waypoints ?= EmptyWaypoints, period: Period, index: Index) -POST /salesToCountry/:period/:index controllers.SalesToCountryController.onSubmit(waypoints: Waypoints ?= EmptyWaypoints, period: Period, index: Index) +GET /salesToCountry/:index controllers.SalesToCountryController.onPageLoad(waypoints: Waypoints ?= EmptyWaypoints, index: Index) +POST /salesToCountry/:index controllers.SalesToCountryController.onSubmit(waypoints: Waypoints ?= EmptyWaypoints, index: Index) + +GET /vatOnSales/:index controllers.VatOnSalesController.onPageLoad(waypoints: Waypoints ?= EmptyWaypoints, index: Index) +POST /vatOnSales/:index controllers.VatOnSalesController.onSubmit(waypoints: Waypoints ?= EmptyWaypoints, index: Index) + +GET /no-other-periods-available controllers.NoOtherPeriodsAvailableController.onPageLoad(waypoints: Waypoints ?= EmptyWaypoints) + +GET /add-sales-country-list controllers.SoldToCountryListController.onPageLoad(waypoints: Waypoints ?= EmptyWaypoints) +POST /add-sales-country-list controllers.SoldToCountryListController.onSubmit(waypoints: Waypoints ?= EmptyWaypoints) -GET /vatOnSales/:period/:index controllers.VatOnSalesController.onPageLoad(waypoints: Waypoints ?= EmptyWaypoints, period: Period, index: Index) -POST /vatOnSales/:period/:index controllers.VatOnSalesController.onSubmit(waypoints: Waypoints ?= EmptyWaypoints, period: Period, index: Index) diff --git a/conf/messages.en b/conf/messages.en index d39504c4..0d578a02 100644 --- a/conf/messages.en +++ b/conf/messages.en @@ -81,12 +81,14 @@ soldGoods.change.hidden = Sold Goods soldToCountry.title = Which country did you sell to? soldToCountry.heading = Which country did you sell to? -soldToCountry.checkYourAnswersLabel = soldToCountry -soldToCountry.error.required = Enter a country you sold to +soldToCountry.checkYourAnswersLabel = Country sold to +soldToCountry.error.required = Select a country you sold to soldToCountry.change.hidden = which country you sold to vatRatesFromCountry.title = Which VAT rates did you sell goods at? vatRatesFromCountry.heading = Which VAT rates did you sell goods at? +vatRatesFromCountry.caption = {0} sales to {1} +vatRatesFromCountry.hint = Select all that apply. vatRatesFromCountry.twelvePercent = 12% vatRatesFromCountry.twentyPercent = 20% vatRatesFromCountry.checkYourAnswersLabel = VAT rate @@ -109,3 +111,19 @@ vatOnSales.option2 = Option 2 vatOnSales.checkYourAnswersLabel = VAT charged vatOnSales.error.required = Select VAT charged vatOnSales.change.hidden = VAT charged + +noOtherPeriodsAvailable.title = You cannot start returns for other months yet +noOtherPeriodsAvailable.heading = You cannot start returns for other months yet +noOtherPeriodsAvailable.p1 = You can only start returns for months that have ended. +noOtherPeriodsAvailable.backToYourAccount = Back to your account + +soldToCountryList.title.singular = You added sales to 1 country +soldToCountryList.title.plural = You added sales to {0} countries +soldToCountryList.heading.singular = You added sales to 1 country +soldToCountryList.heading.plural = You added sales to {0} countries +soldToCountryList.addAnother = Add sales to another country? +soldToCountryList.maximumReached = You have added details for every country +soldToCountryList.error.required = Select yes if you want to add sales to an additional country +soldToCountryList.change.hidden = sales to {0} +soldToCountryList.remove.hidden = sales to {0} +soldToCountryList.addAnother.hint = You must tell us about all of your eligible sales. diff --git a/test/controllers/IndexControllerSpec.scala b/test/controllers/IndexControllerSpec.scala index de217626..d34f091f 100644 --- a/test/controllers/IndexControllerSpec.scala +++ b/test/controllers/IndexControllerSpec.scala @@ -34,7 +34,7 @@ class IndexControllerSpec extends SpecBase { val result = route(application, request).value status(result) mustEqual SEE_OTHER - redirectLocation(result).value mustEqual routes.YourAccountController.onPageLoad.url + redirectLocation(result).value mustEqual routes.YourAccountController.onPageLoad(waypoints).url } } } diff --git a/test/controllers/NoOtherPeriodsAvailableControllerSpec.scala b/test/controllers/NoOtherPeriodsAvailableControllerSpec.scala new file mode 100644 index 00000000..6a3b304a --- /dev/null +++ b/test/controllers/NoOtherPeriodsAvailableControllerSpec.scala @@ -0,0 +1,44 @@ +/* + * Copyright 2023 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 controllers + +import base.SpecBase +import play.api.test.FakeRequest +import play.api.test.Helpers._ +import views.html.NoOtherPeriodsAvailableView + +class NoOtherPeriodsAvailableControllerSpec extends SpecBase { + + "CannotStartReturns Controller" - { + + "must return OK and the correct view for a GET" in { + + val application = applicationBuilder(userAnswers = Some(emptyUserAnswers)).build() + + running(application) { + val request = FakeRequest(GET, routes.NoOtherPeriodsAvailableController.onPageLoad(waypoints).url) + + val result = route(application, request).value + + val view = application.injector.instanceOf[NoOtherPeriodsAvailableView] + + status(result) mustBe OK + contentAsString(result) mustBe view(waypoints)(request, messages(application)).toString + } + } + } +} diff --git a/test/controllers/SalesToCountryControllerSpec.scala b/test/controllers/SalesToCountryControllerSpec.scala index 3db05dbf..46b81d5b 100644 --- a/test/controllers/SalesToCountryControllerSpec.scala +++ b/test/controllers/SalesToCountryControllerSpec.scala @@ -22,6 +22,7 @@ import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.when import org.scalatestplus.mockito.MockitoSugar import pages.SalesToCountryPage +import play.api.data.Form import play.api.inject.bind import play.api.test.FakeRequest import play.api.test.Helpers._ @@ -33,11 +34,11 @@ import scala.concurrent.Future class SalesToCountryControllerSpec extends SpecBase with MockitoSugar { val formProvider = new SalesToCountryFormProvider() - val form = formProvider() + val form: Form[Int] = formProvider() val validAnswer = 0 - lazy val salesToCountryRoute = routes.SalesToCountryController.onPageLoad(waypoints, period, index).url + lazy val salesToCountryRoute: String = routes.SalesToCountryController.onPageLoad(waypoints, index).url "SalesToCountry Controller" - { @@ -59,7 +60,7 @@ class SalesToCountryControllerSpec extends SpecBase with MockitoSugar { "must populate the view correctly on a GET when the question has previously been answered" in { - val userAnswers = emptyUserAnswers.set(SalesToCountryPage(period, index), validAnswer).success.value + val userAnswers = emptyUserAnswers.set(SalesToCountryPage(index), validAnswer).success.value val application = applicationBuilder(userAnswers = Some(userAnswers)).build() @@ -96,7 +97,7 @@ class SalesToCountryControllerSpec extends SpecBase with MockitoSugar { val result = route(application, request).value status(result) mustEqual SEE_OTHER - redirectLocation(result).value mustEqual routes.VatOnSalesController.onPageLoad(waypoints, period, index).url + redirectLocation(result).value mustEqual routes.VatOnSalesController.onPageLoad(waypoints, index).url } } diff --git a/test/controllers/SoldGoodsControllerSpec.scala b/test/controllers/SoldGoodsControllerSpec.scala index 3e299aef..b6b7e0e2 100644 --- a/test/controllers/SoldGoodsControllerSpec.scala +++ b/test/controllers/SoldGoodsControllerSpec.scala @@ -22,6 +22,7 @@ import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.when import org.scalatestplus.mockito.MockitoSugar import pages.SoldGoodsPage +import play.api.data.Form import play.api.inject.bind import play.api.test.FakeRequest import play.api.test.Helpers._ @@ -33,9 +34,9 @@ import scala.concurrent.Future class SoldGoodsControllerSpec extends SpecBase with MockitoSugar { val formProvider = new SoldGoodsFormProvider() - val form = formProvider() + val form: Form[Boolean] = formProvider() - lazy val soldGoodsRoute = routes.SoldGoodsController.onPageLoad(waypoints, period).url + lazy val soldGoodsRoute: String = routes.SoldGoodsController.onPageLoad(waypoints).url "SoldGoods Controller" - { @@ -57,7 +58,7 @@ class SoldGoodsControllerSpec extends SpecBase with MockitoSugar { "must populate the view correctly on a GET when the question has previously been answered" in { - val userAnswers = emptyUserAnswers.set(SoldGoodsPage(period), true).success.value + val userAnswers = emptyUserAnswers.set(SoldGoodsPage, true).success.value val application = applicationBuilder(userAnswers = Some(userAnswers)).build() @@ -94,7 +95,7 @@ class SoldGoodsControllerSpec extends SpecBase with MockitoSugar { val result = route(application, request).value status(result) mustEqual SEE_OTHER - redirectLocation(result).value mustEqual routes.SoldToCountryController.onPageLoad(waypoints, period, index).url + redirectLocation(result).value mustEqual routes.SoldToCountryController.onPageLoad(waypoints, index).url } } diff --git a/test/controllers/SoldToCountryControllerSpec.scala b/test/controllers/SoldToCountryControllerSpec.scala index 859e3af7..e0d77eda 100644 --- a/test/controllers/SoldToCountryControllerSpec.scala +++ b/test/controllers/SoldToCountryControllerSpec.scala @@ -38,7 +38,7 @@ class SoldToCountryControllerSpec extends SpecBase with MockitoSugar { private val form = formProvider(index, Seq.empty) private val country: Country = Arbitrary.arbitrary[Country].sample.value - lazy val soldToCountryRoute = routes.SoldToCountryController.onPageLoad(waypoints, period, index).url + lazy val soldToCountryRoute: String = routes.SoldToCountryController.onPageLoad(waypoints, index).url "SoldToCountry Controller" - { @@ -60,7 +60,7 @@ class SoldToCountryControllerSpec extends SpecBase with MockitoSugar { "must populate the view correctly on a GET when the question has previously been answered" in { - val userAnswers = emptyUserAnswers.set(SoldToCountryPage(period, index), country).success.value + val userAnswers = emptyUserAnswers.set(SoldToCountryPage(index), country).success.value val application = applicationBuilder(userAnswers = Some(userAnswers)).build() @@ -97,7 +97,7 @@ class SoldToCountryControllerSpec extends SpecBase with MockitoSugar { val result = route(application, request).value status(result) mustEqual SEE_OTHER - redirectLocation(result).value mustEqual routes.VatRatesFromCountryController.onPageLoad(waypoints, period, index).url + redirectLocation(result).value mustEqual routes.VatRatesFromCountryController.onPageLoad(waypoints, index).url } } diff --git a/test/controllers/SoldToCountryListControllerSpec.scala b/test/controllers/SoldToCountryListControllerSpec.scala new file mode 100644 index 00000000..b08e86ff --- /dev/null +++ b/test/controllers/SoldToCountryListControllerSpec.scala @@ -0,0 +1,236 @@ +/* + * Copyright 2023 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 controllers + +import base.SpecBase +import forms.SoldToCountryListFormProvider +import models.{Country, Index, UserAnswers, VatOnSales, VatRatesFromCountry} +import org.mockito.ArgumentMatchers.{any, eq => eqTo} +import org.mockito.Mockito.{times, verify, when} +import org.scalatestplus.mockito.MockitoSugar +import pages.{JourneyRecoveryPage, SalesToCountryPage, SoldGoodsPage, SoldToCountryListPage, SoldToCountryPage, VatOnSalesPage, VatRatesFromCountryPage} +import play.api.data.Form +import play.api.inject.bind +import play.api.test.FakeRequest +import play.api.test.Helpers._ +import repositories.SessionRepository +import utils.FutureSyntax.FutureOps +import viewmodels.checkAnswers.SoldToCountryListSummary +import views.html.SoldToCountryListView + +class SoldToCountryListControllerSpec extends SpecBase with MockitoSugar { + + val formProvider = new SoldToCountryListFormProvider() + val form: Form[Boolean] = formProvider() + + private val country: Country = arbitraryCountry.arbitrary.sample.value + private val salesValue: Int = 1234 + + val baseAnswers: UserAnswers = emptyUserAnswers + .set(SoldGoodsPage, true).success.value + .set(SoldToCountryPage(index), country).success.value + .set(VatRatesFromCountryPage(index), Set(VatRatesFromCountry.values.head)).success.value + .set(SalesToCountryPage(index), salesValue).success.value + .set(VatOnSalesPage(index), VatOnSales.values.head).success.value + + lazy val soldToCountryListRoute: String = routes.SoldToCountryListController.onPageLoad(waypoints).url + + "SoldToCountryList Controller" - { + + "must return OK and the correct view for a GET" in { + + val application = applicationBuilder(userAnswers = Some(baseAnswers)).build() + + running(application) { + + val request = FakeRequest(GET, soldToCountryListRoute) + + val result = route(application, request).value + + val view = application.injector.instanceOf[SoldToCountryListView] + + val list = SoldToCountryListSummary.addToListRows(baseAnswers, waypoints, SoldToCountryListPage()) + + status(result) mustBe OK + contentAsString(result) mustBe view(form, waypoints, period, list, canAddSales = true)(request, messages(application)).toString + } + } + + // TODO +// "must populate the view correctly on a GET when the question has previously been answered" in { +// +// val userAnswers = emptyUserAnswers.set(SoldToCountryListPage(), true).success.value +// +// val application = applicationBuilder(userAnswers = Some(userAnswers)).build() +// +// running(application) { +// implicit val msgs: Messages = messages(application) +// +// val request = FakeRequest(GET, soldToCountryListRoute) +// +// val result = route(application, request).value +// +// val view = application.injector.instanceOf[SoldToCountryListView] +// +// val list = SoldToCountryListSummary.row(baseAnswers, waypoints, SoldToCountryListPage()) +// +// status(result) mustBe OK +// contentAsString(result) must not be view(form.fill(true), waypoints, period, list, canAddSalesToEuOrNi = true)(request, messages(application)).toString +// } +// } + + "must return OK and populate the view correctly on a GET when the maximum number of sold to countries have already been added" in { + + val userAnswers = (0 to Country.euCountriesWithNI.size).foldLeft(baseAnswers) { (userAnswers: UserAnswers, index: Int) => + userAnswers.set(SoldToCountryPage(Index(index)), country).success.value + } + + val application = applicationBuilder(userAnswers = Some(userAnswers)).build() + + running(application) { + + val request = FakeRequest(GET, soldToCountryListRoute) + + val view = application.injector.instanceOf[SoldToCountryListView] + + val result = route(application, request).value + + val list = SoldToCountryListSummary.addToListRows(userAnswers, waypoints, SoldToCountryListPage()) + + status(result) mustBe OK + contentAsString(result) mustBe view(form, waypoints, period, list, canAddSales = false)(request, messages(application)).toString + } + } + + "must return OK and populate the view correctly on a GET when just below the maximum number of sold to countries have been added" in { + + val userAnswers = (0 until (Country.euCountriesWithNI.size -1)).foldLeft(baseAnswers) { (userAnswers: UserAnswers, index: Int) => + userAnswers.set(SoldToCountryPage(Index(index)), country).success.value + } + + val application = applicationBuilder(userAnswers = Some(userAnswers)).build() + + running(application) { + + val request = FakeRequest(GET, soldToCountryListRoute) + + val view = application.injector.instanceOf[SoldToCountryListView] + + val result = route(application, request).value + + val list = SoldToCountryListSummary.addToListRows(userAnswers, waypoints, SoldToCountryListPage()) + + status(result) mustBe OK + contentAsString(result) mustBe view(form, waypoints, period, list, canAddSales = true)(request, messages(application)).toString + } + } + + "must redirect to the next page when valid data is submitted" in { + + val mockSessionRepository = mock[SessionRepository] + + when(mockSessionRepository.set(any())) thenReturn true.toFuture + + val application = + applicationBuilder(userAnswers = Some(baseAnswers)) + .overrides( + bind[SessionRepository].toInstance(mockSessionRepository) + ) + .build() + + running(application) { + val request = + FakeRequest(POST, soldToCountryListRoute) + .withFormUrlEncodedBody(("value", "true")) + + val result = route(application, request).value + val expectedAnswers = baseAnswers.set(SoldToCountryListPage(), true).success.value + + status(result) mustBe SEE_OTHER + redirectLocation(result).value mustBe SoldToCountryListPage().navigate(waypoints, baseAnswers, expectedAnswers).url + verify(mockSessionRepository, times(1)).set(eqTo(expectedAnswers)) + } + } + + "must return a Bad Request and errors when invalid data is submitted" in { + + val application = applicationBuilder(userAnswers = Some(baseAnswers)).build() + + running(application) { + + val request = + FakeRequest(POST, soldToCountryListRoute) + .withFormUrlEncodedBody(("value", "")) + + val boundForm = form.bind(Map("value" -> "")) + + val view = application.injector.instanceOf[SoldToCountryListView] + + val result = route(application, request).value + + val list = SoldToCountryListSummary.addToListRows(baseAnswers, waypoints, SoldToCountryListPage()) + + status(result) mustBe BAD_REQUEST + contentAsString(result) mustBe view(boundForm, waypoints, period, list, canAddSales = true)(request, messages(application)).toString // TODO + } + } + + "must redirect to Journey Recovery for a GET if no existing data is found" in { + + val application = applicationBuilder(userAnswers = None).build() + + running(application) { + val request = FakeRequest(GET, soldToCountryListRoute) + + val result = route(application, request).value + + status(result) mustBe SEE_OTHER + redirectLocation(result).value mustBe JourneyRecoveryPage.route(waypoints).url + } + } + + "must redirect to Journey Recovery for a GET if user answers are empty" in { + + val application = applicationBuilder(userAnswers = Some(emptyUserAnswers)).build() + + running(application) { + val request = FakeRequest(GET, soldToCountryListRoute) + + val result = route(application, request).value + + status(result) mustBe SEE_OTHER + redirectLocation(result).value mustBe JourneyRecoveryPage.route(waypoints).url + } + } + + "must redirect to Journey Recovery for a POST if no existing data is found" in { + + val application = applicationBuilder(userAnswers = None).build() + + running(application) { + val request = + FakeRequest(POST, soldToCountryListRoute) + .withFormUrlEncodedBody(("value", "true")) + + val result = route(application, request).value + + status(result) mustBe SEE_OTHER + redirectLocation(result).value mustBe JourneyRecoveryPage.route(waypoints).url + } + } + } +} diff --git a/test/controllers/StartReturnControllerSpec.scala b/test/controllers/StartReturnControllerSpec.scala index a726ebde..9abe769f 100644 --- a/test/controllers/StartReturnControllerSpec.scala +++ b/test/controllers/StartReturnControllerSpec.scala @@ -18,27 +18,19 @@ package controllers import base.SpecBase import forms.StartReturnFormProvider -import models.Country -import org.mockito.ArgumentMatchers.any -import org.mockito.ArgumentMatchersSugar.eqTo -import org.mockito.Mockito.{times, verify, when} -import org.scalacheck.Arbitrary import org.scalatestplus.mockito.MockitoSugar -import pages.SoldToCountryPage -import play.api.inject.bind +import pages.{NoOtherPeriodsAvailablePage, SoldGoodsPage} +import play.api.data.Form import play.api.test.FakeRequest import play.api.test.Helpers._ -import repositories.SessionRepository import views.html.StartReturnView -import scala.concurrent.Future - class StartReturnControllerSpec extends SpecBase with MockitoSugar { val formProvider = new StartReturnFormProvider() - val form = formProvider() + val form: Form[Boolean] = formProvider() - lazy val startReturnRoute = routes.StartReturnController.onPageLoad(waypoints, period).url + lazy val startReturnRoute: String = routes.StartReturnController.onPageLoad(waypoints, period).url "StartReturn Controller" - { @@ -54,8 +46,8 @@ class StartReturnControllerSpec extends SpecBase with MockitoSugar { val view = application.injector.instanceOf[StartReturnView] - status(result) mustEqual OK - contentAsString(result) mustEqual view(form, waypoints, period)(request, messages(application)).toString + status(result) mustBe OK + contentAsString(result) mustBe view(form, waypoints, period)(request, messages(application)).toString } } @@ -72,8 +64,8 @@ class StartReturnControllerSpec extends SpecBase with MockitoSugar { val result = route(application, request).value - status(result) mustEqual SEE_OTHER - redirectLocation(result).value mustEqual routes.SoldGoodsController.onPageLoad(waypoints, period).url + status(result) mustBe SEE_OTHER + redirectLocation(result).value mustBe SoldGoodsPage.route(waypoints).url } } @@ -90,36 +82,8 @@ class StartReturnControllerSpec extends SpecBase with MockitoSugar { val result = route(application, request).value - status(result) mustEqual SEE_OTHER - // TODO should go to no other periods available page when exists - redirectLocation(result).value mustEqual routes.SoldGoodsController.onPageLoad(waypoints, period).url - } - } - - "must clear useranswers when answer is no" in { - val mockSessionRepository = mock[SessionRepository] - - when(mockSessionRepository.clear(any())) thenReturn Future.successful(true) - - val country: Country = Arbitrary.arbitrary[Country].sample.value - - val answers = emptyUserAnswers.set(SoldToCountryPage(period, index), country).success.value - - val application = applicationBuilder(userAnswers = Some(emptyUserAnswers)) - .overrides(bind[SessionRepository].toInstance(mockSessionRepository)) - .build() - - running(application) { - - val request = - FakeRequest(POST, startReturnRoute) - .withFormUrlEncodedBody(("value", "false")) - - val result = route(application, request).value - - status(result) mustEqual SEE_OTHER - redirectLocation(result).value mustEqual routes.SoldGoodsController.onPageLoad(waypoints, period).url - verify(mockSessionRepository, times(1)).clear(eqTo(answers.id)) + status(result) mustBe SEE_OTHER + redirectLocation(result).value mustBe NoOtherPeriodsAvailablePage.route(waypoints).url } } @@ -140,8 +104,8 @@ class StartReturnControllerSpec extends SpecBase with MockitoSugar { val result = route(application, request).value - status(result) mustEqual BAD_REQUEST - contentAsString(result) mustEqual view(boundForm, waypoints, period)(request, messages(application)).toString + status(result) mustBe BAD_REQUEST + contentAsString(result) mustBe view(boundForm, waypoints, period)(request, messages(application)).toString } } } diff --git a/test/controllers/VatOnSalesControllerSpec.scala b/test/controllers/VatOnSalesControllerSpec.scala index f33fbbdb..43fb3d17 100644 --- a/test/controllers/VatOnSalesControllerSpec.scala +++ b/test/controllers/VatOnSalesControllerSpec.scala @@ -22,7 +22,8 @@ import models.VatOnSales import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.when import org.scalatestplus.mockito.MockitoSugar -import pages.VatOnSalesPage +import pages.{SoldToCountryListPage, VatOnSalesPage} +import play.api.data.Form import play.api.inject.bind import play.api.test.FakeRequest import play.api.test.Helpers._ @@ -33,10 +34,10 @@ import scala.concurrent.Future class VatOnSalesControllerSpec extends SpecBase with MockitoSugar { - lazy val vatOnSalesRoute = routes.VatOnSalesController.onPageLoad(waypoints, period, index).url + lazy val vatOnSalesRoute: String = routes.VatOnSalesController.onPageLoad(waypoints, index).url val formProvider = new VatOnSalesFormProvider() - val form = formProvider() + val form: Form[VatOnSales] = formProvider() "VatOnSales Controller" - { @@ -58,7 +59,7 @@ class VatOnSalesControllerSpec extends SpecBase with MockitoSugar { "must populate the view correctly on a GET when the question has previously been answered" in { - val userAnswers = emptyUserAnswers.set(VatOnSalesPage(period, index), VatOnSales.values.head).success.value + val userAnswers = emptyUserAnswers.set(VatOnSalesPage(index), VatOnSales.values.head).success.value val application = applicationBuilder(userAnswers = Some(userAnswers)).build() @@ -96,7 +97,7 @@ class VatOnSalesControllerSpec extends SpecBase with MockitoSugar { status(result) mustEqual SEE_OTHER // TODO - should go to mini CYA - redirectLocation(result).value mustEqual routes.CheckYourAnswersController.onPageLoad().url + redirectLocation(result).value mustEqual SoldToCountryListPage().route(waypoints).url } } diff --git a/test/controllers/VatRatesFromCountryControllerSpec.scala b/test/controllers/VatRatesFromCountryControllerSpec.scala index 8ae86b0a..8c3c840a 100644 --- a/test/controllers/VatRatesFromCountryControllerSpec.scala +++ b/test/controllers/VatRatesFromCountryControllerSpec.scala @@ -18,11 +18,12 @@ package controllers import base.SpecBase import forms.VatRatesFromCountryFormProvider -import models.VatRatesFromCountry +import models.{Country, VatRatesFromCountry} import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.when import org.scalatestplus.mockito.MockitoSugar -import pages.VatRatesFromCountryPage +import pages.{SoldToCountryPage, VatRatesFromCountryPage} +import play.api.data.Form import play.api.inject.bind import play.api.test.FakeRequest import play.api.test.Helpers._ @@ -33,16 +34,19 @@ import scala.concurrent.Future class VatRatesFromCountryControllerSpec extends SpecBase with MockitoSugar { - lazy val vatRatesFromCountryRoute = routes.VatRatesFromCountryController.onPageLoad(waypoints, period, index).url + private val country: Country = arbitraryCountry.arbitrary.sample.value + private val userAnswersWithCountry = emptyUserAnswers.set(SoldToCountryPage(index), country).success.value + + lazy val vatRatesFromCountryRoute: String = routes.VatRatesFromCountryController.onPageLoad(waypoints, index).url val formProvider = new VatRatesFromCountryFormProvider() - val form = formProvider() + val form: Form[Set[VatRatesFromCountry]] = formProvider() "VatRatesFromCountry Controller" - { "must return OK and the correct view for a GET" in { - val application = applicationBuilder(userAnswers = Some(emptyUserAnswers)).build() + val application = applicationBuilder(userAnswers = Some(userAnswersWithCountry)).build() running(application) { val request = FakeRequest(GET, vatRatesFromCountryRoute) @@ -53,13 +57,13 @@ class VatRatesFromCountryControllerSpec extends SpecBase with MockitoSugar { status(result) mustEqual OK - contentAsString(result) mustEqual view(form, waypoints, period, index)(request, messages(application)).toString + contentAsString(result) mustEqual view(form, waypoints, period, index, country)(request, messages(application)).toString } } "must populate the view correctly on a GET when the question has previously been answered" in { - val userAnswers = emptyUserAnswers.set(VatRatesFromCountryPage(period, index), VatRatesFromCountry.values.toSet).success.value + val userAnswers = userAnswersWithCountry.set(VatRatesFromCountryPage(index), VatRatesFromCountry.values.toSet).success.value val application = applicationBuilder(userAnswers = Some(userAnswers)).build() @@ -71,7 +75,14 @@ class VatRatesFromCountryControllerSpec extends SpecBase with MockitoSugar { val result = route(application, request).value status(result) mustEqual OK - contentAsString(result) mustEqual view(form.fill(VatRatesFromCountry.values.toSet), waypoints, period, index)(request, messages(application)).toString + contentAsString(result) mustEqual + view( + form.fill(VatRatesFromCountry.values.toSet), + waypoints, + period, + index, + country + )(request, messages(application)).toString } } @@ -82,7 +93,7 @@ class VatRatesFromCountryControllerSpec extends SpecBase with MockitoSugar { when(mockSessionRepository.set(any())) thenReturn Future.successful(true) val application = - applicationBuilder(userAnswers = Some(emptyUserAnswers)) + applicationBuilder(userAnswers = Some(userAnswersWithCountry)) .overrides( bind[SessionRepository].toInstance(mockSessionRepository) ) @@ -96,13 +107,13 @@ class VatRatesFromCountryControllerSpec extends SpecBase with MockitoSugar { val result = route(application, request).value status(result) mustEqual SEE_OTHER - redirectLocation(result).value mustEqual routes.SalesToCountryController.onPageLoad(waypoints, period, index).url + redirectLocation(result).value mustEqual routes.SalesToCountryController.onPageLoad(waypoints, index).url } } "must return a Bad Request and errors when invalid data is submitted" in { - val application = applicationBuilder(userAnswers = Some(emptyUserAnswers)).build() + val application = applicationBuilder(userAnswers = Some(userAnswersWithCountry)).build() running(application) { val request = @@ -116,7 +127,7 @@ class VatRatesFromCountryControllerSpec extends SpecBase with MockitoSugar { val result = route(application, request).value status(result) mustEqual BAD_REQUEST - contentAsString(result) mustEqual view(boundForm, waypoints, period, index)(request, messages(application)).toString + contentAsString(result) mustEqual view(boundForm, waypoints, period, index, country)(request, messages(application)).toString } } diff --git a/test/controllers/YourAccountControllerSpec.scala b/test/controllers/YourAccountControllerSpec.scala index b67b63da..1ccce050 100644 --- a/test/controllers/YourAccountControllerSpec.scala +++ b/test/controllers/YourAccountControllerSpec.scala @@ -38,7 +38,7 @@ class YourAccountControllerSpec extends SpecBase with MockitoSugar with Generato .build() running(application) { - val request = FakeRequest(GET, routes.YourAccountController.onPageLoad.url) + val request = FakeRequest(GET, routes.YourAccountController.onPageLoad(waypoints).url) val result = route(application, request).value diff --git a/test/controllers/actions/DataRetrievalActionSpec.scala b/test/controllers/actions/DataRetrievalActionSpec.scala index 4fd59a50..7e4a8baf 100644 --- a/test/controllers/actions/DataRetrievalActionSpec.scala +++ b/test/controllers/actions/DataRetrievalActionSpec.scala @@ -18,7 +18,7 @@ package controllers.actions import base.SpecBase import models.requests.{OptionalDataRequest, RegistrationRequest} -import models.{Period, RegistrationWrapper} +import models.RegistrationWrapper import org.mockito.ArgumentMatchers.any import org.mockito.Mockito._ import org.scalacheck.Arbitrary.arbitrary @@ -31,7 +31,7 @@ import scala.concurrent.Future class DataRetrievalActionSpec extends SpecBase with MockitoSugar { - class Harness(period: Period, repository: SessionRepository) extends DataRetrievalAction(period, repository) { + class Harness(repository: SessionRepository) extends DataRetrievalAction(repository) { def callTransform(request: RegistrationRequest[_]): Future[OptionalDataRequest[_]] = transform(request) @@ -45,13 +45,12 @@ class DataRetrievalActionSpec extends SpecBase with MockitoSugar { "must set userAnswers to `None` in the request" in { - val period = arbitrary[Period].sample.value val repository = mock[SessionRepository] val request = RegistrationRequest(FakeRequest(), testCredentials, vrn, iossNumber, registrationWrapper) - when(repository.get(any(), any())) thenReturn Future.successful(None) + when(repository.get(any())) thenReturn Future.successful(None) - val action = new Harness(period, repository) + val action = new Harness(repository) val result = action.callTransform(request).futureValue @@ -63,13 +62,12 @@ class DataRetrievalActionSpec extends SpecBase with MockitoSugar { "must add the userAnswers to the request" in { - val period = arbitrary[Period].sample.value val repository = mock[SessionRepository] val request = RegistrationRequest(FakeRequest(), testCredentials, vrn, iossNumber, registrationWrapper) - when(repository.get(any(), any())) thenReturn Future.successful(Some(emptyUserAnswers)) + when(repository.get(any())) thenReturn Future.successful(Some(emptyUserAnswers)) - val action = new Harness(period, repository) + val action = new Harness(repository) val result = action.callTransform(request).futureValue diff --git a/test/controllers/actions/FakeDataRetrievalAction.scala b/test/controllers/actions/FakeDataRetrievalAction.scala index 8e42c3a0..9e9ad7ab 100644 --- a/test/controllers/actions/FakeDataRetrievalAction.scala +++ b/test/controllers/actions/FakeDataRetrievalAction.scala @@ -16,8 +16,8 @@ package controllers.actions +import models.UserAnswers import models.requests.{OptionalDataRequest, RegistrationRequest} -import models.{Period, UserAnswers} import org.scalatestplus.mockito.MockitoSugar.mock import repositories.SessionRepository @@ -25,7 +25,6 @@ import scala.concurrent.{ExecutionContext, Future} class FakeDataRetrievalAction(dataToReturn: Option[UserAnswers]) extends DataRetrievalAction( - mock[Period], mock[SessionRepository] )(ExecutionContext.Implicits.global) { @@ -36,6 +35,6 @@ class FakeDataRetrievalAction(dataToReturn: Option[UserAnswers]) class FakeDataRetrievalActionProvider(dataToReturn: Option[UserAnswers]) extends DataRetrievalActionProvider(mock[SessionRepository])(ExecutionContext.Implicits.global) { - override def apply(period: Period): DataRetrievalAction = + override def apply(): DataRetrievalAction = new FakeDataRetrievalAction(dataToReturn) } diff --git a/test/controllers/actions/GetRegistrationActionSpec.scala b/test/controllers/actions/GetRegistrationActionSpec.scala index 405367bd..b716fa0d 100644 --- a/test/controllers/actions/GetRegistrationActionSpec.scala +++ b/test/controllers/actions/GetRegistrationActionSpec.scala @@ -45,7 +45,7 @@ class GetRegistrationActionSpec extends SpecBase with MockitoSugar with EitherVa "and a registration can be retrieved from the backend" - { - "must save the registration to the repository and return Right" in { + "must save the registration to the sessionRepository and return Right" in { val registrationWrapper = Arbitrary.arbitrary[RegistrationWrapper].sample.value diff --git a/test/forms/SoldToCountryListFormProviderSpec.scala b/test/forms/SoldToCountryListFormProviderSpec.scala new file mode 100644 index 00000000..8cecff69 --- /dev/null +++ b/test/forms/SoldToCountryListFormProviderSpec.scala @@ -0,0 +1,45 @@ +/* + * Copyright 2023 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 forms + +import forms.behaviours.BooleanFieldBehaviours +import play.api.data.FormError + +class SoldToCountryListFormProviderSpec extends BooleanFieldBehaviours { + + val requiredKey = "soldToCountryList.error.required" + val invalidKey = "error.boolean" + + val form = new SoldToCountryListFormProvider()() + + ".value" - { + + val fieldName = "value" + + behave like booleanField( + form, + fieldName, + invalidError = FormError(fieldName, invalidKey) + ) + + behave like mandatoryField( + form, + fieldName, + requiredError = FormError(fieldName, requiredKey) + ) + } +}