Skip to content

Commit 69dfa10

Browse files
authored
Merge pull request #196 from hmrc/DTR-3002
DTR-3002 Crown Employment Relief
2 parents 961e2d2 + 0ed680d commit 69dfa10

File tree

15 files changed

+762
-28
lines changed

15 files changed

+762
-28
lines changed
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2026 HM Revenue & Customs
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package controllers.ukResidency
18+
19+
import controllers.actions.*
20+
import forms.ukResidency.CrownEmploymentReliefFormProvider
21+
import models.land.LandTypeOfProperty
22+
import models.Mode
23+
import navigation.Navigator
24+
import pages.ukResidency.CrownEmploymentReliefPage
25+
import play.api.data.Form
26+
import play.api.i18n.{I18nSupport, MessagesApi}
27+
import play.api.mvc.{Action, AnyContent, MessagesControllerComponents}
28+
import repositories.SessionRepository
29+
import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendBaseController
30+
import views.html.ukResidency.CrownEmploymentReliefView
31+
32+
import javax.inject.Inject
33+
import scala.concurrent.{ExecutionContext, Future}
34+
35+
class CrownEmploymentReliefController @Inject()(
36+
override val messagesApi: MessagesApi,
37+
sessionRepository: SessionRepository,
38+
navigator: Navigator,
39+
identify: IdentifierAction,
40+
getData: DataRetrievalAction,
41+
requireData: DataRequiredAction,
42+
formProvider: CrownEmploymentReliefFormProvider,
43+
val controllerComponents: MessagesControllerComponents,
44+
view: CrownEmploymentReliefView
45+
)(implicit ec: ExecutionContext) extends FrontendBaseController with I18nSupport {
46+
47+
val form: Form[Boolean] = formProvider()
48+
49+
def onPageLoad(mode: Mode): Action[AnyContent] = (identify andThen getData andThen requireData) {
50+
implicit request =>
51+
52+
val preparedForm = request.userAnswers.get(CrownEmploymentReliefPage) match {
53+
case None => form
54+
case Some(value) => form.fill(value)
55+
}
56+
57+
val propertyType = request.userAnswers.fullReturn
58+
.flatMap(_.land)
59+
.flatMap(_.headOption)
60+
.flatMap(_.propertyType)
61+
.flatMap(LandTypeOfProperty.enumerable.withName)
62+
63+
propertyType match {
64+
case Some(LandTypeOfProperty.Residential | LandTypeOfProperty.Additional) =>
65+
Ok(view(preparedForm, mode))
66+
67+
//TODO - DTR-2511 - SPRINT 12 - update to UK residency check your answers
68+
case _ => Redirect(controllers.routes.JourneyRecoveryController.onPageLoad())
69+
}
70+
}
71+
72+
def onSubmit(mode: Mode): Action[AnyContent] = (identify andThen getData andThen requireData).async {
73+
implicit request =>
74+
75+
form.bindFromRequest().fold(
76+
formWithErrors =>
77+
Future.successful(BadRequest(view(formWithErrors, mode))),
78+
79+
value =>
80+
for {
81+
updatedAnswers <- Future.fromTry(request.userAnswers.set(CrownEmploymentReliefPage, value))
82+
_ <- sessionRepository.set(updatedAnswers)
83+
} yield Redirect(navigator.nextPage(CrownEmploymentReliefPage, mode, updatedAnswers))
84+
)
85+
}
86+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2026 HM Revenue & Customs
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package forms.ukResidency
18+
19+
import forms.mappings.Mappings
20+
import play.api.data.Form
21+
22+
import javax.inject.Inject
23+
24+
class CrownEmploymentReliefFormProvider @Inject() extends Mappings {
25+
26+
def apply(): Form[Boolean] =
27+
Form(
28+
"value" -> boolean("ukResidency.crownEmploymentRelief.error.required")
29+
)
30+
}

app/navigation/Navigator.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import pages.purchaserAgent.*
2525
import pages.vendor.*
2626
import pages.vendorAgent.*
2727
import pages.land.*
28+
import pages.ukResidency.CrownEmploymentReliefPage
2829
import play.api.mvc.Call
2930

3031
import javax.inject.{Inject, Singleton}
@@ -61,6 +62,7 @@ class Navigator @Inject()() {
6162

6263
case purchaserPage if isPurchaserSection(purchaserPage) => purchaserRoutes(purchaserPage)
6364
case landPage if isLandSection(landPage) => landRoutes(landPage)
65+
case uKResidencyPage if isUkResidencySection(uKResidencyPage) => ukResidencyRoutes(uKResidencyPage)
6466
case _ => _ => routes.IndexController.onPageLoad()
6567
}
6668

@@ -172,6 +174,20 @@ class Navigator @Inject()() {
172174
case _ => _ => routes.IndexController.onPageLoad()
173175
}
174176

177+
private def isUkResidencySection(page: Page): Boolean = page match {
178+
179+
case CrownEmploymentReliefPage => true
180+
181+
case _ => false
182+
}
183+
184+
private def ukResidencyRoutes(page: Page): UserAnswers => Call = page match {
185+
case CrownEmploymentReliefPage => //TODO - DTR-2511 - SPRINT 12 - update to UK residency check your answers
186+
_ => controllers.ukResidency.routes.CrownEmploymentReliefController.onPageLoad(NormalMode)
187+
188+
case _ => _ => routes.IndexController.onPageLoad()
189+
}
190+
175191
private val checkRouteMap: Page => UserAnswers => Call = {
176192
case WhoIsMakingThePurchasePage => _ => controllers.purchaser.routes.PurchaserCheckYourAnswersController.onPageLoad()
177193
case NameOfPurchaserPage => _ => controllers.purchaser.routes.PurchaserCheckYourAnswersController.onPageLoad()
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright 2026 HM Revenue & Customs
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package pages.ukResidency
18+
19+
import pages.QuestionPage
20+
import play.api.libs.json.JsPath
21+
22+
case object CrownEmploymentReliefPage extends QuestionPage[Boolean] {
23+
24+
override def path: JsPath = JsPath \ "ukResidencyCurrent" \ toString
25+
26+
override def toString: String = "crownEmploymentRelief"
27+
}

app/pages/ukResidency/placeholder

Whitespace-only changes.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2026 HM Revenue & Customs
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package viewmodels.checkAnswers.ukResidency
18+
19+
import models.{CheckMode, UserAnswers}
20+
import pages.ukResidency.CrownEmploymentReliefPage
21+
import play.api.i18n.Messages
22+
import uk.gov.hmrc.govukfrontend.views.Aliases.HtmlContent
23+
import uk.gov.hmrc.govukfrontend.views.viewmodels.summarylist.SummaryListRow
24+
import viewmodels.govuk.summarylist.*
25+
import viewmodels.implicits.*
26+
27+
object CrownEmploymentReliefSummary {
28+
29+
def row(answers: UserAnswers)(implicit messages: Messages): SummaryListRow =
30+
31+
val changeRoute = controllers.ukResidency.routes.CrownEmploymentReliefController.onPageLoad(CheckMode).url
32+
val label = messages("ukResidency.crownEmploymentRelief.checkYourAnswersLabel")
33+
34+
answers.get(CrownEmploymentReliefPage).map {
35+
answer =>
36+
37+
val value = if (answer) "site.yes" else "site.no"
38+
39+
SummaryListRowViewModel(
40+
key = label,
41+
value = ValueViewModel(value),
42+
actions = Seq(
43+
ActionItemViewModel("site.change", changeRoute)
44+
.withVisuallyHiddenText(messages("ukResidency.crownEmploymentRelief.change.hidden"))
45+
)
46+
)
47+
}.getOrElse {
48+
val value = ValueViewModel(
49+
HtmlContent(
50+
s"""<a href="$changeRoute" class="govuk-link">${messages("ukResidency.crownEmploymentRelief.missing")}</a>""")
51+
)
52+
SummaryListRowViewModel(
53+
key = label,
54+
value = value
55+
)
56+
}
57+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
@*
2+
* Copyright 2026 HM Revenue & Customs
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*@
16+
17+
@import views.html.components._
18+
19+
@this(
20+
layout: templates.Layout,
21+
formHelper: FormWithCSRF,
22+
govukErrorSummary: GovukErrorSummary,
23+
govukRadios: GovukRadios,
24+
govukButton: GovukButton,
25+
caption: Caption,
26+
h1:h1,
27+
p: Paragraph
28+
)
29+
30+
@(form: Form[_], mode: Mode)(implicit request: Request[_], messages: Messages)
31+
32+
@layout(pageTitle = title(form, messages("ukResidency.crownEmploymentRelief.title"), section = Some(messages("site.ukResidency.caption")))) {
33+
34+
@formHelper(action = controllers.ukResidency.routes.CrownEmploymentReliefController.onSubmit(mode), Symbol("autoComplete") -> "off") {
35+
36+
@if(form.errors.nonEmpty) {
37+
@govukErrorSummary(ErrorSummaryViewModel(form))
38+
}
39+
40+
@caption(messages("site.ukResidency.caption"))
41+
@h1(messages("ukResidency.crownEmploymentRelief.heading"))
42+
43+
@p(messages("ukResidency.crownEmploymentRelief.p1"))
44+
45+
@govukRadios(
46+
RadiosViewModel(
47+
field = form("value"),
48+
legend = LegendViewModel(messages("ukResidency.crownEmploymentRelief.subheading")).asPageSubHeading(),
49+
items = Seq(
50+
RadioItem(
51+
value = Some("true"),
52+
content = Text(messages("site.yes"))
53+
),
54+
RadioItem(
55+
value = Some("false"),
56+
content = Text(messages("site.no"))
57+
)
58+
)
59+
)
60+
)
61+
62+
@govukButton(
63+
ButtonViewModel(messages("site.save.continue"))
64+
)
65+
}
66+
}

conf/app.routes

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,3 +368,8 @@ GET /about-the-land/add-area-of-land
368368
POST /about-the-land/add-area-of-land controllers.land.DoYouKnowTheAreaOfLandController.onSubmit(mode: Mode = NormalMode)
369369
GET /about-the-land/add-area-of-land/change controllers.land.DoYouKnowTheAreaOfLandController.onPageLoad(mode: Mode = CheckMode)
370370
POST /about-the-land/add-area-of-land/change controllers.land.DoYouKnowTheAreaOfLandController.onSubmit(mode: Mode = CheckMode)
371+
372+
GET /about-UK-residency/crown-employment-relief controllers.ukResidency.CrownEmploymentReliefController.onPageLoad(mode: Mode = NormalMode)
373+
POST /about-UK-residency/crown-employment-relief controllers.ukResidency.CrownEmploymentReliefController.onSubmit(mode: Mode = NormalMode)
374+
GET /about-UK-residency/crown-employment-relief/change controllers.ukResidency.CrownEmploymentReliefController.onPageLoad(mode: Mode = CheckMode)
375+
POST /about-UK-residency/crown-employment-relief/change controllers.ukResidency.CrownEmploymentReliefController.onSubmit(mode: Mode = CheckMode)

0 commit comments

Comments
 (0)