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 887983c8f..53ab522b7 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 @@ -5458,11 +5458,13 @@ object SwaggerDefinitionsJSON { val postCounterpartyLimitV510 = PostCounterpartyLimitV510( currency = currencyExample.value, - max_single_amount = maxSingleAmountExample.value.toInt, - max_monthly_amount = maxMonthlyAmountExample.value.toInt, + max_single_amount = maxSingleAmountExample.value, + max_monthly_amount = maxMonthlyAmountExample.value, max_number_of_monthly_transactions = maxNumberOfMonthlyTransactionsExample.value.toInt, - max_yearly_amount = maxYearlyAmountExample.value.toInt, - max_number_of_yearly_transactions = maxNumberOfYearlyTransactionsExample.value.toInt + max_yearly_amount = maxYearlyAmountExample.value, + max_number_of_yearly_transactions = maxNumberOfYearlyTransactionsExample.value.toInt, + max_total_amount = maxTotalAmountExample.value, + max_number_of_transactions = maxNumberOfTransactionsExample.value.toInt ) val counterpartyLimitV510 = CounterpartyLimitV510( @@ -5472,11 +5474,13 @@ object SwaggerDefinitionsJSON { view_id = viewIdExample.value, counterparty_id = counterpartyIdExample.value, currency = currencyExample.value, - max_single_amount = maxSingleAmountExample.value.toInt, - max_monthly_amount = maxMonthlyAmountExample.value.toInt, + max_single_amount = maxSingleAmountExample.value, + max_monthly_amount = maxMonthlyAmountExample.value, max_number_of_monthly_transactions = maxNumberOfMonthlyTransactionsExample.value.toInt, - max_yearly_amount = maxYearlyAmountExample.value.toInt, - max_number_of_yearly_transactions = maxNumberOfYearlyTransactionsExample.value.toInt + max_yearly_amount = maxYearlyAmountExample.value, + max_number_of_yearly_transactions = maxNumberOfYearlyTransactionsExample.value.toInt, + max_total_amount = maxTotalAmountExample.value, + max_number_of_transactions = maxNumberOfTransactionsExample.value.toInt ) val atmsJsonV510 = AtmsJsonV510( 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 4a5139885..8bd95d77a 100644 --- a/obp-api/src/main/scala/code/api/util/ErrorMessages.scala +++ b/obp-api/src/main/scala/code/api/util/ErrorMessages.scala @@ -506,10 +506,11 @@ object ErrorMessages { val CreateCounterpartyLimitError = "OBP-30261: Could not create the counterparty limit." val UpdateCounterpartyLimitError = "OBP-30262: Could not update the counterparty limit." val GetCounterpartyLimitError = "OBP-30263: Counterparty limit not found. Please specify a valid value for BANK_ID, ACCOUNT_ID, VIEW_ID or COUNTERPARTY_ID." - val CounterpartyLimitAlreadyExists = "OBP-30264: Counterparty limit already exists. Please specify a different value for BANK_ID, ACCOUNT_ID, VIEW_ID or COUNTERPARTY_ID." + val CounterpartyLimitAlreadyExists = "OBP-30264: Counterparty limit already exists. Please specify a different value for BANK_ID, ACCOUNT_ID, VIEW_ID or COUNTERPARTY_ID." val DeleteCounterpartyLimitError = "OBP-30265: Could not delete the counterparty limit." val CustomViewAlreadyExistsError = "OBP-30266: The custom view is already exists." val UserDoesNotHavePermission = "OBP-30267: The user does not have the permission:" + val CounterpartyLimitValidationError = "OBP-30268: Counterparty Limit Validation Error." val TaxResidenceNotFound = "OBP-30300: Tax Residence not found by TAX_RESIDENCE_ID. " val CustomerAddressNotFound = "OBP-30310: Customer's Address not found by CUSTOMER_ADDRESS_ID. " 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 c1d606f86..4431bbc2d 100644 --- a/obp-api/src/main/scala/code/api/util/ExampleValue.scala +++ b/obp-api/src/main/scala/code/api/util/ExampleValue.scala @@ -2210,20 +2210,26 @@ object ExampleValue { lazy val counterpartyLimitIdExample = ConnectorField("abc9a7e4-6d02-40e3-a129-0b2bf89de9b1","A string that MUST uniquely identify the Counterparty Limit on this OBP instance.") glossaryItems += makeGlossaryItem("counterparty_limit_id", counterpartyLimitIdExample) - lazy val maxSingleAmountExample = ConnectorField("1000",NoDescriptionProvided) + lazy val maxSingleAmountExample = ConnectorField("1000.11",NoDescriptionProvided) glossaryItems += makeGlossaryItem("max_single_amount", maxSingleAmountExample) - lazy val maxMonthlyAmountExample = ConnectorField("10000",NoDescriptionProvided) + lazy val maxMonthlyAmountExample = ConnectorField("10000.11",NoDescriptionProvided) glossaryItems += makeGlossaryItem("max_monthly_amount", maxMonthlyAmountExample) lazy val maxNumberOfMonthlyTransactionsExample = ConnectorField("10",NoDescriptionProvided) glossaryItems += makeGlossaryItem("max_number_of_monthly_transactions", maxNumberOfMonthlyTransactionsExample) - lazy val maxYearlyAmountExample = ConnectorField("12000",NoDescriptionProvided) + lazy val maxYearlyAmountExample = ConnectorField("12000.11",NoDescriptionProvided) glossaryItems += makeGlossaryItem("max_yearly_amount", maxYearlyAmountExample) lazy val maxNumberOfYearlyTransactionsExample = ConnectorField("100",NoDescriptionProvided) glossaryItems += makeGlossaryItem("max_number_of_yearly_transactions", maxNumberOfYearlyTransactionsExample) + + lazy val maxNumberOfTransactionsExample = ConnectorField("100",NoDescriptionProvided) + glossaryItems += makeGlossaryItem("max_number_of_transactions", maxNumberOfTransactionsExample) + + lazy val maxTotalAmountExample = ConnectorField("10000.12",NoDescriptionProvided) + glossaryItems += makeGlossaryItem("max_total_amount", maxTotalAmountExample) lazy val canAddImageUrlExample = ConnectorField(booleanFalse,NoDescriptionProvided) glossaryItems += makeGlossaryItem("can_add_image_url", canAddImageUrlExample) diff --git a/obp-api/src/main/scala/code/api/util/NewStyle.scala b/obp-api/src/main/scala/code/api/util/NewStyle.scala index 4ac213046..c8e30f030 100644 --- a/obp-api/src/main/scala/code/api/util/NewStyle.scala +++ b/obp-api/src/main/scala/code/api/util/NewStyle.scala @@ -4232,11 +4232,13 @@ object NewStyle extends MdcLoggable{ viewId: String, counterpartyId: String, currency: String, - maxSingleAmount: Int, - maxMonthlyAmount: Int, + maxSingleAmount: BigDecimal, + maxMonthlyAmount: BigDecimal, maxNumberOfMonthlyTransactions: Int, - maxYearlyAmount: Int, + maxYearlyAmount: BigDecimal, maxNumberOfYearlyTransactions: Int, + maxTotalAmount: BigDecimal, + maxNumberOfTransactions: Int, callContext: Option[CallContext] ): OBPReturnType[CounterpartyLimitTrait] = Connector.connector.vend.createOrUpdateCounterpartyLimit( @@ -4245,11 +4247,13 @@ object NewStyle extends MdcLoggable{ viewId: String, counterpartyId: String, currency: String, - maxSingleAmount: Int, - maxMonthlyAmount: Int, + maxSingleAmount: BigDecimal, + maxMonthlyAmount: BigDecimal, maxNumberOfMonthlyTransactions: Int, - maxYearlyAmount: Int, + maxYearlyAmount: BigDecimal, maxNumberOfYearlyTransactions: Int, + maxTotalAmount: BigDecimal, + maxNumberOfTransactions: Int, callContext: Option[CallContext] ) map { i => (unboxFullOrFail(i._1, callContext, CreateCounterpartyLimitError), i._2) @@ -4272,6 +4276,46 @@ object NewStyle extends MdcLoggable{ i => (unboxFullOrFail(i._1, callContext, s"$GetCounterpartyLimitError Current BANK_ID($bankId), " + s"ACCOUNT_ID($accountId), VIEW_ID($viewId),COUNTERPARTY_ID($counterpartyId)"), i._2) } + + def getCountOfTransactionsFromAccountToCounterparty( + fromBankId: BankId, + fromAccountId: AccountId, + counterpartyId: CounterpartyId, + fromDate: Date, + toDate: Date, + callContext: Option[CallContext] + ): OBPReturnType[Int] = + Connector.connector.vend.getCountOfTransactionsFromAccountToCounterparty( + fromBankId: BankId, + fromAccountId: AccountId, + counterpartyId: CounterpartyId, + fromDate: Date, + toDate: Date, + callContext: Option[CallContext] + ) map { + i => + (unboxFullOrFail(i._1, callContext, s"$InvalidConnectorResponse ${nameOf(getCountOfTransactionsFromAccountToCounterparty _)}"), i._2) + } + + def getSumOfTransactionsFromAccountToCounterparty( + fromBankId: BankId, + fromAccountId: AccountId, + counterpartyId: CounterpartyId, + fromDate: Date, + toDate:Date, + callContext: Option[CallContext] + ):OBPReturnType[AmountOfMoney] = + Connector.connector.vend.getSumOfTransactionsFromAccountToCounterparty( + fromBankId: BankId, + fromAccountId: AccountId, + counterpartyId: CounterpartyId, + fromDate: Date, + toDate:Date, + callContext: Option[CallContext] + ) map { + i => + (unboxFullOrFail(i._1, callContext, s"$InvalidConnectorResponse ${nameOf(getCountOfTransactionsFromAccountToCounterparty _)}"), i._2) + } def deleteCounterpartyLimit( bankId: String, diff --git a/obp-api/src/main/scala/code/api/util/migration/Migration.scala b/obp-api/src/main/scala/code/api/util/migration/Migration.scala index 6309a8be4..021261e2c 100644 --- a/obp-api/src/main/scala/code/api/util/migration/Migration.scala +++ b/obp-api/src/main/scala/code/api/util/migration/Migration.scala @@ -102,6 +102,7 @@ object Migration extends MdcLoggable { dropMappedBadLoginAttemptIndex() alterMetricColumnUrlLength() populateViewDefinitionCanAddTransactionRequestToBeneficiary() + alterCounterpartyLimitFieldType() } private def dummyScript(): Boolean = { @@ -478,6 +479,13 @@ object Migration extends MdcLoggable { MigrationOfMappedBadLoginAttemptDropIndex.dropUniqueIndex(name) } } + + private def alterCounterpartyLimitFieldType(): Boolean = { + val name = nameOf(alterCounterpartyLimitFieldType) + runOnce(name) { + MigrationOfCounterpartyLimitFieldType.alterCounterpartyLimitFieldType(name) + } + } } /** diff --git a/obp-api/src/main/scala/code/api/util/migration/MigrationOfCounterpartyLimitFieldType.scala b/obp-api/src/main/scala/code/api/util/migration/MigrationOfCounterpartyLimitFieldType.scala new file mode 100644 index 000000000..e42b4e6e6 --- /dev/null +++ b/obp-api/src/main/scala/code/api/util/migration/MigrationOfCounterpartyLimitFieldType.scala @@ -0,0 +1,73 @@ +package code.api.util.migration + +import code.api.util.APIUtil +import code.api.util.migration.Migration.{DbFunction, saveLog} +import code.counterpartylimit.CounterpartyLimit +import net.liftweb.common.Full +import net.liftweb.mapper.Schemifier + +import java.time.format.DateTimeFormatter +import java.time.{ZoneId, ZonedDateTime} + +object MigrationOfCounterpartyLimitFieldType { + + val oneDayAgo = ZonedDateTime.now(ZoneId.of("UTC")).minusDays(1) + val oneYearInFuture = ZonedDateTime.now(ZoneId.of("UTC")).plusYears(1) + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'") + + def alterCounterpartyLimitFieldType(name: String): Boolean = { + DbFunction.tableExists(CounterpartyLimit) + match { + case true => + val startDate = System.currentTimeMillis() + val commitId: String = APIUtil.gitCommit + var isSuccessful = false + + val executedSql = + DbFunction.maybeWrite(true, Schemifier.infoF _) { + APIUtil.getPropsValue("db.driver") match { + case Full(dbDriver) if dbDriver.contains("com.microsoft.sqlserver.jdbc.SQLServerDriver") => + () => + """ + |ALTER TABLE counterpartylimit + |ALTER COLUMN maxsingleamount numeric(16, 10); + | + |ALTER TABLE counterpartylimit + |ALTER COLUMN maxmonthlyamount numeric(16, 10); + | + |ALTER TABLE counterpartylimit + |ALTER COLUMN maxyearlyamount numeric(16, 10); + |""".stripMargin + case _ => + () => + """ + |alter table counterpartylimit + | alter column maxsingleamount type numeric(16, 10) using maxsingleamount::numeric(16, 10); + |alter table counterpartylimit + | alter column maxmonthlyamount type numeric(16, 10) using maxmonthlyamount::numeric(16, 10); + |alter table counterpartylimit + | alter column maxyearlyamount type numeric(16, 10) using maxyearlyamount::numeric(16, 10); + |""".stripMargin + } + } + + val endDate = System.currentTimeMillis() + val comment: String = + s"""Executed SQL: + |$executedSql + |""".stripMargin + isSuccessful = true + saveLog(name, commitId, isSuccessful, startDate, endDate, comment) + isSuccessful + + case false => + val startDate = System.currentTimeMillis() + val commitId: String = APIUtil.gitCommit + val isSuccessful = false + val endDate = System.currentTimeMillis() + val comment: String = s"""${CounterpartyLimit._dbTableNameLC} table does not exist""".stripMargin + saveLog(name, commitId, isSuccessful, startDate, endDate, comment) + isSuccessful + } + } +} \ No newline at end of file diff --git a/obp-api/src/main/scala/code/api/v2_1_0/APIMethods210.scala b/obp-api/src/main/scala/code/api/v2_1_0/APIMethods210.scala index 3213f2391..e94b40687 100644 --- a/obp-api/src/main/scala/code/api/v2_1_0/APIMethods210.scala +++ b/obp-api/src/main/scala/code/api/v2_1_0/APIMethods210.scala @@ -465,7 +465,7 @@ trait APIMethods210 { } // Prevent default value for transaction request type (at least). - _ <- Helper.booleanToFuture(s"From Account Currency is ${fromAccount.currency}, but Requested Transaction Currency is: ${transDetailsJson.value.currency}", cc=callContext) { + _ <- Helper.booleanToFuture(s"$InvalidTransactionRequestCurrency From Account Currency is ${fromAccount.currency}, but Requested Transaction Currency is: ${transDetailsJson.value.currency}", cc=callContext) { transDetailsJson.value.currency == fromAccount.currency } diff --git a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala index c44f3e417..e55b2f25c 100644 --- a/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala +++ b/obp-api/src/main/scala/code/api/v4_0_0/APIMethods400.scala @@ -40,6 +40,7 @@ import code.api.v2_1_0._ import code.api.v3_0_0.{CreateScopeJson, JSONFactory300} import code.api.v3_1_0._ import code.api.v4_0_0.JSONFactory400._ +import code.fx.{MappedFXRate, fx} import code.api.dynamic.endpoint.helper._ import code.api.dynamic.endpoint.helper.practise.PractiseEndpoint import code.api.dynamic.entity.helper.{DynamicEntityHelper, DynamicEntityInfo} @@ -58,6 +59,7 @@ import code.dynamicMessageDoc.JsonDynamicMessageDoc import code.dynamicResourceDoc.JsonDynamicResourceDoc import code.endpointMapping.EndpointMappingCommons import code.entitlement.Entitlement +import code.fx.fx import code.loginattempts.LoginAttempt import code.metadata.counterparties.{Counterparties, MappedCounterparty} import code.metadata.tags.Tags @@ -103,6 +105,8 @@ import net.liftweb.util.Mailer.{From, PlainMailBodyType, Subject, To, XHTMLMailB import net.liftweb.util.{Helpers, Mailer, StringHelpers} import org.apache.commons.lang3.StringUtils +import java.time.{LocalDate, ZoneId, ZonedDateTime} +import java.util.Date import scala.collection.immutable.{List, Nil} import scala.collection.mutable.ArrayBuffer import scala.concurrent.Future @@ -1102,7 +1106,7 @@ trait APIMethods400 extends MdcLoggable { } account = BankIdAccountId(fromAccount.bankId, fromAccount.accountId) - _ <- NewStyle.function.checkAuthorisationToCreateTransactionRequest(viewId, account, u, callContext) +// _ <- NewStyle.function.checkAuthorisationToCreateTransactionRequest(viewId, account, u, callContext) // Check transReqId is valid (existingTransactionRequest, callContext) <- NewStyle.function.getTransactionRequestImpl(transReqId, callContext) @@ -12241,11 +12245,6 @@ object APIMethods400 extends RestHelper with APIMethods400 { isValidCurrencyISOCode(transDetailsJson.value.currency) } - // Prevent default value for transaction request type (at least). - _ <- Helper.booleanToFuture(s"${InvalidISOCurrencyCode} Current input is: '${transDetailsJson.value.currency}'", cc=callContext) { - isValidCurrencyISOCode(transDetailsJson.value.currency) - } - (createdTransactionRequest, callContext) <- transactionRequestTypeValue match { case REFUND => { for { @@ -12413,6 +12412,146 @@ object APIMethods400 extends RestHelper with APIMethods400 { } toCounterpartyId = transactionRequestBodyCounterparty.to.counterparty_id (toCounterparty, callContext) <- NewStyle.function.getCounterpartyByCounterpartyId(CounterpartyId(toCounterpartyId), callContext) + + (counterpartyLimitBox, callContext) <- Connector.connector.vend.getCounterpartyLimit( + bankId.value, + accountId.value, + viewId.value, + toCounterpartyId, + callContext + ) + _<- if(counterpartyLimitBox.isDefined){ + for{ + + counterpartyLimit <- Future.successful(counterpartyLimitBox.head) + maxSingleAmount = counterpartyLimit.maxSingleAmount + maxMonthlyAmount = counterpartyLimit.maxMonthlyAmount + maxNumberOfMonthlyTransactions = counterpartyLimit.maxNumberOfMonthlyTransactions + maxYearlyAmount = counterpartyLimit.maxYearlyAmount + maxNumberOfYearlyTransactions = counterpartyLimit.maxNumberOfYearlyTransactions + maxTotalAmount = counterpartyLimit.maxTotalAmount + maxNumberOfTransactions = counterpartyLimit.maxNumberOfTransactions + + // Get the first day of the current month + firstDayOfMonth: LocalDate = LocalDate.now().withDayOfMonth(1) + + // Get the last day of the current month + lastDayOfMonth: LocalDate = LocalDate.now().withDayOfMonth( + LocalDate.now().lengthOfMonth() + ) + // Get the first day of the current year + firstDayOfYear: LocalDate = LocalDate.now().withDayOfYear(1) + + // Get the last day of the current year + lastDayOfYear: LocalDate = LocalDate.now().withDayOfYear( + LocalDate.now().lengthOfYear() + ) + + // Convert LocalDate to Date + zoneId: ZoneId = ZoneId.systemDefault() + firstCurrentMonthDate: Date = Date.from(firstDayOfMonth.atStartOfDay(zoneId).toInstant) + lastCurrentMonthDate: Date = Date.from(lastDayOfMonth.atStartOfDay(zoneId).toInstant) + + firstCurrentYearDate: Date = Date.from(firstDayOfYear.atStartOfDay(zoneId).toInstant) + lastCurrentYearDate: Date = Date.from(lastDayOfYear.atStartOfDay(zoneId).toInstant) + + defaultFromDate: Date = theEpochTime + defaultToDate: Date = APIUtil.ToDateInFuture + + (sumOfTransactionsFromAccountToCounterpartyMonthly, callContext) <- NewStyle.function.getSumOfTransactionsFromAccountToCounterparty( + fromAccount.bankId: BankId, + fromAccount.accountId: AccountId, + CounterpartyId(toCounterpartyId): CounterpartyId, + firstCurrentMonthDate: Date, + lastCurrentMonthDate: Date, + callContext: Option[CallContext] + ) + + (countOfTransactionsFromAccountToCounterpartyMonthly, callContext) <- NewStyle.function.getCountOfTransactionsFromAccountToCounterparty( + fromAccount.bankId: BankId, + fromAccount.accountId: AccountId, + CounterpartyId(toCounterpartyId): CounterpartyId, + firstCurrentMonthDate: Date, + lastCurrentMonthDate: Date, + callContext: Option[CallContext] + ) + + (sumOfTransactionsFromAccountToCounterpartyYearly, callContext) <- NewStyle.function.getSumOfTransactionsFromAccountToCounterparty( + fromAccount.bankId: BankId, + fromAccount.accountId: AccountId, + CounterpartyId(toCounterpartyId): CounterpartyId, + firstCurrentYearDate: Date, + lastCurrentYearDate: Date, + callContext: Option[CallContext] + ) + + (countOfTransactionsFromAccountToCounterpartyYearly, callContext) <- NewStyle.function.getCountOfTransactionsFromAccountToCounterparty( + fromAccount.bankId: BankId, + fromAccount.accountId: AccountId, + CounterpartyId(toCounterpartyId): CounterpartyId, + firstCurrentYearDate: Date, + lastCurrentYearDate: Date, + callContext: Option[CallContext] + ) + + (sumOfAllTransactionsFromAccountToCounterparty, callContext) <- NewStyle.function.getSumOfTransactionsFromAccountToCounterparty( + fromAccount.bankId: BankId, + fromAccount.accountId: AccountId, + CounterpartyId(toCounterpartyId): CounterpartyId, + defaultFromDate: Date, + defaultToDate: Date, + callContext: Option[CallContext] + ) + + (countOfAllTransactionsFromAccountToCounterparty, callContext) <- NewStyle.function.getCountOfTransactionsFromAccountToCounterparty( + fromAccount.bankId: BankId, + fromAccount.accountId: AccountId, + CounterpartyId(toCounterpartyId): CounterpartyId, + defaultFromDate: Date, + defaultToDate: Date, + callContext: Option[CallContext] + ) + + + currentTransactionAmountWithFxApplied <- NewStyle.function.tryons(s"${InvalidJsonFormat}, it should be $COUNTERPARTY json format", 400, callContext) { + val fromAccountCurrency = fromAccount.currency //eg: if from account currency is EUR + val transferCurrency = transactionRequestBodyCounterparty.value.currency //eg: if the payment json body currency is GBP. + val transferAmount = BigDecimal(transactionRequestBodyCounterparty.value.amount) //eg: if the payment json body amount is 1. + val debitRate = fx.exchangeRate(transferCurrency, fromAccountCurrency, Some(fromAccount.bankId.value), callContext) //eg: the rate here is 1.16278. + fx.convert(transferAmount, debitRate) // 1.16278 Euro + } + + _ <- Helper.booleanToFuture(s"$CounterpartyLimitValidationError max_single_amount is $maxSingleAmount ${fromAccount.currency}, " + + s"but current transaction body amount is ${transactionRequestBodyCounterparty.value.amount} ${transactionRequestBodyCounterparty.value.currency}, " + + s"which is $currentTransactionAmountWithFxApplied ${fromAccount.currency}. ", cc = callContext) { + maxSingleAmount >= currentTransactionAmountWithFxApplied + } + _ <- Helper.booleanToFuture(s"$CounterpartyLimitValidationError max_monthly_amount is $maxSingleAmount, but current monthly amount is ${sumOfTransactionsFromAccountToCounterpartyMonthly.amount}", cc = callContext) { + maxMonthlyAmount >= BigDecimal(sumOfTransactionsFromAccountToCounterpartyMonthly.amount) + } + _ <- Helper.booleanToFuture(s"$CounterpartyLimitValidationError max_number_of_monthly_transactions is $maxSingleAmount, but current count of monthly transactions is ${countOfTransactionsFromAccountToCounterpartyMonthly}", cc = callContext) { + maxNumberOfMonthlyTransactions >= countOfTransactionsFromAccountToCounterpartyMonthly + } + _ <- Helper.booleanToFuture(s"$CounterpartyLimitValidationError max_yearly_amount is $maxYearlyAmount, but current yearly amount is ${sumOfTransactionsFromAccountToCounterpartyYearly.amount}", cc = callContext) { + maxYearlyAmount >= BigDecimal(sumOfTransactionsFromAccountToCounterpartyYearly.amount) + } + result <- Helper.booleanToFuture(s"$CounterpartyLimitValidationError max_number_of_yearly_transactions is $maxNumberOfYearlyTransactions, but current count of yearly transaction is ${countOfTransactionsFromAccountToCounterpartyYearly}", cc = callContext) { + maxNumberOfYearlyTransactions >= countOfTransactionsFromAccountToCounterpartyYearly + } + _ <- Helper.booleanToFuture(s"$CounterpartyLimitValidationError max_total_amount is $maxTotalAmount, but current amount is ${sumOfAllTransactionsFromAccountToCounterparty.amount}", cc = callContext) { + maxTotalAmount >= BigDecimal(sumOfAllTransactionsFromAccountToCounterparty.amount) + } + result <- Helper.booleanToFuture(s"$CounterpartyLimitValidationError max_number_of_transactions is $maxNumberOfTransactions, but current count of all transactions is ${countOfAllTransactionsFromAccountToCounterparty}", cc = callContext) { + maxNumberOfTransactions >= countOfAllTransactionsFromAccountToCounterparty + } + }yield{ + result + } + } + else { + Future.successful(true) + } + toAccount <- NewStyle.function.getBankAccountFromCounterparty(toCounterparty, true, callContext) // Check we can send money to it. _ <- Helper.booleanToFuture(s"$CounterpartyBeneficiaryPermit", cc=callContext) { diff --git a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala index 500a591b3..cb677d5e7 100644 --- a/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala +++ b/obp-api/src/main/scala/code/api/v5_0_0/APIMethods500.scala @@ -950,8 +950,8 @@ trait APIMethods500 { // TODO Add routing scheme as well. In case IBAN is provided this will not work. val fromBankIdAccountId = BankIdAccountId(BankId(postConsentRequestJsonV510.from_account.bank_routing.address), AccountId(postConsentRequestJsonV510.from_account.account_routing.address)) - val vrpViewId = s"_VRP-${UUID.randomUUID.toString}".dropRight(5)// to make sure the length of the viewId is 36. - val targetPermissions = List(//may need getTransactionRequest .. so far only this payments. + val vrpViewId = s"_vrp-${UUID.randomUUID.toString}".dropRight(5)// to make sure the length of the viewId is 36. + val targetPermissions = List(//may need getTransactionRequest . so far only these payments. "can_add_transaction_request_to_beneficiary", "can_get_counterparty" ) @@ -967,7 +967,7 @@ trait APIMethods500 { ) for { - //1st: create the Custom View for the from account. + //1st: create the Custom View for the fromAccount. (fromAccount, callContext) <- NewStyle.function.checkBankAccountExists(fromBankIdAccountId.bankId, fromBankIdAccountId.accountId, callContext) //we do not need sourceViewId so far, we need to get all the view access for the login user, and @@ -988,7 +988,7 @@ trait APIMethods500 { _ <-NewStyle.function.grantAccessToCustomView(vrpView, user, callContext) - //2st: Create a new counterparty on that view (_VRP-9d429899-24f5-42c8-8565-943ffa6a7945) + //2rd: Create a new counterparty on that view (_VRP-9d429899-24f5-42c8-8565-943ffa6a7945) postJson = PostCounterpartyJson400( name = postConsentRequestJsonV510.to_account.counterparty_name, description = postConsentRequestJsonV510.to_account.counterparty_name, @@ -1041,15 +1041,17 @@ trait APIMethods500 { callContext ) - postCounterpartyLimitV510 = PostCounterpartyLimitV510( currency = postConsentRequestJsonV510.to_account.limit.currency, max_single_amount = postConsentRequestJsonV510.to_account.limit.max_single_amount, max_monthly_amount = postConsentRequestJsonV510.to_account.limit.max_monthly_amount, max_number_of_monthly_transactions = postConsentRequestJsonV510.to_account.limit.max_number_of_monthly_transactions, max_yearly_amount = postConsentRequestJsonV510.to_account.limit.max_yearly_amount, - max_number_of_yearly_transactions = postConsentRequestJsonV510.to_account.limit.max_number_of_yearly_transactions + max_number_of_yearly_transactions = postConsentRequestJsonV510.to_account.limit.max_number_of_yearly_transactions, + max_total_amount = postConsentRequestJsonV510.to_account.limit.max_total_amount, + max_number_of_transactions = postConsentRequestJsonV510.to_account.limit.max_number_of_transactions ) + //3rd: create the counterparty limit (counterpartyLimitBox, callContext) <- Connector.connector.vend.getCounterpartyLimit( fromBankIdAccountId.bankId.value, @@ -1058,7 +1060,8 @@ trait APIMethods500 { counterparty.counterpartyId, cc.callContext ) - failMsg = s"$CounterpartyLimitAlreadyExists Current BANK_ID(${fromBankIdAccountId.bankId.value}), ACCOUNT_ID(${fromBankIdAccountId.accountId.value}), VIEW_ID($vrpViewId),COUNTERPARTY_ID(${counterparty.counterpartyId})" + failMsg = s"$CounterpartyLimitAlreadyExists Current BANK_ID(${fromBankIdAccountId.bankId.value}), " + + s"ACCOUNT_ID(${fromBankIdAccountId.accountId.value}), VIEW_ID($vrpViewId),COUNTERPARTY_ID(${counterparty.counterpartyId})" _ <- Helper.booleanToFuture(failMsg, cc = callContext) { counterpartyLimitBox.isEmpty } @@ -1068,11 +1071,13 @@ trait APIMethods500 { viewId = counterparty.thisViewId, counterpartyId = counterparty.counterpartyId, postCounterpartyLimitV510.currency, - postCounterpartyLimitV510.max_single_amount, - postCounterpartyLimitV510.max_monthly_amount, + BigDecimal(postCounterpartyLimitV510.max_single_amount), + BigDecimal(postCounterpartyLimitV510.max_monthly_amount), postCounterpartyLimitV510.max_number_of_monthly_transactions, - postCounterpartyLimitV510.max_yearly_amount, + BigDecimal(postCounterpartyLimitV510.max_yearly_amount), postCounterpartyLimitV510.max_number_of_yearly_transactions, + BigDecimal(postCounterpartyLimitV510.max_total_amount), + postCounterpartyLimitV510.max_number_of_transactions, cc.callContext ) 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 a6d534779..30037f3b9 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 @@ -2989,9 +2989,12 @@ trait APIMethods510 { case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "views" :: ViewId(viewId) ::"counterparties" :: CounterpartyId(counterpartyId) ::"limits" :: Nil JsonPost json -> _ => { cc => implicit val ec = EndpointContext(Some(cc)) for { - postCounterpartyLimitV510 <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the ${classOf[AtmJsonV510]}", 400, cc.callContext) { + postCounterpartyLimitV510 <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the ${classOf[PostCounterpartyLimitV510]}", 400, cc.callContext) { json.extract[PostCounterpartyLimitV510] } + _ <- Helper.booleanToFuture(s"${InvalidISOCurrencyCode} Current input is: '${postCounterpartyLimitV510.currency}'", cc=cc.callContext) { + isValidCurrencyISOCode(postCounterpartyLimitV510.currency) + } (counterpartyLimitBox, callContext) <- Connector.connector.vend.getCounterpartyLimit( bankId.value, accountId.value, @@ -3009,15 +3012,16 @@ trait APIMethods510 { viewId.value, counterpartyId.value, postCounterpartyLimitV510.currency, - postCounterpartyLimitV510.max_single_amount, - postCounterpartyLimitV510.max_monthly_amount, + BigDecimal(postCounterpartyLimitV510.max_single_amount), + BigDecimal(postCounterpartyLimitV510.max_monthly_amount), postCounterpartyLimitV510.max_number_of_monthly_transactions, - postCounterpartyLimitV510.max_yearly_amount, + BigDecimal(postCounterpartyLimitV510.max_yearly_amount), postCounterpartyLimitV510.max_number_of_yearly_transactions, + BigDecimal(postCounterpartyLimitV510.max_total_amount), + postCounterpartyLimitV510.max_number_of_transactions, cc.callContext ) } yield { - (counterpartyLimit.toJValue, HttpCode.`201`(callContext)) } } @@ -3048,24 +3052,29 @@ trait APIMethods510 { case "banks" :: BankId(bankId) :: "accounts" :: AccountId(accountId) :: "views" :: ViewId(viewId) ::"counterparties" :: CounterpartyId(counterpartyId) ::"limits" :: Nil JsonPut json -> _ => { cc => implicit val ec = EndpointContext(Some(cc)) for { - postCounterpartyLimitV510 <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the ${classOf[AtmJsonV510]}", 400, cc.callContext) { + postCounterpartyLimitV510 <- NewStyle.function.tryons(s"$InvalidJsonFormat The Json body should be the ${classOf[PostCounterpartyLimitV510]}", 400, cc.callContext) { json.extract[PostCounterpartyLimitV510] } + _ <- Helper.booleanToFuture(s"${InvalidISOCurrencyCode} Current input is: '${postCounterpartyLimitV510.currency}'", cc=cc.callContext) { + isValidCurrencyISOCode(postCounterpartyLimitV510.currency) + } (counterpartyLimit,callContext) <- NewStyle.function.createOrUpdateCounterpartyLimit( bankId.value, accountId.value, viewId.value, counterpartyId.value, postCounterpartyLimitV510.currency, - postCounterpartyLimitV510.max_single_amount, - postCounterpartyLimitV510.max_monthly_amount, + BigDecimal(postCounterpartyLimitV510.max_single_amount), + BigDecimal(postCounterpartyLimitV510.max_monthly_amount), postCounterpartyLimitV510.max_number_of_monthly_transactions, - postCounterpartyLimitV510.max_yearly_amount, + BigDecimal(postCounterpartyLimitV510.max_yearly_amount), postCounterpartyLimitV510.max_number_of_yearly_transactions, + BigDecimal(postCounterpartyLimitV510.max_total_amount), + postCounterpartyLimitV510.max_number_of_transactions, cc.callContext ) } yield { - (counterpartyLimit.toJValue, HttpCode.`200`(cc.callContext)) + (counterpartyLimit.toJValue, HttpCode.`200`(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 4da50262a..e3eb82634 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 @@ -191,11 +191,13 @@ case class PostAtmJsonV510 ( case class PostCounterpartyLimitV510( currency: String, - max_single_amount: Int, - max_monthly_amount: Int, + max_single_amount: String, + max_monthly_amount: String, max_number_of_monthly_transactions: Int, - max_yearly_amount: Int, - max_number_of_yearly_transactions: Int + max_yearly_amount: String, + max_number_of_yearly_transactions: Int, + max_total_amount: String, + max_number_of_transactions: Int ) case class CounterpartyLimitV510( @@ -205,11 +207,13 @@ case class CounterpartyLimitV510( view_id: String, counterparty_id: String, currency: String, - max_single_amount: Int, - max_monthly_amount: Int, + max_single_amount: String, + max_monthly_amount: String, max_number_of_monthly_transactions: Int, - max_yearly_amount: Int, - max_number_of_yearly_transactions: Int + max_yearly_amount: String, + max_number_of_yearly_transactions: Int, + max_total_amount: String, + max_number_of_transactions: Int ) case class AtmJsonV510 ( @@ -789,7 +793,7 @@ object JSONFactory510 extends CustomJsonFormats { consents.map { c => val jwtPayload: Box[ConsentJWT] = JwtUtil.getSignedPayloadAsJson(c.jsonWebToken).map(parse(_).extract[ConsentJWT]) AllConsentJsonV510( - consent_reference_id = c.id.get.toString, + consent_reference_id = c.consentReferenceId, consumer_id = c.consumerId, created_by_user_id = c.userId, status = c.status, diff --git a/obp-api/src/main/scala/code/bankconnectors/Connector.scala b/obp-api/src/main/scala/code/bankconnectors/Connector.scala index bd272c3bc..87ef777ce 100644 --- a/obp-api/src/main/scala/code/bankconnectors/Connector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/Connector.scala @@ -582,6 +582,10 @@ trait Connector extends MdcLoggable { def getTransactionsCore(bankId: BankId, accountId: AccountId, queryParams: List[OBPQueryParam] = Nil, callContext: Option[CallContext]): OBPReturnType[Box[List[TransactionCore]]] = Future{(Failure(setUnimplementedError(nameOf(getTransactionsCore _))), callContext)} + def getCountOfTransactionsFromAccountToCounterparty(fromBankId: BankId, fromAccountId: AccountId, counterpartyId: CounterpartyId, fromDate: Date, toDate:Date, callContext: Option[CallContext]) :OBPReturnType[Box[Int]] = Future{(Failure(setUnimplementedError(nameOf(getCountOfTransactionsFromAccountToCounterparty _))), callContext: Option[CallContext])} + + def getSumOfTransactionsFromAccountToCounterparty(fromBankId: BankId, fromAccountId: AccountId, counterpartyId: CounterpartyId, fromDate: Date, toDate:Date, callContext: Option[CallContext]):OBPReturnType[Box[AmountOfMoney]] = Future{(Failure(setUnimplementedError(nameOf(getSumOfTransactionsFromAccountToCounterparty _))), callContext: Option[CallContext])} + def getTransactionLegacy(bankId: BankId, accountId : AccountId, transactionId : TransactionId, callContext: Option[CallContext] = None): Box[(Transaction, Option[CallContext])] = Failure(setUnimplementedError(nameOf(getTransactionLegacy _))) def getTransaction(bankId: BankId, accountId : AccountId, transactionId : TransactionId, callContext: Option[CallContext] = None): OBPReturnType[Box[Transaction]] = Future{(Failure(setUnimplementedError(nameOf(getTransaction _))), callContext)} @@ -979,7 +983,10 @@ trait Connector extends MdcLoggable { def getCurrentCurrencies(bankId: BankId, callContext: Option[CallContext]): OBPReturnType[Box[List[String]]] = Future{Failure(setUnimplementedError(nameOf(getCurrentCurrencies _)))} def getCurrentFxRate(bankId: BankId, fromCurrencyCode: String, toCurrencyCode: String, callContext: Option[CallContext]): Box[FXRate] = Failure(setUnimplementedError(nameOf(getCurrentFxRate _))) - + + /** + * There is no mapped implimetaion . it is only use for CBS mode at the moment. + */ def createTransactionAfterChallengev300( initiator: User, fromAccount: BankAccount, @@ -1829,11 +1836,14 @@ trait Connector extends MdcLoggable { viewId: String, counterpartyId: String, currency: String, - maxSingleAmount: Int, - maxMonthlyAmount: Int, + maxSingleAmount: BigDecimal, + maxMonthlyAmount: BigDecimal, maxNumberOfMonthlyTransactions: Int, - maxYearlyAmount: Int, - maxNumberOfYearlyTransactions: Int, callContext: Option[CallContext] + maxYearlyAmount: BigDecimal, + maxNumberOfYearlyTransactions: Int, + maxTotalAmount: BigDecimal, + maxNumberOfTransactions: Int, + callContext: Option[CallContext] ): OBPReturnType[Box[CounterpartyLimitTrait]] = Future{(Failure(setUnimplementedError(nameOf(createOrUpdateCounterpartyLimit _))), callContext)} def getCounterpartyLimit( diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala index 762aeb754..7fd901239 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnector.scala @@ -774,6 +774,41 @@ object LocalMappedConnector extends Connector with MdcLoggable { } } + override def getCountOfTransactionsFromAccountToCounterparty(fromBankId: BankId, fromAccountId: AccountId, counterpartyId: CounterpartyId, fromDate: Date, toDate:Date, callContext: Option[CallContext]) :OBPReturnType[Box[Int]] = { + val queryParams = List(OBPFromDate(fromDate),OBPToDate(toDate), OBPOrdering(None,OBPAscending)) + for{ + (transactionRequestsBox,callContext) <- LocalMappedConnectorInternal.getTransactionRequestsInternal(fromBankId: BankId, fromAccountId: AccountId, counterpartyId: CounterpartyId, queryParams, callContext: Option[CallContext]) + }yield{ + (transactionRequestsBox.map(_.length), callContext) + } + } + + override def getSumOfTransactionsFromAccountToCounterparty(fromBankId: BankId, fromAccountId: AccountId, counterpartyId: CounterpartyId, fromDate: Date, toDate:Date, callContext: Option[CallContext]):OBPReturnType[Box[AmountOfMoney]] = { + + val queryParams = List(OBPFromDate(fromDate),OBPToDate(toDate), OBPOrdering(None,OBPAscending)) + for{ + (fromBankAccount, callContext) <- NewStyle.function.getBankAccount(fromBankId, fromAccountId, callContext) + (transactionRequestsBox,callContext) <- LocalMappedConnectorInternal.getTransactionRequestsInternal(fromBankId: BankId, fromAccountId: AccountId, counterpartyId: CounterpartyId, queryParams, callContext: Option[CallContext]) + // Check the input JSON format, here is just check the common parts of all four types + (amountSum,currency) <- NewStyle.function.tryons(s"$UnknownError can not get the sum of transactions", 400, callContext) { + val transactionRequests = transactionRequestsBox.getOrElse(Nil) + val fromAccountCurrency = fromBankAccount.currency // eg: the fromAccount currency is EUR, and the 1 GBP = 1.16278 Euro. + val allAmounts = for{ + transactionRequest <- transactionRequests + transferCurrency = transactionRequest.mBody_Value_Currency.get //eg: if the payment json body currency is GBP. + transferAmount= BigDecimal(transactionRequest.mBody_Value_Amount.get) //eg: if the payment json body amount is 1. + debitRate = fx.exchangeRate(transferCurrency, fromAccountCurrency, Some(fromBankId.value), callContext) //eg: the rate here is 1.16278. + transactionAmount = fx.convert(transferAmount, debitRate) // 1.16278 Euro + }yield{ + transactionAmount // 1.16278 Euro + } + (allAmounts.sum, fromAccountCurrency) // Here we just sum all the transfer amounts. + } + } yield { + (Full(AmountOfMoney(currency, amountSum.toString())), callContext) + } + } + /** * * refreshes transactions via hbci if the transaction info is sourced from hbci @@ -4351,7 +4386,9 @@ object LocalMappedConnector extends Connector with MdcLoggable { charge, chargePolicy, None, - None) + None, + callContext + ) } map { unboxFullOrFail(_, callContext, s"$InvalidConnectorResponseForCreateTransactionRequestImpl210") } @@ -4505,7 +4542,8 @@ object LocalMappedConnector extends Connector with MdcLoggable { charge, chargePolicy, None, - None + None, + callContext ) saveTransactionRequestReasons(reasons, transactionRequest) transactionRequest @@ -5134,22 +5172,28 @@ object LocalMappedConnector extends Connector with MdcLoggable { viewId: String, counterpartyId: String, currency: String, - maxSingleAmount: Int, - maxMonthlyAmount: Int, + maxSingleAmount: BigDecimal, + maxMonthlyAmount: BigDecimal, maxNumberOfMonthlyTransactions: Int, - maxYearlyAmount: Int, - maxNumberOfYearlyTransactions: Int, callContext: Option[CallContext]) = + maxYearlyAmount: BigDecimal, + maxNumberOfYearlyTransactions: Int, + maxTotalAmount: BigDecimal, + maxNumberOfTransactions: Int, + callContext: Option[CallContext]) = CounterpartyLimitProvider.counterpartyLimit.vend.createOrUpdateCounterpartyLimit( bankId: String, accountId: String, viewId: String, counterpartyId: String, currency: String, - maxSingleAmount: Int, - maxMonthlyAmount: Int, + maxSingleAmount: BigDecimal, + maxMonthlyAmount: BigDecimal, maxNumberOfMonthlyTransactions: Int, - maxYearlyAmount: Int, - maxNumberOfYearlyTransactions: Int) map { + maxYearlyAmount: BigDecimal, + maxNumberOfYearlyTransactions: Int, + maxTotalAmount: BigDecimal, + maxNumberOfTransactions: Int + ) map { (_, callContext) } diff --git a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnectorInternal.scala b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnectorInternal.scala index a373aedd2..6e9a71ee2 100644 --- a/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnectorInternal.scala +++ b/obp-api/src/main/scala/code/bankconnectors/LocalMappedConnectorInternal.scala @@ -22,7 +22,7 @@ import com.openbankproject.commons.model.enums.{AccountRoutingScheme, PaymentSer import net.liftweb.common._ import net.liftweb.json.Serialization.write import net.liftweb.json.{NoTypeHints, Serialization} -import net.liftweb.mapper.By +import net.liftweb.mapper.{Ascending, By, By_<=, By_>=, Descending, OrderBy, QueryParam} import net.liftweb.util.Helpers.{now, tryo} import java.util.Date @@ -92,7 +92,7 @@ object LocalMappedConnectorInternal extends MdcLoggable { } // Prevent default value for transaction request type (at least). - _ <- Helper.booleanToFuture(s"From Account Currency is ${fromAccount.currency}, but Requested instructedAmount.currency is: ${transactionRequestBody.instructedAmount.currency}", cc = callContext) { + _ <- Helper.booleanToFuture(s"$InvalidTransactionRequestCurrency From Account Currency is ${fromAccount.currency}, but Requested instructedAmount.currency is: ${transactionRequestBody.instructedAmount.currency}", cc = callContext) { transactionRequestBody.instructedAmount.currency == fromAccount.currency } @@ -157,7 +157,8 @@ object LocalMappedConnectorInternal extends MdcLoggable { charge, "", // chargePolicy is not used in BG so far. Some(paymentServiceType.toString), - Some(transactionRequestBody) + Some(transactionRequestBody), + callContext ) transactionRequest } map { @@ -631,6 +632,32 @@ object LocalMappedConnectorInternal extends MdcLoggable { } } - def getTransactionRequestStatuses() : Box[TransactionRequestStatus] = Failure(NotImplemented + nameOf(getTransactionRequestStatuses _) ) + def getTransactionRequestsInternal(fromBankId: BankId, fromAccountId: AccountId, counterpartyId: CounterpartyId, queryParams: List[OBPQueryParam], callContext: Option[CallContext]): OBPReturnType[Box[List[MappedTransactionRequest]]] = { + + val fromDate = queryParams.collect { case OBPFromDate(date) => By_>=(MappedTransactionRequest.updatedAt, date) }.headOption + val toDate = queryParams.collect { case OBPToDate(date) => By_<=(MappedTransactionRequest.updatedAt, date) }.headOption + val ordering = queryParams.collect { + //we don't care about the intended sort field and only sort on finish date for now + case OBPOrdering(_, direction) => + direction match { + case OBPAscending => OrderBy(MappedTransactionRequest.updatedAt, Ascending) + case OBPDescending => OrderBy(MappedTransactionRequest.updatedAt, Descending) + } + } + + val optionalParams: Seq[QueryParam[MappedTransactionRequest]] = Seq(fromDate.toSeq, toDate.toSeq, ordering.toSeq).flatten + val mapperParams = Seq( + By(MappedTransactionRequest.mFrom_BankId, fromBankId.value), + By(MappedTransactionRequest.mFrom_AccountId, fromAccountId.value), + By(MappedTransactionRequest.mCounterpartyId, counterpartyId.value), + By(MappedTransactionRequest.mStatus, TransactionRequestStatus.COMPLETED.toString) + ) ++ optionalParams + + Future { + (Full(MappedTransactionRequest.findAll(mapperParams: _*)), callContext) + } + } + def getTransactionRequestStatuses() : Box[TransactionRequestStatus] = Failure(NotImplemented + nameOf(getTransactionRequestStatuses _)) + } diff --git a/obp-api/src/main/scala/code/counterpartylimit/CounterpartyLimit.scala b/obp-api/src/main/scala/code/counterpartylimit/CounterpartyLimit.scala index 3b8399ec8..2d1ab8a6d 100644 --- a/obp-api/src/main/scala/code/counterpartylimit/CounterpartyLimit.scala +++ b/obp-api/src/main/scala/code/counterpartylimit/CounterpartyLimit.scala @@ -31,10 +31,13 @@ trait CounterpartyLimitProviderTrait { viewId: String, counterpartyId: String, currency: String, - maxSingleAmount: Int, - maxMonthlyAmount: Int, + maxSingleAmount: BigDecimal, + maxMonthlyAmount: BigDecimal, maxNumberOfMonthlyTransactions: Int, - maxYearlyAmount: Int, - maxNumberOfYearlyTransactions: Int): Future[Box[CounterpartyLimitTrait]] + maxYearlyAmount: BigDecimal, + maxNumberOfYearlyTransactions: Int, + maxTotalAmount: BigDecimal, + maxNumberOfTransactions: Int + ): Future[Box[CounterpartyLimitTrait]] } diff --git a/obp-api/src/main/scala/code/counterpartylimit/MappedCounterpartyLimit.scala b/obp-api/src/main/scala/code/counterpartylimit/MappedCounterpartyLimit.scala index 96be5d187..0206aced2 100644 --- a/obp-api/src/main/scala/code/counterpartylimit/MappedCounterpartyLimit.scala +++ b/obp-api/src/main/scala/code/counterpartylimit/MappedCounterpartyLimit.scala @@ -7,11 +7,14 @@ import net.liftweb.util.Helpers.tryo import com.openbankproject.commons.ExecutionContext.Implicits.global import net.liftweb.json import net.liftweb.json.Formats -import net.liftweb.json.JsonAST.{JValue,JString} +import net.liftweb.json.JsonAST.{JString, JValue} import net.liftweb.json.JsonDSL._ + import scala.concurrent.Future import com.openbankproject.commons.model.CounterpartyLimitTrait +import java.math.MathContext + object MappedCounterpartyLimitProvider extends CounterpartyLimitProviderTrait { def getCounterpartyLimit( @@ -48,11 +51,13 @@ object MappedCounterpartyLimitProvider extends CounterpartyLimitProviderTrait { viewId: String, counterpartyId: String, currency: String, - maxSingleAmount: Int, - maxMonthlyAmount: Int, + maxSingleAmount: BigDecimal, + maxMonthlyAmount: BigDecimal, maxNumberOfMonthlyTransactions: Int, - maxYearlyAmount: Int, - maxNumberOfYearlyTransactions: Int)= Future { + maxYearlyAmount: BigDecimal, + maxNumberOfYearlyTransactions: Int, + maxTotalAmount: BigDecimal, + maxNumberOfTransactions: Int)= Future { def createCounterpartyLimit(counterpartyLimit: CounterpartyLimit)= { tryo { @@ -66,6 +71,8 @@ object MappedCounterpartyLimitProvider extends CounterpartyLimitProviderTrait { counterpartyLimit.MaxNumberOfMonthlyTransactions(maxNumberOfMonthlyTransactions) counterpartyLimit.MaxYearlyAmount(maxYearlyAmount) counterpartyLimit.MaxNumberOfYearlyTransactions(maxNumberOfYearlyTransactions) + counterpartyLimit.MaxTotalAmount(maxTotalAmount) + counterpartyLimit.MaxNumberOfTransactions(maxNumberOfTransactions) counterpartyLimit.saveMe() } } @@ -105,22 +112,34 @@ class CounterpartyLimit extends CounterpartyLimitTrait with LongKeyedMapper[Coun object Currency extends MappedString(this, 255) - object MaxSingleAmount extends MappedInt(this) { - override def defaultValue = -1 + object MaxSingleAmount extends MappedDecimal(this, MathContext.DECIMAL64, 10){ + override def defaultValue = BigDecimal(0) // Default value for Amount } - object MaxMonthlyAmount extends MappedInt(this) { - override def defaultValue = -1 + + object MaxMonthlyAmount extends MappedDecimal(this, MathContext.DECIMAL64, 10){ + override def defaultValue = BigDecimal(0) // Default value for Amount } + object MaxNumberOfMonthlyTransactions extends MappedInt(this) { override def defaultValue = -1 } - object MaxYearlyAmount extends MappedInt(this) { - override def defaultValue = -1 + + object MaxYearlyAmount extends MappedDecimal(this, MathContext.DECIMAL64, 10){ + override def defaultValue = BigDecimal(0) // Default value for Amount } object MaxNumberOfYearlyTransactions extends MappedInt(this) { override def defaultValue = -1 } + + object MaxTotalAmount extends MappedDecimal(this, MathContext.DECIMAL64, 10){ + override def defaultValue = BigDecimal(0) // Default value for Amount + } + + object MaxNumberOfTransactions extends MappedInt(this) { + override def defaultValue = -1 + } + def counterpartyLimitId: String = CounterpartyLimitId.get def bankId: String = BankId.get @@ -129,13 +148,15 @@ class CounterpartyLimit extends CounterpartyLimitTrait with LongKeyedMapper[Coun def counterpartyId: String = CounterpartyId.get def currency: String = Currency.get - def maxSingleAmount: Int = MaxSingleAmount.get - def maxMonthlyAmount: Int = MaxMonthlyAmount.get + def maxSingleAmount: BigDecimal = MaxSingleAmount.get + def maxMonthlyAmount: BigDecimal = MaxMonthlyAmount.get def maxNumberOfMonthlyTransactions: Int = MaxNumberOfMonthlyTransactions.get - def maxYearlyAmount: Int = MaxYearlyAmount.get + def maxYearlyAmount: BigDecimal = MaxYearlyAmount.get def maxNumberOfYearlyTransactions: Int = MaxNumberOfYearlyTransactions.get + def maxTotalAmount: BigDecimal = MaxTotalAmount.get + def maxNumberOfTransactions: Int = MaxNumberOfTransactions.get - override def toJValue(implicit format: Formats): JValue ={ + override def toJValue(implicit format: Formats): JValue = { ("counterparty_limit_id", counterpartyLimitId) ~ ("bank_id", bankId) ~ ("account_id",accountId) ~ @@ -146,7 +167,9 @@ class CounterpartyLimit extends CounterpartyLimitTrait with LongKeyedMapper[Coun ("max_monthly_amount", maxMonthlyAmount) ~ ("max_number_of_monthly_transactions", maxNumberOfMonthlyTransactions) ~ ("max_yearly_amount", maxYearlyAmount) ~ - ("max_number_of_yearly_transactions", maxNumberOfYearlyTransactions) + ("max_number_of_yearly_transactions", maxNumberOfYearlyTransactions) ~ + ("max_total_amount", maxTotalAmount) ~ + ("max_number_of_transactions", maxNumberOfTransactions) } } diff --git a/obp-api/src/main/scala/code/transactionrequests/MappedTransactionRequestProvider.scala b/obp-api/src/main/scala/code/transactionrequests/MappedTransactionRequestProvider.scala index 9838c6903..8a832409a 100644 --- a/obp-api/src/main/scala/code/transactionrequests/MappedTransactionRequestProvider.scala +++ b/obp-api/src/main/scala/code/transactionrequests/MappedTransactionRequestProvider.scala @@ -1,15 +1,17 @@ package code.transactionrequests -import code.api.util.APIUtil.DateWithMsFormat -import code.api.util.CustomJsonFormats +import code.api.util.APIUtil.{DateWithMsFormat} +import code.api.util.{APIUtil, CallContext, CustomJsonFormats} import code.api.util.ErrorMessages._ -import code.api.v4_0_0.TransactionRequestBodyAgentJsonV400 +import code.api.v2_1_0.TransactionRequestBodyCounterpartyJSON import code.bankconnectors.LocalMappedConnectorInternal +import code.consent.Consents import code.model._ import code.util.{AccountIdString, UUIDString} import com.openbankproject.commons.model._ import com.openbankproject.commons.model.enums.{AccountRoutingScheme, TransactionRequestStatus} import com.openbankproject.commons.model.enums.TransactionRequestTypes +import com.openbankproject.commons.model.enums.TransactionRequestTypes.{COUNTERPARTY, SEPA} import net.liftweb.common.{Box, Failure, Full, Logger} import net.liftweb.json import net.liftweb.json.JsonAST.{JField, JObject, JString} @@ -90,14 +92,20 @@ object MappedTransactionRequestProvider extends TransactionRequestProvider { charge: TransactionRequestCharge, chargePolicy: String, paymentService: Option[String], - berlinGroupPayments: Option[BerlinGroupTransactionRequestCommonBodyJson]): Box[TransactionRequest] = { + berlinGroupPayments: Option[BerlinGroupTransactionRequestCommonBodyJson], + callContext: Option[CallContext]): Box[TransactionRequest] = { - val toAccountRouting = transactionRequestType.value match { - case "SEPA" => + val toAccountRouting = TransactionRequestTypes.withName(transactionRequestType.value) match { + case SEPA => toAccount.accountRoutings.find(_.scheme == AccountRoutingScheme.IBAN.toString) .orElse(toAccount.accountRoutings.headOption) case _ => toAccount.accountRoutings.headOption } + + val counterpartyIdOption = TransactionRequestTypes.withName(transactionRequestType.value) match { + case COUNTERPARTY => Some(transactionRequestCommonBody.asInstanceOf[TransactionRequestBodyCounterpartyJSON].to.counterparty_id) + case _ => None + } val (paymentStartDate, paymentEndDate, executionRule, frequency, dayOfExecution) = if(paymentService == Some("periodic-payments")){ val paymentFields = berlinGroupPayments.asInstanceOf[Option[PeriodicSepaCreditTransfersBerlinGroupV13]] @@ -114,6 +122,9 @@ object MappedTransactionRequestProvider extends TransactionRequestProvider { (null, null, null, null, null) } + val consentIdOption = callContext.map(_.requestHeaders).map(APIUtil.getConsentIdRequestHeaderValue).flatten + val consentOption = consentIdOption.map(consentId =>Consents.consentProvider.vend.getConsentByConsentId(consentId).toOption).flatten + val consentReferenceIdOption = consentOption.map(_.consentReferenceId) // Note: We don't save transaction_ids, status and challenge here. val mappedTransactionRequest = MappedTransactionRequest.create @@ -150,7 +161,7 @@ object MappedTransactionRequestProvider extends TransactionRequestProvider { //.mThisBankId(toAccount.bankId.value) //.mThisAccountId(toAccount.accountId.value) //.mThisViewId(toAccount.v) - //.mCounterpartyId(toAccount.branchId) + .mCounterpartyId(counterpartyIdOption.getOrElse(null)) //.mIsBeneficiary(toAccount.isBeneficiary) //Body from http request: SANDBOX_TAN, FREE_FORM, SEPA and COUNTERPARTY should have the same following fields: @@ -166,6 +177,7 @@ object MappedTransactionRequestProvider extends TransactionRequestProvider { .mPaymentExecutionRule(executionRule) .mPaymentFrequency(frequency) .mPaymentDayOfExecution(dayOfExecution) + .mConsentReferenceId(consentReferenceIdOption.getOrElse(null)) .saveMe Full(mappedTransactionRequest).flatMap(_.toTransactionRequest) @@ -272,6 +284,8 @@ class MappedTransactionRequest extends LongKeyedMapper[MappedTransactionRequest] object mPaymentFrequency extends MappedString(this, 64) //BGv1.3 Open API Document example value: "frequency":"Monthly", object mPaymentDayOfExecution extends MappedString(this, 64)//BGv1.3 Open API Document example value: "dayOfExecution":"01" + object mConsentReferenceId extends MappedString(this, 64) + def updateStatus(newStatus: String) = { mStatus.set(newStatus) } diff --git a/obp-api/src/main/scala/code/transactionrequests/TransactionRequests.scala b/obp-api/src/main/scala/code/transactionrequests/TransactionRequests.scala index 80f4766d8..40b831c9f 100644 --- a/obp-api/src/main/scala/code/transactionrequests/TransactionRequests.scala +++ b/obp-api/src/main/scala/code/transactionrequests/TransactionRequests.scala @@ -1,7 +1,7 @@ package code.transactionrequests -import code.api.util.APIUtil +import code.api.util.{APIUtil, CallContext} import com.openbankproject.commons.model.{TransactionRequest, TransactionRequestChallenge, TransactionRequestCharge, _} import net.liftweb.common.{Box, Logger} import net.liftweb.util.SimpleInjector @@ -76,7 +76,8 @@ trait TransactionRequestProvider { charge: TransactionRequestCharge, chargePolicy: String, paymentService: Option[String], - berlinGroupPayments: Option[BerlinGroupTransactionRequestCommonBodyJson]): Box[TransactionRequest] + berlinGroupPayments: Option[BerlinGroupTransactionRequestCommonBodyJson], + callContext: Option[CallContext]): Box[TransactionRequest] def saveTransactionRequestTransactionImpl(transactionRequestId: TransactionRequestId, transactionId: TransactionId): Box[Boolean] def saveTransactionRequestChallengeImpl(transactionRequestId: TransactionRequestId, challenge: TransactionRequestChallenge): Box[Boolean] diff --git a/obp-api/src/test/scala/code/api/v5_1_0/CounterpartyLimitTest.scala b/obp-api/src/test/scala/code/api/v5_1_0/CounterpartyLimitTest.scala index 9a3685aa9..4824bd5e1 100644 --- a/obp-api/src/test/scala/code/api/v5_1_0/CounterpartyLimitTest.scala +++ b/obp-api/src/test/scala/code/api/v5_1_0/CounterpartyLimitTest.scala @@ -31,11 +31,13 @@ class CounterpartyLimitTest extends V510ServerSetup { val postCounterpartyLimitV510 = code.api.ResourceDocs1_4_0.SwaggerDefinitionsJSON.postCounterpartyLimitV510 val putCounterpartyLimitV510 = PostCounterpartyLimitV510( currency = "EUR", - max_single_amount = 1, - max_monthly_amount = 2, + max_single_amount = "1.1", + max_monthly_amount = "2.1", max_number_of_monthly_transactions = 3, - max_yearly_amount = 4, - max_number_of_yearly_transactions = 5 + max_yearly_amount = "4.1", + max_number_of_yearly_transactions = 5, + max_total_amount="6.1", + max_number_of_transactions=7, ) @@ -116,6 +118,8 @@ class CounterpartyLimitTest extends V510ServerSetup { response510.body.extract[CounterpartyLimitV510].max_number_of_yearly_transactions should equal(putCounterpartyLimitV510.max_number_of_yearly_transactions) response510.body.extract[CounterpartyLimitV510].max_single_amount should equal(putCounterpartyLimitV510.max_single_amount) response510.body.extract[CounterpartyLimitV510].max_yearly_amount should equal(putCounterpartyLimitV510.max_yearly_amount) + response510.body.extract[CounterpartyLimitV510].max_total_amount should equal(putCounterpartyLimitV510.max_total_amount) + response510.body.extract[CounterpartyLimitV510].max_number_of_transactions should equal(putCounterpartyLimitV510.max_number_of_transactions) } { @@ -128,6 +132,8 @@ class CounterpartyLimitTest extends V510ServerSetup { response510.body.extract[CounterpartyLimitV510].max_number_of_yearly_transactions should equal(putCounterpartyLimitV510.max_number_of_yearly_transactions) response510.body.extract[CounterpartyLimitV510].max_single_amount should equal(putCounterpartyLimitV510.max_single_amount) response510.body.extract[CounterpartyLimitV510].max_yearly_amount should equal(putCounterpartyLimitV510.max_yearly_amount) + response510.body.extract[CounterpartyLimitV510].max_total_amount should equal(putCounterpartyLimitV510.max_total_amount) + response510.body.extract[CounterpartyLimitV510].max_number_of_transactions should equal(putCounterpartyLimitV510.max_number_of_transactions) } { diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala index 23248529c..0fce669ce 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModel.scala @@ -33,22 +33,11 @@ import com.openbankproject.commons.model.enums._ import com.openbankproject.commons.util.{ReflectUtils, optional} import net.liftweb.json.JsonAST.{JObject, JValue} import net.liftweb.json.{Formats, JInt, JString} +import net.liftweb.json.JsonDSL._ import java.lang -import scala.collection.immutable.List import scala.reflect.runtime.universe._ -//import code.customeraddress.CustomerAddress -//import code.bankconnectors.InboundAccountCommon -//import code.branches.Branches.BranchT -//import code.context.UserAuthContext -//import code.meetings.Meeting -//import code.taxresidence.TaxResidence -//import code.productcollectionitem.ProductCollectionItem -//import code.productcollection.ProductCollection -//import code.atms.Atms.AtmT -//import code.productattribute.ProductAttribute.ProductAttribute -//import code.accountattribute.AccountAttribute.AccountAttribute -//import code.accountapplication.AccountApplication + abstract class Converter[T, D <% T: TypeTag]{ //this method declared as common method to avoid conflict with Predf#$confirms @@ -621,13 +610,29 @@ case class CounterpartyLimitTraitCommons( viewId: String, counterpartyId: String, currency: String, - maxSingleAmount: Int, - maxMonthlyAmount: Int, + maxSingleAmount: BigDecimal, + maxMonthlyAmount: BigDecimal, maxNumberOfMonthlyTransactions: Int, - maxYearlyAmount: Int, - maxNumberOfYearlyTransactions: Int + maxYearlyAmount: BigDecimal, + maxNumberOfYearlyTransactions: Int, + maxTotalAmount: BigDecimal, + maxNumberOfTransactions: Int, ) extends CounterpartyLimitTrait { - override def toJValue(implicit format: Formats): JValue = ??? + override def toJValue(implicit format: Formats): JValue = { + ("counterparty_limit_id", counterpartyLimitId) ~ + ("bank_id", bankId) ~ + ("account_id",accountId) ~ + ("view_id",viewId) ~ + ("counterparty_id",counterpartyId) ~ + ("currency",currency) ~ + ("max_single_amount", maxSingleAmount) ~ + ("max_monthly_amount", maxMonthlyAmount) ~ + ("max_number_of_monthly_transactions", maxNumberOfMonthlyTransactions) ~ + ("max_yearly_amount", maxYearlyAmount) ~ + ("max_number_of_yearly_transactions", maxNumberOfYearlyTransactions) ~ + ("max_total_amount", maxTotalAmount) ~ + ("max_number_of_transactions", maxNumberOfTransactions) + } } object CounterpartyLimitTraitCommons extends Converter[CounterpartyLimitTrait, CounterpartyLimitTraitCommons] diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala index b1e939464..1c81d8beb 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/model/CommonModelTrait.scala @@ -663,11 +663,13 @@ trait CounterpartyLimitTrait extends JsonAble{ def counterpartyId: String def currency: String - def maxSingleAmount: Int - def maxMonthlyAmount: Int + def maxSingleAmount: BigDecimal + def maxMonthlyAmount: BigDecimal def maxNumberOfMonthlyTransactions: Int - def maxYearlyAmount: Int + def maxYearlyAmount: BigDecimal def maxNumberOfYearlyTransactions: Int + def maxTotalAmount: BigDecimal + def maxNumberOfTransactions: Int } trait EndpointTagT { diff --git a/obp-commons/src/main/scala/com/openbankproject/commons/util/JsonSerializers.scala b/obp-commons/src/main/scala/com/openbankproject/commons/util/JsonSerializers.scala index d286b9f65..8501f6f99 100644 --- a/obp-commons/src/main/scala/com/openbankproject/commons/util/JsonSerializers.scala +++ b/obp-commons/src/main/scala/com/openbankproject/commons/util/JsonSerializers.scala @@ -147,6 +147,7 @@ object BigDecimalSerializer extends Serializer[BigDecimal] { override def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), BigDecimal] = { case (TypeInfo(IntervalClass, _), json) => json match { case JString(s) => BigDecimal(s) +// case JDouble(s) => BigDecimal(s) case x => throw new MappingException("Can't convert " + x + " to BigDecimal") } }