Skip to content

Commit da5a64b

Browse files
committed
Fix to Account Application v3.1.0
1 parent 0318885 commit da5a64b

1 file changed

Lines changed: 27 additions & 8 deletions

File tree

‎obp-api/src/main/scala/code/api/v3_1_0/Http4s310.scala‎

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3063,8 +3063,9 @@ object Http4s310 {
30633063
)
30643064

30653065
// ─── updateAccountApplicationStatus (PUT) ────────────────────────────────
3066-
// Side effect: when status == "ACCEPTED", a new bank account is created for the
3067-
// logged-in user. Preserved verbatim from the Lift implementation.
3066+
// Side effect: when status == "ACCEPTED", a new bank account is created and the
3067+
// APPLICANT (the application's user) becomes its holder. The Lift implementation
3068+
// (and its verbatim port) made the logged-in approver the holder — fixed 2026-09.
30683069

30693070
val updateAccountApplicationStatus: HttpRoutes[IO] = HttpRoutes.of[IO] {
30703071
case req @ PUT -> `prefixPath` / "banks" / _ / "account-applications" / accountApplicationIdStr =>
@@ -3074,24 +3075,36 @@ object Http4s310 {
30743075
_ <- NewStyle.function.tryons(s"$InvalidJsonFormat status should not be blank.", 400, Some(cc)) {
30753076
org.apache.commons.lang3.Validate.notBlank(putJson.status)
30763077
}
3077-
(_, _) <- NewStyle.function.getAccountApplicationById(accountApplicationIdStr, Some(cc))
3078-
(accountApplication, _) <- NewStyle.function.updateAccountApplicationStatus(accountApplicationIdStr, putJson.status, Some(cc))
3079-
userIdOpt = Option(accountApplication.userId)
3080-
customerIdOpt = Option(accountApplication.customerId)
3078+
(applicationBefore, _) <- NewStyle.function.getAccountApplicationById(accountApplicationIdStr, Some(cc))
3079+
userIdOpt = Option(applicationBefore.userId)
3080+
customerIdOpt = Option(applicationBefore.customerId)
30813081
appUser <- unboxOptionOBPReturnType(userIdOpt.map(NewStyle.function.findByUserId(_, Some(cc))))
30823082
customer <- unboxOptionOBPReturnType(customerIdOpt.map(NewStyle.function.getCustomerByCustomerId(_, Some(cc))))
3083+
// Guard BEFORE the status transition commits: failing after it would strand the
3084+
// application as ACCEPTED with no account. A consent-user applicant can only come
3085+
// from a row that predates the creation-side guard (or was written another way).
3086+
_ <- code.util.Helper.booleanToFuture(
3087+
s"$InvalidUserId The application's user is a consent user (an agent identity minted by a Consent). Accounts are held by humans - re-apply with the granting user's USER_ID.",
3088+
failCode = 400, cc = Some(cc))(!appUser.exists(_.isConsentUser))
3089+
(accountApplication, _) <- NewStyle.function.updateAccountApplicationStatus(accountApplicationIdStr, putJson.status, Some(cc))
30833090
_ <- putJson.status match {
30843091
case "ACCEPTED" =>
3092+
// The APPLICANT becomes the holder. The Lift-era code (ported verbatim) made the
3093+
// approving admin the holder and left appUser unused — every accepted application
3094+
// handed the account to whoever clicked approve. Customer-only applications
3095+
// (userId empty) keep the legacy approver-as-holder behaviour: there is no user
3096+
// to hold, and refusing here would strand the just-committed ACCEPTED status.
30853097
for {
30863098
accountId <- Future(AccountId(java.util.UUID.randomUUID().toString))
3099+
holder = appUser.getOrElse(user)
30873100
(_, _) <- NewStyle.function.createBankAccount(
30883101
bank.bankId, accountId,
30893102
accountApplication.productCode.value,
30903103
"", "EUR", BigDecimal("0"),
3091-
user.name, "",
3104+
holder.name, "",
30923105
List.empty, Some(cc))
30933106
success <- code.model.dataAccess.BankAccountCreation.setAccountHolderAndRefreshUserAccountAccess(
3094-
bank.bankId, accountId, user, Some(cc))
3107+
bank.bankId, accountId, holder, Some(cc))
30953108
} yield success
30963109
case _ => Future("")
30973110
}
@@ -3227,6 +3240,12 @@ object Http4s310 {
32273240
org.apache.commons.lang3.Validate.isTrue(postedData.user_id.isDefined || postedData.customer_id.isDefined)
32283241
}
32293242
appUser <- unboxOptionOBPReturnType(postedData.user_id.map(NewStyle.function.findByUserId(_, Some(cc))))
3243+
// Explicit target: fail loud rather than redirect (see the entitlement endpoints).
3244+
// On ACCEPTED the application's user becomes the account holder, so a consent
3245+
// user must be rejected here, before the application is stored.
3246+
_ <- code.util.Helper.booleanToFuture(
3247+
s"$InvalidUserId user_id names a consent user (an agent identity minted by a Consent). Accounts are held by humans - use the granting user's USER_ID.",
3248+
failCode = 400, cc = Some(cc))(!appUser.exists(_.isConsentUser))
32303249
customer <- unboxOptionOBPReturnType(postedData.customer_id.map(NewStyle.function.getCustomerByCustomerId(_, Some(cc))))
32313250
(accountApplication, _) <- NewStyle.function.createAccountApplication(
32323251
productCode = ProductCode(postedData.product_code),

0 commit comments

Comments
 (0)