Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -22,40 +22,27 @@ import play.api.mvc.AnyContent
import play.api.mvc.ControllerComponents
import uk.gov.hmrc.agentregistration.shared.ApplicationReference
import uk.gov.hmrc.agentregistration.shared.PersonReference
import uk.gov.hmrc.agentregistration.shared.lists.IndividualName
import uk.gov.hmrc.agentregistration.shared.risking.EntityFailure
import uk.gov.hmrc.agentregistration.shared.risking.IndividualFailure
import uk.gov.hmrc.agentregistration.shared.risking.RiskedEntity
import uk.gov.hmrc.agentregistration.shared.risking.RiskedIndividual
import uk.gov.hmrc.agentregistration.shared.risking.RiskingOutcome
import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress
import uk.gov.hmrc.agentregistration.shared.risking.RiskingOutcome.*
import uk.gov.hmrc.agentregistration.shared.risking.*
import uk.gov.hmrc.agentregistration.shared.risking.RiskingProgress.ReceivedRiskingResults
import uk.gov.hmrc.agentregistration.shared.util.SafeEquals.=!=
import uk.gov.hmrc.agentregistration.shared.util.SafeEquals.===
import uk.gov.hmrc.agentregistrationrisking.action.Actions
import uk.gov.hmrc.agentregistrationrisking.model.ApplicationForRisking
import uk.gov.hmrc.agentregistrationrisking.model.ApplicationWithIndividuals
import uk.gov.hmrc.agentregistrationrisking.model.IndividualForRisking
import uk.gov.hmrc.agentregistrationrisking.model.RiskingFileName
import uk.gov.hmrc.agentregistrationrisking.repository.ApplicationForRiskingRepo
import uk.gov.hmrc.agentregistrationrisking.repository.IndividualForRiskingRepo
import uk.gov.hmrc.agentregistrationrisking.services.ApplicationForRiskingService
import uk.gov.hmrc.agentregistrationrisking.services.RiskingOutcomeHelper
import uk.gov.hmrc.agentregistration.shared.risking.RiskingOutcome.*

import java.time.LocalDate
import java.time.Clock
import java.time.ZoneId
import javax.inject.Inject
import scala.concurrent.ExecutionContext
import scala.concurrent.Future

class RiskingProgressController @Inject() (
actions: Actions,
cc: ControllerComponents,
applicationForRiskingRepo: ApplicationForRiskingRepo,
individualForRiskingRepo: IndividualForRiskingRepo,
applicationForRiskingService: ApplicationForRiskingService,
clock: Clock
applicationForRiskingService: ApplicationForRiskingService
)(using ExecutionContext)
extends BackendController(cc):

Expand All @@ -66,13 +53,7 @@ extends BackendController(cc):
.getApplicationWithIndividuals(personReference)
.map:
case None => NoContent
case Some(applicationWithIndividuals) =>
Ok(Json.toJson(
RiskingProgressController.toRiskingProgress(
applicationWithIndividuals = applicationWithIndividuals,
riskingCompletedDate = LocalDate.now(clock) // TODO! This has to come from DB
)
))
case Some(applicationWithIndividuals) => Ok(Json.toJson(RiskingProgressController.toRiskingProgress(applicationWithIndividuals)))

def getRiskingProgressForApplicant(applicationReference: ApplicationReference): Action[AnyContent] = actions
.authorised
Expand All @@ -82,21 +63,15 @@ extends BackendController(cc):
.map:
case None => NoContent
case Some(applicationWithIndividuals: ApplicationWithIndividuals) =>
Ok(Json.toJson(
RiskingProgressController.toRiskingProgress(
applicationWithIndividuals = applicationWithIndividuals,
riskingCompletedDate = LocalDate.now(clock) // TODO! This has to come from DB
)
))
Ok(Json.toJson(RiskingProgressController.toRiskingProgress(applicationWithIndividuals)))

object RiskingProgressController:

def toRiskingProgress(
applicationWithIndividuals: ApplicationWithIndividuals,
riskingCompletedDate: LocalDate
): RiskingProgress =
private val displayZone: ZoneId = ZoneId.of("Europe/London")

def toRiskingProgress(applicationWithIndividuals: ApplicationWithIndividuals): RiskingProgress =
val maybeRiskingFileName: Option[RiskingFileName] = applicationWithIndividuals.application.riskingFileName
val maybeReceivedRiskingResults: Option[ReceivedRiskingResults] = receivedRiskingResults(applicationWithIndividuals, riskingCompletedDate)
val maybeReceivedRiskingResults: Option[ReceivedRiskingResults] = receivedRiskingResults(applicationWithIndividuals)

(maybeRiskingFileName, maybeReceivedRiskingResults) match
// format: off
Expand All @@ -105,63 +80,42 @@ object RiskingProgressController:
case (Some(_), Some(receivedRiskingResults)) => receivedRiskingResults
// format: on

private def receivedRiskingResults(
applicationWithIndividuals: ApplicationWithIndividuals,
riskingCompletedDate: LocalDate
): Option[ReceivedRiskingResults] =
import cats.implicits._
private def receivedRiskingResults(applicationWithIndividuals: ApplicationWithIndividuals): Option[ReceivedRiskingResults] =
import cats.implicits.*
for
outcome: RiskingOutcome <- RiskingOutcomeHelper.computeRiskingOutcome(applicationWithIndividuals)
riskedEntity: RiskedEntity <- applicationWithIndividuals
.application
.failures.map: failures =>
.entityRiskingResult.map: result =>
RiskedEntity(
applicationReference = applicationWithIndividuals.application.applicationReference,
failures = failures
failures = result.failures
)
riskedIndividuals: Seq[RiskedIndividual] <-
applicationWithIndividuals
.individuals
.map: individual =>
individual.failures.map: (failures: Seq[IndividualFailure]) =>
individual.individualRiskingResult.map: result =>
RiskedIndividual(
personReference = individual.personReference,
individualName = individual.individualProvidedDetails.individualName,
failures = failures
failures = result.failures
)
.sequence
yield outcome match
case Approved => RiskingProgress.Approved
case FailedFixable =>
RiskingProgress.FailedFixable(
riskedEntity = riskedEntity,
riskedIndividuals = riskedIndividuals,
riskingCompletedDate = riskingCompletedDate
)
case FailedNonFixable =>
RiskingProgress.FailedNonFixable(
riskedEntity = riskedEntity,
riskedIndividuals = riskedIndividuals,
riskingCompletedDate = riskingCompletedDate
)

private def maybeRiskedEntity(applicationWithIndividuals: ApplicationWithIndividuals): Option[RiskedEntity] = applicationWithIndividuals
.application
.failures
.map((failures: List[EntityFailure]) => RiskedEntity(applicationWithIndividuals.application.applicationReference, failures))

private def maybeRiskedIndividuals(applicationWithIndividuals: ApplicationWithIndividuals): Option[Seq[RiskedIndividual]] =
import cats.implicits._
applicationWithIndividuals
.individuals
.map: individual =>
individual
.failures
.map(failures =>
RiskedIndividual(
personReference = individual.individualProvidedDetails.personReference,
individualName = individual.individualProvidedDetails.individualName,
failures = failures
)
latestDate <- applicationWithIndividuals.riskingCompletedDate
yield
val riskingCompletedDate = latestDate.atZone(displayZone).toLocalDate
outcome match
case Approved => RiskingProgress.Approved
case FailedFixable =>
RiskingProgress.FailedFixable(
riskedEntity = riskedEntity,
riskedIndividuals = riskedIndividuals,
riskingCompletedDate = riskingCompletedDate
)
case FailedNonFixable =>
RiskingProgress.FailedNonFixable(
riskedEntity = riskedEntity,
riskedIndividuals = riskedIndividuals,
riskingCompletedDate = riskingCompletedDate
)
.sequence
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ extends BackendController(cc):
agentApplication = submitForRiskingRequest.agentApplication,
createdAt = createdAt,
lastUpdatedAt = createdAt,
failures = None,
entityRiskingResult = None,
isSubscribed = false,
isEmailSent = false
)
Expand All @@ -93,5 +93,5 @@ extends BackendController(cc):
individualProvidedDetails = individualProvidedDetails,
createdAt = createdAt,
lastUpdatedAt = createdAt,
failures = None
individualRiskingResult = None
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package uk.gov.hmrc.agentregistrationrisking.model

import uk.gov.hmrc.agentregistration.shared.AgentApplication
import uk.gov.hmrc.agentregistration.shared.ApplicationReference
import uk.gov.hmrc.agentregistration.shared.risking.EntityFailure
import play.api.libs.json.Json
import play.api.libs.json.OFormat

Expand All @@ -30,7 +29,7 @@ final case class ApplicationForRisking(
agentApplication: AgentApplication, // snapshot of the AgentAPplication when sent for risking
createdAt: Instant,
lastUpdatedAt: Instant,
failures: Option[List[EntityFailure]],
entityRiskingResult: Option[EntityRiskingResult],
isSubscribed: Boolean,
isEmailSent: Boolean
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,23 @@ package uk.gov.hmrc.agentregistrationrisking.model

import uk.gov.hmrc.agentregistration.shared.ApplicationReference

import java.time.Instant

final case class ApplicationWithIndividuals(
application: ApplicationForRisking,
individuals: Seq[IndividualForRisking]
)
):

/** Latest moment a Minerva result landed across the entity and all individuals. None if any record is still missing its `riskingCompletedDate`. */
def riskingCompletedDate: Option[Instant] =
import cats.data.NonEmptyList
import cats.implicits.*
import cats.kernel.Order
given Order[Instant] = Order.fromOrdering(Ordering[Instant])
for
appDate <- application.entityRiskingResult.map(_.receivedAt)
individualDates <- individuals.map(_.individualRiskingResult.map(_.receivedAt)).toList.sequence
yield NonEmptyList(appDate, individualDates).maximum

object ApplicationWithIndividuals:

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2026 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 uk.gov.hmrc.agentregistrationrisking.model

import play.api.libs.json.Json
import play.api.libs.json.OFormat
import uk.gov.hmrc.agentregistration.shared.risking.EntityFailure

import java.time.Instant

/** The Minerva risking outcome for an application/entity: the failure list and the moment we received it. Both arrive together — they cannot be set
* independently.
*/
final case class EntityRiskingResult(
failures: List[EntityFailure],
receivedAt: Instant
)

object EntityRiskingResult:
given OFormat[EntityRiskingResult] = Json.format[EntityRiskingResult]
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ final case class IndividualForRisking(
individualProvidedDetails: IndividualProvidedDetails,
createdAt: Instant,
lastUpdatedAt: Instant,
failures: Option[List[IndividualFailure]]
individualRiskingResult: Option[IndividualRiskingResult]
):

// values that we do not store at the moment
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2026 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 uk.gov.hmrc.agentregistrationrisking.model

import play.api.libs.json.Json
import play.api.libs.json.OFormat
import uk.gov.hmrc.agentregistration.shared.risking.IndividualFailure

import java.time.Instant

/** The Minerva risking outcome for a single individual: the failure list and the moment we received it. Both arrive together — they cannot be set
* independently.
*/
final case class IndividualRiskingResult(
failures: List[IndividualFailure],
receivedAt: Instant
)

object IndividualRiskingResult:
given OFormat[IndividualRiskingResult] = Json.format[IndividualRiskingResult]
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ object RiskingFileDataRecord:
val contactDetails = app.getApplicantContactDetails
this.apply(
recordType = RecordType.Entity,
resubmission = applicationForRisking.failures.isDefined,
resubmission = applicationForRisking.entityRiskingResult.isDefined,
applicationReference = Some(app.applicationReference),
applicantName = Some(contactDetails.applicantName),
applicantPhone = contactDetails.telephoneNumber,
Expand Down Expand Up @@ -152,7 +152,7 @@ object RiskingFileDataRecord:
val details = individualForRisking.individualProvidedDetails
this.apply(
recordType = RecordType.Individual,
resubmission = individualForRisking.failures.isDefined,
resubmission = individualForRisking.individualRiskingResult.isDefined,
applicationReference = None,
applicantName = None,
applicantPhone = None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ extends Repo[ApplicationReference, ApplicationForRisking](
collection
.find(
Filters.and(
Filters.exists(FieldNames.failures),
Filters.exists(FieldNames.entityRiskingResult),
Filters.eq(FieldNames.isSubscribed, false)
)
).toFuture()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ object FieldNames:
val personReference: String = "personReference"
val personReferenceIndex: String = personReference + "Index"

val failures: String = "failures"
val entityRiskingResult: String = "entityRiskingResult"

val isSubscribed: String = "isSubscribed"
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ object RiskingOutcomeHelper:
for
entityOutcome: RiskingOutcome <- applicationWithIndividuals
.application
.failures
.map(_.outcomeForEntity)
.entityRiskingResult
.map(_.failures.outcomeForEntity)
individualsOutcome: RiskingOutcome <- maybeOutcomeForIndividuals(applicationWithIndividuals.individuals)
yield foldOutcomes(entityOutcome, individualsOutcome)

def maybeOutcomeForIndividuals(individuals: Seq[IndividualForRisking]): Option[RiskingOutcome] =
import cats.implicits.*
individuals
.map(_.failures.map(_.outcome))
.map(_.individualRiskingResult.map(_.failures.outcome))
.sequence
.map(_.foldLeft[RiskingOutcome](RiskingOutcome.Approved)(foldOutcomes))

Expand Down Expand Up @@ -86,21 +86,21 @@ object RiskingOutcomeHelper:

def maybeRiskedEntity(applicationWithIndividuals: ApplicationWithIndividuals): Option[RiskedEntity] = applicationWithIndividuals
.application
.failures
.map((failures: List[EntityFailure]) => RiskedEntity(applicationWithIndividuals.application.applicationReference, failures))
.entityRiskingResult
.map(entityRiskingResult => RiskedEntity(applicationWithIndividuals.application.applicationReference, entityRiskingResult.failures))

def maybeRiskedIndividuals(applicationWithIndividuals: ApplicationWithIndividuals): Option[Seq[RiskedIndividual]] =
import cats.implicits.*
applicationWithIndividuals
.individuals
.map: individual =>
individual
.failures
.map(failures =>
.individualRiskingResult
.map(individualRiskingResult =>
RiskedIndividual(
personReference = individual.individualProvidedDetails.personReference,
individualName = individual.individualProvidedDetails.individualName,
failures = failures
failures = individualRiskingResult.failures
)
)
.sequence
Loading