feat(aml): enforce review locking and sync STR case conclusions to linked alerts

This commit is contained in:
2026-09-18 10:29:17 +05:30
parent 521d61d6b7
commit c1e6e036d0
3 changed files with 119 additions and 15 deletions
@@ -136,6 +136,40 @@ public class AmlAlert extends AbstractAuditableWithUTCDateTimeCustom<Long> {
: (ruleCodes.length() > 500 ? ruleCodes.substring(0, 500) : ruleCodes);
}
/**
* Whether this alert is being worked by somebody other than the given officer.
*
* <p>
* An alert under review belongs to whoever opened it until they decide it or hand it back. A second officer
* reading the same case and reaching their own verdict is the failure this exists to prevent: the transaction
* would carry two reviews, and whichever was written last would be the one on file.
*/
public boolean isHeldByAnother(final AppUser officer) {
return AmlAlertStatus.UNDER_REVIEW.name().equals(this.status) && this.reviewedBy != null && officer != null
&& !this.reviewedBy.getId().equals(officer.getId());
}
/**
* Brings the alert into line with the Principal Officer's conclusion on the case it opened.
*
* <p>
* An escalation is a referral, not a verdict: it says the AML Officer wanted the case looked at. The verdict is
* the Principal Officer's, and when they conclude the case clear the alert has to say so too. Left as ESCALATED,
* the feed would go on showing a transaction as suspicious after the bank had decided it was not - and the officer
* who cleared it would have no way to see their own conclusion reflected where the alert is worked.
*
* <p>
* Distinct from {@link #whitelist}: this does not pass the alert's own immutability guard, because it is not a
* second officer overturning a decision. It is the case this alert was referred into, coming back with its answer.
*/
public void applyStrConclusion(final boolean suspicious, final AppUser principalOfficer, final String notes) {
this.reviewedBy = principalOfficer;
this.reviewedOn = LocalDateTime.now();
this.reviewNotes = notes;
this.disposition = (suspicious ? AmlAlertDisposition.ESCALATE : AmlAlertDisposition.WHITELIST).name();
this.status = (suspicious ? AmlAlertStatus.ESCALATED : AmlAlertStatus.WHITELISTED).name();
}
public void whitelist(AppUser reviewer, String notes) {
this.reviewedBy = reviewer;
this.reviewedOn = LocalDateTime.now();