Skip to content

Commit 074c7a6

Browse files
committed
DDCE-7698 Update declare the employee to exclude
1 parent 0f1ed35 commit 074c7a6

13 files changed

+510
-95
lines changed

app/controllers/ExclusionListController.scala

Lines changed: 98 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ package controllers
1919
import config.PbikAppConfig
2020
import connectors.PbikConnector
2121
import controllers.actions.{AuthAction, NoSessionCheckAction}
22-
import models._
22+
import models.*
2323
import models.auth.AuthenticatedRequest
2424
import models.form.MandatoryRadioButton
2525
import models.v1.IabdType.IabdType
26-
import models.v1.exclusion._
26+
import models.v1.exclusion.*
2727
import models.v1.trace.{TracePeopleByNinoRequest, TracePeopleByPersonalDetailsRequest, TracePersonResponse}
2828
import play.api.Logging
2929
import play.api.data.Form
3030
import play.api.i18n.{I18nSupport, Messages, MessagesApi}
31-
import play.api.mvc._
31+
import play.api.mvc.*
3232
import services.{BikListService, ExclusionService, SessionService}
3333
import uk.gov.hmrc.domain.EmpRef
3434
import uk.gov.hmrc.http.HeaderCarrier
@@ -37,10 +37,11 @@ import uk.gov.hmrc.play.bootstrap.controller.WithUnsafeDefaultFormBinding
3737
import uk.gov.hmrc.play.bootstrap.frontend.controller.FrontendController
3838
import uk.gov.hmrc.play.http.HeaderCarrierConverter
3939
import utils.Exceptions.InvalidBikTypeException
40-
import utils._
40+
import utils.*
4141
import views.html.ErrorPage
42-
import views.html.exclusion._
42+
import views.html.exclusion.*
4343

44+
import java.lang.ProcessBuilder
4445
import javax.inject.{Inject, Singleton}
4546
import scala.concurrent.{ExecutionContext, Future}
4647

@@ -65,6 +66,8 @@ class ExclusionListController @Inject() (
6566
ninoExclusionSearchFormView: NinoExclusionSearchForm,
6667
noNinoExclusionSearchFormView: NoNinoExclusionSearchForm,
6768
searchResultsView: SearchResults,
69+
searchResultsMPBIKView: SearchResultsMPBIK,
70+
declareEmployeeMPBIKView: DeclareEmployeeMPBIK,
6871
whatNextExclusionView: WhatNextExclusion,
6972
removalConfirmationView: RemovalConfirmation,
7073
whatNextRescindView: WhatNextRescind
@@ -433,7 +436,8 @@ class ExclusionListController @Inject() (
433436
(authenticate andThen noSessionCheck).async { implicit request =>
434437
if (exclusionsAllowed) {
435438
val futureResult = formType match {
436-
case ControllersReferenceDataCodes.FORM_TYPE_NINO => searchResultsByNino(isCurrentTaxYear, iabdType, formType)
439+
case ControllersReferenceDataCodes.FORM_TYPE_NINO =>
440+
searchResultsByNino(isCurrentTaxYear, iabdType, formType)
437441
case ControllersReferenceDataCodes.FORM_TYPE_NONINO =>
438442
searchResultsByPersonalDetails(isCurrentTaxYear, iabdType, formType)
439443
}
@@ -478,10 +482,11 @@ class ExclusionListController @Inject() (
478482
iabdType: IabdType,
479483
currentExclusions: List[TracePersonResponse]
480484
)(implicit request: AuthenticatedRequest[?]): Result = {
485+
val uniqueMatch: Int = 1
481486
val uniqueListOfMatches: List[TracePersonResponse] =
482487
exclusionService.searchResultsRemoveAlreadyExcluded(currentExclusions, listOfMatches)
483488
uniqueListOfMatches.size match {
484-
case 0 =>
489+
case 0 =>
485490
logger.warn("[ExclusionListController][searchResultsHandleValidResult] List of un-excluded matches is empty")
486491
val message = Messages("ExclusionSearch.Fail.Exists.P")
487492
if (listOfMatches.nonEmpty) {
@@ -511,17 +516,37 @@ class ExclusionListController @Inject() (
511516
)
512517
)
513518
}
514-
case _ =>
515-
Ok(
516-
searchResultsView(
517-
controllersReferenceData.yearRange,
518-
isCurrentTaxYear,
519-
iabdType,
520-
uniqueListOfMatches,
521-
formMappings.individualSelectionForm,
522-
formType
519+
case matches =>
520+
if (mpbikToggle) {
521+
if (matches == uniqueMatch) { // testing with n < 1
522+
Redirect(
523+
routes.ExclusionListController
524+
.declareEmployeeToExclude(isCurrentTaxYear, iabdType)
525+
)
526+
} else {
527+
Ok(
528+
searchResultsMPBIKView(
529+
controllersReferenceData.yearRange,
530+
isCurrentTaxYear,
531+
iabdType,
532+
uniqueListOfMatches,
533+
formMappings.individualSelectionForm,
534+
formType
535+
)
536+
)
537+
}
538+
} else {
539+
Ok(
540+
searchResultsView(
541+
controllersReferenceData.yearRange,
542+
isCurrentTaxYear,
543+
iabdType,
544+
uniqueListOfMatches,
545+
formMappings.individualSelectionForm,
546+
formType
547+
)
523548
)
524-
)
549+
}
525550
}
526551
}
527552

@@ -533,29 +558,53 @@ class ExclusionListController @Inject() (
533558
.bindFromRequest()
534559
.fold(
535560
formWithErrors =>
536-
Future.successful(
537-
BadRequest(
538-
searchResultsView(
539-
controllersReferenceData.yearRange,
540-
year,
541-
iabdType,
542-
session.get.listOfMatches.get.pbikExclusionList,
543-
formWithErrors,
544-
formType
561+
if (mpbikToggle) {
562+
Future.successful(
563+
BadRequest(
564+
searchResultsMPBIKView(
565+
controllersReferenceData.yearRange,
566+
year,
567+
iabdType,
568+
session.get.listOfMatches.get.pbikExclusionList,
569+
formWithErrors,
570+
formType
571+
)
545572
)
546573
)
547-
),
574+
} else {
575+
Future.successful(
576+
BadRequest(
577+
searchResultsView(
578+
controllersReferenceData.yearRange,
579+
year,
580+
iabdType,
581+
session.get.listOfMatches.get.pbikExclusionList,
582+
formWithErrors,
583+
formType
584+
)
585+
)
586+
)
587+
},
548588
values => {
549589
val individualsDetails: Option[TracePersonResponse] =
550590
session.get.listOfMatches.get.pbikExclusionList
551591
.find(person => person.nationalInsuranceNumber == values.nino)
552-
validateRequest(year, iabdType).flatMap { _ =>
553-
commitExclusion(
554-
year,
555-
iabdType,
556-
session.get.listOfMatches.get.updatedEmployerOptimisticLock,
557-
individualsDetails
592+
if (mpbikToggle && individualsDetails.isDefined) {
593+
Future.successful(
594+
Redirect(
595+
routes.ExclusionListController
596+
.declareEmployeeToExclude(year, iabdType)
597+
)
558598
)
599+
} else {
600+
validateRequest(year, iabdType).flatMap { _ =>
601+
commitExclusion(
602+
year,
603+
iabdType,
604+
session.get.listOfMatches.get.updatedEmployerOptimisticLock,
605+
individualsDetails
606+
)
607+
}
559608
}
560609
}
561610
)
@@ -575,6 +624,22 @@ class ExclusionListController @Inject() (
575624
}
576625
}
577626

627+
def declareEmployeeToExclude(year: String, iabdType: IabdType): Action[AnyContent] =
628+
(authenticate andThen noSessionCheck).async { implicit request =>
629+
val resultFuture = for {
630+
_ <- validateRequest(year, iabdType)
631+
session <- sessionService.fetchPbikSession()
632+
} yield Ok(
633+
declareEmployeeMPBIKView(
634+
controllersReferenceData.yearRange,
635+
year,
636+
iabdType,
637+
session.get.listOfMatches.get.pbikExclusionList.head
638+
)
639+
)
640+
controllersReferenceData.responseErrorHandler(resultFuture)
641+
}
642+
578643
def commitExclusion(
579644
year: String,
580645
iabdType: IabdType,

app/utils/FormMappings.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ class FormMappings @Inject() (pbikAppConfig: PbikAppConfig, val messagesApi: Mes
170170
.verifying(
171171
Messages("error.incorrect.nino"),
172172
nino => cleanupNino(nino).isEmpty || cleanupNino(nino).matches(validNinoFormat)
173-
)
173+
)
174174
)((firstname, surname, nino) =>
175175
NinoForm(
176176
firstname.trim,
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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 utils.TaxDateUtils
18+
@import views.templatemodels.PageTitle
19+
20+
@this(
21+
govukLayoutWrapper: GovukLayoutWrapper,
22+
govukWarningText: GovukWarningText,
23+
formWithCSRF: FormWithCSRF,
24+
taxDateUtils: TaxDateUtils
25+
)
26+
27+
@(
28+
taxYearRange: TaxYearRange,
29+
year: String,
30+
iabdType: IabdType,
31+
employee: TracePersonResponse
32+
)(implicit request: AuthenticatedRequest[?], messages: Messages)
33+
34+
@expenseOrBenefit = @{messages(s"BenefitInKind.label.${iabdType.id}")}
35+
@govukLayoutWrapper(PageTitle(messages("ExclusionSearchMPBIK.title.single"))) {
36+
37+
<h1 id="title" class="govuk-heading-xl">
38+
<span class="govuk-caption-l">@messages("ExclusionSearchMPBIK.title.span.taxYear.cy0", s"${taxYearRange.cy}", s"${taxYearRange.cyplus1}")</span>
39+
@messages("ExclusionSearchMPBIK.title.single")
40+
</h1>
41+
42+
<h2 class="govuk-heading-m">@expenseOrBenefit</h2>
43+
44+
<p class="govuk-body">@messages("ExclusionImportantMPBIK.Reminder", expenseOrBenefit, taxDateUtils.getDisplayTodayDate())</p>
45+
46+
<div class="govuk-inset-text">
47+
@messages("ExclusionSearchMPBIK.inset.text")
48+
</div>
49+
50+
<dl class="govuk-summary-list">
51+
<div class="govuk-summary-list__row">
52+
<dt class="govuk-summary-list__key">@messages("ExclusionSearchMPBIK.field.employee.name")</dt>
53+
<dd class="govuk-summary-list__value">@employee.fullName</dd>
54+
</div>
55+
<div class="govuk-summary-list__row">
56+
<dt class="govuk-summary-list__key">@messages("ExclusionSearchMPBIK.field.employee.ni")</dt>
57+
<dd class="govuk-summary-list__value">@employee.nationalInsuranceNumber</dd>
58+
</div>
59+
<div class="govuk-summary-list__row">
60+
<dt class="govuk-summary-list__key">@messages("ExclusionSearchMPBIK.field.employee.payrollId")</dt>
61+
<dd class="govuk-summary-list__value">@employee.getWorksPayrollNumber</dd>
62+
</div>
63+
</dl>
64+
65+
@govukWarningText(WarningText(
66+
iconFallbackText = Some(messages("site.warning")),
67+
content = Text(messages("ExclusionSearchMPBIK.warning"))
68+
))
69+
70+
<p id="i-confirm-that" class="govuk-body">
71+
@messages("ExclusionSearchMPBIK.confirmation." + request.userType + ".p")
72+
</p>
73+
<ul class="govuk-list govuk-list--bullet">
74+
<li>@messages("ExclusionSearchMPBIK.confirmation." + request.userType + ".bullet1")</li>
75+
<li>@messages("ExclusionSearchMPBIK.confirmation." + request.userType + ".bullet2")</li>
76+
</ul>
77+
78+
<p id="i-understand-that" class="govuk-body">
79+
@messages("ExclusionSearchMPBIK.understanding." + request.userType + ".p")
80+
</p>
81+
<ul class="govuk-list govuk-list--bullet">
82+
<li>@messages("ExclusionSearchMPBIK.understanding." + request.userType + ".bullet1")</li>
83+
</ul>
84+
85+
<div class="govuk-form-group">
86+
<a href="@routes.ExclusionListController.updateExclusions(year, iabdType)" class="govuk-button" role="button" id="button-confirm">@messages("Service.confirmAndContinue")</a>
87+
</div>
88+
89+
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
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 utils.TaxDateUtils
18+
@import views.templatemodels.PageTitle
19+
20+
@this(
21+
govukLayoutWrapper: GovukLayoutWrapper,
22+
govukWarningText: GovukWarningText,
23+
formWithCSRF: FormWithCSRF,
24+
taxDateUtils: TaxDateUtils
25+
)
26+
27+
@(
28+
taxYearRange: TaxYearRange,
29+
year: String,
30+
iabdType: IabdType,
31+
listOfMatches: List[TracePersonResponse],
32+
listOfMatchesForm: Form[ExclusionNino],
33+
formType: String
34+
)(implicit request: Request[?], messages: Messages)
35+
36+
@govukLayoutWrapper(PageTitle(messages("ExclusionSearchMPBIK.title.multiple"), listOfMatchesForm.hasErrors)) {
37+
38+
@if(listOfMatchesForm("individualNino").hasErrors) {
39+
<div class="govuk-error-summary" role="group" aria-labelledby="error-summary-heading-1" tabindex="-1">
40+
41+
<h2 class="govuk-error-summary__title" id="error-summary-heading-1">
42+
@messages("Service.errorSummary.heading")
43+
</h2>
44+
<div class="govuk-error-summary__body">
45+
<ul class="govuk-list govuk-error-summary__list">
46+
@if(listOfMatchesForm.error("individualNino").get.message.equals("error.required")){
47+
<li><a href="#employee-radio-0">@messages("error.exclusion.multi.selection")</a></li>
48+
}
49+
</ul>
50+
</div>
51+
</div>
52+
}
53+
54+
<h1 id="title" class="govuk-heading-xl">
55+
<span class="govuk-caption-l">@messages("ExclusionSearchMPBIK.form.tax.year", s"${taxYearRange.cy}", s"${taxYearRange.cyplus1}")</span>
56+
@messages("ExclusionSearchMPBIK.title.multiple")
57+
</h1>
58+
59+
@formWithCSRF(action = routes.ExclusionListController.updateMultipleExclusions(year, iabdType, formType)) {
60+
<div class="govuk-form-group @if(listOfMatchesForm("individualNino").hasErrors){govuk-form-group--error}">
61+
<fieldset class="govuk-fieldset" @if(listOfMatchesForm("individualNino").hasErrors){aria-describedby="selection-error"}>
62+
@if(listOfMatchesForm("individualNino").hasErrors) {
63+
<span id="selection-error" class="govuk-error-message">
64+
<span class="govuk-visually-hidden">@messages("Service.error"):</span>
65+
@messages("error.exclusion.multi.selection")
66+
</span>
67+
}
68+
<div class="govuk-radios">
69+
@for(index <- 0 until listOfMatches.length) {
70+
<div class="govuk-radios__item">
71+
<input class="govuk-radios__input" id="employee-radio-@index" type="radio" aria-describedby="employee-hint-@index" name="individualNino" value='@listOfMatches(index).nationalInsuranceNumber'>
72+
<label class="govuk-label govuk-radios__label govuk-label--s" for="employee-radio-@index">
73+
@listOfMatches(index).fullName
74+
</label>
75+
<span id="employee-hint-@index" class="govuk-hint govuk-radios__hint">
76+
@messages("Service.field.nino"): @listOfMatches(index).nationalInsuranceNumber <br>
77+
@messages("Service.field.worksnumber"): @listOfMatches(index).getWorksPayrollNumber
78+
</span>
79+
</div>
80+
}
81+
</div>
82+
</fieldset>
83+
</div>
84+
85+
<input type="submit" class="govuk-button" role="button" id="button-continue" value="@messages("Service.continueMPBIK")">
86+
}
87+
}

0 commit comments

Comments
 (0)