Apb 11788 - Special Case to allow setting previously Failed applications to be Success - #143
geoffreywatson wants to merge 14 commits into
Conversation
|
* APB-11588 feature flag sending notifications for failed fixable Signed-off-by: pav <173694350+paweldigital@users.noreply.github.com> * APB-11588 PR Signed-off-by: pav <173694350+paweldigital@users.noreply.github.com> --------- Signed-off-by: pav <173694350+paweldigital@users.noreply.github.com>
…ations # Conflicts: # conf/application.conf
…g into APB-11788 Signed-off-by: geoffreywatson <9043337+geoffreywatson@users.noreply.github.com>
| to be Approved instead (to allow for an ASA to be created). For example, where an applicant has successfully | ||
| appealed a Failed Non-Fixable. | ||
| */ | ||
| def specialCaseApprovePreviouslyFailedApplications()(using request: RequestHeader): Future[Unit] = |
There was a problem hiding this comment.
I do not see apart from testing where that function is called
There was a problem hiding this comment.
ah, oh dear...I think Pav moved changed the functionality so all that logic is run from a scheduler instead....I manually tested this before rebasing so I'll need to add this method call to that scheduled process.
| ) | ||
| ).toFuture().map(_ => ()) | ||
| } | ||
|
|
There was a problem hiding this comment.
In this project we decided not to do field level updates. We update whole records
There was a problem hiding this comment.
Few flags that needs to reset too isEmailSent=false, correctiveActionExpiryDate=None, emailSentAt=None , isSubscribed=false. Please note emailSentAt is new in my PR
| def specialCaseApprovePreviouslyFailedApplications()(using request: RequestHeader): Future[Unit] = | ||
| if (appConfig.enableUnsetRiskingResponses) { | ||
| logger.info("[SpecialCaseApprovePreviouslyFailedApplications] feature ENABLED.") | ||
| Future.sequence( |
There was a problem hiding this comment.
Future sequence has some drow back like failed fast that leave orphan futures etc. would be better to use ProcessInSequence.processInSequence
| ) | ||
| .toFuture().map(_ => ()) | ||
| } | ||
|
|
There was a problem hiding this comment.
Small suggestion — the Repo base class already provides upsert(...) that does the same whole-document replaceOne under the hood. It's used everywhere else in the codebase (e.g. BackendNotificationService, EmailServiceForApprovedApplications, EmailServiceForFailedNonFixable). Would you mind switching this to upsert(updated) for consistency?
| if (appConfig.enableUnsetRiskingResponses) { | ||
| logger.info("[SpecialCaseApprovePreviouslyFailedApplications] feature ENABLED.") | ||
| ProcessInSequence.processInSequence[ApplicationReference, Unit]( | ||
| appConfig.applicationIdsForUnsettingRiskingResponses.map(ApplicationReference(_)) |
There was a problem hiding this comment.
Small suggestion — switch processInSequence to processAllInSequence so a single problematic config entry doesn't fail I think. The other batch services (BackendNotificationService, EmailServiceForFailedNonFixable, RiskingArchivalService) use this pattern to get per-item error
| private def processFeatureFlagged(applicationWithIndividuals: ApplicationWithIndividuals)(using RequestHeader): Future[Unit] = | ||
| val applicationForRisking: ApplicationForRisking = applicationWithIndividuals.application | ||
| applicationForRisking.overallStatus.riskingOutcome.getOrThrowExpectedDataMissing("risking outcome") match | ||
| case RiskingOutcome.FailedFixable => | ||
| if appConfig.Features.fixableFailures | ||
| then process(applicationWithIndividuals) | ||
| else | ||
| logger.info(s"Not notifying backend for application ${applicationForRisking.applicationReference} with failed fixable outcome because of feature flag: ${appConfig.Features.fixableFailures}") | ||
| Future.unit | ||
| case _ => process(applicationWithIndividuals) | ||
|
|
There was a problem hiding this comment.
This code is already on the main branch. Could you please check that your merge from main was completed correctly?
| CorrectiveAction | ||
| Base64 | ||
| Features |
There was a problem hiding this comment.
This code is already on the main branch. Could you please check that your merge from main was completed correctly?
| def findAlreadyRiskedApplication(applicationReference: ApplicationReference): Future[Option[ApplicationWithIndividuals]] = findApplicationWithIndividuals( | ||
| applicationFilter = Filters.and( | ||
| Filters.eq(FieldNames.applicationReference, applicationReference.value), | ||
| Filters.exists(FieldNames.entityRiskingResult), |
There was a problem hiding this comment.
riskingOutcome can only exist if entityRiskingResult is already present, so we don't really need that condition in the filter.
That said, I'm happy to leave it as is since this is one-off migration code that will be removed afterwards.
| Filters.exists(FieldNames.entityRiskingResult), | ||
| Filters.exists(FieldNames.overallStatus.riskingOutcome, true) | ||
| ), | ||
| individualForAllFilter = Filters.exists(FieldNames.individualRiskingResult) |
| individualForAllFilter = Filters.exists(FieldNames.individualRiskingResult) | ||
| ) | ||
|
|
||
| def findAlreadyRiskedApplication(applicationReference: ApplicationReference): Future[Option[ApplicationWithIndividuals]] = findApplicationWithIndividuals( |
There was a problem hiding this comment.
I don't think this needs to fetch the application together with its individuals. It should be simpler and sufficient to fetch just the application.
|
|
||
| val riskingOutcomeIndividualDetailsFix: RiskingOutcomeIndividual.FailedFixable = RiskingOutcomeIndividual.FailedFixable( | ||
| fixes = Seq( | ||
| IndividualFix._10.IndividualDetailsFix( | ||
| dateOfBirth = Some(IndividualDateOfBirth.Provided(dateOfBirth)), | ||
| nino = Some(IndividualNino.Provided(nino)), | ||
| saUtr = Some(IndividualSaUtr.Provided(saUtr)), | ||
| isConfirmed = None | ||
| ) | ||
| ) | ||
| ) |
There was a problem hiding this comment.
This code is already on the main branch. Could you please check that your merge from main was completed correctly?
|
|
||
| import java.time.ZoneOffset | ||
|
|
||
| class BackendNotificationServiceFeatureFlagSpec |
There was a problem hiding this comment.
This is already in the main branch
| class BackendNotificationServiceSpec | ||
| extends ISpec: | ||
|
|
||
| override protected def configOverrides: Map[String, Any] = Map( |
There was a problem hiding this comment.
already on main branch
|
|
||
| riskingResultsService.specialCaseApprovePreviouslyFailedApplications().futureValue | ||
|
|
||
| val modified = applicationForRiskingRepo.findById(ApplicationReference(failedNonFixableAppRef)).futureValue |
There was a problem hiding this comment.
Could you please name the value and add the type annotation, as we usually do in this codebase?
| ) | ||
| ) | ||
|
|
||
| modified.flatMap(_.overallStatus.riskingOutcome) shouldBe Some(RiskingOutcome.Approved) |
There was a problem hiding this comment.
Could you please assert the entire object, to ensure that only the expected fields are changed?
Also, please use OptionValues from ScalaTest (as we do in other places) when asserting on Option values.
| individualForAllFilter = Filters.exists(FieldNames.individualRiskingResult) | ||
| ) | ||
|
|
||
| def findAlreadyRiskedApplication(applicationReference: ApplicationReference): Future[Option[ApplicationWithIndividuals]] = findApplicationWithIndividuals( |
There was a problem hiding this comment.
I don't think this function is needed. The goal is to find the application and its individuals by applicationReference, so it would be simpler to reuse the existing functions that are already dedicated to this purpose instead of introducing new ones.
Also, please note that in this project all custom find functions need to be covered by unit tests. This is another reason to prefer reusing the existing functionality rather than adding a new method that would require additional testing.
Please use the existing methods:
ApplicationForRiskingRepo.findById(
applicationRef: ApplicationReference
): Future[Option[ApplicationForRisking]]to retrieve the application, and:
IndividualForRiskingRepo.findByApplicationReference(
applicationReference: ApplicationReference
): Future[Seq[IndividualForRisking]]to retrieve the corresponding individuals.
| to be Approved instead (to allow for an ASA to be created). For example, where an applicant has successfully | ||
| appealed a Failed Non-Fixable. | ||
| */ | ||
| def specialCaseApprovePreviouslyFailedApplications()(using request: RequestHeader): Future[Unit] = |
There was a problem hiding this comment.
Could you rename the function so that its name better reflects what it does? It would make the code easier to understand and read.
Something like:
def updateApplicationOutcomeToApproved
or a similar name that clearly describes the action being performed.
As a general convention, we'd like function names to contain a verb so that it is immediately clear what the function does.
| [APB-11788] special case / temporary solution for allowing applications that have already been determined as Failed | ||
| to be Approved instead (to allow for an ASA to be created). For example, where an applicant has successfully | ||
| appealed a Failed Non-Fixable. | ||
| */ |
There was a problem hiding this comment.
Sorry for bothering, but could you convert this into a proper ScalaDoc comment instead of a multiline comment?
| if (appConfig.enableUnsetRiskingResponses) { | ||
| logger.info("[SpecialCaseApprovePreviouslyFailedApplications] feature ENABLED.") | ||
| ProcessInSequence.processAllInSequence[ApplicationReference, Unit]( | ||
| appConfig.applicationIdsForUnsettingRiskingResponses.map(ApplicationReference(_)) | ||
| )(appRef => | ||
| logger.info(s"[SpecialCaseApprovePreviouslyFailedApplications] Trying from config appRef: ${appRef.value}") | ||
| applicationForRiskingRepo.findAlreadyRiskedApplication(appRef).flatMap { | ||
| case Some(appWithIndividuals) => | ||
| appWithIndividuals.application.overallStatus.riskingOutcome match { | ||
| case Some(RiskingOutcome.FailedNonFixable | RiskingOutcome.FailedFixable) => | ||
| for { | ||
| _ <- updateRiskingResults(RiskingResult.ForEntity( | ||
| applicationReference = appWithIndividuals.application.applicationReference, |
There was a problem hiding this comment.
Could you consider a simpler implementation, something along these lines?
def updateApplicationOutcomeToApprovedIfNeeded(): Future[Unit] =
if appConfig.enableUnsetRiskingResponses
then updateApplicationOutcomeToApproved()
else Future.unit
private def requiresUpdate(application: ApplicationForRisking): Boolean = ...
private def requiresUpdate(individual: IndividualForRisking): Boolean = ...
private def updateApplicationOutcomeToApproved(): Future[Unit] =
ProcessInSequence.processAllInSequence(appConfig.applicationIdsForUnsettingRiskingResponses):
applicationReference =>
for
application <- applicationForRiskingRepo.findById(applicationReference)
individuals <- individualForRiskingRepo.findByApplicationReference(applicationReference)
updatedIndividuals = individuals.map(_.copy(...))
_ <-
if requiresUpdate(application)
then applicationForRiskingRepo.upsert(application.copy(...))
else Future.unit
_ <- ProcessInSequence.processAllInSequence(individuals)(individual =>
if requiresUpdate(individual)
then individualForRiskingRepo.upsert(updatedIndividual))
else Future.unit
yield logger.info("all done")
//please include missing logger statements, I skipeed them for simplicity
//probably better if the last two setps could be implemented as private helper functions.
| val hipBaseUrl: String = servicesConfig.baseUrl("hip") | ||
| val hipAuthToken: HipAuthToken = HipAuthToken(config.get[String]("microservice.services.hip.authorization-token")) | ||
| val enableUnsetRiskingResponses: Boolean = servicesConfig.getBoolean("features.enable-unset-risking-responses") | ||
| val applicationIdsForUnsettingRiskingResponses: Seq[String] = config.get[Seq[String]]("applicationIdsForUnsettingRiskingResponses") |
There was a problem hiding this comment.
Could you please change the type signature to Seq[ApplicationReference]?
Also, since these are application references, could you rename this value and the corresponding config property to:
val applicationReferencesToBeApproved: Seq[ApplicationReference]and in appconfig:
applicationReferencesToBeApproved = []
Applications that have been approved following an appeals process may be added to a new config array (the id's) so the results can be overturned and the Failed state is instead set to Success.
Unsets the previous Failed responses and replaces with Success, allowing the applications to be subscribed to ASA, with all side effects (new emails and audit events).