From 00bd381310d954ab55627617101b5185212d0000 Mon Sep 17 00:00:00 2001 From: Hongwei Date: Thu, 24 Apr 2025 10:02:54 +0200 Subject: [PATCH 01/10] refactor/add BankAccountBalance class and update related references --- .../main/scala/bootstrap/liftweb/Boot.scala | 1 + .../bankconnectors/LocalMappedConnector.scala | 2 +- .../rabbitmq/RabbitMQConnector_vOct2024.scala | 2 +- .../rest/RestConnector_vMar2019.scala | 2 +- .../StoredProcedureConnector_vDec2019.scala | 2 +- .../model/dataAccess/BankAccountBalance.scala | 29 +++++++++++++++++++ .../commons/model/BankingModel.scala | 10 +++++-- 7 files changed, 42 insertions(+), 6 deletions(-) create mode 100644 obp-api/src/main/scala/code/model/dataAccess/BankAccountBalance.scala diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala index 3f96a4e4f3..37723d3f14 100644 --- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala @@ -1129,6 +1129,7 @@ object ToSchemify { CustomerAccountLink, TransactionIdMapping, RegulatedEntityAttribute, + BankAccountBalance ) // start grpc server diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index 0ee18569f5..93dd6b97d6 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -990,7 +990,7 @@ object LocalMappedConnector extends Connector with MdcLoggable { label = bankAccount.label, bankId = bankAccount.bankId.value, accountRoutings = bankAccount.accountRoutings.map(accountRounting => AccountRouting(accountRounting.scheme, accountRounting.address)), - balances = List(BankAccountBalance(AmountOfMoney(bankAccount.currency, bankAccount.balance.toString),"OpeningBooked")), + balances = List(OneAccountBalance(AmountOfMoney(bankAccount.currency, bankAccount.balance.toString),"OpeningBooked")), overallBalance = AmountOfMoney(bankAccount.currency, bankAccount.balance.toString), overallBalanceDate = now ) diff --git a/obp-api/src/main/scala/code/bankconnectors/rabbitmq/RabbitMQConnector_vOct2024.scala b/obp-api/src/main/scala/code/bankconnectors/rabbitmq/RabbitMQConnector_vOct2024.scala index ea08ff63c5..c43e37f691 100644 --- a/obp-api/src/main/scala/code/bankconnectors/rabbitmq/RabbitMQConnector_vOct2024.scala +++ b/obp-api/src/main/scala/code/bankconnectors/rabbitmq/RabbitMQConnector_vOct2024.scala @@ -1071,7 +1071,7 @@ trait RabbitMQConnector_vOct2024 extends Connector with MdcLoggable { bankId=bankIdExample.value, accountRoutings=List( AccountRouting(scheme=accountRoutingSchemeExample.value, address=accountRoutingAddressExample.value)), - balances=List( BankAccountBalance(balance= AmountOfMoney(currency=balanceCurrencyExample.value, + balances=List( OneAccountBalance(balance= AmountOfMoney(currency=balanceCurrencyExample.value, amount=balanceAmountExample.value), balanceType="string")), overallBalance= AmountOfMoney(currency=currencyExample.value, diff --git a/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala b/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala index 92343ceb14..445f38ba0a 100644 --- a/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala +++ b/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala @@ -1098,7 +1098,7 @@ trait RestConnector_vMar2019 extends Connector with MdcLoggable { bankId=bankIdExample.value, accountRoutings=List( AccountRouting(scheme=accountRoutingSchemeExample.value, address=accountRoutingAddressExample.value)), - balances=List( BankAccountBalance(balance= AmountOfMoney(currency=balanceCurrencyExample.value, + balances=List( OneAccountBalance(balance= AmountOfMoney(currency=balanceCurrencyExample.value, amount=balanceAmountExample.value), balanceType="string")), overallBalance= AmountOfMoney(currency=currencyExample.value, diff --git a/obp-api/src/main/scala/code/bankconnectors/storedprocedure/StoredProcedureConnector_vDec2019.scala b/obp-api/src/main/scala/code/bankconnectors/storedprocedure/StoredProcedureConnector_vDec2019.scala index b1675d8b55..7459065a59 100644 --- a/obp-api/src/main/scala/code/bankconnectors/storedprocedure/StoredProcedureConnector_vDec2019.scala +++ b/obp-api/src/main/scala/code/bankconnectors/storedprocedure/StoredProcedureConnector_vDec2019.scala @@ -1078,7 +1078,7 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable { bankId=bankIdExample.value, accountRoutings=List( AccountRouting(scheme=accountRoutingSchemeExample.value, address=accountRoutingAddressExample.value)), - balances=List( BankAccountBalance(balance= AmountOfMoney(currency=balanceCurrencyExample.value, + balances=List( OneAccountBalance(balance= AmountOfMoney(currency=balanceCurrencyExample.value, amount=balanceAmountExample.value), balanceType="string")), overallBalance= AmountOfMoney(currency=currencyExample.value, diff --git a/obp-api/src/main/scala/code/model/dataAccess/BankAccountBalance.scala b/obp-api/src/main/scala/code/model/dataAccess/BankAccountBalance.scala new file mode 100644 index 0000000000..6ca859991f --- /dev/null +++ b/obp-api/src/main/scala/code/model/dataAccess/BankAccountBalance.scala @@ -0,0 +1,29 @@ +package code.model.dataAccess + +import com.openbankproject.commons.model._ +import net.liftweb.common.Box +import net.liftweb.mapper._ +import code.util.Helper + +class BankAccountBalance extends BankAccountBalanceTrait with LongKeyedMapper[BankAccountBalance] with CreatedUpdated with IdPK{ + + override def getSingleton = BankAccountBalance + + object AccountId_ extends MappedLongForeignKey(this, MappedBankAccount) + object BalanceType extends MappedString(this, 255) + //this is the smallest unit of currency! eg. cents, yen, pence, øre, etc. + object BalanceAmount extends MappedLong(this) + + val foreignMappedBankAccount: Box[MappedBankAccount] = AccountId_.foreign + val foreignMappedBankAccountCurrency = foreignMappedBankAccount.map(_.currency).getOrElse("EUR") + + override def accountId : AccountId = { + foreignMappedBankAccount.map(_.accountId).getOrElse(AccountId("")) + } + override def balanceType: String = BalanceType.get + override def balanceAmount: BigDecimal = Helper.smallestCurrencyUnitToBigDecimal(BalanceAmount.get, foreignMappedBankAccountCurrency) + + +} + +object BankAccountBalance extends BankAccountBalance with LongKeyedMetaMapper[BankAccountBalance] {} diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala index 06a1f0a0ed..f385523763 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala @@ -228,6 +228,12 @@ trait BankAccount{ def attributes : Option[List[Attribute]] = None } +trait BankAccountBalanceTrait { + def accountId : AccountId + def balanceType: String + def balanceAmount: BigDecimal +} + //This class is used for propagate the BankAccount as the parameters over different methods. case class BankAccountInMemory( //BankAccount Trait @@ -383,12 +389,12 @@ case class AccountBalances( label: String, bankId: String, accountRoutings: List[AccountRouting], - balances: List[BankAccountBalance], + balances: List[OneAccountBalance], overallBalance: AmountOfMoney, overallBalanceDate: Date ) -case class BankAccountBalance( +case class OneAccountBalance( balance: AmountOfMoney, balanceType: String, ) From 470632595e88585a62aebd867036e23af6c6cfb5 Mon Sep 17 00:00:00 2001 From: Hongwei Date: Thu, 24 Apr 2025 14:47:42 +0200 Subject: [PATCH 02/10] refactor/OBPv510 add BankAccountBalance class and related API endpoints for balance management --- .../main/scala/bootstrap/liftweb/Boot.scala | 2 +- .../SwaggerDefinitionsJSON.scala | 15 ++ .../main/scala/code/api/util/ApiRole.scala | 18 +- .../src/main/scala/code/api/util/ApiTag.scala | 1 + .../scala/code/api/util/ExampleValue.scala | 6 + .../newstyle/BankAccountBalanceNewStyle.scala | 91 ++++++++ .../scala/code/api/v5_1_0/APIMethods510.scala | 215 ++++++++++++++++++ .../code/api/v5_1_0/JSONFactory5.1.0.scala | 40 +++- .../BankAccountBalance.scala | 33 +++ .../BankAccountBalanceProvider.scala | 112 +++++++++ .../model/dataAccess/BankAccountBalance.scala | 29 --- .../commons/model/BankingModel.scala | 11 +- 12 files changed, 536 insertions(+), 37 deletions(-) create mode 100644 obp-api/src/main/scala/code/api/util/newstyle/BankAccountBalanceNewStyle.scala create mode 100644 obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala create mode 100644 obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalanceProvider.scala delete mode 100644 obp-api/src/main/scala/code/model/dataAccess/BankAccountBalance.scala diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala index 37723d3f14..dd71ba290d 100644 --- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala @@ -1129,7 +1129,7 @@ object ToSchemify { CustomerAccountLink, TransactionIdMapping, RegulatedEntityAttribute, - BankAccountBalance + code.bankaccountbalance.BankAccountBalance ) // start grpc server diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index a37cb357d9..061691dbbb 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -5669,7 +5669,22 @@ object SwaggerDefinitionsJSON { lazy val regulatedEntityAttributesJsonV510 = RegulatedEntityAttributesJsonV510( List(regulatedEntityAttributeResponseJsonV510) ) + + lazy val bankAccountBalanceRequestJsonV510 = BankAccountBalanceRequestJsonV510( + balance_type = balanceTypeExample.value, + balance_amount = balanceAmountExample.value + ) + + lazy val bankAccountBalanceResponseJsonV510 = BankAccountBalanceResponseJsonV510( + balance_id = balanceIdExample.value, + account_id = accountIdExample.value, + balance_type = balanceTypeExample.value, + balance_amount = balanceAmountExample.value + ) + lazy val bankAccountBalancesJsonV510 = BankAccountBalancesJsonV510( + balances = List(bankAccountBalanceResponseJsonV510) + ) //The common error or success format. //Just some helper format to use in Json case class NotSupportedYet() diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index 33297ba016..7b228f65f9 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -1009,7 +1009,23 @@ object ApiRole extends MdcLoggable{ case class CanGetBankLevelEndpointTag(requiresBankId: Boolean = true) extends ApiRole lazy val canGetBankLevelEndpointTag = CanGetBankLevelEndpointTag() - + + // BankAccountBalance roles + case class CanCreateBankAccountBalance(requiresBankId: Boolean = false) extends ApiRole + lazy val canCreateBankAccountBalance = CanCreateBankAccountBalance() + + case class CanGetBankAccountBalance(requiresBankId: Boolean = false) extends ApiRole + lazy val canGetBankAccountBalance = CanGetBankAccountBalance() + + case class CanGetBankAccountBalances(requiresBankId: Boolean = false) extends ApiRole + lazy val canGetBankAccountBalances = CanGetBankAccountBalances() + + case class CanUpdateBankAccountBalance(requiresBankId: Boolean = false) extends ApiRole + lazy val canUpdateBankAccountBalance = CanUpdateBankAccountBalance() + + case class CanDeleteBankAccountBalance(requiresBankId: Boolean = false) extends ApiRole + lazy val canDeleteBankAccountBalance = CanDeleteBankAccountBalance() + case class CanCreateHistoricalTransactionAtBank(requiresBankId: Boolean = true) extends ApiRole lazy val canCreateHistoricalTransactionAtBank = CanCreateHistoricalTransactionAtBank() diff --git a/obp-api/src/main/scala/code/api/util/ApiTag.scala b/obp-api/src/main/scala/code/api/util/ApiTag.scala index 8e62651504..db3934772b 100644 --- a/obp-api/src/main/scala/code/api/util/ApiTag.scala +++ b/obp-api/src/main/scala/code/api/util/ApiTag.scala @@ -67,6 +67,7 @@ object ApiTag { val apiTagMXOpenFinance = ResourceDocTag("MXOpenFinance") val apiTagAggregateMetrics = ResourceDocTag("Aggregate-Metrics") val apiTagSystemIntegrity = ResourceDocTag("System-Integrity") + val apiTagBalance = ResourceDocTag("Balance") val apiTagWebhook = ResourceDocTag("Webhook") val apiTagMockedData = ResourceDocTag("Mocked-Data") val apiTagConsent = ResourceDocTag("Consent") diff --git a/obp-api/src/main/scala/code/api/util/ExampleValue.scala b/obp-api/src/main/scala/code/api/util/ExampleValue.scala index 610f24125f..18c385d308 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -298,6 +298,12 @@ object ExampleValue { lazy val accountTypeExample = ConnectorField("AC","A short code that represents the type of the account as provided by the bank.") lazy val balanceAmountExample = ConnectorField("50.89", "The balance on the account.") + + lazy val balanceTypeExample = ConnectorField("openingBooked", "The balance type.") + glossaryItems += makeGlossaryItem("balance_type", balanceTypeExample) + + lazy val balanceIdExample = ConnectorField("7uy8a7e4-6d02-40e3-a129-0b2bf89de8uh", "A string that MUST uniquely identify the Account Balance on this OBP instance, can be used in all cache.") + glossaryItems += makeGlossaryItem("balance_id", balanceIdExample) lazy val amountExample = ConnectorField("10.12", "The balance on the account.") diff --git a/obp-api/src/main/scala/code/api/util/newstyle/BankAccountBalanceNewStyle.scala b/obp-api/src/main/scala/code/api/util/newstyle/BankAccountBalanceNewStyle.scala new file mode 100644 index 0000000000..4fa3232e41 --- /dev/null +++ b/obp-api/src/main/scala/code/api/util/newstyle/BankAccountBalanceNewStyle.scala @@ -0,0 +1,91 @@ +package code.api.util.newstyle + +import code.api.util.APIUtil.{OBPReturnType, unboxFullOrFail} +import code.api.util.ErrorMessages.{InvalidConnectorResponse} +import code.api.util.CallContext +import code.bankaccountbalance.{BankAccountBalanceX} +import com.openbankproject.commons.ExecutionContext.Implicits.global +import com.openbankproject.commons.model.{AccountId, BankAccountBalanceTrait} +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.model.BalanceId + + +object BankAccountBalanceNewStyle { + + def getBankAccountBalances( + accountId: AccountId, + callContext: Option[CallContext] + ): OBPReturnType[List[BankAccountBalanceTrait]] = { + BankAccountBalanceX.bankAccountBalanceProvider.vend.getBankAccountBalances(accountId).map { + result => + ( + unboxFullOrFail( + result, + callContext, + s"$InvalidConnectorResponse ${nameOf(getBankAccountBalances _)}", + 404), + callContext + ) + } + } + + def getBankAccountBalanceById( + balanceId: BalanceId, + callContext: Option[CallContext] + ): OBPReturnType[BankAccountBalanceTrait] = { + BankAccountBalanceX.bankAccountBalanceProvider.vend.getBankAccountBalanceById(balanceId).map { + result => + ( + unboxFullOrFail( + result, + callContext, + s"$InvalidConnectorResponse ${nameOf(getBankAccountBalanceById _)}", + 404), + callContext + ) + } + } + + def createOrUpdateBankAccountBalance( + balanceId: Option[BalanceId], + accountId: AccountId, + balanceType: String, + balanceAmount: BigDecimal, + callContext: Option[CallContext] + ): OBPReturnType[BankAccountBalanceTrait] = { + BankAccountBalanceX.bankAccountBalanceProvider.vend.createOrUpdateBankAccountBalance( + balanceId, + accountId, + balanceType, + balanceAmount + ).map { + result => + ( + unboxFullOrFail( + result, + callContext, + s"$InvalidConnectorResponse ${nameOf(createOrUpdateBankAccountBalance _)}", + 400), + callContext + ) + } + } + + def deleteBankAccountBalance( + balanceId: BalanceId, + callContext: Option[CallContext] + ): OBPReturnType[Boolean] = { + BankAccountBalanceX.bankAccountBalanceProvider.vend.deleteBankAccountBalance(balanceId).map { + result => + ( + unboxFullOrFail( + result, + callContext, + s"$InvalidConnectorResponse ${nameOf(deleteBankAccountBalance _)}", + 400), + callContext + ) + } + } + +} \ No newline at end of file diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index 3c1449092b..0253362202 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -188,6 +188,221 @@ trait APIMethods510 { } } + staticResourceDocs += ResourceDoc( + createBankAccountBalance, + implementedInApiVersion, + nameOf(createBankAccountBalance), + "POST", + "/accounts/ACCOUNT_ID/balances", + "Create Bank Account Balance", + s"""Create a new Balance for a Bank Account. + | + |${userAuthenticationMessage(true)} + | + |""", + bankAccountBalanceRequestJsonV510, + bankAccountBalanceResponseJsonV510, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + List(apiTagAccount, apiTagBalance), + Some(List(canCreateBankAccountBalance)) + ) + + lazy val createBankAccountBalance: OBPEndpoint = { + case "accounts" :: AccountId(accountId) :: "balances" :: Nil JsonPost json -> _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- SS.user + postedData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $BankAccountBalanceRequestJsonV510 ", 400, callContext) { + json.extract[BankAccountBalanceRequestJsonV510] + } + balanceAmount <- NewStyle.function.tryons(s"$InvalidNumber Current balance_amount is ${postedData.balance_amount}" , 400, cc.callContext) { + BigDecimal(postedData.balance_amount) + } + (balance, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.createOrUpdateBankAccountBalance( + balanceId = None, + accountId = accountId, + balanceType = postedData.balance_type, + balanceAmount = balanceAmount, + callContext = cc.callContext + ) + } yield { + (JSONFactory510.createBankAccountBalanceJson(balance), HttpCode.`201`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + getBankAccountBalanceById, + implementedInApiVersion, + nameOf(getBankAccountBalanceById), + "GET", + "/accounts/ACCOUNT_ID/balances/BALANCE_ID", + "Get Bank Account Balance By ID", + s"""Get a specific Bank Account Balance by its BALANCE_ID. + | + |${userAuthenticationMessage(true)} + | + |""", + EmptyBody, + bankAccountBalanceResponseJsonV510, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + UnknownError + ), + List(apiTagAccount, apiTagBalance), + Some(List(canGetBankAccountBalance)) + ) + + lazy val getBankAccountBalanceById: OBPEndpoint = { + case "accounts" :: AccountId(accountId) :: "balances" :: BalanceId(balanceId) :: Nil JsonGet _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- SS.user + (balance, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalanceById( + balanceId, + callContext + ) + } yield { + (JSONFactory510.createBankAccountBalanceJson(balance), HttpCode.`200`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + getAllBankAccountBalances, + implementedInApiVersion, + nameOf(getAllBankAccountBalances), + "GET", + "/accounts/ACCOUNT_ID/balances", + "Get All Bank Account Balances", + s"""Get all Balances for a Bank Account. + | + |${userAuthenticationMessage(true)} + | + |""", + EmptyBody, + bankAccountBalancesJsonV510, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + UnknownError + ), + List(apiTagAccount, apiTagBalance), + Some(List(canGetBankAccountBalances)) + ) + + lazy val getAllBankAccountBalances: OBPEndpoint = { + case "accounts" :: AccountId(accountId) :: "balances" :: Nil JsonGet _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- SS.user + (balances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances( + accountId, + callContext + ) + } yield { + (JSONFactory510.createBankAccountBalancesJson(balances), HttpCode.`200`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + updateBankAccountBalance, + implementedInApiVersion, + nameOf(updateBankAccountBalance), + "PUT", + "/accounts/ACCOUNT_ID/balances/BALANCE_ID", + "Update Bank Account Balance", + s"""Update an existing Bank Account Balance specified by BALANCE_ID. + | + |${userAuthenticationMessage(true)} + | + |""", + bankAccountBalanceRequestJsonV510, + bankAccountBalanceResponseJsonV510, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + List(apiTagAccount, apiTagBalance), + Some(List(canUpdateBankAccountBalance)) + ) + + lazy val updateBankAccountBalance: OBPEndpoint = { + case "accounts" :: AccountId(accountId) :: "balances" :: BalanceId(balanceId) :: Nil JsonPut json -> _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- SS.user + postedData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the BankAccountBalanceRequestJsonV510 ", 400, callContext) { + json.extract[BankAccountBalanceRequestJsonV510] + } + balanceAmount <- NewStyle.function.tryons(s"$InvalidNumber Current balance_amount is ${postedData.balance_amount}" , 400, cc.callContext) { + BigDecimal(postedData.balance_amount) + } + (balance, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.createOrUpdateBankAccountBalance( + balanceId = Some(balanceId), + accountId = accountId, + balanceType = postedData.balance_type, + balanceAmount = balanceAmount, + callContext = callContext + ) + } yield { + (JSONFactory510.createBankAccountBalanceJson(balance), HttpCode.`200`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + deleteBankAccountBalance, + implementedInApiVersion, + nameOf(deleteBankAccountBalance), + "DELETE", + "/accounts/ACCOUNT_ID/balances/BALANCE_ID", + "Delete Bank Account Balance", + s"""Delete a Bank Account Balance specified by BALANCE_ID. + | + |${userAuthenticationMessage(true)} + | + |""", + EmptyBody, + EmptyBody, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + UnknownError + ), + List(apiTagAccount, apiTagBalance), + Some(List(canDeleteBankAccountBalance)) + ) + + lazy val deleteBankAccountBalance: OBPEndpoint = { + case "accounts" :: AccountId(accountId) :: "balances" :: BalanceId(balanceId) :: Nil JsonDelete _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- SS.user + (deleted, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.deleteBankAccountBalance( + balanceId, + callContext + ) + } yield { + (Full(deleted), HttpCode.`204`(callContext)) + } + } + } + staticResourceDocs += ResourceDoc( createRegulatedEntity, diff --git a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala index 427cec8abd..e0391c9163 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala @@ -611,6 +611,23 @@ case class SyncExternalUserJson(user_id: String) case class UserValidatedJson(is_validated: Boolean) + +case class BankAccountBalanceRequestJsonV510( + balance_type: String, + balance_amount: String +) + +case class BankAccountBalanceResponseJsonV510( + account_id: String, + balance_id: String, + balance_type: String, + balance_amount: String +) + +case class BankAccountBalancesJsonV510( + balances: List[BankAccountBalanceResponseJsonV510] +) + object JSONFactory510 extends CustomJsonFormats { def createTransactionRequestJson(tr : TransactionRequest, transactionRequestAttributes: List[TransactionRequestAttributeTrait] ) : TransactionRequestJsonV510 = { @@ -651,7 +668,7 @@ object JSONFactory510 extends CustomJsonFormats { createTransactionRequestJson(transactionRequest, transactionRequestAttributes) )) } - + def createViewJson(view: View): CustomViewJsonV510 = { val alias = if (view.usePublicAliasIfOneExists) @@ -1073,7 +1090,7 @@ object JSONFactory510 extends CustomJsonFormats { logo_url = if (c.logoUrl.get == null || c.logoUrl.get.isEmpty ) null else Some(c.logoUrl.get) ) } - + def createConsumersJson(consumers:List[Consumer]) = { ConsumersJsonV510(consumers.map(createConsumerJSON(_,None))) } @@ -1100,7 +1117,7 @@ object JSONFactory510 extends CustomJsonFormats { agent_number = agent.number ))) } - + def createRegulatedEntityAttributeJson(attribute: RegulatedEntityAttributeTrait): RegulatedEntityAttributeResponseJsonV510 = { RegulatedEntityAttributeResponseJsonV510( regulated_entity_id = attribute.regulatedEntityId.value, @@ -1119,7 +1136,20 @@ object JSONFactory510 extends CustomJsonFormats { attributes.map(createRegulatedEntityAttributeJson) ) } - -} + def createBankAccountBalanceJson(balance: BankAccountBalanceTrait): BankAccountBalanceResponseJsonV510 = { + BankAccountBalanceResponseJsonV510( + balance_id = balance.balanceId.value, + account_id = balance.accountId.value, + balance_type = balance.balanceType, + balance_amount = balance.balanceAmount.toString + ) + } + def createBankAccountBalancesJson(balances: List[BankAccountBalanceTrait]): BankAccountBalancesJsonV510 = { + BankAccountBalancesJsonV510( + balances.map(createBankAccountBalanceJson) + ) + } + +} diff --git a/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala b/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala new file mode 100644 index 0000000000..343dbeb229 --- /dev/null +++ b/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala @@ -0,0 +1,33 @@ +package code.bankaccountbalance + +import code.model.dataAccess.MappedBankAccount +import code.util.{Helper, MappedUUID} + +import com.openbankproject.commons.model.{AccountId, BalanceId, BankAccountBalanceTrait} +import net.liftweb.common.{Box, Empty, Full, Logger} +import net.liftweb.mapper._ + + +class BankAccountBalance extends BankAccountBalanceTrait with LongKeyedMapper[BankAccountBalance] with CreatedUpdated with IdPK { + + override def getSingleton = BankAccountBalance + + object BalanceId_ extends MappedUUID(this) +// object AccountId_ extends MappedLongForeignKey(this, MappedBankAccount) + object AccountId_ extends MappedUUID(this) + object BalanceType extends MappedString(this, 255) + //this is the smallest unit of currency! eg. cents, yen, pence, øre, etc. + object BalanceAmount extends MappedLong(this) + + val foreignMappedBankAccount: Box[MappedBankAccount] = code.model.dataAccess.MappedBankAccount.find( + By(MappedBankAccount.theAccountId, AccountId_.get) + ) + val foreignMappedBankAccountCurrency = foreignMappedBankAccount.map(_.currency).getOrElse("EUR") + + override def balanceId: BalanceId = BalanceId(BalanceId_.get) + override def accountId: AccountId = AccountId(AccountId_.get) + override def balanceType: String = BalanceType.get + override def balanceAmount: BigDecimal = Helper.smallestCurrencyUnitToBigDecimal(BalanceAmount.get, foreignMappedBankAccountCurrency) +} + +object BankAccountBalance extends BankAccountBalance with LongKeyedMetaMapper[BankAccountBalance] {} diff --git a/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalanceProvider.scala b/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalanceProvider.scala new file mode 100644 index 0000000000..97bc181ec9 --- /dev/null +++ b/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalanceProvider.scala @@ -0,0 +1,112 @@ +package code.bankaccountbalance + +import code.model.dataAccess.MappedBankAccount +import code.util.{Helper, MappedUUID} +import com.openbankproject.commons.ExecutionContext.Implicits.global +import com.openbankproject.commons.model.{AccountId, BankAccountBalanceTrait} +import net.liftweb.common.{Box, Empty, Full} +import net.liftweb.mapper._ +import net.liftweb.util.Helpers.tryo +import net.liftweb.util.SimpleInjector +import com.openbankproject.commons.model.BalanceId + +import scala.concurrent.Future + +object BankAccountBalanceX extends SimpleInjector { + + val bankAccountBalanceProvider = new Inject(buildOne _) {} + + def buildOne: BankAccountBalanceProviderTrait = MappedBankAccountBalanceProvider + + // Helper to get the count out of an option + def countOfBankAccountBalance(listOpt: Option[List[BankAccountBalance]]): Int = { + val count = listOpt match { + case Some(list) => list.size + case None => 0 + } + count + } +} + +trait BankAccountBalanceProviderTrait { + + def getBankAccountBalances(accountId: AccountId): Future[Box[List[BankAccountBalance]]] + + def getBankAccountBalanceById(balanceId: BalanceId): Future[Box[BankAccountBalance]] + + def createOrUpdateBankAccountBalance( + balanceId: Option[BalanceId], + accountId: AccountId, + balanceType: String, + balanceAmount: BigDecimal): Future[Box[BankAccountBalance]] + + def deleteBankAccountBalance(balanceId: BalanceId): Future[Box[Boolean]] + +} + +object MappedBankAccountBalanceProvider extends BankAccountBalanceProviderTrait { + + override def getBankAccountBalances(accountId: AccountId): Future[Box[List[BankAccountBalance]]] = Future { + tryo{ + BankAccountBalance.findAll( + By(BankAccountBalance.AccountId_,accountId.value) + )} + } + + override def getBankAccountBalanceById(balanceId: BalanceId): Future[Box[BankAccountBalance]] = Future { + // Find a balance by its ID + BankAccountBalance.find( + By(BankAccountBalance.BalanceId_, balanceId.value) + ) + } + + override def createOrUpdateBankAccountBalance( + balanceId: Option[BalanceId], + accountId: AccountId, + balanceType: String, + balanceAmount: BigDecimal + ): Future[Box[BankAccountBalance]] = Future { + // Get the MappedBankAccount for the given account ID + val mappedBankAccount = code.model.dataAccess.MappedBankAccount + .find( + By(MappedBankAccount.theAccountId, accountId.value) + ) + + mappedBankAccount match { + case Full(account) => + balanceId match { + case Some(id) => + BankAccountBalance.find( + By(BankAccountBalance.BalanceId_, id.value) + ) match { + case Full(balance) => + tryo { + balance + .AccountId_(accountId.value) + .BalanceType(balanceType) + .BalanceAmount(Helper.convertToSmallestCurrencyUnits(balanceAmount, account.currency)) + .saveMe() + } + case _ => Empty + } + case _ => + tryo { + BankAccountBalance.create + .AccountId_(accountId.value) + .BalanceType(balanceType) + .BalanceAmount(Helper.convertToSmallestCurrencyUnits(balanceAmount, account.currency)) + .saveMe() + } + } + case _ => Empty + } + } + + override def deleteBankAccountBalance(balanceId: BalanceId): Future[Box[Boolean]] = Future { + // Delete a balance by its ID + BankAccountBalance.find( + By(BankAccountBalance.BalanceId_, balanceId.value) + ).map(_.delete_!) + } + +} diff --git a/obp-api/src/main/scala/code/model/dataAccess/BankAccountBalance.scala b/obp-api/src/main/scala/code/model/dataAccess/BankAccountBalance.scala deleted file mode 100644 index 6ca859991f..0000000000 --- a/obp-api/src/main/scala/code/model/dataAccess/BankAccountBalance.scala +++ /dev/null @@ -1,29 +0,0 @@ -package code.model.dataAccess - -import com.openbankproject.commons.model._ -import net.liftweb.common.Box -import net.liftweb.mapper._ -import code.util.Helper - -class BankAccountBalance extends BankAccountBalanceTrait with LongKeyedMapper[BankAccountBalance] with CreatedUpdated with IdPK{ - - override def getSingleton = BankAccountBalance - - object AccountId_ extends MappedLongForeignKey(this, MappedBankAccount) - object BalanceType extends MappedString(this, 255) - //this is the smallest unit of currency! eg. cents, yen, pence, øre, etc. - object BalanceAmount extends MappedLong(this) - - val foreignMappedBankAccount: Box[MappedBankAccount] = AccountId_.foreign - val foreignMappedBankAccountCurrency = foreignMappedBankAccount.map(_.currency).getOrElse("EUR") - - override def accountId : AccountId = { - foreignMappedBankAccount.map(_.accountId).getOrElse(AccountId("")) - } - override def balanceType: String = BalanceType.get - override def balanceAmount: BigDecimal = Helper.smallestCurrencyUnitToBigDecimal(BalanceAmount.get, foreignMappedBankAccountCurrency) - - -} - -object BankAccountBalance extends BankAccountBalance with LongKeyedMetaMapper[BankAccountBalance] {} diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala index f385523763..c916b45b78 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala @@ -136,6 +136,14 @@ object RegulatedEntityId { def unapply(id : String) = Some(RegulatedEntityId(id)) } +case class BalanceId(val value : String) { + override def toString = value +} + +object BalanceId { + def unapply(id : String) = Some(BalanceId(id)) +} + case class AccountId(val value : String) { override def toString = value } @@ -229,7 +237,8 @@ trait BankAccount{ } trait BankAccountBalanceTrait { - def accountId : AccountId + def balanceId: BalanceId + def accountId: AccountId def balanceType: String def balanceAmount: BigDecimal } From 0e4beeaae2ba63de8f4c89b24c403a306f103731 Mon Sep 17 00:00:00 2001 From: Hongwei Date: Thu, 24 Apr 2025 15:10:11 +0200 Subject: [PATCH 03/10] refactor/add BankAccountBalanceTest class for comprehensive balance management testing --- .../api/v5_1_0/BankAccountBalanceTest.scala | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 obp-api/src/test/scala/code/api/v5_1_0/BankAccountBalanceTest.scala diff --git a/obp-api/src/test/scala/code/api/v5_1_0/BankAccountBalanceTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/BankAccountBalanceTest.scala new file mode 100644 index 0000000000..5d979f57eb --- /dev/null +++ b/obp-api/src/test/scala/code/api/v5_1_0/BankAccountBalanceTest.scala @@ -0,0 +1,179 @@ +package code.api.v5_1_0 + +import code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON._ +import code.api.util.APIUtil.OAuth._ +import code.api.util.ApiRole._ +import code.api.util.ErrorMessages +import code.api.v5_1_0.APIMethods510.Implementations5_1_0 +import code.entitlement.Entitlement +import code.setup.DefaultUsers +import com.github.dwickern.macros.NameOf.nameOf +import com.openbankproject.commons.model.ErrorMessage +import com.openbankproject.commons.util.ApiVersion +import net.liftweb.json.Serialization.write +import org.scalatest.Tag + +class BankAccountBalanceTest extends V510ServerSetup with DefaultUsers { + + object VersionOfApi extends Tag(ApiVersion.v5_1_0.toString) + object Create extends Tag(nameOf(Implementations5_1_0.createBankAccountBalance)) + object Update extends Tag(nameOf(Implementations5_1_0.updateBankAccountBalance)) + object Delete extends Tag(nameOf(Implementations5_1_0.deleteBankAccountBalance)) + object GetAll extends Tag(nameOf(Implementations5_1_0.getAllBankAccountBalances)) + object GetOne extends Tag(nameOf(Implementations5_1_0.getBankAccountBalanceById)) + + lazy val bankId = testBankId1.value + lazy val accountId = testAccountId1.value + lazy val balanceId = createMockBalance(bankId, accountId) + + def createMockBalance(bankId: String, accountId: String): String = { + val json = bankAccountBalanceRequestJsonV510 + val entitlement = Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateBankAccountBalance.toString) + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").POST <@ user1 + val response = makePostRequest(request, write(json)) + Entitlement.entitlement.vend.deleteEntitlement(entitlement) + (response.body.extract[BankAccountBalanceResponseJsonV510].balance_id) + } + + feature("Create Bank Account Balance") { + + scenario("401 Unauthorized", Create, VersionOfApi) { + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").POST + val response = makePostRequest(request, write(bankAccountBalanceRequestJsonV510)) + response.code should equal(401) + response.body.extract[ErrorMessage].message should equal(ErrorMessages.UserNotLoggedIn) + } + + scenario("403 Forbidden (no role)", Create, VersionOfApi) { + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").POST <@ user1 + val response = makePostRequest(request, write(bankAccountBalanceRequestJsonV510)) + response.code should equal(403) + response.body.extract[ErrorMessage].message should startWith(ErrorMessages.UserHasMissingRoles + CanCreateBankAccountBalance) + } + + scenario("201 Success + Field Echo", Create, VersionOfApi) { + val entitlement = Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateBankAccountBalance.toString) + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").POST <@ user1 + val response = makePostRequest(request, write(bankAccountBalanceRequestJsonV510)) + response.code should equal(201) + val created = response.body.extract[BankAccountBalanceResponseJsonV510] + created.balance_type should equal(bankAccountBalanceRequestJsonV510.balance_type) + created.balance_amount should equal(bankAccountBalanceRequestJsonV510.balance_amount) + created.account_id should equal(accountId) + Entitlement.entitlement.vend.deleteEntitlement(entitlement) + } + } + + feature("Update Bank Account Balance") { + + scenario("401 Unauthorized", Update, VersionOfApi) { + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).PUT + val response = makePutRequest(request, write(bankAccountBalanceRequestJsonV510)) + response.code should equal(401) + } + + scenario("403 Forbidden", Update, VersionOfApi) { + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).PUT <@ user1 + val response = makePutRequest(request, write(bankAccountBalanceRequestJsonV510)) + response.code should equal(403) + } + + scenario("200 Success", Update, VersionOfApi) { + lazy val bankId = testBankId1.value + lazy val accountId = testAccountId1.value + lazy val balanceId = createMockBalance(bankId, accountId) + + val entitlement = Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanUpdateBankAccountBalance.toString) + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).PUT <@ user1 + val response = makePutRequest(request, write(bankAccountBalanceRequestJsonV510)) + response.code should equal(200) + Entitlement.entitlement.vend.deleteEntitlement(entitlement) + } + } + + feature("Delete Bank Account Balance") { + lazy val bankId = testBankId1.value + lazy val accountId = testAccountId1.value + lazy val balanceId = createMockBalance(bankId, accountId) + + scenario("401 Unauthorized", Delete, VersionOfApi) { + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).DELETE + val response = makeDeleteRequest(request) + response.code should equal(401) + } + + scenario("403 Forbidden", Delete, VersionOfApi) { + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).DELETE <@ user1 + val response = makeDeleteRequest(request) + response.code should equal(403) + } + + scenario("204 Success", Delete, VersionOfApi) { + lazy val bankId = testBankId1.value + lazy val accountId = testAccountId1.value + lazy val balanceId = createMockBalance(bankId, accountId) + + val entitlement = Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteBankAccountBalance.toString) + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).DELETE <@ user1 + val response = makeDeleteRequest(request) + response.code should equal(204) + Entitlement.entitlement.vend.deleteEntitlement(entitlement) + } + } + + feature("Get All Bank Account Balances") { + lazy val bankId = testBankId1.value + lazy val accountId = testAccountId1.value + lazy val balanceId = createMockBalance(bankId, accountId) + + scenario("401 Unauthorized", GetAll, VersionOfApi) { + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").GET + val response = makeGetRequest(request) + response.code should equal(401) + } + + scenario("403 Forbidden", GetAll, VersionOfApi) { + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").GET <@ user1 + val response = makeGetRequest(request) + response.code should equal(403) + } + + scenario("200 Success", GetAll, VersionOfApi) { + lazy val bankId = testBankId1.value + lazy val accountId = testAccountId1.value + val entitlement = Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetBankAccountBalances.toString) + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").GET <@ user1 + val response = makeGetRequest(request) + response.code should equal(200) + Entitlement.entitlement.vend.deleteEntitlement(entitlement) + } + } + + feature("Get Bank Account Balance by ID") { + lazy val bankId = testBankId1.value + lazy val accountId = testAccountId1.value + + scenario("401 Unauthorized", GetOne, VersionOfApi) { + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).GET + val response = makeGetRequest(request) + response.code should equal(401) + } + + scenario("403 Forbidden", GetOne, VersionOfApi) { + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).GET <@ user1 + val response = makeGetRequest(request) + response.code should equal(403) + } + + scenario("200 Success", GetOne, VersionOfApi) { + lazy val bankId = testBankId1.value + lazy val accountId = testAccountId1.value + lazy val balanceId = createMockBalance(bankId, accountId) + val entitlement = Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetBankAccountBalance.toString) + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).GET <@ user1 + val response = makeGetRequest(request) + response.code should equal(200) + Entitlement.entitlement.vend.deleteEntitlement(entitlement) + } + } +} \ No newline at end of file From d7bd348442088eae6dbf606df91a3e560700c7ec Mon Sep 17 00:00:00 2001 From: Hongwei Date: Thu, 24 Apr 2025 15:46:10 +0200 Subject: [PATCH 04/10] refactor/remove unused BankAccountBalance roles and update related references --- .../main/scala/bootstrap/liftweb/Boot.scala | 4 +- .../main/scala/code/api/util/ApiRole.scala | 30 ++++----- .../scala/code/api/v5_1_0/APIMethods510.scala | 15 ++--- .../BankAccountBalance.scala | 8 +-- .../bankconnectors/LocalMappedConnector.scala | 2 +- .../rabbitmq/RabbitMQConnector_vOct2024.scala | 2 +- .../rest/RestConnector_vMar2019.scala | 2 +- .../StoredProcedureConnector_vDec2019.scala | 2 +- .../api/v5_1_0/BankAccountBalanceTest.scala | 64 ++++--------------- .../commons/model/BankingModel.scala | 4 +- 10 files changed, 43 insertions(+), 90 deletions(-) diff --git a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala index dd71ba290d..1789176348 100644 --- a/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala +++ b/obp-api/src/main/scala/bootstrap/liftweb/Boot.scala @@ -137,7 +137,7 @@ import code.regulatedentities.attribute.RegulatedEntityAttribute import com.openbankproject.commons.model.ErrorMessage import com.openbankproject.commons.util.Functions.Implicits._ import com.openbankproject.commons.util.{ApiVersion, Functions} - +import code.bankaccountbalance.BankAccountBalance import javax.mail.internet.MimeMessage import net.liftweb.common._ import net.liftweb.db.{DB, DBLogEntry} @@ -1129,7 +1129,7 @@ object ToSchemify { CustomerAccountLink, TransactionIdMapping, RegulatedEntityAttribute, - code.bankaccountbalance.BankAccountBalance + BankAccountBalance ) // start grpc server diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index 7b228f65f9..04ba4a1609 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -1010,21 +1010,21 @@ object ApiRole extends MdcLoggable{ case class CanGetBankLevelEndpointTag(requiresBankId: Boolean = true) extends ApiRole lazy val canGetBankLevelEndpointTag = CanGetBankLevelEndpointTag() - // BankAccountBalance roles - case class CanCreateBankAccountBalance(requiresBankId: Boolean = false) extends ApiRole - lazy val canCreateBankAccountBalance = CanCreateBankAccountBalance() - - case class CanGetBankAccountBalance(requiresBankId: Boolean = false) extends ApiRole - lazy val canGetBankAccountBalance = CanGetBankAccountBalance() - - case class CanGetBankAccountBalances(requiresBankId: Boolean = false) extends ApiRole - lazy val canGetBankAccountBalances = CanGetBankAccountBalances() - - case class CanUpdateBankAccountBalance(requiresBankId: Boolean = false) extends ApiRole - lazy val canUpdateBankAccountBalance = CanUpdateBankAccountBalance() - - case class CanDeleteBankAccountBalance(requiresBankId: Boolean = false) extends ApiRole - lazy val canDeleteBankAccountBalance = CanDeleteBankAccountBalance() +// // BankAccountBalance roles +// case class CanCreateBankAccountBalance(requiresBankId: Boolean = false) extends ApiRole +// lazy val canCreateBankAccountBalance = CanCreateBankAccountBalance() +// +// case class CanGetBankAccountBalance(requiresBankId: Boolean = false) extends ApiRole +// lazy val canGetBankAccountBalance = CanGetBankAccountBalance() +// +// case class CanGetBankAccountBalances(requiresBankId: Boolean = false) extends ApiRole +// lazy val canGetBankAccountBalances = CanGetBankAccountBalances() +// +// case class CanUpdateBankAccountBalance(requiresBankId: Boolean = false) extends ApiRole +// lazy val canUpdateBankAccountBalance = CanUpdateBankAccountBalance() +// +// case class CanDeleteBankAccountBalance(requiresBankId: Boolean = false) extends ApiRole +// lazy val canDeleteBankAccountBalance = CanDeleteBankAccountBalance() case class CanCreateHistoricalTransactionAtBank(requiresBankId: Boolean = true) extends ApiRole lazy val canCreateHistoricalTransactionAtBank = CanCreateHistoricalTransactionAtBank() diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index 0253362202..8f3ea8ef9b 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -208,8 +208,7 @@ trait APIMethods510 { InvalidJsonFormat, UnknownError ), - List(apiTagAccount, apiTagBalance), - Some(List(canCreateBankAccountBalance)) + List(apiTagAccount, apiTagBalance) ) lazy val createBankAccountBalance: OBPEndpoint = { @@ -256,8 +255,7 @@ trait APIMethods510 { UserHasMissingRoles, UnknownError ), - List(apiTagAccount, apiTagBalance), - Some(List(canGetBankAccountBalance)) + List(apiTagAccount, apiTagBalance) ) lazy val getBankAccountBalanceById: OBPEndpoint = { @@ -295,8 +293,7 @@ trait APIMethods510 { UserHasMissingRoles, UnknownError ), - List(apiTagAccount, apiTagBalance), - Some(List(canGetBankAccountBalances)) + List(apiTagAccount, apiTagBalance) ) lazy val getAllBankAccountBalances: OBPEndpoint = { @@ -335,8 +332,7 @@ trait APIMethods510 { InvalidJsonFormat, UnknownError ), - List(apiTagAccount, apiTagBalance), - Some(List(canUpdateBankAccountBalance)) + List(apiTagAccount, apiTagBalance) ) lazy val updateBankAccountBalance: OBPEndpoint = { @@ -383,8 +379,7 @@ trait APIMethods510 { UserHasMissingRoles, UnknownError ), - List(apiTagAccount, apiTagBalance), - Some(List(canDeleteBankAccountBalance)) + List(apiTagAccount, apiTagBalance) ) lazy val deleteBankAccountBalance: OBPEndpoint = { diff --git a/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala b/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala index 343dbeb229..2f7906b738 100644 --- a/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala +++ b/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala @@ -19,10 +19,10 @@ class BankAccountBalance extends BankAccountBalanceTrait with LongKeyedMapper[Ba //this is the smallest unit of currency! eg. cents, yen, pence, øre, etc. object BalanceAmount extends MappedLong(this) - val foreignMappedBankAccount: Box[MappedBankAccount] = code.model.dataAccess.MappedBankAccount.find( - By(MappedBankAccount.theAccountId, AccountId_.get) - ) - val foreignMappedBankAccountCurrency = foreignMappedBankAccount.map(_.currency).getOrElse("EUR") +// val foreignMappedBankAccount: Box[MappedBankAccount] = code.model.dataAccess.MappedBankAccount.find( +// By(MappedBankAccount.theAccountId, AccountId_.get) +// ) + val foreignMappedBankAccountCurrency = "EUR" //foreignMappedBankAccount.map(_.currency).getOrElse("EUR") override def balanceId: BalanceId = BalanceId(BalanceId_.get) override def accountId: AccountId = AccountId(AccountId_.get) diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index 93dd6b97d6..0ee18569f5 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -990,7 +990,7 @@ object LocalMappedConnector extends Connector with MdcLoggable { label = bankAccount.label, bankId = bankAccount.bankId.value, accountRoutings = bankAccount.accountRoutings.map(accountRounting => AccountRouting(accountRounting.scheme, accountRounting.address)), - balances = List(OneAccountBalance(AmountOfMoney(bankAccount.currency, bankAccount.balance.toString),"OpeningBooked")), + balances = List(BankAccountBalance(AmountOfMoney(bankAccount.currency, bankAccount.balance.toString),"OpeningBooked")), overallBalance = AmountOfMoney(bankAccount.currency, bankAccount.balance.toString), overallBalanceDate = now ) diff --git a/obp-api/src/main/scala/code/bankconnectors/rabbitmq/RabbitMQConnector_vOct2024.scala b/obp-api/src/main/scala/code/bankconnectors/rabbitmq/RabbitMQConnector_vOct2024.scala index c43e37f691..ea08ff63c5 100644 --- a/obp-api/src/main/scala/code/bankconnectors/rabbitmq/RabbitMQConnector_vOct2024.scala +++ b/obp-api/src/main/scala/code/bankconnectors/rabbitmq/RabbitMQConnector_vOct2024.scala @@ -1071,7 +1071,7 @@ trait RabbitMQConnector_vOct2024 extends Connector with MdcLoggable { bankId=bankIdExample.value, accountRoutings=List( AccountRouting(scheme=accountRoutingSchemeExample.value, address=accountRoutingAddressExample.value)), - balances=List( OneAccountBalance(balance= AmountOfMoney(currency=balanceCurrencyExample.value, + balances=List( BankAccountBalance(balance= AmountOfMoney(currency=balanceCurrencyExample.value, amount=balanceAmountExample.value), balanceType="string")), overallBalance= AmountOfMoney(currency=currencyExample.value, diff --git a/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala b/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala index 445f38ba0a..92343ceb14 100644 --- a/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala +++ b/obp-api/src/main/scala/code/bankconnectors/rest/RestConnector_vMar2019.scala @@ -1098,7 +1098,7 @@ trait RestConnector_vMar2019 extends Connector with MdcLoggable { bankId=bankIdExample.value, accountRoutings=List( AccountRouting(scheme=accountRoutingSchemeExample.value, address=accountRoutingAddressExample.value)), - balances=List( OneAccountBalance(balance= AmountOfMoney(currency=balanceCurrencyExample.value, + balances=List( BankAccountBalance(balance= AmountOfMoney(currency=balanceCurrencyExample.value, amount=balanceAmountExample.value), balanceType="string")), overallBalance= AmountOfMoney(currency=currencyExample.value, diff --git a/obp-api/src/main/scala/code/bankconnectors/storedprocedure/StoredProcedureConnector_vDec2019.scala b/obp-api/src/main/scala/code/bankconnectors/storedprocedure/StoredProcedureConnector_vDec2019.scala index 7459065a59..b1675d8b55 100644 --- a/obp-api/src/main/scala/code/bankconnectors/storedprocedure/StoredProcedureConnector_vDec2019.scala +++ b/obp-api/src/main/scala/code/bankconnectors/storedprocedure/StoredProcedureConnector_vDec2019.scala @@ -1078,7 +1078,7 @@ trait StoredProcedureConnector_vDec2019 extends Connector with MdcLoggable { bankId=bankIdExample.value, accountRoutings=List( AccountRouting(scheme=accountRoutingSchemeExample.value, address=accountRoutingAddressExample.value)), - balances=List( OneAccountBalance(balance= AmountOfMoney(currency=balanceCurrencyExample.value, + balances=List( BankAccountBalance(balance= AmountOfMoney(currency=balanceCurrencyExample.value, amount=balanceAmountExample.value), balanceType="string")), overallBalance= AmountOfMoney(currency=currencyExample.value, diff --git a/obp-api/src/test/scala/code/api/v5_1_0/BankAccountBalanceTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/BankAccountBalanceTest.scala index 5d979f57eb..4ddfe1de17 100644 --- a/obp-api/src/test/scala/code/api/v5_1_0/BankAccountBalanceTest.scala +++ b/obp-api/src/test/scala/code/api/v5_1_0/BankAccountBalanceTest.scala @@ -28,66 +28,48 @@ class BankAccountBalanceTest extends V510ServerSetup with DefaultUsers { def createMockBalance(bankId: String, accountId: String): String = { val json = bankAccountBalanceRequestJsonV510 - val entitlement = Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateBankAccountBalance.toString) - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").POST <@ user1 + val request = (v5_1_0_Request / "accounts" / accountId / "balances").POST <@ user1 val response = makePostRequest(request, write(json)) - Entitlement.entitlement.vend.deleteEntitlement(entitlement) (response.body.extract[BankAccountBalanceResponseJsonV510].balance_id) } feature("Create Bank Account Balance") { scenario("401 Unauthorized", Create, VersionOfApi) { - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").POST + val request = (v5_1_0_Request / "accounts" / accountId / "balances").POST val response = makePostRequest(request, write(bankAccountBalanceRequestJsonV510)) response.code should equal(401) response.body.extract[ErrorMessage].message should equal(ErrorMessages.UserNotLoggedIn) } - scenario("403 Forbidden (no role)", Create, VersionOfApi) { - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").POST <@ user1 - val response = makePostRequest(request, write(bankAccountBalanceRequestJsonV510)) - response.code should equal(403) - response.body.extract[ErrorMessage].message should startWith(ErrorMessages.UserHasMissingRoles + CanCreateBankAccountBalance) - } - scenario("201 Success + Field Echo", Create, VersionOfApi) { - val entitlement = Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanCreateBankAccountBalance.toString) - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").POST <@ user1 + val request = (v5_1_0_Request / "accounts" / accountId / "balances").POST <@ user1 val response = makePostRequest(request, write(bankAccountBalanceRequestJsonV510)) response.code should equal(201) val created = response.body.extract[BankAccountBalanceResponseJsonV510] created.balance_type should equal(bankAccountBalanceRequestJsonV510.balance_type) created.balance_amount should equal(bankAccountBalanceRequestJsonV510.balance_amount) created.account_id should equal(accountId) - Entitlement.entitlement.vend.deleteEntitlement(entitlement) } } feature("Update Bank Account Balance") { scenario("401 Unauthorized", Update, VersionOfApi) { - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).PUT + val request = (v5_1_0_Request / "accounts" / accountId / "balances" / balanceId).PUT val response = makePutRequest(request, write(bankAccountBalanceRequestJsonV510)) response.code should equal(401) } - scenario("403 Forbidden", Update, VersionOfApi) { - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).PUT <@ user1 - val response = makePutRequest(request, write(bankAccountBalanceRequestJsonV510)) - response.code should equal(403) - } scenario("200 Success", Update, VersionOfApi) { lazy val bankId = testBankId1.value lazy val accountId = testAccountId1.value lazy val balanceId = createMockBalance(bankId, accountId) - val entitlement = Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanUpdateBankAccountBalance.toString) - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).PUT <@ user1 + val request = (v5_1_0_Request / "accounts" / accountId / "balances" / balanceId).PUT <@ user1 val response = makePutRequest(request, write(bankAccountBalanceRequestJsonV510)) response.code should equal(200) - Entitlement.entitlement.vend.deleteEntitlement(entitlement) } } @@ -97,27 +79,19 @@ class BankAccountBalanceTest extends V510ServerSetup with DefaultUsers { lazy val balanceId = createMockBalance(bankId, accountId) scenario("401 Unauthorized", Delete, VersionOfApi) { - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).DELETE + val request = (v5_1_0_Request / "accounts" / accountId / "balances" / balanceId).DELETE val response = makeDeleteRequest(request) response.code should equal(401) } - scenario("403 Forbidden", Delete, VersionOfApi) { - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).DELETE <@ user1 - val response = makeDeleteRequest(request) - response.code should equal(403) - } - scenario("204 Success", Delete, VersionOfApi) { lazy val bankId = testBankId1.value lazy val accountId = testAccountId1.value lazy val balanceId = createMockBalance(bankId, accountId) - val entitlement = Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanDeleteBankAccountBalance.toString) - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).DELETE <@ user1 + val request = (v5_1_0_Request / "accounts" / accountId / "balances" / balanceId).DELETE <@ user1 val response = makeDeleteRequest(request) response.code should equal(204) - Entitlement.entitlement.vend.deleteEntitlement(entitlement) } } @@ -127,25 +101,17 @@ class BankAccountBalanceTest extends V510ServerSetup with DefaultUsers { lazy val balanceId = createMockBalance(bankId, accountId) scenario("401 Unauthorized", GetAll, VersionOfApi) { - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").GET + val request = (v5_1_0_Request / "accounts" / accountId / "balances").GET val response = makeGetRequest(request) response.code should equal(401) } - scenario("403 Forbidden", GetAll, VersionOfApi) { - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").GET <@ user1 - val response = makeGetRequest(request) - response.code should equal(403) - } - scenario("200 Success", GetAll, VersionOfApi) { lazy val bankId = testBankId1.value lazy val accountId = testAccountId1.value - val entitlement = Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetBankAccountBalances.toString) - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").GET <@ user1 + val request = (v5_1_0_Request / "accounts" / accountId / "balances").GET <@ user1 val response = makeGetRequest(request) response.code should equal(200) - Entitlement.entitlement.vend.deleteEntitlement(entitlement) } } @@ -154,26 +120,18 @@ class BankAccountBalanceTest extends V510ServerSetup with DefaultUsers { lazy val accountId = testAccountId1.value scenario("401 Unauthorized", GetOne, VersionOfApi) { - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).GET + val request = (v5_1_0_Request / "accounts" / accountId / "balances" / balanceId).GET val response = makeGetRequest(request) response.code should equal(401) } - scenario("403 Forbidden", GetOne, VersionOfApi) { - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).GET <@ user1 - val response = makeGetRequest(request) - response.code should equal(403) - } - scenario("200 Success", GetOne, VersionOfApi) { lazy val bankId = testBankId1.value lazy val accountId = testAccountId1.value lazy val balanceId = createMockBalance(bankId, accountId) - val entitlement = Entitlement.entitlement.vend.addEntitlement("", resourceUser1.userId, CanGetBankAccountBalance.toString) - val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).GET <@ user1 + val request = (v5_1_0_Request / "accounts" / accountId / "balances" / balanceId).GET <@ user1 val response = makeGetRequest(request) response.code should equal(200) - Entitlement.entitlement.vend.deleteEntitlement(entitlement) } } } \ No newline at end of file diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala index c916b45b78..2775ed740d 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala @@ -398,12 +398,12 @@ case class AccountBalances( label: String, bankId: String, accountRoutings: List[AccountRouting], - balances: List[OneAccountBalance], + balances: List[BankAccountBalance], overallBalance: AmountOfMoney, overallBalanceDate: Date ) -case class OneAccountBalance( +case class BankAccountBalance( balance: AmountOfMoney, balanceType: String, ) From b5dff9665bea8b1801b98904d4907ac438f8d5e8 Mon Sep 17 00:00:00 2001 From: Hongwei Date: Thu, 24 Apr 2025 16:25:49 +0200 Subject: [PATCH 05/10] refactor/update foreignMappedBankAccountCurrency to use tryo for safer currency retrieval --- .../code/bankaccountbalance/BankAccountBalance.scala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala b/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala index 2f7906b738..7bbc19fa56 100644 --- a/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala +++ b/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala @@ -6,6 +6,7 @@ import code.util.{Helper, MappedUUID} import com.openbankproject.commons.model.{AccountId, BalanceId, BankAccountBalanceTrait} import net.liftweb.common.{Box, Empty, Full, Logger} import net.liftweb.mapper._ +import net.liftweb.util.Helpers.tryo class BankAccountBalance extends BankAccountBalanceTrait with LongKeyedMapper[BankAccountBalance] with CreatedUpdated with IdPK { @@ -19,10 +20,12 @@ class BankAccountBalance extends BankAccountBalanceTrait with LongKeyedMapper[Ba //this is the smallest unit of currency! eg. cents, yen, pence, øre, etc. object BalanceAmount extends MappedLong(this) -// val foreignMappedBankAccount: Box[MappedBankAccount] = code.model.dataAccess.MappedBankAccount.find( -// By(MappedBankAccount.theAccountId, AccountId_.get) -// ) - val foreignMappedBankAccountCurrency = "EUR" //foreignMappedBankAccount.map(_.currency).getOrElse("EUR") + val foreignMappedBankAccountCurrency = tryo{code.model.dataAccess.MappedBankAccount + .find( + By(MappedBankAccount.theAccountId, AccountId_.get)) + .map(_.currency) + .getOrElse("EUR") + }.getOrElse("EUR") override def balanceId: BalanceId = BalanceId(BalanceId_.get) override def accountId: AccountId = AccountId(AccountId_.get) From e78fd370f4ccee16ac94dbd2d3200640e942c763 Mon Sep 17 00:00:00 2001 From: Hongwei Date: Mon, 28 Apr 2025 08:17:12 +0200 Subject: [PATCH 06/10] refactor/update default bank ID documentation to specify default value as OBP --- obp-api/src/main/resources/props/sample.props.template | 4 ++-- release_notes.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template index 2190e6c321..d813eb35f3 100644 --- a/obp-api/src/main/resources/props/sample.props.template +++ b/obp-api/src/main/resources/props/sample.props.template @@ -589,9 +589,9 @@ apiOptions.getProductsIsPublic = true apiOptions.getTransactionTypesIsPublic = true apiOptions.getCurrentFxRateIsPublic = true -## Default Bank. Incase the server wants to support a default bank so developers don't have to specify BANK_ID +## Default Bank. Incase the server wants to support a default bank so developers don't have to specify BANK_ID, the default value is OBP. ## e.g. developers could use /my/accounts as well as /my/banks/BANK_ID/accounts -defaultBank.bank_id=THE_DEFAULT_BANK_ID +defaultBank.bank_id=OBP diff --git a/release_notes.md b/release_notes.md index d5dbc5dfff..cbd940748b 100644 --- a/release_notes.md +++ b/release_notes.md @@ -189,7 +189,7 @@ Date Commit Action 02/05/2017 3084827 added 1 new caching props to sample.props.template api.cache.ttl.seconds.APIMethods121.getTransactions. If it's omitted default value is 0 i.e. no caching. This cacahe is from API level. 10/05/2017 7f95a5c added allow_public_views=false, we will not create the public views and will not access them (if public views are exsiting)when it is false. 17/07/2017 1530231 added account_id.length=64, this will set all relevant accountid length to 64, when create new sandbox. -17/02/2016 e3bead1 Added Props defaultBank.bank_id. Default Bank. Incase the server wants to support a default bank so developers don't have to specify BANK_ID +17/02/2016 e3bead1 Added Props defaultBank.bank_id. Default Bank. Incase the server wants to support a default bank so developers don't have to specify BANK_ID. The default value is OBP. ``` From 4496aade085d342115b23e33ba247c734715fc9d Mon Sep 17 00:00:00 2001 From: Hongwei Date: Mon, 28 Apr 2025 08:38:04 +0200 Subject: [PATCH 07/10] refactor/update API documentation for getBankAccountsBalancesThroughView to include VIEW_ID --- obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index 8f3ea8ef9b..69966bafce 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -4109,7 +4109,7 @@ trait APIMethods510 { nameOf(getBankAccountsBalancesThroughView), "GET", "/banks/BANK_ID/views/VIEW_ID/balances", - "Get Account Balances by BANK_ID", + "Get Account Balances by BANK_ID through the VIEW_ID", """Get the Balances for the Account specified by BANK_ID.""", EmptyBody, accountBalancesV400Json, From 0194fb35bb3521dcc492edd12a1adf072cdf43b4 Mon Sep 17 00:00:00 2001 From: Hongwei Date: Mon, 28 Apr 2025 08:38:35 +0200 Subject: [PATCH 08/10] refactor/add bankId to BankAccountBalanceTrait and implement in BankAccountBalance --- .../scala/code/bankaccountbalance/BankAccountBalance.scala | 6 +++--- .../com/openbankproject/commons/model/BankingModel.scala | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala b/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala index 7bbc19fa56..882f4c213b 100644 --- a/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala +++ b/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala @@ -3,8 +3,7 @@ package code.bankaccountbalance import code.model.dataAccess.MappedBankAccount import code.util.{Helper, MappedUUID} -import com.openbankproject.commons.model.{AccountId, BalanceId, BankAccountBalanceTrait} -import net.liftweb.common.{Box, Empty, Full, Logger} +import com.openbankproject.commons.model.{BankId, AccountId, BalanceId, BankAccountBalanceTrait} import net.liftweb.mapper._ import net.liftweb.util.Helpers.tryo @@ -13,8 +12,8 @@ class BankAccountBalance extends BankAccountBalanceTrait with LongKeyedMapper[Ba override def getSingleton = BankAccountBalance + object BankId_ extends MappedUUID(this) object BalanceId_ extends MappedUUID(this) -// object AccountId_ extends MappedLongForeignKey(this, MappedBankAccount) object AccountId_ extends MappedUUID(this) object BalanceType extends MappedString(this, 255) //this is the smallest unit of currency! eg. cents, yen, pence, øre, etc. @@ -28,6 +27,7 @@ class BankAccountBalance extends BankAccountBalanceTrait with LongKeyedMapper[Ba }.getOrElse("EUR") override def balanceId: BalanceId = BalanceId(BalanceId_.get) + override def bankId: BankId = BankId(BankId_.get) override def accountId: AccountId = AccountId(AccountId_.get) override def balanceType: String = BalanceType.get override def balanceAmount: BigDecimal = Helper.smallestCurrencyUnitToBigDecimal(BalanceAmount.get, foreignMappedBankAccountCurrency) diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala index 2775ed740d..3a320f8bc3 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala @@ -238,6 +238,7 @@ trait BankAccount{ trait BankAccountBalanceTrait { def balanceId: BalanceId + def bankId: BankId def accountId: AccountId def balanceType: String def balanceAmount: BigDecimal From 8ee76083b0579681d3248505e2a6c565640e2aa0 Mon Sep 17 00:00:00 2001 From: Hongwei Date: Mon, 28 Apr 2025 12:49:30 +0200 Subject: [PATCH 09/10] refactor/update Bank Account Balance endpoints to include bankId in the URL structure --- .../SwaggerDefinitionsJSON.scala | 3 +- .../main/scala/code/api/util/ApiRole.scala | 16 +- .../scala/code/api/util/ErrorMessages.scala | 2 + .../newstyle/BankAccountBalanceNewStyle.scala | 14 +- .../scala/code/api/v5_1_0/APIMethods510.scala | 433 +++++++++--------- .../code/api/v5_1_0/JSONFactory5.1.0.scala | 4 +- .../BankAccountBalance.scala | 4 +- .../BankAccountBalanceProvider.scala | 12 +- .../api/v5_1_0/BankAccountBalanceTest.scala | 49 +- .../commons/model/BankingModel.scala | 2 +- 10 files changed, 293 insertions(+), 246 deletions(-) diff --git a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala index 061691dbbb..9f417d64f8 100644 --- a/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala +++ b/obp-api/src/main/scala/code/api/ResourceDocs1_4_0/SwaggerDefinitionsJSON.scala @@ -5676,8 +5676,9 @@ object SwaggerDefinitionsJSON { ) lazy val bankAccountBalanceResponseJsonV510 = BankAccountBalanceResponseJsonV510( - balance_id = balanceIdExample.value, + bank_id = bankIdExample.value, account_id = accountIdExample.value, + balance_id = balanceIdExample.value, balance_type = balanceTypeExample.value, balance_amount = balanceAmountExample.value ) diff --git a/obp-api/src/main/scala/code/api/util/ApiRole.scala b/obp-api/src/main/scala/code/api/util/ApiRole.scala index 04ba4a1609..07862ae154 100644 --- a/obp-api/src/main/scala/code/api/util/ApiRole.scala +++ b/obp-api/src/main/scala/code/api/util/ApiRole.scala @@ -1011,20 +1011,20 @@ object ApiRole extends MdcLoggable{ lazy val canGetBankLevelEndpointTag = CanGetBankLevelEndpointTag() // // BankAccountBalance roles -// case class CanCreateBankAccountBalance(requiresBankId: Boolean = false) extends ApiRole -// lazy val canCreateBankAccountBalance = CanCreateBankAccountBalance() + case class CanCreateBankAccountBalance(requiresBankId: Boolean = true) extends ApiRole + lazy val canCreateBankAccountBalance = CanCreateBankAccountBalance() // // case class CanGetBankAccountBalance(requiresBankId: Boolean = false) extends ApiRole // lazy val canGetBankAccountBalance = CanGetBankAccountBalance() // // case class CanGetBankAccountBalances(requiresBankId: Boolean = false) extends ApiRole // lazy val canGetBankAccountBalances = CanGetBankAccountBalances() -// -// case class CanUpdateBankAccountBalance(requiresBankId: Boolean = false) extends ApiRole -// lazy val canUpdateBankAccountBalance = CanUpdateBankAccountBalance() -// -// case class CanDeleteBankAccountBalance(requiresBankId: Boolean = false) extends ApiRole -// lazy val canDeleteBankAccountBalance = CanDeleteBankAccountBalance() + + case class CanUpdateBankAccountBalance(requiresBankId: Boolean = true) extends ApiRole + lazy val canUpdateBankAccountBalance = CanUpdateBankAccountBalance() + + case class CanDeleteBankAccountBalance(requiresBankId: Boolean = true) extends ApiRole + lazy val canDeleteBankAccountBalance = CanDeleteBankAccountBalance() case class CanCreateHistoricalTransactionAtBank(requiresBankId: Boolean = true) extends ApiRole lazy val canCreateHistoricalTransactionAtBank = CanCreateHistoricalTransactionAtBank() diff --git a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala index 48afde5e0c..21ed948e71 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -250,6 +250,8 @@ object ErrorMessages { val InvalidUserProvider = "OBP-20103: Invalid DAuth User Provider." val UserNotFoundByProviderAndProvideId= "OBP-20104: User not found by PROVIDER and PROVIDER_ID." + val BankAccountBalanceNotFoundById = "OBP-20105: BankAccountBalance not found. Please specify a valid value for BALANCE_ID." + // OAuth 2 val ApplicationNotIdentified = "OBP-20200: The application cannot be identified. " val Oauth2IsNotAllowed = "OBP-20201: OAuth2 is not allowed at this instance." diff --git a/obp-api/src/main/scala/code/api/util/newstyle/BankAccountBalanceNewStyle.scala b/obp-api/src/main/scala/code/api/util/newstyle/BankAccountBalanceNewStyle.scala index 4fa3232e41..78ad1c3ab6 100644 --- a/obp-api/src/main/scala/code/api/util/newstyle/BankAccountBalanceNewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/newstyle/BankAccountBalanceNewStyle.scala @@ -1,11 +1,11 @@ package code.api.util.newstyle import code.api.util.APIUtil.{OBPReturnType, unboxFullOrFail} -import code.api.util.ErrorMessages.{InvalidConnectorResponse} +import code.api.util.ErrorMessages.{BankAccountBalanceNotFoundById, InvalidConnectorResponse} import code.api.util.CallContext -import code.bankaccountbalance.{BankAccountBalanceX} +import code.bankaccountbalance.BankAccountBalanceX import com.openbankproject.commons.ExecutionContext.Implicits.global -import com.openbankproject.commons.model.{AccountId, BankAccountBalanceTrait} +import com.openbankproject.commons.model.{AccountId, BankAccountBalanceTrait, BankId} import com.github.dwickern.macros.NameOf.nameOf import com.openbankproject.commons.model.BalanceId @@ -39,7 +39,7 @@ object BankAccountBalanceNewStyle { unboxFullOrFail( result, callContext, - s"$InvalidConnectorResponse ${nameOf(getBankAccountBalanceById _)}", + s"$BankAccountBalanceNotFoundById Current BALANCE_ID(${balanceId.value})", 404), callContext ) @@ -47,15 +47,17 @@ object BankAccountBalanceNewStyle { } def createOrUpdateBankAccountBalance( - balanceId: Option[BalanceId], + bankId: BankId, accountId: AccountId, + balanceId: Option[BalanceId], balanceType: String, balanceAmount: BigDecimal, callContext: Option[CallContext] ): OBPReturnType[BankAccountBalanceTrait] = { BankAccountBalanceX.bankAccountBalanceProvider.vend.createOrUpdateBankAccountBalance( - balanceId, + bankId, accountId, + balanceId, balanceType, balanceAmount ).map { diff --git a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala index 69966bafce..7112b89a8b 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/APIMethods510.scala @@ -188,217 +188,6 @@ trait APIMethods510 { } } - staticResourceDocs += ResourceDoc( - createBankAccountBalance, - implementedInApiVersion, - nameOf(createBankAccountBalance), - "POST", - "/accounts/ACCOUNT_ID/balances", - "Create Bank Account Balance", - s"""Create a new Balance for a Bank Account. - | - |${userAuthenticationMessage(true)} - | - |""", - bankAccountBalanceRequestJsonV510, - bankAccountBalanceResponseJsonV510, - List( - $UserNotLoggedIn, - UserHasMissingRoles, - InvalidJsonFormat, - UnknownError - ), - List(apiTagAccount, apiTagBalance) - ) - - lazy val createBankAccountBalance: OBPEndpoint = { - case "accounts" :: AccountId(accountId) :: "balances" :: Nil JsonPost json -> _ => { - cc => - implicit val ec = EndpointContext(Some(cc)) - for { - (Full(u), callContext) <- SS.user - postedData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $BankAccountBalanceRequestJsonV510 ", 400, callContext) { - json.extract[BankAccountBalanceRequestJsonV510] - } - balanceAmount <- NewStyle.function.tryons(s"$InvalidNumber Current balance_amount is ${postedData.balance_amount}" , 400, cc.callContext) { - BigDecimal(postedData.balance_amount) - } - (balance, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.createOrUpdateBankAccountBalance( - balanceId = None, - accountId = accountId, - balanceType = postedData.balance_type, - balanceAmount = balanceAmount, - callContext = cc.callContext - ) - } yield { - (JSONFactory510.createBankAccountBalanceJson(balance), HttpCode.`201`(callContext)) - } - } - } - - staticResourceDocs += ResourceDoc( - getBankAccountBalanceById, - implementedInApiVersion, - nameOf(getBankAccountBalanceById), - "GET", - "/accounts/ACCOUNT_ID/balances/BALANCE_ID", - "Get Bank Account Balance By ID", - s"""Get a specific Bank Account Balance by its BALANCE_ID. - | - |${userAuthenticationMessage(true)} - | - |""", - EmptyBody, - bankAccountBalanceResponseJsonV510, - List( - $UserNotLoggedIn, - UserHasMissingRoles, - UnknownError - ), - List(apiTagAccount, apiTagBalance) - ) - - lazy val getBankAccountBalanceById: OBPEndpoint = { - case "accounts" :: AccountId(accountId) :: "balances" :: BalanceId(balanceId) :: Nil JsonGet _ => { - cc => - implicit val ec = EndpointContext(Some(cc)) - for { - (Full(u), callContext) <- SS.user - (balance, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalanceById( - balanceId, - callContext - ) - } yield { - (JSONFactory510.createBankAccountBalanceJson(balance), HttpCode.`200`(callContext)) - } - } - } - - staticResourceDocs += ResourceDoc( - getAllBankAccountBalances, - implementedInApiVersion, - nameOf(getAllBankAccountBalances), - "GET", - "/accounts/ACCOUNT_ID/balances", - "Get All Bank Account Balances", - s"""Get all Balances for a Bank Account. - | - |${userAuthenticationMessage(true)} - | - |""", - EmptyBody, - bankAccountBalancesJsonV510, - List( - $UserNotLoggedIn, - UserHasMissingRoles, - UnknownError - ), - List(apiTagAccount, apiTagBalance) - ) - - lazy val getAllBankAccountBalances: OBPEndpoint = { - case "accounts" :: AccountId(accountId) :: "balances" :: Nil JsonGet _ => { - cc => - implicit val ec = EndpointContext(Some(cc)) - for { - (Full(u), callContext) <- SS.user - (balances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances( - accountId, - callContext - ) - } yield { - (JSONFactory510.createBankAccountBalancesJson(balances), HttpCode.`200`(callContext)) - } - } - } - - staticResourceDocs += ResourceDoc( - updateBankAccountBalance, - implementedInApiVersion, - nameOf(updateBankAccountBalance), - "PUT", - "/accounts/ACCOUNT_ID/balances/BALANCE_ID", - "Update Bank Account Balance", - s"""Update an existing Bank Account Balance specified by BALANCE_ID. - | - |${userAuthenticationMessage(true)} - | - |""", - bankAccountBalanceRequestJsonV510, - bankAccountBalanceResponseJsonV510, - List( - $UserNotLoggedIn, - UserHasMissingRoles, - InvalidJsonFormat, - UnknownError - ), - List(apiTagAccount, apiTagBalance) - ) - - lazy val updateBankAccountBalance: OBPEndpoint = { - case "accounts" :: AccountId(accountId) :: "balances" :: BalanceId(balanceId) :: Nil JsonPut json -> _ => { - cc => - implicit val ec = EndpointContext(Some(cc)) - for { - (Full(u), callContext) <- SS.user - postedData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the BankAccountBalanceRequestJsonV510 ", 400, callContext) { - json.extract[BankAccountBalanceRequestJsonV510] - } - balanceAmount <- NewStyle.function.tryons(s"$InvalidNumber Current balance_amount is ${postedData.balance_amount}" , 400, cc.callContext) { - BigDecimal(postedData.balance_amount) - } - (balance, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.createOrUpdateBankAccountBalance( - balanceId = Some(balanceId), - accountId = accountId, - balanceType = postedData.balance_type, - balanceAmount = balanceAmount, - callContext = callContext - ) - } yield { - (JSONFactory510.createBankAccountBalanceJson(balance), HttpCode.`200`(callContext)) - } - } - } - - staticResourceDocs += ResourceDoc( - deleteBankAccountBalance, - implementedInApiVersion, - nameOf(deleteBankAccountBalance), - "DELETE", - "/accounts/ACCOUNT_ID/balances/BALANCE_ID", - "Delete Bank Account Balance", - s"""Delete a Bank Account Balance specified by BALANCE_ID. - | - |${userAuthenticationMessage(true)} - | - |""", - EmptyBody, - EmptyBody, - List( - $UserNotLoggedIn, - UserHasMissingRoles, - UnknownError - ), - List(apiTagAccount, apiTagBalance) - ) - - lazy val deleteBankAccountBalance: OBPEndpoint = { - case "accounts" :: AccountId(accountId) :: "balances" :: BalanceId(balanceId) :: Nil JsonDelete _ => { - cc => - implicit val ec = EndpointContext(Some(cc)) - for { - (Full(u), callContext) <- SS.user - (deleted, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.deleteBankAccountBalance( - balanceId, - callContext - ) - } yield { - (Full(deleted), HttpCode.`204`(callContext)) - } - } - } - - staticResourceDocs += ResourceDoc( createRegulatedEntity, implementedInApiVersion, @@ -5050,6 +4839,228 @@ trait APIMethods510 { } } + staticResourceDocs += ResourceDoc( + createBankAccountBalance, + implementedInApiVersion, + nameOf(createBankAccountBalance), + "POST", + "/banks/BANK_ID/accounts/ACCOUNT_ID/balances", + "Create Bank Account Balance", + s"""Create a new Balance for a Bank Account. + | + |${userAuthenticationMessage(true)} + | + |""", + bankAccountBalanceRequestJsonV510, + bankAccountBalanceResponseJsonV510, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + List(apiTagAccount, apiTagBalance), + Some(List(canCreateBankAccountBalance)) + ) + + lazy val createBankAccountBalance: OBPEndpoint = { + case "banks" :: BankId(bankId):: "accounts" :: AccountId(accountId) :: "balances" :: Nil JsonPost json -> _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- SS.user + postedData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the $BankAccountBalanceRequestJsonV510 ", 400, callContext) { + json.extract[BankAccountBalanceRequestJsonV510] + } + balanceAmount <- NewStyle.function.tryons(s"$InvalidNumber Current balance_amount is ${postedData.balance_amount}" , 400, cc.callContext) { + BigDecimal(postedData.balance_amount) + } + (balance, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.createOrUpdateBankAccountBalance( + bankId = bankId, + accountId = accountId, + balanceId = None, + balanceType = postedData.balance_type, + balanceAmount = balanceAmount, + callContext = cc.callContext + ) + } yield { + (JSONFactory510.createBankAccountBalanceJson(balance), HttpCode.`201`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + getBankAccountBalanceById, + implementedInApiVersion, + nameOf(getBankAccountBalanceById), + "GET", + "/banks/BANK_ID/accounts/ACCOUNT_ID/balances/BALANCE_ID", + "Get Bank Account Balance By ID", + s"""Get a specific Bank Account Balance by its BALANCE_ID. + | + |${userAuthenticationMessage(true)} + | + |""", + EmptyBody, + bankAccountBalanceResponseJsonV510, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + UnknownError + ), + List(apiTagAccount, apiTagBalance) + ) + + lazy val getBankAccountBalanceById: OBPEndpoint = { + case "banks" :: BankId(bankId):: "accounts" :: AccountId(accountId) :: "balances" :: BalanceId(balanceId) :: Nil JsonGet _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- SS.user + (balance, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalanceById( + balanceId, + callContext + ) + } yield { + (JSONFactory510.createBankAccountBalanceJson(balance), HttpCode.`200`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + getAllBankAccountBalances, + implementedInApiVersion, + nameOf(getAllBankAccountBalances), + "GET", + "/banks/BANK_ID/accounts/ACCOUNT_ID/balances", + "Get All Bank Account Balances", + s"""Get all Balances for a Bank Account. + | + |${userAuthenticationMessage(true)} + | + |""", + EmptyBody, + bankAccountBalancesJsonV510, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + UnknownError + ), + List(apiTagAccount, apiTagBalance) + ) + + lazy val getAllBankAccountBalances: OBPEndpoint = { + case "banks" :: BankId(bankId):: "accounts" :: AccountId(accountId) :: "balances" :: Nil JsonGet _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- SS.user + (balances, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalances( + accountId, + callContext + ) + } yield { + (JSONFactory510.createBankAccountBalancesJson(balances), HttpCode.`200`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + updateBankAccountBalance, + implementedInApiVersion, + nameOf(updateBankAccountBalance), + "PUT", + "/banks/BANK_ID/accounts/ACCOUNT_ID/balances/BALANCE_ID", + "Update Bank Account Balance", + s"""Update an existing Bank Account Balance specified by BALANCE_ID. + | + |${userAuthenticationMessage(true)} + | + |""", + bankAccountBalanceRequestJsonV510, + bankAccountBalanceResponseJsonV510, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + InvalidJsonFormat, + UnknownError + ), + List(apiTagAccount, apiTagBalance), + Some(List(canUpdateBankAccountBalance)) + ) + + lazy val updateBankAccountBalance: OBPEndpoint = { + case "banks" :: BankId(bankId):: "accounts" :: AccountId(accountId) :: "balances" :: BalanceId(balanceId) :: Nil JsonPut json -> _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- SS.user + postedData <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the BankAccountBalanceRequestJsonV510 ", 400, callContext) { + json.extract[BankAccountBalanceRequestJsonV510] + } + balanceAmount <- NewStyle.function.tryons(s"$InvalidNumber Current balance_amount is ${postedData.balance_amount}" , 400, cc.callContext) { + BigDecimal(postedData.balance_amount) + } + (balance, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalanceById( + balanceId, + callContext + ) + (balance, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.createOrUpdateBankAccountBalance( + bankId = bankId, + accountId = accountId, + balanceId = Some(balanceId), + balanceType = postedData.balance_type, + balanceAmount = balanceAmount, + callContext = callContext + ) + } yield { + (JSONFactory510.createBankAccountBalanceJson(balance), HttpCode.`200`(callContext)) + } + } + } + + staticResourceDocs += ResourceDoc( + deleteBankAccountBalance, + implementedInApiVersion, + nameOf(deleteBankAccountBalance), + "DELETE", + "/banks/BANK_ID/accounts/ACCOUNT_ID/balances/BALANCE_ID", + "Delete Bank Account Balance", + s"""Delete a Bank Account Balance specified by BALANCE_ID. + | + |${userAuthenticationMessage(true)} + | + |""", + EmptyBody, + EmptyBody, + List( + $UserNotLoggedIn, + UserHasMissingRoles, + UnknownError + ), + List(apiTagAccount, apiTagBalance), + Some(List(canDeleteBankAccountBalance)) + ) + + lazy val deleteBankAccountBalance: OBPEndpoint = { + case "banks" :: BankId(bankId):: "accounts" :: AccountId(accountId) :: "balances" :: BalanceId(balanceId) :: Nil JsonDelete _ => { + cc => + implicit val ec = EndpointContext(Some(cc)) + for { + (Full(u), callContext) <- SS.user + (balance, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.getBankAccountBalanceById( + balanceId, + callContext + ) + (deleted, callContext) <- code.api.util.newstyle.BankAccountBalanceNewStyle.deleteBankAccountBalance( + balanceId, + callContext + ) + } yield { + (Full(deleted), HttpCode.`204`(callContext)) + } + } + } } } diff --git a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala index e0391c9163..80bc6c3688 100644 --- a/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala +++ b/obp-api/src/main/scala/code/api/v5_1_0/JSONFactory5.1.0.scala @@ -618,6 +618,7 @@ case class BankAccountBalanceRequestJsonV510( ) case class BankAccountBalanceResponseJsonV510( + bank_id: String, account_id: String, balance_id: String, balance_type: String, @@ -1139,8 +1140,9 @@ object JSONFactory510 extends CustomJsonFormats { def createBankAccountBalanceJson(balance: BankAccountBalanceTrait): BankAccountBalanceResponseJsonV510 = { BankAccountBalanceResponseJsonV510( - balance_id = balance.balanceId.value, + bank_id = balance.bankId.value, account_id = balance.accountId.value, + balance_id = balance.balanceId.value, balance_type = balance.balanceType, balance_amount = balance.balanceAmount.toString ) diff --git a/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala b/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala index 882f4c213b..5f6e0712c4 100644 --- a/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala +++ b/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalance.scala @@ -13,8 +13,8 @@ class BankAccountBalance extends BankAccountBalanceTrait with LongKeyedMapper[Ba override def getSingleton = BankAccountBalance object BankId_ extends MappedUUID(this) - object BalanceId_ extends MappedUUID(this) object AccountId_ extends MappedUUID(this) + object BalanceId_ extends MappedUUID(this) object BalanceType extends MappedString(this, 255) //this is the smallest unit of currency! eg. cents, yen, pence, øre, etc. object BalanceAmount extends MappedLong(this) @@ -26,9 +26,9 @@ class BankAccountBalance extends BankAccountBalanceTrait with LongKeyedMapper[Ba .getOrElse("EUR") }.getOrElse("EUR") - override def balanceId: BalanceId = BalanceId(BalanceId_.get) override def bankId: BankId = BankId(BankId_.get) override def accountId: AccountId = AccountId(AccountId_.get) + override def balanceId: BalanceId = BalanceId(BalanceId_.get) override def balanceType: String = BalanceType.get override def balanceAmount: BigDecimal = Helper.smallestCurrencyUnitToBigDecimal(BalanceAmount.get, foreignMappedBankAccountCurrency) } diff --git a/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalanceProvider.scala b/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalanceProvider.scala index 97bc181ec9..2568257b40 100644 --- a/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalanceProvider.scala +++ b/obp-api/src/main/scala/code/bankaccountbalance/BankAccountBalanceProvider.scala @@ -1,9 +1,9 @@ package code.bankaccountbalance import code.model.dataAccess.MappedBankAccount -import code.util.{Helper, MappedUUID} +import code.util.Helper import com.openbankproject.commons.ExecutionContext.Implicits.global -import com.openbankproject.commons.model.{AccountId, BankAccountBalanceTrait} +import com.openbankproject.commons.model.{BankId, AccountId} import net.liftweb.common.{Box, Empty, Full} import net.liftweb.mapper._ import net.liftweb.util.Helpers.tryo @@ -35,8 +35,9 @@ trait BankAccountBalanceProviderTrait { def getBankAccountBalanceById(balanceId: BalanceId): Future[Box[BankAccountBalance]] def createOrUpdateBankAccountBalance( - balanceId: Option[BalanceId], + bankId: BankId, accountId: AccountId, + balanceId: Option[BalanceId], balanceType: String, balanceAmount: BigDecimal): Future[Box[BankAccountBalance]] @@ -61,8 +62,9 @@ object MappedBankAccountBalanceProvider extends BankAccountBalanceProviderTrait } override def createOrUpdateBankAccountBalance( - balanceId: Option[BalanceId], + bankId: BankId, accountId: AccountId, + balanceId: Option[BalanceId], balanceType: String, balanceAmount: BigDecimal ): Future[Box[BankAccountBalance]] = Future { @@ -82,6 +84,7 @@ object MappedBankAccountBalanceProvider extends BankAccountBalanceProviderTrait case Full(balance) => tryo { balance + .BankId_(bankId.value) .AccountId_(accountId.value) .BalanceType(balanceType) .BalanceAmount(Helper.convertToSmallestCurrencyUnits(balanceAmount, account.currency)) @@ -92,6 +95,7 @@ object MappedBankAccountBalanceProvider extends BankAccountBalanceProviderTrait case _ => tryo { BankAccountBalance.create + .BankId_(bankId.value) .AccountId_(accountId.value) .BalanceType(balanceType) .BalanceAmount(Helper.convertToSmallestCurrencyUnits(balanceAmount, account.currency)) diff --git a/obp-api/src/test/scala/code/api/v5_1_0/BankAccountBalanceTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/BankAccountBalanceTest.scala index 4ddfe1de17..0d8b260c42 100644 --- a/obp-api/src/test/scala/code/api/v5_1_0/BankAccountBalanceTest.scala +++ b/obp-api/src/test/scala/code/api/v5_1_0/BankAccountBalanceTest.scala @@ -27,49 +27,67 @@ class BankAccountBalanceTest extends V510ServerSetup with DefaultUsers { lazy val balanceId = createMockBalance(bankId, accountId) def createMockBalance(bankId: String, accountId: String): String = { + val entitlement = Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateBankAccountBalance.toString) val json = bankAccountBalanceRequestJsonV510 - val request = (v5_1_0_Request / "accounts" / accountId / "balances").POST <@ user1 + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").POST <@ user1 val response = makePostRequest(request, write(json)) + Entitlement.entitlement.vend.deleteEntitlement(entitlement) (response.body.extract[BankAccountBalanceResponseJsonV510].balance_id) } feature("Create Bank Account Balance") { scenario("401 Unauthorized", Create, VersionOfApi) { - val request = (v5_1_0_Request / "accounts" / accountId / "balances").POST + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").POST val response = makePostRequest(request, write(bankAccountBalanceRequestJsonV510)) response.code should equal(401) response.body.extract[ErrorMessage].message should equal(ErrorMessages.UserNotLoggedIn) } + scenario("403 Forbidden (no role)", Create, VersionOfApi) { + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").POST <@ user1 + val response = makePostRequest(request, write(bankAccountBalanceRequestJsonV510)) + response.code should equal(403) + response.body.extract[ErrorMessage].message should startWith(ErrorMessages.UserHasMissingRoles + CanCreateBankAccountBalance.toString) + } + scenario("201 Success + Field Echo", Create, VersionOfApi) { - val request = (v5_1_0_Request / "accounts" / accountId / "balances").POST <@ user1 + val entitlement = Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanCreateBankAccountBalance.toString) + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").POST <@ user1 val response = makePostRequest(request, write(bankAccountBalanceRequestJsonV510)) response.code should equal(201) val created = response.body.extract[BankAccountBalanceResponseJsonV510] created.balance_type should equal(bankAccountBalanceRequestJsonV510.balance_type) created.balance_amount should equal(bankAccountBalanceRequestJsonV510.balance_amount) created.account_id should equal(accountId) + Entitlement.entitlement.vend.deleteEntitlement(entitlement) } } feature("Update Bank Account Balance") { scenario("401 Unauthorized", Update, VersionOfApi) { - val request = (v5_1_0_Request / "accounts" / accountId / "balances" / balanceId).PUT + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).PUT val response = makePutRequest(request, write(bankAccountBalanceRequestJsonV510)) response.code should equal(401) } + scenario("403 Forbidden", Update, VersionOfApi) { + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).PUT <@ user1 + val response = makePutRequest(request, write(bankAccountBalanceRequestJsonV510)) + response.code should equal(403) + } scenario("200 Success", Update, VersionOfApi) { lazy val bankId = testBankId1.value lazy val accountId = testAccountId1.value lazy val balanceId = createMockBalance(bankId, accountId) - val request = (v5_1_0_Request / "accounts" / accountId / "balances" / balanceId).PUT <@ user1 + val entitlement = Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanUpdateBankAccountBalance.toString) + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).PUT <@ user1 val response = makePutRequest(request, write(bankAccountBalanceRequestJsonV510)) response.code should equal(200) + Entitlement.entitlement.vend.deleteEntitlement(entitlement) } } @@ -79,19 +97,26 @@ class BankAccountBalanceTest extends V510ServerSetup with DefaultUsers { lazy val balanceId = createMockBalance(bankId, accountId) scenario("401 Unauthorized", Delete, VersionOfApi) { - val request = (v5_1_0_Request / "accounts" / accountId / "balances" / balanceId).DELETE + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).DELETE val response = makeDeleteRequest(request) response.code should equal(401) } + scenario("403 Forbidden", Delete, VersionOfApi) { + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).DELETE <@ user1 + val response = makeDeleteRequest(request) + response.code should equal(403) + } + scenario("204 Success", Delete, VersionOfApi) { lazy val bankId = testBankId1.value lazy val accountId = testAccountId1.value lazy val balanceId = createMockBalance(bankId, accountId) - - val request = (v5_1_0_Request / "accounts" / accountId / "balances" / balanceId).DELETE <@ user1 + val entitlement = Entitlement.entitlement.vend.addEntitlement(bankId, resourceUser1.userId, CanDeleteBankAccountBalance.toString) + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).DELETE <@ user1 val response = makeDeleteRequest(request) response.code should equal(204) + Entitlement.entitlement.vend.deleteEntitlement(entitlement) } } @@ -101,7 +126,7 @@ class BankAccountBalanceTest extends V510ServerSetup with DefaultUsers { lazy val balanceId = createMockBalance(bankId, accountId) scenario("401 Unauthorized", GetAll, VersionOfApi) { - val request = (v5_1_0_Request / "accounts" / accountId / "balances").GET + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").GET val response = makeGetRequest(request) response.code should equal(401) } @@ -109,7 +134,7 @@ class BankAccountBalanceTest extends V510ServerSetup with DefaultUsers { scenario("200 Success", GetAll, VersionOfApi) { lazy val bankId = testBankId1.value lazy val accountId = testAccountId1.value - val request = (v5_1_0_Request / "accounts" / accountId / "balances").GET <@ user1 + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances").GET <@ user1 val response = makeGetRequest(request) response.code should equal(200) } @@ -120,7 +145,7 @@ class BankAccountBalanceTest extends V510ServerSetup with DefaultUsers { lazy val accountId = testAccountId1.value scenario("401 Unauthorized", GetOne, VersionOfApi) { - val request = (v5_1_0_Request / "accounts" / accountId / "balances" / balanceId).GET + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).GET val response = makeGetRequest(request) response.code should equal(401) } @@ -129,7 +154,7 @@ class BankAccountBalanceTest extends V510ServerSetup with DefaultUsers { lazy val bankId = testBankId1.value lazy val accountId = testAccountId1.value lazy val balanceId = createMockBalance(bankId, accountId) - val request = (v5_1_0_Request / "accounts" / accountId / "balances" / balanceId).GET <@ user1 + val request = (v5_1_0_Request / "banks" / bankId / "accounts" / accountId / "balances" / balanceId).GET <@ user1 val response = makeGetRequest(request) response.code should equal(200) } diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala index 3a320f8bc3..caf5613a12 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/BankingModel.scala @@ -237,9 +237,9 @@ trait BankAccount{ } trait BankAccountBalanceTrait { - def balanceId: BalanceId def bankId: BankId def accountId: AccountId + def balanceId: BalanceId def balanceType: String def balanceAmount: BigDecimal } From 5a3ad390d1c038c279cb1ae1a4755df049e45ab0 Mon Sep 17 00:00:00 2001 From: Hongwei Date: Tue, 29 Apr 2025 09:29:38 +0200 Subject: [PATCH 10/10] refactor/update BankAccountBalance methods to use Connector for improved structure and consistency --- .../newstyle/BankAccountBalanceNewStyle.scala | 70 ++++++++----------- .../scala/code/bankconnectors/Connector.scala | 25 +++++++ .../bankconnectors/LocalMappedConnector.scala | 49 ++++++++++++- 3 files changed, 101 insertions(+), 43 deletions(-) diff --git a/obp-api/src/main/scala/code/api/util/newstyle/BankAccountBalanceNewStyle.scala b/obp-api/src/main/scala/code/api/util/newstyle/BankAccountBalanceNewStyle.scala index 78ad1c3ab6..c45360194e 100644 --- a/obp-api/src/main/scala/code/api/util/newstyle/BankAccountBalanceNewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/newstyle/BankAccountBalanceNewStyle.scala @@ -1,12 +1,12 @@ package code.api.util.newstyle import code.api.util.APIUtil.{OBPReturnType, unboxFullOrFail} -import code.api.util.ErrorMessages.{BankAccountBalanceNotFoundById, InvalidConnectorResponse} +import code.bankconnectors.Connector +import code.api.util.{APIUtil, CallContext} import code.api.util.CallContext -import code.bankaccountbalance.BankAccountBalanceX +import code.api.util.ErrorMessages.BankAccountBalanceNotFoundById import com.openbankproject.commons.ExecutionContext.Implicits.global import com.openbankproject.commons.model.{AccountId, BankAccountBalanceTrait, BankId} -import com.github.dwickern.macros.NameOf.nameOf import com.openbankproject.commons.model.BalanceId @@ -16,16 +16,11 @@ object BankAccountBalanceNewStyle { accountId: AccountId, callContext: Option[CallContext] ): OBPReturnType[List[BankAccountBalanceTrait]] = { - BankAccountBalanceX.bankAccountBalanceProvider.vend.getBankAccountBalances(accountId).map { - result => - ( - unboxFullOrFail( - result, - callContext, - s"$InvalidConnectorResponse ${nameOf(getBankAccountBalances _)}", - 404), - callContext - ) + Connector.connector.vend.getBankAccountBalancesByAccountId( + accountId: AccountId, + callContext: Option[CallContext] + ) map { + i => (APIUtil.connectorEmptyResponse(i._1, callContext), i._2) } } @@ -33,12 +28,15 @@ object BankAccountBalanceNewStyle { balanceId: BalanceId, callContext: Option[CallContext] ): OBPReturnType[BankAccountBalanceTrait] = { - BankAccountBalanceX.bankAccountBalanceProvider.vend.getBankAccountBalanceById(balanceId).map { + Connector.connector.vend.getBankAccountBalanceById( + balanceId: BalanceId, + callContext: Option[CallContext] + ).map { result => ( unboxFullOrFail( - result, - callContext, + result._1, + result._2, s"$BankAccountBalanceNotFoundById Current BALANCE_ID(${balanceId.value})", 404), callContext @@ -54,22 +52,15 @@ object BankAccountBalanceNewStyle { balanceAmount: BigDecimal, callContext: Option[CallContext] ): OBPReturnType[BankAccountBalanceTrait] = { - BankAccountBalanceX.bankAccountBalanceProvider.vend.createOrUpdateBankAccountBalance( - bankId, - accountId, - balanceId, - balanceType, - balanceAmount - ).map { - result => - ( - unboxFullOrFail( - result, - callContext, - s"$InvalidConnectorResponse ${nameOf(createOrUpdateBankAccountBalance _)}", - 400), - callContext - ) + Connector.connector.vend.createOrUpdateBankAccountBalance( + bankId: BankId, + accountId: AccountId, + balanceId: Option[BalanceId], + balanceType: String, + balanceAmount: BigDecimal, + callContext: Option[CallContext] + ) map { + i => (APIUtil.connectorEmptyResponse(i._1, callContext), i._2) } } @@ -77,16 +68,11 @@ object BankAccountBalanceNewStyle { balanceId: BalanceId, callContext: Option[CallContext] ): OBPReturnType[Boolean] = { - BankAccountBalanceX.bankAccountBalanceProvider.vend.deleteBankAccountBalance(balanceId).map { - result => - ( - unboxFullOrFail( - result, - callContext, - s"$InvalidConnectorResponse ${nameOf(deleteBankAccountBalance _)}", - 400), - callContext - ) + Connector.connector.vend.deleteBankAccountBalance( + balanceId: BalanceId, + callContext: Option[CallContext] + ) map { + i => (APIUtil.connectorEmptyResponse(i._1, callContext), i._2) } } diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index fdf9efe7d3..d73e249de1 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -1893,4 +1893,29 @@ trait Connector extends MdcLoggable { regulatedEntityId: String, callContext: Option[CallContext] ): OBPReturnType[Box[RegulatedEntityTrait]] = Future{(Failure(setUnimplementedError(nameOf(getRegulatedEntityByEntityId _))), callContext)} + + def getBankAccountBalancesByAccountId( + accountId: AccountId, + callContext: Option[CallContext] + ): OBPReturnType[Box[List[BankAccountBalanceTrait]]] = Future{(Failure(setUnimplementedError(nameOf(getBankAccountBalancesByAccountId(_, _)))), callContext)} + + def getBankAccountBalanceById( + balanceId: BalanceId, + callContext: Option[CallContext] + ): OBPReturnType[Box[BankAccountBalanceTrait]] = Future{(Failure(setUnimplementedError(nameOf(getBankAccountBalanceById _))), callContext)} + + + def createOrUpdateBankAccountBalance( + bankId: BankId, + accountId: AccountId, + balanceId: Option[BalanceId], + balanceType: String, + balanceAmount: BigDecimal, + callContext: Option[CallContext] + ): OBPReturnType[Box[BankAccountBalanceTrait]] = Future{(Failure(setUnimplementedError(nameOf(createOrUpdateBankAccountBalance _))), callContext)} + + def deleteBankAccountBalance( + balanceId: BalanceId, + callContext: Option[CallContext] + ): OBPReturnType[Box[Boolean]] = Future{(Failure(setUnimplementedError(nameOf(deleteBankAccountBalance _))), callContext)} } diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index 0ee18569f5..c10405e2a3 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -32,6 +32,7 @@ import code.customeraddress.CustomerAddressX import code.customerattribute.CustomerAttributeX import code.directdebit.DirectDebits import code.endpointTag.EndpointTag +import code.bankaccountbalance.BankAccountBalanceX import com.openbankproject.commons.model.EndpointTagT import code.fx.{MappedFXRate, fx} import code.kycchecks.KycChecks @@ -5362,6 +5363,52 @@ object LocalMappedConnector extends Connector with MdcLoggable { MappedRegulatedEntityProvider.getRegulatedEntityByEntityId(regulatedEntityId) } map { (_, callContext) - } + } + + override def getBankAccountBalancesByAccountId( + accountId: AccountId, + callContext: Option[CallContext] + ): OBPReturnType[Box[List[BankAccountBalanceTrait]]] = { + BankAccountBalanceX.bankAccountBalanceProvider.vend.getBankAccountBalances(accountId).map { + (_, callContext) + } + } + + override def getBankAccountBalanceById( + balanceId: BalanceId, + callContext: Option[CallContext] + ): OBPReturnType[Box[BankAccountBalanceTrait]] = { + BankAccountBalanceX.bankAccountBalanceProvider.vend.getBankAccountBalanceById(balanceId).map { + (_, callContext) + } + } + + override def createOrUpdateBankAccountBalance( + bankId: BankId, + accountId: AccountId, + balanceId: Option[BalanceId], + balanceType: String, + balanceAmount: BigDecimal, + callContext: Option[CallContext] + ): OBPReturnType[Box[BankAccountBalanceTrait]] = { + BankAccountBalanceX.bankAccountBalanceProvider.vend.createOrUpdateBankAccountBalance( + bankId, + accountId, + balanceId, + balanceType, + balanceAmount + ).map { + (_, callContext) + } + } + + override def deleteBankAccountBalance( + balanceId: BalanceId, + callContext: Option[CallContext] + ): OBPReturnType[Box[Boolean]] = { + BankAccountBalanceX.bankAccountBalanceProvider.vend.deleteBankAccountBalance(balanceId).map { + (_, callContext) + } + } }