Skip to content

Commit 120f869

Browse files
authored
APB-10940 Sole trader journey enabled inc individual journey (#212)
1 parent c9c68f1 commit 120f869

File tree

25 files changed

+579
-47
lines changed

25 files changed

+579
-47
lines changed

app/uk/gov/hmrc/agentregistrationfrontend/RoutesExports.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package uk.gov.hmrc.agentregistrationfrontend
1818

1919
object RoutesExports:
2020

21+
export uk.gov.hmrc.play.bootstrap.binders.RedirectUrl
2122
export uk.gov.hmrc.agentregistration.shared.BusinessType
2223
export uk.gov.hmrc.agentregistration.shared.AgentType
2324
export uk.gov.hmrc.agentregistration.shared.LinkId

app/uk/gov/hmrc/agentregistrationfrontend/config/AppConfig.scala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class AppConfig @Inject() (
7777
.addParam("affinityGroup", affinityGroup.toString.toLowerCase)
7878

7979
def applicationLink(linkId: String): Uri = uri"$thisFrontendBaseUrl/agent-registration/provide-details/start/$linkId"
80+
def soleTraderProvideDetailsLink(linkId: String): Uri = uri"$thisFrontendBaseUrl/agent-registration/provide-details/match-application/$linkId"
8081

8182
val welshLanguageSupportEnabled: Boolean = configuration.getOptional[Boolean]("features.welsh-language-support").getOrElse(false)
8283
val contactFrontendId: String = configuration.get[String]("contact-frontend.serviceId") // TODO placeholder
@@ -85,6 +86,8 @@ class AppConfig @Inject() (
8586
.getOptional[Boolean]("ignoreEmailVerification")
8687
.getOrElse(false)
8788

89+
val allowedRedirectHosts: Set[String] = configuration.getOptional[Seq[String]]("allowed-redirect-hosts").getOrElse(Nil).toSet
90+
8891
/*
8992
* GRS CONFIG START
9093
*/

app/uk/gov/hmrc/agentregistrationfrontend/controllers/AppRoutes.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import uk.gov.hmrc.agentregistrationfrontend.controllers.applicant.checkfailed.r
2727
import uk.gov.hmrc.agentregistrationfrontend.controllers.applicant.applicantcontactdetails.routes as applicantcontactdetailsRoutes
2828
import uk.gov.hmrc.agentregistrationfrontend.controllers.applicant.agentdetails.routes as agentdetailsRoutes
2929
import uk.gov.hmrc.agentregistrationfrontend.controllers.applicant.listdetails.routes as listdetailsRoutes
30+
import uk.gov.hmrc.agentregistrationfrontend.controllers.applicant.listdetails.soletrader.routes as listdetailsSoleTraderRoutes
3031
import uk.gov.hmrc.agentregistrationfrontend.controllers.applicant.listdetails.nonincorporated.routes as listdetailsNonIncorporatedRoutes
3132
import uk.gov.hmrc.agentregistrationfrontend.controllers.applicant.listdetails.incorporated.routes as listdetailsIncorporatedRoutes
3233
import uk.gov.hmrc.agentregistrationfrontend.controllers.applicant.listdetails.otherrelevantindividuals.routes as listdetailsOthersRoutes
@@ -94,6 +95,10 @@ object AppRoutes:
9495

9596
val CheckYourAnswersController = listdetailsRoutes.CheckYourAnswersController
9697

98+
object soletrader:
99+
100+
val ProveIdentityController = listdetailsSoleTraderRoutes.ProveIdentityController
101+
97102
object nonincorporated:
98103

99104
val NumberOfKeyIndividualsController = listdetailsNonIncorporatedRoutes.NumberOfKeyIndividualsController

app/uk/gov/hmrc/agentregistrationfrontend/controllers/SignOutController.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ import play.api.mvc.Action
2020
import play.api.mvc.AnyContent
2121
import play.api.mvc.DefaultActionBuilder
2222
import play.api.mvc.MessagesControllerComponents
23-
import play.api.mvc.Result
2423
import sttp.model.Uri.UriContext
2524
import uk.gov.hmrc.agentregistrationfrontend.config.AppConfig
25+
import uk.gov.hmrc.agentregistrationfrontend.util.RequestSupport.validateRedirectUrl
2626
import uk.gov.hmrc.agentregistrationfrontend.views.html.TimedOutPage
27+
import uk.gov.hmrc.play.bootstrap.binders.RedirectUrl
2728

2829
import javax.inject.Inject
2930
import javax.inject.Singleton
@@ -37,17 +38,18 @@ class SignOutController @Inject() (
3738
)
3839
extends FrontendControllerBase(mcc):
3940

40-
private def signOutWithContinue(continue: String): Result =
41-
val signOutAndRedirectUrl: String = uri"""${appConfig.basFrontendSignOutUrlBase}?${Map("continue" -> continue)}""".toString
41+
def signOutWithContinue(continue: RedirectUrl): Action[AnyContent] = defaultActionBuilder:
42+
val validatedRedirect = validateRedirectUrl(continue, appConfig.allowedRedirectHosts)
43+
val signOutAndRedirectUrl: String = uri"""${appConfig.basFrontendSignOutUrlBase}?${Map("continue" -> validatedRedirect)}""".toString
4244
Redirect(signOutAndRedirectUrl)
4345

4446
def signOut: Action[AnyContent] = defaultActionBuilder:
4547
val continueUrl = uri"${appConfig.thisFrontendBaseUrl + AppRoutes.apply.AgentApplicationController.landing.url}"
46-
signOutWithContinue(continueUrl.toString)
48+
Redirect(AppRoutes.SignOutController.signOutWithContinue(RedirectUrl(continueUrl.toString)))
4749

4850
def timeOut: Action[AnyContent] = defaultActionBuilder:
4951
val continueUrl = uri"${appConfig.thisFrontendBaseUrl + AppRoutes.SignOutController.timedOut.url}"
50-
signOutWithContinue(continueUrl.toString)
52+
Redirect(AppRoutes.SignOutController.signOutWithContinue(RedirectUrl(continueUrl.toString)))
5153

5254
def timedOut: Action[AnyContent] = defaultActionBuilder:
5355
implicit request =>

app/uk/gov/hmrc/agentregistrationfrontend/controllers/applicant/aboutyourbusiness/UserRoleController.scala

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import play.api.mvc.*
2020
import uk.gov.hmrc.agentregistration.shared.BusinessType
2121
import uk.gov.hmrc.agentregistration.shared.BusinessType.SoleTrader
2222
import uk.gov.hmrc.agentregistration.shared.UserRole
23+
import uk.gov.hmrc.agentregistration.shared.util.SafeEquals.===
2324
import uk.gov.hmrc.agentregistrationfrontend.action.applicant.ApplicantActions
2425
import uk.gov.hmrc.agentregistrationfrontend.controllers.applicant.FrontendController
2526
import uk.gov.hmrc.agentregistrationfrontend.forms.UserRoleForm
@@ -49,9 +50,10 @@ extends FrontendController(mcc, actions):
4950
.ensureValidForm(UserRoleForm.form, implicit request => view(_, userRoleOptionForBusinessType(request.getBusinessType))):
5051
implicit request =>
5152
val userRole: UserRole = request.get
52-
Redirect(
53-
AppRoutes.apply.aboutyourbusiness.TypeOfSignInController.show.url
54-
).addToSession(userRole)
53+
// TODO: currently no support for non-business owners on the sole trader journey, but there will be once designed
54+
if request.getBusinessType === SoleTrader && userRole === UserRole.Authorised
55+
then Redirect(AppRoutes.apply.AgentApplicationController.genericExitPage).addToSession(userRole)
56+
else Redirect(AppRoutes.apply.aboutyourbusiness.TypeOfSignInController.show.url).addToSession(userRole)
5557

5658
private def userRoleOptionForBusinessType(
5759
businessType: BusinessType

app/uk/gov/hmrc/agentregistrationfrontend/controllers/applicant/internal/GrsController.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,10 @@ import uk.gov.hmrc.agentregistration.shared.businessdetails.BusinessDetailsSoleT
3232
import uk.gov.hmrc.agentregistration.shared.util.Errors.getOrThrowExpectedDataMissing
3333
import uk.gov.hmrc.agentregistration.shared.util.SafeEquals.===
3434
import uk.gov.hmrc.agentregistrationfrontend.action.applicant.ApplicantActions
35-
import uk.gov.hmrc.agentregistrationfrontend.controllers.applicant.internal.GrsController.*
3635
import uk.gov.hmrc.agentregistrationfrontend.controllers.applicant.FrontendController
37-
import uk.gov.hmrc.agentregistrationfrontend.model.grs.JourneyId
36+
import uk.gov.hmrc.agentregistrationfrontend.controllers.applicant.internal.GrsController.*
3837
import uk.gov.hmrc.agentregistrationfrontend.model.grs.JourneyData
38+
import uk.gov.hmrc.agentregistrationfrontend.model.grs.JourneyId
3939
import uk.gov.hmrc.agentregistrationfrontend.model.grs.RegistrationStatus
4040
import uk.gov.hmrc.agentregistrationfrontend.services.GrsService
4141
import uk.gov.hmrc.agentregistrationfrontend.services.applicant.AgentApplicationService
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/*
2+
* Copyright 2025 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 uk.gov.hmrc.agentregistrationfrontend.controllers.applicant.listdetails.soletrader
18+
19+
import com.softwaremill.quicklens.modify
20+
import play.api.mvc.Action
21+
import play.api.mvc.AnyContent
22+
import play.api.mvc.MessagesControllerComponents
23+
import uk.gov.hmrc.agentregistration.shared.AgentApplication
24+
import uk.gov.hmrc.agentregistration.shared.AgentApplicationSoleTrader
25+
import uk.gov.hmrc.agentregistration.shared.UserRole
26+
import uk.gov.hmrc.agentregistration.shared.businessdetails.BusinessDetailsSoleTrader
27+
import uk.gov.hmrc.agentregistration.shared.contactdetails.ApplicantContactDetails
28+
import uk.gov.hmrc.agentregistration.shared.individual.IndividualProvidedDetails
29+
import uk.gov.hmrc.agentregistration.shared.individual.IndividualVerifiedEmailAddress
30+
import uk.gov.hmrc.agentregistration.shared.individual.ProvidedDetailsState
31+
import uk.gov.hmrc.agentregistration.shared.lists.IndividualName
32+
import uk.gov.hmrc.agentregistration.shared.util.SafeEquals.===
33+
import uk.gov.hmrc.agentregistrationfrontend.action.applicant.ApplicantActions
34+
import uk.gov.hmrc.agentregistrationfrontend.controllers.applicant.FrontendController
35+
import uk.gov.hmrc.agentregistrationfrontend.services.individual.IndividualProvideDetailsService
36+
import uk.gov.hmrc.agentregistrationfrontend.views.html.applicant.listdetails.soletrader.ProveIdentityPage
37+
38+
import javax.inject.Inject
39+
import javax.inject.Singleton
40+
41+
@Singleton
42+
class ProveIdentityController @Inject() (
43+
mcc: MessagesControllerComponents,
44+
actions: ApplicantActions,
45+
view: ProveIdentityPage,
46+
individualProvideDetailsService: IndividualProvideDetailsService
47+
)
48+
extends FrontendController(mcc, actions):
49+
50+
private type DataWithSoleTrader = IndividualProvidedDetails *: List[IndividualProvidedDetails] *: DataWithApplication
51+
52+
private val baseAction: ActionBuilderWithData[DataWithSoleTrader] = actions
53+
.getApplicationInProgress
54+
.refine(implicit request =>
55+
val agentApplication: AgentApplication = request.get
56+
individualProvideDetailsService.findAllByApplicationId(agentApplication.agentApplicationId).map: individualsList =>
57+
request.add[List[IndividualProvidedDetails]](individualsList)
58+
)
59+
.refine(implicit request =>
60+
val existingList: List[IndividualProvidedDetails] = request.get
61+
val agentApplication: AgentApplication = request.get
62+
existingList match
63+
case Nil =>
64+
agentApplication match
65+
case agentApplication: AgentApplicationSoleTrader =>
66+
val soleTraderDetails: BusinessDetailsSoleTrader = agentApplication.getBusinessDetails
67+
val applicantContactDetails: ApplicantContactDetails = agentApplication.getApplicantContactDetails
68+
val newRecord = individualProvideDetailsService.create(
69+
agentApplicationId = agentApplication.agentApplicationId,
70+
individualName = IndividualName(s"${soleTraderDetails.fullName.firstName} ${soleTraderDetails.fullName.lastName}"),
71+
isPersonOfControl = agentApplication.getUserRole === UserRole.Owner
72+
)
73+
.modify(_.providedDetailsState)
74+
.setTo(ProvidedDetailsState.AccessConfirmed) // no link to send - access to individual journey is provided by the task list
75+
.modify(_.isPersonOfControl)
76+
.setTo(agentApplication.getUserRole === UserRole.Owner)
77+
.modify(_.telephoneNumber)
78+
.setTo(applicantContactDetails.telephoneNumber)
79+
.modify(_.emailAddress)
80+
.setTo(Some(IndividualVerifiedEmailAddress(
81+
emailAddress = applicantContactDetails.getVerifiedEmail,
82+
isVerified = true
83+
)))
84+
.modify(_.hasApprovedApplication)
85+
.setTo(Some(true)) // Sole trader owners applicants are always approved as they are the same person
86+
.modify(_.hmrcStandardForAgentsAgreed)
87+
.setTo(agentApplication.hmrcStandardForAgentsAgreed)
88+
individualProvideDetailsService
89+
.upsertForApplication(newRecord)
90+
.map: _ =>
91+
request.add[IndividualProvidedDetails](newRecord)
92+
case _: AgentApplication.IsNotSoleTrader =>
93+
logger.warn(s"Unexpected application type for sole trader details, applicationId:[${agentApplication.agentApplicationId}], redirect to task list for overview")
94+
Redirect(AppRoutes.apply.TaskListController.show)
95+
case soleTrader :: Nil => request.add[IndividualProvidedDetails](soleTrader) // we have already visited this page and the record has been created
96+
case _ =>
97+
logger.warn(s"Unexpected multiple provided details records for sole trader application, applicationId:[${agentApplication.agentApplicationId}], cannot recover from this state so exiting")
98+
Redirect(AppRoutes.apply.AgentApplicationController.genericExitPage)
99+
)
100+
101+
def show: Action[AnyContent] = baseAction:
102+
implicit request =>
103+
val individualProvidedDetails: IndividualProvidedDetails = request.get
104+
Ok(view(
105+
agentApplication = request.get[AgentApplication],
106+
hasProvedIdentity = individualProvidedDetails.hasFinished
107+
))

app/uk/gov/hmrc/agentregistrationfrontend/controllers/individual/CheckYourAnswersController.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import play.api.mvc.Action
2222
import play.api.mvc.AnyContent
2323
import play.api.mvc.MessagesControllerComponents
2424
import com.softwaremill.quicklens.modify
25+
import uk.gov.hmrc.agentregistration.shared.AgentApplication
2526
import uk.gov.hmrc.agentregistration.shared.LinkId
2627
import uk.gov.hmrc.agentregistration.shared.StateOfAgreement
2728
import uk.gov.hmrc.agentregistration.shared.individual.IndividualProvidedDetails
@@ -76,6 +77,19 @@ extends FrontendController(mcc, actions):
7677
implicit request =>
7778
Redirect(AppRoutes.providedetails.IndividualHmrcStandardForAgentsController.show(linkId).url)
7879
)
80+
.ensure(
81+
// we have all the answers complete
82+
// but only show the CYA page if not sole trader
83+
// as sole trader answers were copied in from the application
84+
!_.get[AgentApplication].isSoleTrader,
85+
implicit request =>
86+
individualProvideDetailsService.upsert(
87+
request.get[IndividualProvidedDetails]
88+
.modify(_.providedDetailsState)
89+
.setTo(Finished)
90+
).map: _ =>
91+
Redirect(AppRoutes.providedetails.IndividualConfirmationController.show(linkId).url)
92+
)
7993

8094
def show(linkId: LinkId): Action[AnyContent] =
8195
baseAction(linkId):
@@ -94,3 +108,9 @@ extends FrontendController(mcc, actions):
94108
.setTo(Finished)
95109
).map: _ =>
96110
Redirect(AppRoutes.providedetails.IndividualConfirmationController.show(linkId))
111+
112+
extension (agentApplication: AgentApplication)
113+
def isSoleTrader: Boolean =
114+
agentApplication match
115+
case _: AgentApplication.IsSoleTrader => true
116+
case _ => false

app/uk/gov/hmrc/agentregistrationfrontend/controllers/individual/IndividualConfirmationController.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,10 @@ extends FrontendController(mcc, actions):
5454
.map: optBpr =>
5555
Ok(individualConfirmationPage(
5656
applicantName = applicantName.value,
57-
entityName = optBpr.map(_.getEntityName).getOrThrowExpectedDataMissing("BPR is missing")
57+
entityName = optBpr.map(_.getEntityName).getOrThrowExpectedDataMissing("BPR is missing"),
58+
isSoleTrader =
59+
agentApplication match {
60+
case _: AgentApplication.IsSoleTrader => true
61+
case _ => false
62+
}
5863
))

app/uk/gov/hmrc/agentregistrationfrontend/model/AgentApplicationExt.scala

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,12 @@ extension (agentApplication: AgentApplication)
4545
case a: AgentApplication.IsIncorporated =>
4646
NumberOfIndividuals.isKeyIndividualListComplete(existingList.count(_.isPersonOfControl), a.numberOfIndividuals)
4747
&& otherRelevantIndividualsComplete(existingList)
48-
case _ => false
48+
case _: AgentApplication.IsSoleTrader => true
4949

50-
val listProgressComplete = listDetailsCompleted(existingList) && existingList.forall(_.hasFinished)
50+
val listProgressComplete =
51+
existingList.nonEmpty
52+
&& listDetailsCompleted(existingList)
53+
&& existingList.forall(_.hasFinished)
5154
// any state other than Precreated indicates the link has been sent; require the list to be non-empty
5255
val listSharingComplete =
5356
listDetailsCompleted(existingList) &&

0 commit comments

Comments
 (0)