Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SASS-9686: Foreign Property - Expenses section: 'Consolidated or individual expenses' page #457

Merged
merged 11 commits into from
Dec 3, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2024 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.foreign.expenses

import controllers.actions._
import forms.foreign.expenses.ConsolidatedOrIndividualExpensesFormProvider

import javax.inject.Inject
import models.{ConsolidatedOrIndividualExpenses, Mode}
import navigation.ForeignPropertyNavigator
import pages.foreign.CalculatedPremiumLeaseTaxablePage
import pages.foreign.expenses.ConsolidatedOrIndividualExpensesPage
import play.api.i18n.{MessagesApi, I18nSupport}
import play.api.mvc.{Action, AnyContent, MessagesControllerComponents}
import repositories.SessionRepository
import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController
import views.html.foreign.expenses.ConsolidatedOrIndividualExpensesView
import play.api.data.Form

import scala.concurrent.{ExecutionContext, Future}

class ConsolidatedOrIndividualExpensesController @Inject()(
override val messagesApi: MessagesApi,
sessionRepository: SessionRepository,
foreignPropertyNavigator: ForeignPropertyNavigator,
identify: IdentifierAction,
getData: DataRetrievalAction,
requireData: DataRequiredAction,
formProvider: ConsolidatedOrIndividualExpensesFormProvider,
val controllerComponents: MessagesControllerComponents,
view: ConsolidatedOrIndividualExpensesView
)(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport {

def onPageLoad(taxYear: Int, countryCode: String, mode: Mode): Action[AnyContent] = (identify andThen getData andThen requireData) {
implicit request =>
val form: Form[ConsolidatedOrIndividualExpenses] = formProvider(request.user.isAgentMessageKey)
val preparedForm = request.userAnswers.get(ConsolidatedOrIndividualExpensesPage(countryCode)) match {
case None => form
case Some(value) => form.fill(value)
}

Ok(view(preparedForm, mode, request.user.isAgentMessageKey, taxYear, countryCode))
}

def onSubmit(taxYear: Int, countryCode: String, mode: Mode): Action[AnyContent] = (identify andThen getData andThen requireData).async {
implicit request =>
val form: Form[ConsolidatedOrIndividualExpenses] = formProvider(request.user.isAgentMessageKey)
form.bindFromRequest().fold(
formWithErrors =>
Future.successful(BadRequest(view(formWithErrors, mode, request.user.isAgentMessageKey, taxYear, countryCode))),

value =>
for {
updatedAnswers <- Future.fromTry(request.userAnswers.set(ConsolidatedOrIndividualExpensesPage(countryCode), value))
_ <- sessionRepository.set(updatedAnswers)
} yield Redirect(foreignPropertyNavigator.nextPage(ConsolidatedOrIndividualExpensesPage(countryCode), taxYear, mode, request.userAnswers, updatedAnswers))
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2024 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.foreign.expenses

import javax.inject.Inject
import forms.mappings.Mappings
import play.api.data.Form
import play.api.data.Forms.mapping
import models.ConsolidatedOrIndividualExpenses
import uk.gov.voa.play.form.ConditionalMappings.mandatoryIfTrue

class ConsolidatedOrIndividualExpensesFormProvider @Inject() extends Mappings {

def apply(individualOrAgent: String): Form[ConsolidatedOrIndividualExpenses] =
Form[ConsolidatedOrIndividualExpenses](
mapping(
"consolidatedOrIndividualExpenses" -> boolean(s"consolidatedOrIndividualExpenses.error.required.${individualOrAgent}"),
"consolidatedExpensesAmount" -> {
mandatoryIfTrue("consolidatedOrIndividualExpenses",
currency(
s"consolidatedOrIndividualExpenses.amount.error.required.${individualOrAgent}",
s"consolidatedOrIndividualExpenses.amount.error.twoDecimalPlaces.${individualOrAgent}",
s"consolidatedOrIndividualExpenses.amount.error.nonNumerical.${individualOrAgent}")
.verifying(inRange(BigDecimal(0), BigDecimal(1000000000),
"consolidatedOrIndividualExpenses.amount.error.outOfRange"))
)
}
)(ConsolidatedOrIndividualExpenses.apply)(ConsolidatedOrIndividualExpenses.unapply)
)
}
25 changes: 25 additions & 0 deletions app/models/ConsolidatedOrIndividualExpenses.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2024 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 models

import play.api.libs.json.{Format, Json}

final case class ConsolidatedOrIndividualExpenses(consolidatedOrIndividualExpensesYesNo: Boolean, enteredAmount: Option[BigDecimal])

object ConsolidatedOrIndividualExpenses {
implicit val format: Format[ConsolidatedOrIndividualExpenses] = Json.format
}
2 changes: 1 addition & 1 deletion app/pages/PageConstants.scala
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ object PageConstants {

val foreignTaxPath: PropertyType => String = labelForPropertyType(_, "ForeignTax")

val foreignPropertyExpensesPath: PropertyType => String = labelForPropertyType(_, "expenses")
val foreignPropertyExpensesPath: PropertyType => String = labelForPropertyType(_, "Expenses")

val foreignPropertySectionFinished: String = "foreignPropertySectionFinished"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright 2024 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.foreign.expenses

import models.{ConsolidatedOrIndividualExpenses, ForeignProperty}
import pages.QuestionPage
import play.api.libs.json.JsPath
import pages.PageConstants.foreignPropertyExpensesPath

case class ConsolidatedOrIndividualExpensesPage(countryCode: String) extends QuestionPage[ConsolidatedOrIndividualExpenses] {

override def path: JsPath = JsPath \ foreignPropertyExpensesPath(ForeignProperty) \ countryCode.toUpperCase \ toString

override def toString: String = "consolidatedOrIndividualExpenses"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2024 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.foreign.expenses

import models.{UserAnswers, CheckMode, ConsolidatedOrIndividualExpenses}
import play.api.i18n.Messages
import play.twirl.api.HtmlFormat
import uk.gov.hmrc.govukfrontend.views.viewmodels.content.HtmlContent
import uk.gov.hmrc.govukfrontend.views.viewmodels.summarylist.SummaryListRow
import viewmodels.govuk.summarylist._
import viewmodels.implicits._
import controllers.foreign.expenses.routes.ConsolidatedOrIndividualExpensesController
import pages.foreign.expenses.ConsolidatedOrIndividualExpensesPage
import viewmodels.checkAnswers.FormatUtils.{keyCssClass, bigDecimalCurrency, valueCssClass}

object ConsolidatedOrIndividualExpensesSummary {

def row(taxYear: Int, countryCode: String, answers: UserAnswers)(implicit messages: Messages): Option[SummaryListRow] =
answers.get(ConsolidatedOrIndividualExpensesPage(countryCode)).flatMap {
case ConsolidatedOrIndividualExpenses(true, amount) =>
Some(SummaryListRowViewModel(
key = KeyViewModel("consolidatedOrIndividualExpenses.checkYourAnswersLabel").withCssClass(keyCssClass),
value = ValueViewModel(bigDecimalCurrency(amount.get)).withCssClass(valueCssClass),
actions = Seq(
ActionItemViewModel("site.change",
ConsolidatedOrIndividualExpensesController.onPageLoad(taxYear, countryCode, CheckMode).url)
.withVisuallyHiddenText(messages("consolidatedOrIndividualExpenses.change.hidden"))
)))
case ConsolidatedOrIndividualExpenses(false, _) =>
Some(SummaryListRowViewModel(
key = KeyViewModel("consolidatedOrIndividualExpenses.checkYourAnswersLabel").withCssClass(keyCssClass),
value = ValueViewModel("site.no").withCssClass(valueCssClass),
actions = Seq(
ActionItemViewModel("site.change",
ConsolidatedOrIndividualExpensesController.onPageLoad(taxYear, countryCode, CheckMode).url)
.withVisuallyHiddenText(messages("consolidatedOrIndividualExpenses.change.hidden"))
)
))
case _ => Option.empty[SummaryListRow]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
@*
* Copyright 2024 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 controllers.foreign.expenses.routes.ConsolidatedOrIndividualExpensesController
@import viewmodels.InputWidth._

@this(
layout: templates.Layout,
formHelper: FormWithCSRF,
govukErrorSummary: GovukErrorSummary,
govukRadios: GovukRadios,
govukButton: GovukButton,
govukInput: GovukInput
)

@(form: Form[_], mode: Mode, individualOrAgent: String, taxYear: Int, countryCode: String)(implicit request: Request[_], messages: Messages)

@layout(pageTitle = title(form, messages("consolidatedOrIndividualExpenses.title"))) {

@formHelper(action = ConsolidatedOrIndividualExpensesController.onSubmit(taxYear, countryCode, mode), 'autoComplete -> "off") {

@if(form.errors.nonEmpty) {
@govukErrorSummary(ErrorSummaryViewModel(form, errorLinkOverrides = Map("value" -> "value_0")))
}

<h1 class="govuk-heading-l">@messages("consolidatedOrIndividualExpenses.heading")</h1>

<p class="govuk-body">@messages(s"consolidatedOrIndividualExpenses.p1.${individualOrAgent}")</p>

<ul id="list" class="govuk-list govuk-list--bullet" >
<li>@messages("consolidatedOrIndividualExpenses.bullet1")</li>
<li>@messages("consolidatedOrIndividualExpenses.bullet2")</li>
<li>@messages("consolidatedOrIndividualExpenses.bullet3")</li>
<li>@messages("consolidatedOrIndividualExpenses.bullet4")</li>
<li>@messages("consolidatedOrIndividualExpenses.bullet5")</li>
<li>@messages("consolidatedOrIndividualExpenses.bullet6")</li>
</ul>

<p class="govuk-body">@messages(s"consolidatedOrIndividualExpenses.p2")</p>

<p class="govuk-body">@messages(s"consolidatedOrIndividualExpenses.p3.${individualOrAgent}")</p>

@govukRadios(
RadiosViewModel.yesNoWithConditionalHtml(
field = form("consolidatedOrIndividualExpenses"),
legend = LegendViewModel(messages(s"consolidatedOrIndividualExpenses.radio.legend.${individualOrAgent}")).withCssClass("govuk-fieldset__legend govuk-fieldset__legend--m"),
items = RadiosViewModel.yesNoItemsWithConditionalHtml(
field = form("consolidatedOrIndividualExpenses"),
conditionalYesHtml = Some (
govukInput(
InputViewModel(
field = form("consolidatedExpensesAmount"),
label = LabelViewModel(messages("consolidatedOrIndividualExpenses.radio.question.label"))
)
.withWidth(Fixed10)
.withPrefix(PrefixOrSuffix(content = Text("£")))
)
),
yesText = messages("consolidatedOrIndividualExpenses.consolidatedExpenses"),
noText = messages("consolidatedOrIndividualExpenses.individualExpenses")
)
)
)
@govukButton(
ButtonViewModel(messages("site.continue"))
)
}
}
5 changes: 5 additions & 0 deletions conf/foreign.routes
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,8 @@ GET /:taxYear/foreign-property/expenses/:countryCode/foreign-other-allowa
POST /:taxYear/foreign-property/expenses/:countryCode/foreign-other-allowable-property-expenses controllers.foreign.expenses.ForeignOtherAllowablePropertyExpensesController.onSubmit(taxYear: Int, countryCode: String, mode: Mode = NormalMode)
GET /:taxYear/foreign-property/expenses/:countryCode/change-foreign-other-allowable-property-expenses controllers.foreign.expenses.ForeignOtherAllowablePropertyExpensesController.onPageLoad(taxYear: Int, countryCode: String, mode: Mode = CheckMode)
POST /:taxYear/foreign-property/expenses/:countryCode/change-foreign-other-allowable-property-expenses controllers.foreign.expenses.ForeignOtherAllowablePropertyExpensesController.onSubmit(taxYear: Int, countryCode: String, mode: Mode = CheckMode)

GET /:taxYear/foreign-property/expenses/:countryCode/consolidated-or-individual-expenses controllers.foreign.expenses.ConsolidatedOrIndividualExpensesController.onPageLoad(taxYear: Int, countryCode: String, mode: Mode = NormalMode)
POST /:taxYear/foreign-property/expenses/:countryCode/consolidated-or-individual-expenses controllers.foreign.expenses.ConsolidatedOrIndividualExpensesController.onSubmit(taxYear: Int, countryCode: String, mode: Mode = NormalMode)
GET /:taxYear/foreign-property/expenses/:countryCode/change-consolidated-or-individual-expenses controllers.foreign.expenses.ConsolidatedOrIndividualExpensesController.onPageLoad(taxYear: Int, countryCode: String, mode: Mode = CheckMode)
POST /:taxYear/foreign-property/expenses/:countryCode/change-consolidated-or-individual-expenses controllers.foreign.expenses.ConsolidatedOrIndividualExpensesController.onSubmit(taxYear: Int, countryCode: String, mode: Mode = CheckMode)
32 changes: 31 additions & 1 deletion conf/messages.cy
Original file line number Diff line number Diff line change
Expand Up @@ -2175,4 +2175,34 @@ foreignOtherAllowablePropertyExpenses.error.nonNumeric.agent = *Missing Welsh*
foreignOtherAllowablePropertyExpenses.error.required.individual = *Missing Welsh*
foreignOtherAllowablePropertyExpenses.error.required.agent = *Missing Welsh*
foreignOtherAllowablePropertyExpenses.error.outOfRange = *Missing Welsh*
foreignOtherAllowablePropertyExpenses.change.hidden = *Missing Welsh*
foreignOtherAllowablePropertyExpenses.change.hidden = *Missing Welsh*

consolidatedOrIndividualExpenses.title = *Missing Welsh*
consolidatedOrIndividualExpenses.heading = *Missing Welsh*
consolidatedOrIndividualExpenses.p1.individual = *Missing Welsh*
consolidatedOrIndividualExpenses.p1.agent = *Missing Welsh*
consolidatedOrIndividualExpenses.bullet1 = *Missing Welsh*
consolidatedOrIndividualExpenses.bullet2 = *Missing Welsh*
consolidatedOrIndividualExpenses.bullet3 = *Missing Welsh*
consolidatedOrIndividualExpenses.bullet4 = *Missing Welsh*
consolidatedOrIndividualExpenses.bullet5 = *Missing Welsh*
consolidatedOrIndividualExpenses.bullet6 = *Missing Welsh*
consolidatedOrIndividualExpenses.p2 = *Missing Welsh*
consolidatedOrIndividualExpenses.p3.individual = *Missing Welsh*
consolidatedOrIndividualExpenses.p3.agent = *Missing Welsh*
consolidatedOrIndividualExpenses.consolidatedExpenses = *Missing Welsh*
consolidatedOrIndividualExpenses.individualExpenses = *Missing Welsh*
consolidatedOrIndividualExpenses.radio.legend.individual = *Missing Welsh*
consolidatedOrIndividualExpenses.radio.legend.agent = *Missing Welsh*
consolidatedOrIndividualExpenses.radio.question.label = *Missing Welsh*
consolidatedOrIndividualExpenses.checkYourAnswersLabel = *Missing Welsh*
consolidatedOrIndividualExpenses.error.required.individual = *Missing Welsh*
consolidatedOrIndividualExpenses.error.required.agent = *Missing Welsh*
consolidatedOrIndividualExpenses.amount.error.required.individual = *Missing Welsh*
consolidatedOrIndividualExpenses.amount.error.required.agent = *Missing Welsh*
consolidatedOrIndividualExpenses.amount.error.twoDecimalPlaces.individual = *Missing Welsh*
consolidatedOrIndividualExpenses.amount.error.twoDecimalPlaces.agent = *Missing Welsh*
consolidatedOrIndividualExpenses.amount.error.nonNumerical.individual = *Missing Welsh*
consolidatedOrIndividualExpenses.amount.error.nonNumerical.agent = *Missing Welsh*
consolidatedOrIndividualExpenses.amount.error.outOfRange = *Missing Welsh*
consolidatedOrIndividualExpenses.change.hidden = *Missing Welsh*
Loading