diff --git a/api-docs/apiLive.htm b/api-docs/apiLive.htm index c2fa3283c..3139b1a5b 100644 --- a/api-docs/apiLive.htm +++ b/api-docs/apiLive.htm @@ -3312,6 +3312,38 @@ + + Beneficiary for Third Party Transfer + /self/beneficiaries/tpt/template + + Beneficiary TPT Template + + + + + + /self/beneficiaries/tpt + Add TPT Beneficiary + Get TPT Beneficiary List + Update TPT Beneficiary + Delete TPT Beneficiary + + + Third Party Account Transfers + self/accounttransfers/template?type="tpt" + + Third Party Account Transfer Template + + + + + + self/accounttransfers?type="tpt" + Third Party Account Transfer + + + + @@ -42193,7 +42225,35 @@ GET https://DomainName/api/v1/self/savingsaccounts/{accountId}/charges/{savingsA { - "accountOptions": [ + "fromAccountOptions": [ + { + "accountId": 1, + "accountNo": "00000001", + "accountType": { + "id": 2, + "code": "accountType.savings", + "value": "Savings Account" + }, + "clientId": 1, + "clientName": "ABC", + "officeId": 1, + "officeName": "HEAD OFFICE" + }, + { + "accountId": 5, + "accountNo": "00000005", + "accountType": { + "id": 1, + "code": "accountType.loan", + "value": "Loan Account" + }, + "clientId": 2, + "clientName": "XYZ", + "officeId": 3, + "officeName": "REGIONAL OFFICE" + } + ], + "toAccountOptions": [ { "accountId": 1, "accountNo": "00000001", @@ -42221,6 +42281,7 @@ GET https://DomainName/api/v1/self/savingsaccounts/{accountId}/charges/{savingsA "officeName": "REGIONAL OFFICE" } ] + } @@ -42262,6 +42323,322 @@ No Request Body: +   +
+
+

Beneficiary Third Party Transfer Template

+

Returns Account Type enumerations. Self User is expected to know office name and account number to be able to add beneficiary.

+

Example Requests:

+
/self/beneficiaries/tpt/template
+
+
+ GET https://DomainName/api/v1/self/beneficiaries/tpt/template + + + { + "accountTypeOptions": + [ + { + "id": 2, + "code": "accountType.savings", + "value": "Savings Account" + }, + { + "id": 1, + "code": "accountType.loan", + "value": "Loan Account" + } + ] + } + +
+
+ +   +
+
+

Add TPT Beneficiary

+

Api to add third party beneficiary linked to current user.

+

Parameter Definitions

+

name : Nick name for beneficiary, should be unique for an self service user

+

officeName : Office Name of beneficiary(not id)

+

accountNumber : Account Number of beneficiary(not id)

+

transferLimit : Each transfer initiated to this account will not exceed this amount

+

Example Requests:

+
/self/beneficiaries/tpt
+ + + + + + + +
Mandatory Fields
name, officeName, accountNumber, accountType
+
+ + + + + + + +
Optional Fields
transferLimit +
+
+ +
+ POST https://DomainName/api/v1/self/beneficiaries/tpt + + +POST self/beneficiaries/tpt +Content-Type: application/json +Request Body: +{ + "locale": "en_GB", + "name": "beneficiary nick name", + "officeName": "HEAD OFFICE", + "accountNumber": "0000001", + "accountType": 1, + "transferLimit": 1000 + +} + + +{ + "resourceId": 5 +} + +
+
+ +   +
+
+

Get All TPT Beneficiary

+

Api to get all third party beneficiary linked to current user.

+

Example Requests:

+
/self/beneficiaries/tpt
+
+ +
+ GET https://DomainName/api/v1/self/beneficiaries/tpt + + +GET self/beneficiaries/tpt +Content-Type: application/json + + +{ +[ + { + "id": 1, + "name": "Client2Savings", + "officeName": "Test Office", + "clientName": "FN2 LN2", + "accountType": { + "id": 2, + "code": "accountType.savings", + "value": "Savings Account" + }, + "accountNumber": "000000002", + "transferLimit": 0 + }, + { + "id": 4, + "name": "Client2Loan", + "officeName": "Test Office", + "clientName": "FN2 LN2", + "accountType": { + "id": 1, + "code": "accountType.loan", + "value": "Loan Account" + }, + "accountNumber": "000000002", + "transferLimit": 1000 + }] +} + +
+
+ +   +
+
+

Update TPT Beneficiary

+

Api to update third party beneficiary linked to current user.

+

Example Requests:

+
/self/beneficiaries/tpt/{beneficiaryId}
+
+ + + + + + + +
Optional Fields
name, transferLimit +
+
+ +
+ PUT https://DomainName/api/v1/self/beneficiaries/tpt/{beneficiaryId} + + +PUT self/beneficiaries/tpt/5 +Content-Type: application/json +Request Body: +{ + "name": "beneficiary nick name", + "transferLimit": 1000 + +} + + +{ + "resourceId": 5, + "changes": { + "transferLimit": 1000, + "name": "Client22" + } +} + +
+
+ +   +
+
+

Delete TPT Beneficiary

+

Api to delete third party beneficiary linked to current user.

+

Example Requests:

+
/self/beneficiaries/tpt/{beneficiaryId}
+
+ +
+ DELETE https://DomainName/api/v1/self/beneficiaries/tpt/{beneficiaryId} + + +DELETE self/beneficiaries/tpt/5 +Content-Type: application/json + + +{ + "resourceId": 5 +} + +
+
+ +   +
+
+

Third Party Account Transfer Template

+

Returns list of loan/savings accounts that can be used for third party account transfer

+

Example Requests:

+
self/accounttransfers/template?type="tpt"
+
+
+ GET https://DomainName/api/v1/self/accounttransfers/template?type="tpt" + + +{ + "fromAccountOptions": [ + { + "accountId": 1, + "accountNo": "00000001", + "accountType": { + "id": 2, + "code": "accountType.savings", + "value": "Savings Account" + }, + "clientId": 1, + "clientName": "ABC", + "officeId": 1, + "officeName": "HEAD OFFICE" + }, + { + "accountId": 5, + "accountNo": "00000005", + "accountType": { + "id": 1, + "code": "accountType.loan", + "value": "Loan Account" + }, + "clientId": 2, + "clientName": "XYZ", + "officeId": 3, + "officeName": "REGIONAL OFFICE" + } + ], + "toAccountOptions": [ + { + "accountId": 2, + "accountNo": "00000002", + "accountType": { + "id": 2, + "code": "accountType.savings", + "value": "Savings Account" + }, + "clientId": 2, + "clientName": "ABC", + "officeId": 1, + "officeName": "HEAD OFFICE" + }, + { + "accountId": 6, + "accountNo": "00000006", + "accountType": { + "id": 1, + "code": "accountType.loan", + "value": "Loan Account" + }, + "clientId": 3, + "clientName": "XYZ", + "officeId": 4, + "officeName": "REGIONAL OFFICE" + } + ] + +} + +
+
+ +   +
+
+

Third Party Account Transfer

+

Ability to create new third party transfer of monetary funds from one account to another.

+
+
+ POST https://DomainName/api/v1/self/accounttransfers?type="tpt" + POST self/accounttransfers?type="tpt" +Content-Type: application/json +No Request Body: +{ +"fromOfficeId": 1, +"fromClientId": 1, +"fromAccountType": 2, +"fromAccountId": 1, +"toOfficeId": 1, +"toClientId": 2, +"toAccountType": 2, +"toAccountId": 2, +"dateFormat": "dd MMMM yyyy", +"locale": "en", +"transferDate": "01 August 2011", +"transferAmount": "112.45", +"transferDescription": "A description of the transfer" +} + + +{ + "savingsId": 1, + "resourceId": 1 +} + +
+
diff --git a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java index bd1357494..5751e0e3d 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java +++ b/fineract-provider/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java @@ -2702,4 +2702,29 @@ public class CommandWrapperBuilder { this.href = "/savingsaccounts/" + accountId + "?commad=updateTaxWithHoldTax"; return this; } + + public CommandWrapperBuilder addSelfServiceBeneficiaryTPT() { + this.actionName = "CREATE"; + this.entityName = "SSBENEFICIARYTPT"; + this.entityId = null; + this.href = "/self/beneficiaries/tpt"; + return this; + } + + public CommandWrapperBuilder updateSelfServiceBeneficiaryTPT(final Long beneficiaryId) { + this.actionName = "UPDATE"; + this.entityName = "SSBENEFICIARYTPT"; + this.entityId = beneficiaryId; + this.href = "/self/beneficiaries/tpt/"+beneficiaryId; + return this; + } + + public CommandWrapperBuilder deleteSelfServiceBeneficiaryTPT(final Long beneficiaryId) { + this.actionName = "DELETE"; + this.entityName = "SSBENEFICIARYTPT"; + this.entityId = beneficiaryId; + this.href = "/self/beneficiaries/tpt/"+beneficiaryId; + return this; + } + } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java index 75aefb219..58494c271 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainService.java @@ -79,4 +79,8 @@ public interface ConfigurationDomainService { boolean isChangeEmiIfRepaymentDateSameAsDisbursementDateEnabled(); + boolean isDailyTPTLimitEnabled(); + + Long getDailyTPTLimit(); + } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java index 356d098e7..67ebdc5ba 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/configuration/domain/ConfigurationDomainServiceJpa.java @@ -19,6 +19,7 @@ package org.apache.fineract.infrastructure.configuration.domain; import java.util.Date; + import org.apache.commons.lang.StringUtils; import org.apache.fineract.infrastructure.cache.domain.CacheType; import org.apache.fineract.infrastructure.cache.domain.PlatformCache; @@ -209,6 +210,7 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService return defaultValue; } + @Override public boolean isBackdatePenaltiesEnabled() { final String propertyName = "backdate-penalties-enabled"; final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName); @@ -263,5 +265,19 @@ public class ConfigurationDomainServiceJpa implements ConfigurationDomainService return property.isEnabled(); } + @Override + public boolean isDailyTPTLimitEnabled() { + final String propertyName = "daily-tpt-limit"; + final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName); + return property.isEnabled(); + } + + @Override + public Long getDailyTPTLimit() { + final String propertyName = "daily-tpt-limit"; + final GlobalConfigurationProperty property = this.globalConfigurationRepository.findOneByNameWithNotFoundDetection(propertyName); + return property.getValue(); + } + } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java index 8bf9199b4..740dcb57b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/core/service/TenantDatabaseUpgradeService.java @@ -74,7 +74,7 @@ public class TenantDatabaseUpgradeService { flyway.migrate(); } catch (FlywayException e) { String betterMessage = e.getMessage() + "; for Tenant DB URL: " + connectionProtocol + ", username: " - + connection.getSchemaPassword(); + + connection.getSchemaUsername(); throw new FlywayException(betterMessage, e.getCause()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedularWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedularWritePlatformServiceJpaRepositoryImpl.java index f4ac6ac63..7e5d5de73 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedularWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedularWritePlatformServiceJpaRepositoryImpl.java @@ -136,11 +136,11 @@ public class SchedularWritePlatformServiceJpaRepositoryImpl implements Schedular boolean isStopExecution = false; final ScheduledJobDetail scheduledJobDetail = this.scheduledJobDetailsRepository.findByJobKeyWithLock(jobKey); if (scheduledJobDetail.isCurrentlyRunning() - || (triggerType == SchedulerServiceConstants.TRIGGER_TYPE_CRON && (scheduledJobDetail.getNextRunTime().after(new Date())))) { + || (triggerType.equals(SchedulerServiceConstants.TRIGGER_TYPE_CRON) && (scheduledJobDetail.getNextRunTime().after(new Date())))) { isStopExecution = true; } final SchedulerDetail schedulerDetail = retriveSchedulerDetail(); - if (triggerType == SchedulerServiceConstants.TRIGGER_TYPE_CRON && schedulerDetail.isSuspended()) { + if (triggerType.equals(SchedulerServiceConstants.TRIGGER_TYPE_CRON) && schedulerDetail.isSuspended()) { scheduledJobDetail.updateTriggerMisfired(true); isStopExecution = true; } else if (!isStopExecution) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformService.java index c053b0115..0434261b3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformService.java @@ -18,12 +18,14 @@ */ package org.apache.fineract.portfolio.account.service; +import java.math.BigDecimal; import java.util.Collection; import org.apache.fineract.infrastructure.core.service.Page; import org.apache.fineract.infrastructure.core.service.SearchParameters; import org.apache.fineract.portfolio.account.PortfolioAccountType; import org.apache.fineract.portfolio.account.data.AccountTransferData; +import org.joda.time.LocalDate; public interface AccountTransfersReadPlatformService { @@ -42,4 +44,7 @@ public interface AccountTransfersReadPlatformService { AccountTransferData retrieveRefundByTransferTemplate(Long fromOfficeId, Long fromClientId, Long fromAccountId, Integer fromAccountType, Long toOfficeId, Long toClientId, Long toAccountId, Integer toAccountType); + + BigDecimal getTotalTransactionAmount(Long accountId, Integer accountType, + LocalDate transactionDate); } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java index 9dbe4c939..08af09180 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/account/service/AccountTransfersReadPlatformServiceImpl.java @@ -45,6 +45,8 @@ import org.apache.fineract.portfolio.account.exception.AccountTransferNotFoundEx import org.apache.fineract.portfolio.client.data.ClientData; import org.apache.fineract.portfolio.client.service.ClientReadPlatformService; import org.joda.time.LocalDate; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.jdbc.core.JdbcTemplate; @@ -53,485 +55,643 @@ import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @Service -public class AccountTransfersReadPlatformServiceImpl implements AccountTransfersReadPlatformService { - - private final JdbcTemplate jdbcTemplate; - private final ClientReadPlatformService clientReadPlatformService; - private final OfficeReadPlatformService officeReadPlatformService; - private final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService; - - // mapper - private final AccountTransfersMapper accountTransfersMapper; - - // pagination - private final PaginationHelper paginationHelper = new PaginationHelper<>(); - - @Autowired - public AccountTransfersReadPlatformServiceImpl(final RoutingDataSource dataSource, - final ClientReadPlatformService clientReadPlatformService, final OfficeReadPlatformService officeReadPlatformService, - final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService) { - this.jdbcTemplate = new JdbcTemplate(dataSource); - this.clientReadPlatformService = clientReadPlatformService; - this.officeReadPlatformService = officeReadPlatformService; - this.portfolioAccountReadPlatformService = portfolioAccountReadPlatformService; - - this.accountTransfersMapper = new AccountTransfersMapper(); - } - - @Override - public AccountTransferData retrieveTemplate(final Long fromOfficeId, final Long fromClientId, final Long fromAccountId, - final Integer fromAccountType, final Long toOfficeId, final Long toClientId, final Long toAccountId, final Integer toAccountType) { - - final EnumOptionData loanAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN); - final EnumOptionData savingsAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS); - - final Integer mostRelevantFromAccountType = fromAccountType; - final Collection fromAccountTypeOptions = Arrays.asList(savingsAccountType, loanAccountType); - final Collection toAccountTypeOptions; - if (mostRelevantFromAccountType != null && mostRelevantFromAccountType == 1) { - // overpaid loan amt transfer to savings account - toAccountTypeOptions = Arrays.asList(savingsAccountType); - } else { - toAccountTypeOptions = Arrays.asList(loanAccountType, savingsAccountType); - } - final Integer mostRelevantToAccountType = toAccountType; - - final EnumOptionData fromAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantFromAccountType); - final EnumOptionData toAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantToAccountType); - - // from settings - OfficeData fromOffice = null; - ClientData fromClient = null; - PortfolioAccountData fromAccount = null; - - OfficeData toOffice = null; - ClientData toClient = null; - PortfolioAccountData toAccount = null; - - // template - Collection fromAccountOptions = null; - Collection toAccountOptions = null; - - Long mostRelevantFromOfficeId = fromOfficeId; - Long mostRelevantFromClientId = fromClientId; - - Long mostRelevantToOfficeId = toOfficeId; - Long mostRelevantToClientId = toClientId; - - if (fromAccountId != null) { - Integer accountType; - if (mostRelevantFromAccountType == 1) { - accountType = PortfolioAccountType.LOAN.getValue(); - } else { - accountType = PortfolioAccountType.SAVINGS.getValue(); - } - fromAccount = this.portfolioAccountReadPlatformService.retrieveOne(fromAccountId, accountType); - - // override provided fromClient with client of account - mostRelevantFromClientId = fromAccount.clientId(); - } - - if (mostRelevantFromClientId != null) { - fromClient = this.clientReadPlatformService.retrieveOne(mostRelevantFromClientId); - mostRelevantFromOfficeId = fromClient.officeId(); - long[] loanStatus = null; - if (mostRelevantFromAccountType == 1) { - loanStatus = new long[] { 300, 700 }; - } - PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(mostRelevantFromAccountType, mostRelevantFromClientId, - loanStatus); - fromAccountOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(portfolioAccountDTO); - } - - Collection fromOfficeOptions = null; - Collection fromClientOptions = null; - if (mostRelevantFromOfficeId != null) { - fromOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantFromOfficeId); - fromOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown(); - fromClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantFromOfficeId); - } - - // defaults - final LocalDate transferDate = DateUtils.getLocalDateOfTenant(); - Collection toOfficeOptions = fromOfficeOptions; - Collection toClientOptions = null; - - if (toAccountId != null && fromAccount != null) { - toAccount = this.portfolioAccountReadPlatformService.retrieveOne(toAccountId, mostRelevantToAccountType, - fromAccount.currencyCode()); - mostRelevantToClientId = toAccount.clientId(); - } - - if (mostRelevantToClientId != null) { - toClient = this.clientReadPlatformService.retrieveOne(mostRelevantToClientId); - mostRelevantToOfficeId = toClient.officeId(); - - toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId); - - toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId); - } - - if (mostRelevantToOfficeId != null) { - toOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantToOfficeId); - toOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown(); - - toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId); - if (toClientOptions != null && toClientOptions.size() == 1) { - toClient = new ArrayList<>(toClientOptions).get(0); - - toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId); - } - } - - return AccountTransferData.template(fromOffice, fromClient, fromAccountTypeData, fromAccount, transferDate, toOffice, toClient, - toAccountTypeData, toAccount, fromOfficeOptions, fromClientOptions, fromAccountTypeOptions, fromAccountOptions, - toOfficeOptions, toClientOptions, toAccountTypeOptions, toAccountOptions); - } - - private Collection retrieveToAccounts(final PortfolioAccountData excludeThisAccountFromOptions, - final Integer toAccountType, final Long toClientId) { - - final String currencyCode = excludeThisAccountFromOptions != null ? excludeThisAccountFromOptions.currencyCode() : null; - - PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(toAccountType, toClientId, currencyCode, null, null); - Collection accountOptions = this.portfolioAccountReadPlatformService - .retrieveAllForLookup(portfolioAccountDTO); - if (!CollectionUtils.isEmpty(accountOptions)) { - accountOptions.remove(excludeThisAccountFromOptions); - } else { - accountOptions = null; - } - - return accountOptions; - } - - @Override - public Page retrieveAll(final SearchParameters searchParameters, final Long accountDetailId) { - - final StringBuilder sqlBuilder = new StringBuilder(200); - sqlBuilder.append("select SQL_CALC_FOUND_ROWS "); - sqlBuilder.append(this.accountTransfersMapper.schema()); - Object[] finalObjectArray = {}; - if (accountDetailId != null) { - sqlBuilder.append(" where att.account_transfer_details_id=?"); - finalObjectArray = new Object[] { accountDetailId }; - } - - if (searchParameters.isOrderByRequested()) { - sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); - - if (searchParameters.isSortOrderProvided()) { - sqlBuilder.append(' ').append(searchParameters.getSortOrder()); - } - } - - if (searchParameters.isLimited()) { - sqlBuilder.append(" limit ").append(searchParameters.getLimit()); - if (searchParameters.isOffset()) { - sqlBuilder.append(" offset ").append(searchParameters.getOffset()); - } - } - - final String sqlCountRows = "SELECT FOUND_ROWS()"; - return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray, - this.accountTransfersMapper); - } - - @Override - public AccountTransferData retrieveOne(final Long transferId) { - - try { - final String sql = "select " + this.accountTransfersMapper.schema() + " where att.id = ?"; - - return this.jdbcTemplate.queryForObject(sql, this.accountTransfersMapper, new Object[] { transferId }); - } catch (final EmptyResultDataAccessException e) { - throw new AccountTransferNotFoundException(transferId); - } - } - - @Override - public Collection fetchPostInterestTransactionIds(final Long accountId) { - final String sql = "select att.from_savings_transaction_id from m_account_transfer_transaction att inner join m_account_transfer_details atd on atd.id = att.account_transfer_details_id where atd.from_savings_account_id=? and att.is_reversed =0 and atd.transfer_type = ?"; - - final List transactionId = this.jdbcTemplate.queryForList(sql, Long.class, accountId, - AccountTransferType.INTEREST_TRANSFER.getValue()); - - return transactionId; - } - - private static final class AccountTransfersMapper implements RowMapper { - - private final String schemaSql; - - public AccountTransfersMapper() { - final StringBuilder sqlBuilder = new StringBuilder(400); - sqlBuilder.append("att.id as id, att.is_reversed as isReversed,"); - sqlBuilder.append("att.transaction_date as transferDate, att.amount as transferAmount,"); - sqlBuilder.append("att.description as transferDescription,"); - sqlBuilder.append("att.currency_code as currencyCode, att.currency_digits as currencyDigits,"); - sqlBuilder.append("att.currency_multiplesof as inMultiplesOf, "); - sqlBuilder.append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); - sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); - sqlBuilder.append("fromoff.id as fromOfficeId, fromoff.name as fromOfficeName,"); - sqlBuilder.append("tooff.id as toOfficeId, tooff.name as toOfficeName,"); - sqlBuilder.append("fromclient.id as fromClientId, fromclient.display_name as fromClientName,"); - sqlBuilder.append("toclient.id as toClientId, toclient.display_name as toClientName,"); - sqlBuilder.append("fromsavacc.id as fromSavingsAccountId, fromsavacc.account_no as fromSavingsAccountNo,"); - sqlBuilder.append("fromloanacc.id as fromLoanAccountId, fromloanacc.account_no as fromLoanAccountNo,"); - sqlBuilder.append("tosavacc.id as toSavingsAccountId, tosavacc.account_no as toSavingsAccountNo,"); - sqlBuilder.append("toloanacc.id as toLoanAccountId, toloanacc.account_no as toLoanAccountNo,"); - sqlBuilder.append("fromsavtran.id as fromSavingsAccountTransactionId,"); - sqlBuilder.append("fromsavtran.transaction_type_enum as fromSavingsAccountTransactionType,"); - sqlBuilder.append("tosavtran.id as toSavingsAccountTransactionId,"); - sqlBuilder.append("tosavtran.transaction_type_enum as toSavingsAccountTransactionType"); - sqlBuilder.append(" FROM m_account_transfer_transaction att "); - sqlBuilder.append("left join m_account_transfer_details atd on atd.id = att.account_transfer_details_id "); - sqlBuilder.append("join m_currency curr on curr.code = att.currency_code "); - sqlBuilder.append("join m_office fromoff on fromoff.id = atd.from_office_id "); - sqlBuilder.append("join m_office tooff on tooff.id = atd.to_office_id "); - sqlBuilder.append("join m_client fromclient on fromclient.id = atd.from_client_id "); - sqlBuilder.append("join m_client toclient on toclient.id = atd.to_client_id "); - sqlBuilder.append("left join m_savings_account fromsavacc on fromsavacc.id = atd.from_savings_account_id "); - sqlBuilder.append("left join m_loan fromloanacc on fromloanacc.id = atd.from_loan_account_id "); - sqlBuilder.append("left join m_savings_account tosavacc on tosavacc.id = atd.to_savings_account_id "); - sqlBuilder.append("left join m_loan toloanacc on toloanacc.id = atd.to_loan_account_id "); - sqlBuilder.append("left join m_savings_account_transaction fromsavtran on fromsavtran.id = att.from_savings_transaction_id "); - sqlBuilder.append("left join m_savings_account_transaction tosavtran on tosavtran.id = att.to_savings_transaction_id "); - sqlBuilder.append("left join m_loan_transaction fromloantran on fromloantran.id = att.from_savings_transaction_id "); - sqlBuilder.append("left join m_loan_transaction toloantran on toloantran.id = att.to_savings_transaction_id "); - - this.schemaSql = sqlBuilder.toString(); - } - - public String schema() { - return this.schemaSql; - } - - @Override - public AccountTransferData mapRow(final ResultSet rs, @SuppressWarnings("unused") final int rowNum) throws SQLException { - - final Long id = rs.getLong("id"); - final boolean reversed = rs.getBoolean("isReversed"); - - final LocalDate transferDate = JdbcSupport.getLocalDate(rs, "transferDate"); - final BigDecimal transferAmount = JdbcSupport.getBigDecimalDefaultToZeroIfNull(rs, "transferAmount"); - final String transferDescription = rs.getString("transferDescription"); - - final String currencyCode = rs.getString("currencyCode"); - final String currencyName = rs.getString("currencyName"); - final String currencyNameCode = rs.getString("currencyNameCode"); - final String currencyDisplaySymbol = rs.getString("currencyDisplaySymbol"); - final Integer currencyDigits = JdbcSupport.getInteger(rs, "currencyDigits"); - final Integer inMultiplesOf = JdbcSupport.getInteger(rs, "inMultiplesOf"); - final CurrencyData currency = new CurrencyData(currencyCode, currencyName, currencyDigits, inMultiplesOf, - currencyDisplaySymbol, currencyNameCode); - - final Long fromOfficeId = JdbcSupport.getLong(rs, "fromOfficeId"); - final String fromOfficeName = rs.getString("fromOfficeName"); - final OfficeData fromOffice = OfficeData.dropdown(fromOfficeId, fromOfficeName, null); - - final Long toOfficeId = JdbcSupport.getLong(rs, "toOfficeId"); - final String toOfficeName = rs.getString("toOfficeName"); - final OfficeData toOffice = OfficeData.dropdown(toOfficeId, toOfficeName, null); - - final Long fromClientId = JdbcSupport.getLong(rs, "fromClientId"); - final String fromClientName = rs.getString("fromClientName"); - final ClientData fromClient = ClientData.lookup(fromClientId, fromClientName, fromOfficeId, fromOfficeName); - - final Long toClientId = JdbcSupport.getLong(rs, "toClientId"); - final String toClientName = rs.getString("toClientName"); - final ClientData toClient = ClientData.lookup(toClientId, toClientName, toOfficeId, toOfficeName); - - final Long fromSavingsAccountId = JdbcSupport.getLong(rs, "fromSavingsAccountId"); - final String fromSavingsAccountNo = rs.getString("fromSavingsAccountNo"); - final Long fromLoanAccountId = JdbcSupport.getLong(rs, "fromLoanAccountId"); - final String fromLoanAccountNo = rs.getString("fromLoanAccountNo"); - PortfolioAccountData fromAccount = null; - EnumOptionData fromAccountType = null; - if (fromSavingsAccountId != null) { - fromAccount = PortfolioAccountData.lookup(fromSavingsAccountId, fromSavingsAccountNo); - fromAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS); - } else if (fromLoanAccountId != null) { - fromAccount = PortfolioAccountData.lookup(fromLoanAccountId, fromLoanAccountNo); - fromAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN); - } - - PortfolioAccountData toAccount = null; - EnumOptionData toAccountType = null; - final Long toSavingsAccountId = JdbcSupport.getLong(rs, "toSavingsAccountId"); - final String toSavingsAccountNo = rs.getString("toSavingsAccountNo"); - final Long toLoanAccountId = JdbcSupport.getLong(rs, "toLoanAccountId"); - final String toLoanAccountNo = rs.getString("toLoanAccountNo"); - - if (toSavingsAccountId != null) { - toAccount = PortfolioAccountData.lookup(toSavingsAccountId, toSavingsAccountNo); - toAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS); - } else if (toLoanAccountId != null) { - toAccount = PortfolioAccountData.lookup(toLoanAccountId, toLoanAccountNo); - toAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN); - } - - return AccountTransferData.instance(id, reversed, transferDate, currency, transferAmount, transferDescription, fromOffice, - toOffice, fromClient, toClient, fromAccountType, fromAccount, toAccountType, toAccount); - } - } - - @Override - public boolean isAccountTransfer(final Long transactionId, final PortfolioAccountType accountType) { - final StringBuilder sql = new StringBuilder("select count(*) from m_account_transfer_transaction at where "); - if (accountType.isLoanAccount()) { - sql.append("at.from_loan_transaction_id=").append(transactionId).append(" or at.to_loan_transaction_id=").append(transactionId); - } else { - sql.append("at.from_savings_transaction_id=").append(transactionId).append(" or at.to_savings_transaction_id=") - .append(transactionId); - } - - @SuppressWarnings("deprecation") - final int count = this.jdbcTemplate.queryForInt(sql.toString()); - return count > 0; - } - - @Override - public Page retrieveByStandingInstruction(final Long id, final SearchParameters searchParameters) { - - final StringBuilder sqlBuilder = new StringBuilder(200); - sqlBuilder.append("select SQL_CALC_FOUND_ROWS "); - sqlBuilder - .append(this.accountTransfersMapper.schema()) - .append(" join m_account_transfer_standing_instructions atsi on atsi.account_transfer_details_id = att.account_transfer_details_id "); - sqlBuilder.append(" where atsi.id = ?"); - - if (searchParameters != null) { - if (searchParameters.isOrderByRequested()) { - sqlBuilder.append(" order by ").append(searchParameters.getOrderBy()); - - if (searchParameters.isSortOrderProvided()) { - sqlBuilder.append(' ').append(searchParameters.getSortOrder()); - } - } - - if (searchParameters.isLimited()) { - sqlBuilder.append(" limit ").append(searchParameters.getLimit()); - if (searchParameters.isOffset()) { - sqlBuilder.append(" offset ").append(searchParameters.getOffset()); - } - } - } - - final Object[] finalObjectArray = { id }; - final String sqlCountRows = "SELECT FOUND_ROWS()"; - return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, sqlBuilder.toString(), finalObjectArray, - this.accountTransfersMapper); - } - - @Override - public AccountTransferData retrieveRefundByTransferTemplate(final Long fromOfficeId, final Long fromClientId, final Long fromAccountId, - final Integer fromAccountType, final Long toOfficeId, final Long toClientId, final Long toAccountId, final Integer toAccountType) { - // TODO Auto-generated method stub - final EnumOptionData loanAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.LOAN); - final EnumOptionData savingsAccountType = AccountTransferEnumerations.accountType(PortfolioAccountType.SAVINGS); - - final Integer mostRelevantFromAccountType = fromAccountType; - final Collection fromAccountTypeOptions = Arrays.asList(savingsAccountType, loanAccountType); - final Collection toAccountTypeOptions; - if (mostRelevantFromAccountType == 1) { - // overpaid loan amt transfer to savings account - toAccountTypeOptions = Arrays.asList(savingsAccountType); - } else { - toAccountTypeOptions = Arrays.asList(loanAccountType, savingsAccountType); - } - final Integer mostRelevantToAccountType = toAccountType; - - final EnumOptionData fromAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantFromAccountType); - final EnumOptionData toAccountTypeData = AccountTransferEnumerations.accountType(mostRelevantToAccountType); - - // from settings - OfficeData fromOffice = null; - ClientData fromClient = null; - PortfolioAccountData fromAccount = null; - - OfficeData toOffice = null; - ClientData toClient = null; - PortfolioAccountData toAccount = null; - - // template - Collection fromAccountOptions = null; - Collection toAccountOptions = null; - - Long mostRelevantFromOfficeId = fromOfficeId; - Long mostRelevantFromClientId = fromClientId; - - Long mostRelevantToOfficeId = toOfficeId; - Long mostRelevantToClientId = toClientId; - - if (fromAccountId != null) { - Integer accountType; - if (mostRelevantFromAccountType == 1) { - accountType = PortfolioAccountType.LOAN.getValue(); - } else { - accountType = PortfolioAccountType.SAVINGS.getValue(); - } - fromAccount = this.portfolioAccountReadPlatformService.retrieveOneByPaidInAdvance(fromAccountId, accountType); - - // override provided fromClient with client of account - mostRelevantFromClientId = fromAccount.clientId(); - } - - if (mostRelevantFromClientId != null) { - fromClient = this.clientReadPlatformService.retrieveOne(mostRelevantFromClientId); - mostRelevantFromOfficeId = fromClient.officeId(); - long[] loanStatus = null; - if (mostRelevantFromAccountType == 1) { - loanStatus = new long[] { 300, 700 }; - } - PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO(mostRelevantFromAccountType, mostRelevantFromClientId, - loanStatus); - fromAccountOptions = this.portfolioAccountReadPlatformService.retrieveAllForLookup(portfolioAccountDTO); - } - - Collection fromOfficeOptions = null; - Collection fromClientOptions = null; - if (mostRelevantFromOfficeId != null) { - fromOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantFromOfficeId); - fromOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown(); - fromClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantFromOfficeId); - } - - // defaults - final LocalDate transferDate = DateUtils.getLocalDateOfTenant(); - Collection toOfficeOptions = fromOfficeOptions; - Collection toClientOptions = null; - - if (toAccountId != null && fromAccount != null) { - toAccount = this.portfolioAccountReadPlatformService.retrieveOne(toAccountId, mostRelevantToAccountType, - fromAccount.currencyCode()); - mostRelevantToClientId = toAccount.clientId(); - } - - if (mostRelevantToClientId != null) { - toClient = this.clientReadPlatformService.retrieveOne(mostRelevantToClientId); - mostRelevantToOfficeId = toClient.officeId(); - - toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId); - - toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId); - } - - if (mostRelevantToOfficeId != null) { - toOffice = this.officeReadPlatformService.retrieveOffice(mostRelevantToOfficeId); - toOfficeOptions = this.officeReadPlatformService.retrieveAllOfficesForDropdown(); - - toClientOptions = this.clientReadPlatformService.retrieveAllForLookupByOfficeId(mostRelevantToOfficeId); - if (toClientOptions != null && toClientOptions.size() == 1) { - toClient = new ArrayList<>(toClientOptions).get(0); - - toAccountOptions = retrieveToAccounts(fromAccount, mostRelevantToAccountType, mostRelevantToClientId); - } - } - - return AccountTransferData.template(fromOffice, fromClient, fromAccountTypeData, fromAccount, transferDate, toOffice, toClient, - toAccountTypeData, toAccount, fromOfficeOptions, fromClientOptions, fromAccountTypeOptions, fromAccountOptions, - toOfficeOptions, toClientOptions, toAccountTypeOptions, toAccountOptions); - } +public class AccountTransfersReadPlatformServiceImpl implements + AccountTransfersReadPlatformService { + + private final JdbcTemplate jdbcTemplate; + private final ClientReadPlatformService clientReadPlatformService; + private final OfficeReadPlatformService officeReadPlatformService; + private final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService; + + // mapper + private final AccountTransfersMapper accountTransfersMapper; + + // pagination + private final PaginationHelper paginationHelper = new PaginationHelper<>(); + private final DateTimeFormatter formatter = DateTimeFormat + .forPattern("yyyy-MM-dd"); + + @Autowired + public AccountTransfersReadPlatformServiceImpl( + final RoutingDataSource dataSource, + final ClientReadPlatformService clientReadPlatformService, + final OfficeReadPlatformService officeReadPlatformService, + final PortfolioAccountReadPlatformService portfolioAccountReadPlatformService) { + this.jdbcTemplate = new JdbcTemplate(dataSource); + this.clientReadPlatformService = clientReadPlatformService; + this.officeReadPlatformService = officeReadPlatformService; + this.portfolioAccountReadPlatformService = portfolioAccountReadPlatformService; + + this.accountTransfersMapper = new AccountTransfersMapper(); + } + + @Override + public AccountTransferData retrieveTemplate(final Long fromOfficeId, + final Long fromClientId, final Long fromAccountId, + final Integer fromAccountType, final Long toOfficeId, + final Long toClientId, final Long toAccountId, + final Integer toAccountType) { + + final EnumOptionData loanAccountType = AccountTransferEnumerations + .accountType(PortfolioAccountType.LOAN); + final EnumOptionData savingsAccountType = AccountTransferEnumerations + .accountType(PortfolioAccountType.SAVINGS); + + final Integer mostRelevantFromAccountType = fromAccountType; + final Collection fromAccountTypeOptions = Arrays + .asList(savingsAccountType, loanAccountType); + final Collection toAccountTypeOptions; + if (mostRelevantFromAccountType != null + && mostRelevantFromAccountType == 1) { + // overpaid loan amt transfer to savings account + toAccountTypeOptions = Arrays.asList(savingsAccountType); + } else { + toAccountTypeOptions = Arrays.asList(loanAccountType, + savingsAccountType); + } + final Integer mostRelevantToAccountType = toAccountType; + + final EnumOptionData fromAccountTypeData = AccountTransferEnumerations + .accountType(mostRelevantFromAccountType); + final EnumOptionData toAccountTypeData = AccountTransferEnumerations + .accountType(mostRelevantToAccountType); + + // from settings + OfficeData fromOffice = null; + ClientData fromClient = null; + PortfolioAccountData fromAccount = null; + + OfficeData toOffice = null; + ClientData toClient = null; + PortfolioAccountData toAccount = null; + + // template + Collection fromAccountOptions = null; + Collection toAccountOptions = null; + + Long mostRelevantFromOfficeId = fromOfficeId; + Long mostRelevantFromClientId = fromClientId; + + Long mostRelevantToOfficeId = toOfficeId; + Long mostRelevantToClientId = toClientId; + + if (fromAccountId != null) { + Integer accountType; + if (mostRelevantFromAccountType == 1) { + accountType = PortfolioAccountType.LOAN.getValue(); + } else { + accountType = PortfolioAccountType.SAVINGS.getValue(); + } + fromAccount = this.portfolioAccountReadPlatformService.retrieveOne( + fromAccountId, accountType); + + // override provided fromClient with client of account + mostRelevantFromClientId = fromAccount.clientId(); + } + + if (mostRelevantFromClientId != null) { + fromClient = this.clientReadPlatformService + .retrieveOne(mostRelevantFromClientId); + mostRelevantFromOfficeId = fromClient.officeId(); + long[] loanStatus = null; + if (mostRelevantFromAccountType == 1) { + loanStatus = new long[] { 300, 700 }; + } + PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO( + mostRelevantFromAccountType, mostRelevantFromClientId, + loanStatus); + fromAccountOptions = this.portfolioAccountReadPlatformService + .retrieveAllForLookup(portfolioAccountDTO); + } + + Collection fromOfficeOptions = null; + Collection fromClientOptions = null; + if (mostRelevantFromOfficeId != null) { + fromOffice = this.officeReadPlatformService + .retrieveOffice(mostRelevantFromOfficeId); + fromOfficeOptions = this.officeReadPlatformService + .retrieveAllOfficesForDropdown(); + fromClientOptions = this.clientReadPlatformService + .retrieveAllForLookupByOfficeId(mostRelevantFromOfficeId); + } + + // defaults + final LocalDate transferDate = DateUtils.getLocalDateOfTenant(); + Collection toOfficeOptions = fromOfficeOptions; + Collection toClientOptions = null; + + if (toAccountId != null && fromAccount != null) { + toAccount = this.portfolioAccountReadPlatformService.retrieveOne( + toAccountId, mostRelevantToAccountType, + fromAccount.currencyCode()); + mostRelevantToClientId = toAccount.clientId(); + } + + if (mostRelevantToClientId != null) { + toClient = this.clientReadPlatformService + .retrieveOne(mostRelevantToClientId); + mostRelevantToOfficeId = toClient.officeId(); + + toClientOptions = this.clientReadPlatformService + .retrieveAllForLookupByOfficeId(mostRelevantToOfficeId); + + toAccountOptions = retrieveToAccounts(fromAccount, + mostRelevantToAccountType, mostRelevantToClientId); + } + + if (mostRelevantToOfficeId != null) { + toOffice = this.officeReadPlatformService + .retrieveOffice(mostRelevantToOfficeId); + toOfficeOptions = this.officeReadPlatformService + .retrieveAllOfficesForDropdown(); + + toClientOptions = this.clientReadPlatformService + .retrieveAllForLookupByOfficeId(mostRelevantToOfficeId); + if (toClientOptions != null && toClientOptions.size() == 1) { + toClient = new ArrayList<>(toClientOptions).get(0); + + toAccountOptions = retrieveToAccounts(fromAccount, + mostRelevantToAccountType, mostRelevantToClientId); + } + } + + return AccountTransferData.template(fromOffice, fromClient, + fromAccountTypeData, fromAccount, transferDate, toOffice, + toClient, toAccountTypeData, toAccount, fromOfficeOptions, + fromClientOptions, fromAccountTypeOptions, fromAccountOptions, + toOfficeOptions, toClientOptions, toAccountTypeOptions, + toAccountOptions); + } + + private Collection retrieveToAccounts( + final PortfolioAccountData excludeThisAccountFromOptions, + final Integer toAccountType, final Long toClientId) { + + final String currencyCode = excludeThisAccountFromOptions != null ? excludeThisAccountFromOptions + .currencyCode() : null; + + PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO( + toAccountType, toClientId, currencyCode, null, null); + Collection accountOptions = this.portfolioAccountReadPlatformService + .retrieveAllForLookup(portfolioAccountDTO); + if (!CollectionUtils.isEmpty(accountOptions)) { + accountOptions.remove(excludeThisAccountFromOptions); + } else { + accountOptions = null; + } + + return accountOptions; + } + + @Override + public Page retrieveAll( + final SearchParameters searchParameters, final Long accountDetailId) { + + final StringBuilder sqlBuilder = new StringBuilder(200); + sqlBuilder.append("select SQL_CALC_FOUND_ROWS "); + sqlBuilder.append(this.accountTransfersMapper.schema()); + Object[] finalObjectArray = {}; + if (accountDetailId != null) { + sqlBuilder.append(" where att.account_transfer_details_id=?"); + finalObjectArray = new Object[] { accountDetailId }; + } + + if (searchParameters.isOrderByRequested()) { + sqlBuilder.append(" order by ").append( + searchParameters.getOrderBy()); + + if (searchParameters.isSortOrderProvided()) { + sqlBuilder.append(' ').append(searchParameters.getSortOrder()); + } + } + + if (searchParameters.isLimited()) { + sqlBuilder.append(" limit ").append(searchParameters.getLimit()); + if (searchParameters.isOffset()) { + sqlBuilder.append(" offset ").append( + searchParameters.getOffset()); + } + } + + final String sqlCountRows = "SELECT FOUND_ROWS()"; + return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, + sqlBuilder.toString(), finalObjectArray, + this.accountTransfersMapper); + } + + @Override + public AccountTransferData retrieveOne(final Long transferId) { + + try { + final String sql = "select " + this.accountTransfersMapper.schema() + + " where att.id = ?"; + + return this.jdbcTemplate.queryForObject(sql, + this.accountTransfersMapper, new Object[] { transferId }); + } catch (final EmptyResultDataAccessException e) { + throw new AccountTransferNotFoundException(transferId); + } + } + + @Override + public Collection fetchPostInterestTransactionIds(final Long accountId) { + final String sql = "select att.from_savings_transaction_id from m_account_transfer_transaction att inner join m_account_transfer_details atd on atd.id = att.account_transfer_details_id where atd.from_savings_account_id=? and att.is_reversed =0 and atd.transfer_type = ?"; + + final List transactionId = this.jdbcTemplate.queryForList(sql, + Long.class, accountId, + AccountTransferType.INTEREST_TRANSFER.getValue()); + + return transactionId; + } + + private static final class AccountTransfersMapper implements + RowMapper { + + private final String schemaSql; + + public AccountTransfersMapper() { + final StringBuilder sqlBuilder = new StringBuilder(400); + sqlBuilder.append("att.id as id, att.is_reversed as isReversed,"); + sqlBuilder + .append("att.transaction_date as transferDate, att.amount as transferAmount,"); + sqlBuilder.append("att.description as transferDescription,"); + sqlBuilder + .append("att.currency_code as currencyCode, att.currency_digits as currencyDigits,"); + sqlBuilder.append("att.currency_multiplesof as inMultiplesOf, "); + sqlBuilder + .append("curr.name as currencyName, curr.internationalized_name_code as currencyNameCode, "); + sqlBuilder.append("curr.display_symbol as currencyDisplaySymbol, "); + sqlBuilder + .append("fromoff.id as fromOfficeId, fromoff.name as fromOfficeName,"); + sqlBuilder + .append("tooff.id as toOfficeId, tooff.name as toOfficeName,"); + sqlBuilder + .append("fromclient.id as fromClientId, fromclient.display_name as fromClientName,"); + sqlBuilder + .append("toclient.id as toClientId, toclient.display_name as toClientName,"); + sqlBuilder + .append("fromsavacc.id as fromSavingsAccountId, fromsavacc.account_no as fromSavingsAccountNo,"); + sqlBuilder + .append("fromloanacc.id as fromLoanAccountId, fromloanacc.account_no as fromLoanAccountNo,"); + sqlBuilder + .append("tosavacc.id as toSavingsAccountId, tosavacc.account_no as toSavingsAccountNo,"); + sqlBuilder + .append("toloanacc.id as toLoanAccountId, toloanacc.account_no as toLoanAccountNo,"); + sqlBuilder + .append("fromsavtran.id as fromSavingsAccountTransactionId,"); + sqlBuilder + .append("fromsavtran.transaction_type_enum as fromSavingsAccountTransactionType,"); + sqlBuilder.append("tosavtran.id as toSavingsAccountTransactionId,"); + sqlBuilder + .append("tosavtran.transaction_type_enum as toSavingsAccountTransactionType"); + sqlBuilder.append(" FROM m_account_transfer_transaction att "); + sqlBuilder + .append("left join m_account_transfer_details atd on atd.id = att.account_transfer_details_id "); + sqlBuilder + .append("join m_currency curr on curr.code = att.currency_code "); + sqlBuilder + .append("join m_office fromoff on fromoff.id = atd.from_office_id "); + sqlBuilder + .append("join m_office tooff on tooff.id = atd.to_office_id "); + sqlBuilder + .append("join m_client fromclient on fromclient.id = atd.from_client_id "); + sqlBuilder + .append("join m_client toclient on toclient.id = atd.to_client_id "); + sqlBuilder + .append("left join m_savings_account fromsavacc on fromsavacc.id = atd.from_savings_account_id "); + sqlBuilder + .append("left join m_loan fromloanacc on fromloanacc.id = atd.from_loan_account_id "); + sqlBuilder + .append("left join m_savings_account tosavacc on tosavacc.id = atd.to_savings_account_id "); + sqlBuilder + .append("left join m_loan toloanacc on toloanacc.id = atd.to_loan_account_id "); + sqlBuilder + .append("left join m_savings_account_transaction fromsavtran on fromsavtran.id = att.from_savings_transaction_id "); + sqlBuilder + .append("left join m_savings_account_transaction tosavtran on tosavtran.id = att.to_savings_transaction_id "); + sqlBuilder + .append("left join m_loan_transaction fromloantran on fromloantran.id = att.from_savings_transaction_id "); + sqlBuilder + .append("left join m_loan_transaction toloantran on toloantran.id = att.to_savings_transaction_id "); + + this.schemaSql = sqlBuilder.toString(); + } + + public String schema() { + return this.schemaSql; + } + + @Override + public AccountTransferData mapRow(final ResultSet rs, + @SuppressWarnings("unused") final int rowNum) + throws SQLException { + + final Long id = rs.getLong("id"); + final boolean reversed = rs.getBoolean("isReversed"); + + final LocalDate transferDate = JdbcSupport.getLocalDate(rs, + "transferDate"); + final BigDecimal transferAmount = JdbcSupport + .getBigDecimalDefaultToZeroIfNull(rs, "transferAmount"); + final String transferDescription = rs + .getString("transferDescription"); + + final String currencyCode = rs.getString("currencyCode"); + final String currencyName = rs.getString("currencyName"); + final String currencyNameCode = rs.getString("currencyNameCode"); + final String currencyDisplaySymbol = rs + .getString("currencyDisplaySymbol"); + final Integer currencyDigits = JdbcSupport.getInteger(rs, + "currencyDigits"); + final Integer inMultiplesOf = JdbcSupport.getInteger(rs, + "inMultiplesOf"); + final CurrencyData currency = new CurrencyData(currencyCode, + currencyName, currencyDigits, inMultiplesOf, + currencyDisplaySymbol, currencyNameCode); + + final Long fromOfficeId = JdbcSupport.getLong(rs, "fromOfficeId"); + final String fromOfficeName = rs.getString("fromOfficeName"); + final OfficeData fromOffice = OfficeData.dropdown(fromOfficeId, + fromOfficeName, null); + + final Long toOfficeId = JdbcSupport.getLong(rs, "toOfficeId"); + final String toOfficeName = rs.getString("toOfficeName"); + final OfficeData toOffice = OfficeData.dropdown(toOfficeId, + toOfficeName, null); + + final Long fromClientId = JdbcSupport.getLong(rs, "fromClientId"); + final String fromClientName = rs.getString("fromClientName"); + final ClientData fromClient = ClientData.lookup(fromClientId, + fromClientName, fromOfficeId, fromOfficeName); + + final Long toClientId = JdbcSupport.getLong(rs, "toClientId"); + final String toClientName = rs.getString("toClientName"); + final ClientData toClient = ClientData.lookup(toClientId, + toClientName, toOfficeId, toOfficeName); + + final Long fromSavingsAccountId = JdbcSupport.getLong(rs, + "fromSavingsAccountId"); + final String fromSavingsAccountNo = rs + .getString("fromSavingsAccountNo"); + final Long fromLoanAccountId = JdbcSupport.getLong(rs, + "fromLoanAccountId"); + final String fromLoanAccountNo = rs.getString("fromLoanAccountNo"); + PortfolioAccountData fromAccount = null; + EnumOptionData fromAccountType = null; + if (fromSavingsAccountId != null) { + fromAccount = PortfolioAccountData.lookup(fromSavingsAccountId, + fromSavingsAccountNo); + fromAccountType = AccountTransferEnumerations + .accountType(PortfolioAccountType.SAVINGS); + } else if (fromLoanAccountId != null) { + fromAccount = PortfolioAccountData.lookup(fromLoanAccountId, + fromLoanAccountNo); + fromAccountType = AccountTransferEnumerations + .accountType(PortfolioAccountType.LOAN); + } + + PortfolioAccountData toAccount = null; + EnumOptionData toAccountType = null; + final Long toSavingsAccountId = JdbcSupport.getLong(rs, + "toSavingsAccountId"); + final String toSavingsAccountNo = rs + .getString("toSavingsAccountNo"); + final Long toLoanAccountId = JdbcSupport.getLong(rs, + "toLoanAccountId"); + final String toLoanAccountNo = rs.getString("toLoanAccountNo"); + + if (toSavingsAccountId != null) { + toAccount = PortfolioAccountData.lookup(toSavingsAccountId, + toSavingsAccountNo); + toAccountType = AccountTransferEnumerations + .accountType(PortfolioAccountType.SAVINGS); + } else if (toLoanAccountId != null) { + toAccount = PortfolioAccountData.lookup(toLoanAccountId, + toLoanAccountNo); + toAccountType = AccountTransferEnumerations + .accountType(PortfolioAccountType.LOAN); + } + + return AccountTransferData.instance(id, reversed, transferDate, + currency, transferAmount, transferDescription, fromOffice, + toOffice, fromClient, toClient, fromAccountType, + fromAccount, toAccountType, toAccount); + } + } + + @Override + public boolean isAccountTransfer(final Long transactionId, + final PortfolioAccountType accountType) { + final StringBuilder sql = new StringBuilder( + "select count(*) from m_account_transfer_transaction at where "); + if (accountType.isLoanAccount()) { + sql.append("at.from_loan_transaction_id=").append(transactionId) + .append(" or at.to_loan_transaction_id=") + .append(transactionId); + } else { + sql.append("at.from_savings_transaction_id=").append(transactionId) + .append(" or at.to_savings_transaction_id=") + .append(transactionId); + } + + @SuppressWarnings("deprecation") + final int count = this.jdbcTemplate.queryForInt(sql.toString()); + return count > 0; + } + + @Override + public Page retrieveByStandingInstruction( + final Long id, final SearchParameters searchParameters) { + + final StringBuilder sqlBuilder = new StringBuilder(200); + sqlBuilder.append("select SQL_CALC_FOUND_ROWS "); + sqlBuilder + .append(this.accountTransfersMapper.schema()) + .append(" join m_account_transfer_standing_instructions atsi on atsi.account_transfer_details_id = att.account_transfer_details_id "); + sqlBuilder.append(" where atsi.id = ?"); + + if (searchParameters != null) { + if (searchParameters.isOrderByRequested()) { + sqlBuilder.append(" order by ").append( + searchParameters.getOrderBy()); + + if (searchParameters.isSortOrderProvided()) { + sqlBuilder.append(' ').append( + searchParameters.getSortOrder()); + } + } + + if (searchParameters.isLimited()) { + sqlBuilder.append(" limit ") + .append(searchParameters.getLimit()); + if (searchParameters.isOffset()) { + sqlBuilder.append(" offset ").append( + searchParameters.getOffset()); + } + } + } + + final Object[] finalObjectArray = { id }; + final String sqlCountRows = "SELECT FOUND_ROWS()"; + return this.paginationHelper.fetchPage(this.jdbcTemplate, sqlCountRows, + sqlBuilder.toString(), finalObjectArray, + this.accountTransfersMapper); + } + + @Override + public AccountTransferData retrieveRefundByTransferTemplate( + final Long fromOfficeId, final Long fromClientId, + final Long fromAccountId, final Integer fromAccountType, + final Long toOfficeId, final Long toClientId, + final Long toAccountId, final Integer toAccountType) { + // TODO Auto-generated method stub + final EnumOptionData loanAccountType = AccountTransferEnumerations + .accountType(PortfolioAccountType.LOAN); + final EnumOptionData savingsAccountType = AccountTransferEnumerations + .accountType(PortfolioAccountType.SAVINGS); + + final Integer mostRelevantFromAccountType = fromAccountType; + final Collection fromAccountTypeOptions = Arrays + .asList(savingsAccountType, loanAccountType); + final Collection toAccountTypeOptions; + if (mostRelevantFromAccountType == 1) { + // overpaid loan amt transfer to savings account + toAccountTypeOptions = Arrays.asList(savingsAccountType); + } else { + toAccountTypeOptions = Arrays.asList(loanAccountType, + savingsAccountType); + } + final Integer mostRelevantToAccountType = toAccountType; + + final EnumOptionData fromAccountTypeData = AccountTransferEnumerations + .accountType(mostRelevantFromAccountType); + final EnumOptionData toAccountTypeData = AccountTransferEnumerations + .accountType(mostRelevantToAccountType); + + // from settings + OfficeData fromOffice = null; + ClientData fromClient = null; + PortfolioAccountData fromAccount = null; + + OfficeData toOffice = null; + ClientData toClient = null; + PortfolioAccountData toAccount = null; + + // template + Collection fromAccountOptions = null; + Collection toAccountOptions = null; + + Long mostRelevantFromOfficeId = fromOfficeId; + Long mostRelevantFromClientId = fromClientId; + + Long mostRelevantToOfficeId = toOfficeId; + Long mostRelevantToClientId = toClientId; + + if (fromAccountId != null) { + Integer accountType; + if (mostRelevantFromAccountType == 1) { + accountType = PortfolioAccountType.LOAN.getValue(); + } else { + accountType = PortfolioAccountType.SAVINGS.getValue(); + } + fromAccount = this.portfolioAccountReadPlatformService + .retrieveOneByPaidInAdvance(fromAccountId, accountType); + + // override provided fromClient with client of account + mostRelevantFromClientId = fromAccount.clientId(); + } + + if (mostRelevantFromClientId != null) { + fromClient = this.clientReadPlatformService + .retrieveOne(mostRelevantFromClientId); + mostRelevantFromOfficeId = fromClient.officeId(); + long[] loanStatus = null; + if (mostRelevantFromAccountType == 1) { + loanStatus = new long[] { 300, 700 }; + } + PortfolioAccountDTO portfolioAccountDTO = new PortfolioAccountDTO( + mostRelevantFromAccountType, mostRelevantFromClientId, + loanStatus); + fromAccountOptions = this.portfolioAccountReadPlatformService + .retrieveAllForLookup(portfolioAccountDTO); + } + + Collection fromOfficeOptions = null; + Collection fromClientOptions = null; + if (mostRelevantFromOfficeId != null) { + fromOffice = this.officeReadPlatformService + .retrieveOffice(mostRelevantFromOfficeId); + fromOfficeOptions = this.officeReadPlatformService + .retrieveAllOfficesForDropdown(); + fromClientOptions = this.clientReadPlatformService + .retrieveAllForLookupByOfficeId(mostRelevantFromOfficeId); + } + + // defaults + final LocalDate transferDate = DateUtils.getLocalDateOfTenant(); + Collection toOfficeOptions = fromOfficeOptions; + Collection toClientOptions = null; + + if (toAccountId != null && fromAccount != null) { + toAccount = this.portfolioAccountReadPlatformService.retrieveOne( + toAccountId, mostRelevantToAccountType, + fromAccount.currencyCode()); + mostRelevantToClientId = toAccount.clientId(); + } + + if (mostRelevantToClientId != null) { + toClient = this.clientReadPlatformService + .retrieveOne(mostRelevantToClientId); + mostRelevantToOfficeId = toClient.officeId(); + + toClientOptions = this.clientReadPlatformService + .retrieveAllForLookupByOfficeId(mostRelevantToOfficeId); + + toAccountOptions = retrieveToAccounts(fromAccount, + mostRelevantToAccountType, mostRelevantToClientId); + } + + if (mostRelevantToOfficeId != null) { + toOffice = this.officeReadPlatformService + .retrieveOffice(mostRelevantToOfficeId); + toOfficeOptions = this.officeReadPlatformService + .retrieveAllOfficesForDropdown(); + + toClientOptions = this.clientReadPlatformService + .retrieveAllForLookupByOfficeId(mostRelevantToOfficeId); + if (toClientOptions != null && toClientOptions.size() == 1) { + toClient = new ArrayList<>(toClientOptions).get(0); + + toAccountOptions = retrieveToAccounts(fromAccount, + mostRelevantToAccountType, mostRelevantToClientId); + } + } + + return AccountTransferData.template(fromOffice, fromClient, + fromAccountTypeData, fromAccount, transferDate, toOffice, + toClient, toAccountTypeData, toAccount, fromOfficeOptions, + fromClientOptions, fromAccountTypeOptions, fromAccountOptions, + toOfficeOptions, toClientOptions, toAccountTypeOptions, + toAccountOptions); + } + + @Override + public BigDecimal getTotalTransactionAmount(Long accountId, + Integer accountType, LocalDate transactionDate) { + StringBuilder sqlBuilder = new StringBuilder( + " select sum(trans.amount) as totalTransactionAmount "); + sqlBuilder.append(" from m_account_transfer_details as det "); + sqlBuilder + .append(" inner join m_account_transfer_transaction as trans "); + sqlBuilder.append(" on det.id = trans.account_transfer_details_id "); + sqlBuilder.append(" where trans.is_reversed = 0 "); + sqlBuilder.append(" and trans.transaction_date = ? "); + sqlBuilder + .append(" and IF(1=?, det.from_loan_account_id = ?, det.from_savings_account_id = ?) "); + + return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), + new Object[] { this.formatter.print(transactionDate), + accountType, accountId, accountId }, BigDecimal.class); + } } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java index 845cc671c..b306d705c 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepository.java @@ -69,6 +69,8 @@ public interface LoanRepository extends JpaRepository, JpaSpecificat public static final String DOES_PRODUCT_HAVE_NON_CLOSED_LOANS = "select case when (count (loan) > 0) then true else false end from Loan loan where loan.loanProduct.id = :productId and loan.loanStatus in (100,200,300,303,304,700)"; + public static final String FIND_BY_ACCOUNT_NUMBER = "from Loan loan where loan.accountNumber = :accountNumber and loan.loanStatus in (100,200,300,303,304)"; + @Query(FIND_GROUP_LOANS_DISBURSED_AFTER) List getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId, @Param("loanType") Integer loanType); @@ -144,5 +146,7 @@ public interface LoanRepository extends JpaRepository, JpaSpecificat @Query(DOES_PRODUCT_HAVE_NON_CLOSED_LOANS) boolean doNonClosedLoanAccountsExistForProduct(@Param("productId") Long productId); - + + @Query(FIND_BY_ACCOUNT_NUMBER) + Loan findNonClosedLoanByAccountNumber(@Param("accountNumber") String accountNumber); } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java index b34e89794..b614b259e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountRepository.java @@ -45,4 +45,7 @@ public interface SavingsAccountRepository extends JpaRepository templateData = this.selfAccountTransferReadService - .retrieveSelfAccountTemplateData(user); - final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper .process(uriInfo.getQueryParameters()); - return this.toApiJsonSerializer.serialize(settings, - new SelfAccountTransferData(templateData)); + Collection selfTemplateData = this.selfAccountTransferReadService + .retrieveSelfAccountTemplateData(user); + + if (type.equals("tpt")) { + Collection tptTemplateData = this.tptBeneficiaryReadPlatformService + .retrieveTPTSelfAccountTemplateData(user); + return this.toApiJsonSerializer.serialize(settings, + new SelfAccountTransferData(selfTemplateData, + tptTemplateData)); + } + + return this.toApiJsonSerializer + .serialize(settings, new SelfAccountTransferData( + selfTemplateData, selfTemplateData)); } @POST @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) - public String create(final String apiRequestBodyAsJson) { - this.dataValidator.validateCreate(apiRequestBodyAsJson); + public String create( + @DefaultValue("") @QueryParam("type") final String type, + final String apiRequestBodyAsJson) { + Map params = this.dataValidator.validateCreate(type, + apiRequestBodyAsJson); + if (type.equals("tpt")) { + checkForLimits(params); + } return this.accountTransfersApiResource.create(apiRequestBodyAsJson); } + private void checkForLimits(Map params) { + SelfAccountTemplateData fromAccount = (SelfAccountTemplateData) params + .get("fromAccount"); + SelfAccountTemplateData toAccount = (SelfAccountTemplateData) params + .get("toAccount"); + LocalDate transactionDate = (LocalDate) params.get("transactionDate"); + BigDecimal transactionAmount = (BigDecimal) params + .get("transactionAmount"); + + AppUser user = this.context.authenticatedUser(); + Long transferLimit = this.tptBeneficiaryReadPlatformService + .getTransferLimit(user.getId(), toAccount.getAccountId(), + toAccount.getAccountType()); + if (transferLimit != null && transferLimit > 0) { + if (transactionAmount.compareTo(new BigDecimal(transferLimit)) > 0) { + throw new BeneficiaryTransferLimitExceededException(); + } + } + + if (this.configurationDomainService.isDailyTPTLimitEnabled()) { + Long dailyTPTLimit = this.configurationDomainService + .getDailyTPTLimit(); + if (dailyTPTLimit != null && dailyTPTLimit > 0) { + BigDecimal dailyTPTLimitBD = new BigDecimal(dailyTPTLimit); + BigDecimal totTransactionAmount = this.accountTransfersReadPlatformService + .getTotalTransactionAmount(fromAccount.getAccountId(), + fromAccount.getAccountType(), transactionDate); + if (totTransactionAmount != null + && totTransactionAmount.compareTo(BigDecimal.ZERO) > 0) { + if (dailyTPTLimitBD.compareTo(totTransactionAmount) <= 0 + || dailyTPTLimitBD.compareTo(totTransactionAmount + .add(transactionAmount)) < 0) { + throw new DailyTPTTransactionAmountLimitExceededException( + fromAccount.getAccountId(), + fromAccount.getAccountType()); + } + } + } + } + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfBeneficiariesTPTApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfBeneficiariesTPTApiConstants.java new file mode 100644 index 000000000..affe03b6c --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfBeneficiariesTPTApiConstants.java @@ -0,0 +1,53 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.api; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public interface SelfBeneficiariesTPTApiConstants { + + public static final String BENEFICIARY_ENTITY_NAME = "SSBENEFICIARYTPT"; + public static final String RESOURCE_NAME = "beneficiary"; + public static final String LOCALE = "locale"; + public static final String NAME_PARAM_NAME = "name"; + public static final String OFFICE_NAME_PARAM_NAME = "officeName"; + public static final String ACCOUNT_TYPE_PARAM_NAME = "accountType"; + public static final String ACCOUNT_NUMBER_PARAM_NAME = "accountNumber"; + public static final String TRANSFER_LIMIT_PARAM_NAME = "transferLimit"; + + public static final String ID_PARAM_NAME = "id"; + public static final String CLIENT_NAME_PARAM_NAME = "clientName"; + public static final String ACCOUNT_TYPE_OPTIONS_PARAM_NAME = "accountTypeOptions"; + + public static final Set CREATE_REQUEST_DATA_PARAMETERS = new HashSet<>( + Arrays.asList(LOCALE, NAME_PARAM_NAME, OFFICE_NAME_PARAM_NAME, + ACCOUNT_NUMBER_PARAM_NAME, ACCOUNT_TYPE_PARAM_NAME, + TRANSFER_LIMIT_PARAM_NAME)); + + public static final Set UPDATE_REQUEST_DATA_PARAMETERS = new HashSet<>( + Arrays.asList(NAME_PARAM_NAME, TRANSFER_LIMIT_PARAM_NAME)); + + public static final Set RESPONSE_DATA_PARAMETERS = new HashSet<>( + Arrays.asList(NAME_PARAM_NAME, OFFICE_NAME_PARAM_NAME, + ACCOUNT_NUMBER_PARAM_NAME, ACCOUNT_TYPE_PARAM_NAME, + TRANSFER_LIMIT_PARAM_NAME, ID_PARAM_NAME, + CLIENT_NAME_PARAM_NAME, ACCOUNT_TYPE_OPTIONS_PARAM_NAME)); +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfBeneficiariesTPTApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfBeneficiariesTPTApiResource.java new file mode 100644 index 000000000..344d9916e --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/api/SelfBeneficiariesTPTApiResource.java @@ -0,0 +1,161 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.api; + +import java.util.Arrays; +import java.util.Collection; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; + +import org.apache.fineract.commands.domain.CommandWrapper; +import org.apache.fineract.commands.service.CommandWrapperBuilder; +import org.apache.fineract.commands.service.PortfolioCommandSourceWritePlatformService; +import org.apache.fineract.infrastructure.core.api.ApiRequestParameterHelper; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.serialization.ApiRequestJsonSerializationSettings; +import org.apache.fineract.infrastructure.core.serialization.DefaultToApiJsonSerializer; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.account.PortfolioAccountType; +import org.apache.fineract.portfolio.account.service.AccountTransferEnumerations; +import org.apache.fineract.portfolio.self.account.data.SelfBeneficiariesTPTData; +import org.apache.fineract.portfolio.self.account.service.SelfBeneficiariesTPTReadPlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Component; + +@Path("/self/beneficiaries/tpt") +@Component +@Scope("singleton") +public class SelfBeneficiariesTPTApiResource { + + private final PlatformSecurityContext context; + private final DefaultToApiJsonSerializer toApiJsonSerializer; + private final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService; + private final ApiRequestParameterHelper apiRequestParameterHelper; + private final SelfBeneficiariesTPTReadPlatformService readPlatformService; + + @Autowired + public SelfBeneficiariesTPTApiResource( + final PlatformSecurityContext context, + final DefaultToApiJsonSerializer toApiJsonSerializer, + final PortfolioCommandSourceWritePlatformService commandsSourceWritePlatformService, + final ApiRequestParameterHelper apiRequestParameterHelper, + final SelfBeneficiariesTPTReadPlatformService readPlatformService) { + this.context = context; + this.toApiJsonSerializer = toApiJsonSerializer; + this.commandsSourceWritePlatformService = commandsSourceWritePlatformService; + this.apiRequestParameterHelper = apiRequestParameterHelper; + this.readPlatformService = readPlatformService; + } + + @GET + @Path("template") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String template(@Context final UriInfo uriInfo) { + + final EnumOptionData loanAccountType = AccountTransferEnumerations + .accountType(PortfolioAccountType.LOAN); + final EnumOptionData savingsAccountType = AccountTransferEnumerations + .accountType(PortfolioAccountType.SAVINGS); + + final Collection accountTypeOptions = Arrays.asList( + savingsAccountType, loanAccountType); + + SelfBeneficiariesTPTData templateData = new SelfBeneficiariesTPTData( + accountTypeOptions); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper + .process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, templateData, + SelfBeneficiariesTPTApiConstants.RESPONSE_DATA_PARAMETERS); + } + + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String add(final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder() + .addSelfServiceBeneficiaryTPT().withJson(apiRequestBodyAsJson) + .build(); + final CommandProcessingResult result = this.commandsSourceWritePlatformService + .logCommandSource(commandRequest); + return this.toApiJsonSerializer.serialize(result); + } + + @PUT + @Path("{beneficiaryId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String update(@PathParam("beneficiaryId") final Long beneficiaryId, + final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder() + .updateSelfServiceBeneficiaryTPT(beneficiaryId) + .withJson(apiRequestBodyAsJson).build(); + final CommandProcessingResult result = this.commandsSourceWritePlatformService + .logCommandSource(commandRequest); + return this.toApiJsonSerializer.serialize(result); + } + + @DELETE + @Path("{beneficiaryId}") + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String delete(@PathParam("beneficiaryId") final Long beneficiaryId, + final String apiRequestBodyAsJson) { + + final CommandWrapper commandRequest = new CommandWrapperBuilder() + .deleteSelfServiceBeneficiaryTPT(beneficiaryId) + .withJson(apiRequestBodyAsJson).build(); + final CommandProcessingResult result = this.commandsSourceWritePlatformService + .logCommandSource(commandRequest); + return this.toApiJsonSerializer.serialize(result); + } + + @GET + @Consumes({ MediaType.APPLICATION_JSON }) + @Produces({ MediaType.APPLICATION_JSON }) + public String retrieveAll(@Context final UriInfo uriInfo) { + + this.context.authenticatedUser().validateHasReadPermission( + SelfBeneficiariesTPTApiConstants.BENEFICIARY_ENTITY_NAME); + + final Collection beneficiaries = this.readPlatformService + .retrieveAll(); + + final ApiRequestJsonSerializationSettings settings = this.apiRequestParameterHelper + .process(uriInfo.getQueryParameters()); + return this.toApiJsonSerializer.serialize(settings, beneficiaries, + SelfBeneficiariesTPTApiConstants.RESPONSE_DATA_PARAMETERS); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTemplateData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTemplateData.java index 03e872b4e..17b2ebe3c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTemplateData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTemplateData.java @@ -103,4 +103,13 @@ public class SelfAccountTemplateData implements .toHashCode(); } + public Long getAccountId() { + return this.accountId; + } + + public Integer getAccountType() { + return this.accountType.getId().intValue(); + } + + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferData.java index b3d6669f8..5ca23c29f 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferData.java @@ -23,11 +23,14 @@ import java.util.Collection; @SuppressWarnings("unused") public class SelfAccountTransferData { - private final Collection accountOptions; + private final Collection fromAccountOptions; + private final Collection toAccountOptions; public SelfAccountTransferData( - final Collection accountOptions) { - this.accountOptions = accountOptions; + final Collection fromAccountOptions, + Collection toAccountOptions) { + this.fromAccountOptions = fromAccountOptions; + this.toAccountOptions = toAccountOptions; } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferDataValidator.java index c78852fbd..f986a678e 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfAccountTransferDataValidator.java @@ -27,10 +27,16 @@ import static org.apache.fineract.portfolio.account.AccountDetailConstants.toAcc import static org.apache.fineract.portfolio.account.AccountDetailConstants.toClientIdParamName; import static org.apache.fineract.portfolio.account.AccountDetailConstants.toOfficeIdParamName; import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.ACCOUNT_TRANSFER_RESOURCE_NAME; +import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.transferAmountParamName; +import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.transferDateParamName; +import static org.apache.fineract.portfolio.account.api.AccountTransfersApiConstants.transferDescriptionParamName; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.fineract.infrastructure.core.data.ApiParameterError; @@ -40,7 +46,9 @@ import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidati import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; import org.apache.fineract.portfolio.self.account.service.SelfAccountTransferReadService; +import org.apache.fineract.portfolio.self.account.service.SelfBeneficiariesTPTReadPlatformService; import org.apache.fineract.useradministration.domain.AppUser; +import org.joda.time.LocalDate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -51,19 +59,22 @@ public class SelfAccountTransferDataValidator { private final PlatformSecurityContext context; private final SelfAccountTransferReadService selfAccountTransferReadService; + private final SelfBeneficiariesTPTReadPlatformService tptBeneficiaryReadPlatformService; private final FromJsonHelper fromApiJsonHelper; @Autowired public SelfAccountTransferDataValidator( final PlatformSecurityContext context, final SelfAccountTransferReadService selfAccountTransferReadService, + final SelfBeneficiariesTPTReadPlatformService tptBeneficiaryReadPlatformService, final FromJsonHelper fromApiJsonHelper) { this.context = context; this.selfAccountTransferReadService = selfAccountTransferReadService; + this.tptBeneficiaryReadPlatformService = tptBeneficiaryReadPlatformService; this.fromApiJsonHelper = fromApiJsonHelper; } - public void validateCreate(String apiRequestBodyAsJson) { + public Map validateCreate(String type, String apiRequestBodyAsJson) { if (StringUtils.isBlank(apiRequestBodyAsJson)) { throw new InvalidJsonException(); } @@ -126,6 +137,16 @@ public class SelfAccountTransferDataValidator { "Cannot transfer from Loan account to another Loan account."); } + final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed(transferDateParamName, element); + baseDataValidator.reset().parameter(transferDateParamName).value(transactionDate).notNull(); + + final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(transferAmountParamName, element); + baseDataValidator.reset().parameter(transferAmountParamName).value(transactionAmount).notNull().positiveAmount(); + + final String transactionDescription = this.fromApiJsonHelper.extractStringNamed(transferDescriptionParamName, element); + baseDataValidator.reset().parameter(transferDescriptionParamName).value(transactionDescription).notBlank() + .notExceedingLengthOf(200); + throwExceptionIfValidationWarningsExist(dataValidationErrors); SelfAccountTemplateData fromAccount = new SelfAccountTemplateData( @@ -133,21 +154,35 @@ public class SelfAccountTransferDataValidator { SelfAccountTemplateData toAccount = new SelfAccountTemplateData( toAccountId, toAccountType, toClientId, toOfficeId); - validateSelfUserAccounts(fromAccount, toAccount, baseDataValidator); + validateUserAccounts(fromAccount, toAccount, baseDataValidator, type); throwExceptionIfValidationWarningsExist(dataValidationErrors); + + Map ret = new HashMap<>(); + ret.put("fromAccount", fromAccount); + ret.put("toAccount", toAccount); + ret.put("transactionDate", transactionDate); + ret.put("transactionAmount", transactionAmount); + + return ret; } - private void validateSelfUserAccounts( + private void validateUserAccounts( final SelfAccountTemplateData fromAccount, final SelfAccountTemplateData toAccount, - final DataValidatorBuilder baseDataValidator) { + final DataValidatorBuilder baseDataValidator, final String type) { AppUser user = this.context.authenticatedUser(); - Collection userValidAccounts = this.selfAccountTransferReadService + Collection validFromAccounts = this.selfAccountTransferReadService .retrieveSelfAccountTemplateData(user); + Collection validToAccounts = validFromAccounts; + if (type.equals("tpt")) { + validToAccounts = this.tptBeneficiaryReadPlatformService + .retrieveTPTSelfAccountTemplateData(user); + } + boolean validFromAccount = false; - for (SelfAccountTemplateData validAccount : userValidAccounts) { + for (SelfAccountTemplateData validAccount : validFromAccounts) { if (validAccount.equals(fromAccount)) { validFromAccount = true; break; @@ -155,7 +190,7 @@ public class SelfAccountTransferDataValidator { } boolean validToAccount = false; - for (SelfAccountTemplateData validAccount : userValidAccounts) { + for (SelfAccountTemplateData validAccount : validToAccounts) { if (validAccount.equals(toAccount)) { validToAccount = true; break; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfBeneficiariesTPTData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfBeneficiariesTPTData.java new file mode 100644 index 000000000..ff56606c1 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfBeneficiariesTPTData.java @@ -0,0 +1,69 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.data; + +import java.util.Collection; + +import org.apache.fineract.infrastructure.core.data.EnumOptionData; + +public class SelfBeneficiariesTPTData { + @SuppressWarnings("unused") + private final Long id; + @SuppressWarnings("unused") + private final String name; + @SuppressWarnings("unused") + private final String officeName; + @SuppressWarnings("unused") + private final String clientName; + @SuppressWarnings("unused") + private final EnumOptionData accountType; + @SuppressWarnings("unused") + private final String accountNumber; + @SuppressWarnings("unused") + private final Long transferLimit; + @SuppressWarnings("unused") + private final Collection accountTypeOptions; + + public SelfBeneficiariesTPTData( + final Collection accountTypeOptions) { + this.accountTypeOptions = accountTypeOptions; + this.id = null; + this.name = null; + this.officeName = null; + this.clientName = null; + this.accountType = null; + this.accountNumber = null; + this.transferLimit = null; + } + + public SelfBeneficiariesTPTData(final Long id, final String name, + final String officeName, final String clientName, + final EnumOptionData accountType, final String accountNumber, + final Long transferLimit) { + this.accountTypeOptions = null; + this.id = id; + this.name = name; + this.officeName = officeName; + this.clientName = clientName; + this.accountType = accountType; + this.accountNumber = accountNumber; + this.transferLimit = transferLimit; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfBeneficiariesTPTDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfBeneficiariesTPTDataValidator.java new file mode 100644 index 000000000..2ba289959 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/data/SelfBeneficiariesTPTDataValidator.java @@ -0,0 +1,165 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.data; + +import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.RESOURCE_NAME; +import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.NAME_PARAM_NAME; +import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.OFFICE_NAME_PARAM_NAME; +import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.ACCOUNT_TYPE_PARAM_NAME; +import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.ACCOUNT_NUMBER_PARAM_NAME; +import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.TRANSFER_LIMIT_PARAM_NAME; +import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.CREATE_REQUEST_DATA_PARAMETERS; +import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.UPDATE_REQUEST_DATA_PARAMETERS; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang.StringUtils; +import org.apache.fineract.infrastructure.core.data.ApiParameterError; +import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; +import org.apache.fineract.infrastructure.core.exception.InvalidJsonException; +import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; +import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; +import org.apache.fineract.portfolio.account.PortfolioAccountType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.google.gson.JsonElement; +import com.google.gson.reflect.TypeToken; + +@Component +public class SelfBeneficiariesTPTDataValidator { + + private final FromJsonHelper fromApiJsonHelper; + + @Autowired + public SelfBeneficiariesTPTDataValidator( + final FromJsonHelper fromApiJsonHelper) { + this.fromApiJsonHelper = fromApiJsonHelper; + } + + public HashMap validateForCreate(String json) { + if (StringUtils.isBlank(json)) { + throw new InvalidJsonException(); + } + + final Type typeOfMap = new TypeToken>() { + }.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, + CREATE_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( + dataValidationErrors).resource(RESOURCE_NAME); + final JsonElement element = this.fromApiJsonHelper.parse(json); + + final String name = this.fromApiJsonHelper.extractStringNamed( + NAME_PARAM_NAME, element); + baseDataValidator.reset().parameter(NAME_PARAM_NAME).value(name) + .notBlank().notExceedingLengthOf(50); + + final String officeName = this.fromApiJsonHelper.extractStringNamed( + OFFICE_NAME_PARAM_NAME, element); + baseDataValidator.reset().parameter(OFFICE_NAME_PARAM_NAME) + .value(officeName).notBlank() + .notExceedingLengthOf(50); + + final String accountNo = this.fromApiJsonHelper.extractStringNamed( + ACCOUNT_NUMBER_PARAM_NAME, element); + baseDataValidator.reset().parameter(ACCOUNT_NUMBER_PARAM_NAME) + .value(accountNo).notBlank().notExceedingLengthOf(20); + + final Integer accountType = this.fromApiJsonHelper.extractIntegerNamed( + ACCOUNT_TYPE_PARAM_NAME, element, this.fromApiJsonHelper + .extractLocaleParameter(element.getAsJsonObject())); + baseDataValidator + .reset() + .parameter(ACCOUNT_TYPE_PARAM_NAME) + .value(accountType) + .notNull() + .isOneOfTheseValues(PortfolioAccountType.LOAN.getValue(), + PortfolioAccountType.SAVINGS.getValue()); + + final Long transferLimit = this.fromApiJsonHelper.extractLongNamed( + TRANSFER_LIMIT_PARAM_NAME, element); + baseDataValidator.reset().parameter(TRANSFER_LIMIT_PARAM_NAME) + .value(transferLimit).ignoreIfNull().longGreaterThanZero(); + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + + HashMap ret = new HashMap<>(); + ret.put(NAME_PARAM_NAME, name); + ret.put(OFFICE_NAME_PARAM_NAME, officeName); + ret.put(ACCOUNT_NUMBER_PARAM_NAME, accountNo); + ret.put(ACCOUNT_TYPE_PARAM_NAME, accountType); + ret.put(TRANSFER_LIMIT_PARAM_NAME, transferLimit); + + return ret; + } + + public HashMap validateForUpdate(String json) { + if (StringUtils.isBlank(json)) { + throw new InvalidJsonException(); + } + + final Type typeOfMap = new TypeToken>() { + }.getType(); + this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, + UPDATE_REQUEST_DATA_PARAMETERS); + + final List dataValidationErrors = new ArrayList<>(); + final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder( + dataValidationErrors).resource(RESOURCE_NAME); + final JsonElement element = this.fromApiJsonHelper.parse(json); + + HashMap ret = new HashMap<>(); + + if (this.fromApiJsonHelper.parameterExists(NAME_PARAM_NAME, element)) { + final String name = this.fromApiJsonHelper.extractStringNamed( + NAME_PARAM_NAME, element); + baseDataValidator.reset().parameter(NAME_PARAM_NAME).value(name) + .notBlank().notExceedingLengthOf(50); + ret.put(NAME_PARAM_NAME, name); + } + + if (this.fromApiJsonHelper.parameterExists(TRANSFER_LIMIT_PARAM_NAME, + element)) { + final Long transferLimit = this.fromApiJsonHelper.extractLongNamed( + TRANSFER_LIMIT_PARAM_NAME, element); + baseDataValidator.reset().parameter(TRANSFER_LIMIT_PARAM_NAME) + .value(transferLimit).ignoreIfNull().longGreaterThanZero(); + ret.put(TRANSFER_LIMIT_PARAM_NAME, transferLimit); + } + + throwExceptionIfValidationWarningsExist(dataValidationErrors); + + return ret; + } + + private void throwExceptionIfValidationWarningsExist( + final List dataValidationErrors) { + if (!dataValidationErrors.isEmpty()) { + throw new PlatformApiDataValidationException(dataValidationErrors); + } + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/domain/SelfBeneficiariesTPT.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/domain/SelfBeneficiariesTPT.java new file mode 100644 index 000000000..76d0432c2 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/domain/SelfBeneficiariesTPT.java @@ -0,0 +1,136 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.domain; + +import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.NAME_PARAM_NAME; +import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.TRANSFER_LIMIT_PARAM_NAME; +import java.util.HashMap; +import java.util.Map; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +import org.springframework.data.jpa.domain.AbstractPersistable; + +@Entity +@Table(name = "m_selfservice_beneficiaries_tpt", uniqueConstraints = { @UniqueConstraint(columnNames = { + "name", "app_user_id", "is_active" }, name = "name") }) +public class SelfBeneficiariesTPT extends AbstractPersistable { + + @Column(name = "app_user_id", nullable = false) + private Long appUserId; + + @Column(name = "name", length = 50, nullable = false) + private String name; + + @Column(name = "office_id", nullable = false) + private Long officeId; + + @Column(name = "client_id", nullable = false) + private Long clientId; + + @Column(name = "account_id", nullable = false) + private Long accountId; + + @Column(name = "account_type", nullable = false) + private Integer accountType; + + @Column(name = "transfer_limit", nullable = true) + private Long transferLimit; + + @Column(name = "is_active", nullable = false) + private boolean isActive = true; + + protected SelfBeneficiariesTPT() { + // + } + + public SelfBeneficiariesTPT(Long appUserId, String name, Long officeId, + Long clientId, Long accountId, Integer accountType, + Long transferLimit) { + this.appUserId = appUserId; + this.name = name; + this.officeId = officeId; + this.clientId = clientId; + this.accountId = accountId; + this.accountType = accountType; + this.transferLimit = transferLimit; + } + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public Long getTransferLimit() { + return this.transferLimit; + } + + public void setTransferLimit(Long transferLimit) { + this.transferLimit = transferLimit; + } + + public boolean isActive() { + return this.isActive; + } + + public void setActive(boolean isActive) { + this.isActive = isActive; + } + + public Long getAppUserId() { + return this.appUserId; + } + + public Long getOfficeId() { + return this.officeId; + } + + public Long getClientId() { + return this.clientId; + } + + public Long getAccountId() { + return this.accountId; + } + + public Integer getAccountType() { + return this.accountType; + } + + public Map update(String newName, Long newTransferLimit) { + Map changes = new HashMap<>(); + if (!this.name.equals(newName)) { + this.name = newName; + changes.put(NAME_PARAM_NAME, newName); + } + if ((this.transferLimit !=null && !this.transferLimit.equals(newTransferLimit)) + || (this.transferLimit == null && newTransferLimit != null)) { + this.transferLimit = newTransferLimit; + changes.put(TRANSFER_LIMIT_PARAM_NAME, newTransferLimit); + } + return changes; + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/domain/SelfBeneficiariesTPTRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/domain/SelfBeneficiariesTPTRepository.java new file mode 100644 index 000000000..a74ebeaba --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/domain/SelfBeneficiariesTPTRepository.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; + +public interface SelfBeneficiariesTPTRepository extends + JpaRepository, + JpaSpecificationExecutor { + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/BeneficiaryTransferLimitExceededException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/BeneficiaryTransferLimitExceededException.java new file mode 100644 index 000000000..3623d3344 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/BeneficiaryTransferLimitExceededException.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class BeneficiaryTransferLimitExceededException extends AbstractPlatformDomainRuleException { + + public BeneficiaryTransferLimitExceededException() { + super("error.msg.beneficiary.transfer.amount.limit.for.beneficiary.exceeded", + "Transfer amount limit for beneficiary exceeded"); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/DailyTPTTransactionAmountLimitExceededException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/DailyTPTTransactionAmountLimitExceededException.java new file mode 100644 index 000000000..fb365f8fc --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/DailyTPTTransactionAmountLimitExceededException.java @@ -0,0 +1,31 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class DailyTPTTransactionAmountLimitExceededException extends AbstractPlatformDomainRuleException { + + public DailyTPTTransactionAmountLimitExceededException(Long accountId, + Integer accountType) { + super("error.msg.beneficiary.daily.tpt.transfer.limit.for.fromaccountid."+accountId+".fromaccounttype."+accountType+".exceeded", + "Daily third party transfer limit for the source account excceeded"); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/InvalidAccountInformationException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/InvalidAccountInformationException.java new file mode 100644 index 000000000..7362077be --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/InvalidAccountInformationException.java @@ -0,0 +1,32 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class InvalidAccountInformationException extends + AbstractPlatformDomainRuleException { + public InvalidAccountInformationException(final String officeName, + final String accountNumber, final String accountType) { + super("error.msg.beneficiary.invalid.account.details.with.officeName." + + officeName + ".accountNumber." + accountNumber + + ".accountType." + accountType, + "Invalid Office Name, Account Number, Account Type combination"); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/InvalidBeneficiaryException.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/InvalidBeneficiaryException.java new file mode 100644 index 000000000..daac892e1 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/exception/InvalidBeneficiaryException.java @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.exception; + +import org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException; + +public class InvalidBeneficiaryException extends + AbstractPlatformDomainRuleException { + public InvalidBeneficiaryException(final Long beneficiaryId) { + super("error.msg.beneficiary.invalid.beneficiary.id." + beneficiaryId, + "Beneficiary ID doesn't belong to the User"); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/AddSelfBeneficiariesTPTCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/AddSelfBeneficiariesTPTCommandHandler.java new file mode 100644 index 000000000..cd88fa1ae --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/AddSelfBeneficiariesTPTCommandHandler.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.self.account.service.SelfBeneficiariesTPTWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@CommandType(entity = "SSBENEFICIARYTPT", action = "CREATE") +public class AddSelfBeneficiariesTPTCommandHandler implements + NewCommandSourceHandler { + private final SelfBeneficiariesTPTWritePlatformService writePlatformService; + + @Autowired + public AddSelfBeneficiariesTPTCommandHandler( + final SelfBeneficiariesTPTWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.writePlatformService.add(command); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/DeleteSelfBeneficiariesTPTCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/DeleteSelfBeneficiariesTPTCommandHandler.java new file mode 100644 index 000000000..46f378030 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/DeleteSelfBeneficiariesTPTCommandHandler.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.self.account.service.SelfBeneficiariesTPTWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@CommandType(entity = "SSBENEFICIARYTPT", action = "DELETE") +public class DeleteSelfBeneficiariesTPTCommandHandler implements + NewCommandSourceHandler { + private final SelfBeneficiariesTPTWritePlatformService writePlatformService; + + @Autowired + public DeleteSelfBeneficiariesTPTCommandHandler( + final SelfBeneficiariesTPTWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.writePlatformService.delete(command); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/UpdateSelfBeneficiariesTPTCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/UpdateSelfBeneficiariesTPTCommandHandler.java new file mode 100644 index 000000000..e92123ea6 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/handler/UpdateSelfBeneficiariesTPTCommandHandler.java @@ -0,0 +1,46 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.handler; + +import org.apache.fineract.commands.annotation.CommandType; +import org.apache.fineract.commands.handler.NewCommandSourceHandler; +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.portfolio.self.account.service.SelfBeneficiariesTPTWritePlatformService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +@CommandType(entity = "SSBENEFICIARYTPT", action = "UPDATE") +public class UpdateSelfBeneficiariesTPTCommandHandler implements + NewCommandSourceHandler { + private final SelfBeneficiariesTPTWritePlatformService writePlatformService; + + @Autowired + public UpdateSelfBeneficiariesTPTCommandHandler( + final SelfBeneficiariesTPTWritePlatformService writePlatformService) { + this.writePlatformService = writePlatformService; + } + + @Override + public CommandProcessingResult processCommand(final JsonCommand command) { + return this.writePlatformService.update(command); + } + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformService.java new file mode 100644 index 000000000..d60b47f99 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformService.java @@ -0,0 +1,36 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.service; + +import java.util.Collection; + +import org.apache.fineract.portfolio.self.account.data.SelfAccountTemplateData; +import org.apache.fineract.portfolio.self.account.data.SelfBeneficiariesTPTData; +import org.apache.fineract.useradministration.domain.AppUser; + +public interface SelfBeneficiariesTPTReadPlatformService { + + Collection retrieveAll(); + + Collection retrieveTPTSelfAccountTemplateData( + AppUser user); + + Long getTransferLimit(Long id, Long accountId, Integer accountType); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformServiceImpl.java new file mode 100644 index 000000000..c209a4d1b --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTReadPlatformServiceImpl.java @@ -0,0 +1,222 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.service; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; + +import org.apache.fineract.infrastructure.core.data.EnumOptionData; +import org.apache.fineract.infrastructure.core.service.RoutingDataSource; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.account.PortfolioAccountType; +import org.apache.fineract.portfolio.account.service.AccountTransferEnumerations; +import org.apache.fineract.portfolio.self.account.data.SelfAccountTemplateData; +import org.apache.fineract.portfolio.self.account.data.SelfBeneficiariesTPTData; +import org.apache.fineract.useradministration.domain.AppUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; +import org.springframework.stereotype.Service; + +@Service +public class SelfBeneficiariesTPTReadPlatformServiceImpl implements + SelfBeneficiariesTPTReadPlatformService { + + private final PlatformSecurityContext context; + private final JdbcTemplate jdbcTemplate; + private final BeneficiaryMapper mapper; + private final AccountTemplateMapper accountTemplateMapper; + + @Autowired + public SelfBeneficiariesTPTReadPlatformServiceImpl( + final PlatformSecurityContext context, + final RoutingDataSource dataSource) { + this.context = context; + this.jdbcTemplate = new JdbcTemplate(dataSource); + this.mapper = new BeneficiaryMapper(); + this.accountTemplateMapper = new AccountTemplateMapper(); + } + + @Override + public Collection retrieveAll() { + AppUser user = this.context.authenticatedUser(); + return this.jdbcTemplate.query(this.mapper.schema(), this.mapper, + new Object[] { user.getId(), user.getId() }); + } + + @Override + public Collection retrieveTPTSelfAccountTemplateData( + AppUser user) { + return this.jdbcTemplate.query(this.accountTemplateMapper.schema(), + this.accountTemplateMapper, + new Object[] { user.getId(), user.getId() }); + } + + private static final class BeneficiaryMapper implements + RowMapper { + + private final String schemaSql; + + public BeneficiaryMapper() { + final StringBuilder sqlBuilder = new StringBuilder( + "(select b.id as id, "); + sqlBuilder.append(" b.name as name, "); + sqlBuilder.append(" o.name as officeName, "); + sqlBuilder.append(" c.display_name as clientName, "); + sqlBuilder.append(" b.account_type as accountType, "); + sqlBuilder.append(" s.account_no as accountNumber, "); + sqlBuilder.append(" b.transfer_limit as transferLimit "); + sqlBuilder.append(" from m_selfservice_beneficiaries_tpt as b "); + sqlBuilder + .append(" inner join m_office as o on b.office_id = o.id "); + sqlBuilder + .append(" inner join m_client as c on b.client_id = c.id "); + sqlBuilder + .append(" inner join m_savings_account as s on b.account_id = s.id "); + sqlBuilder.append(" where b.is_active = 1 "); + sqlBuilder.append(" and b.account_type = 2 "); + sqlBuilder.append(" and b.app_user_id = ?) "); + sqlBuilder.append(" union all "); + sqlBuilder.append(" (select b.id as id, "); + sqlBuilder.append(" b.name as name, "); + sqlBuilder.append(" o.name as officeName, "); + sqlBuilder.append(" c.display_name as clientName, "); + sqlBuilder.append(" b.account_type as accountType, "); + sqlBuilder.append(" l.account_no as accountNumber, "); + sqlBuilder.append(" b.transfer_limit as transferLimit "); + sqlBuilder.append(" from m_selfservice_beneficiaries_tpt as b "); + sqlBuilder + .append(" inner join m_office as o on b.office_id = o.id "); + sqlBuilder + .append(" inner join m_client as c on b.client_id = c.id "); + sqlBuilder + .append(" inner join m_loan as l on b.account_id = l.id "); + sqlBuilder.append(" where b.is_active = 1 "); + sqlBuilder.append(" and b.account_type = 1 "); + sqlBuilder.append(" and b.app_user_id = ?) "); + + this.schemaSql = sqlBuilder.toString(); + } + + public String schema() { + return this.schemaSql; + } + + @Override + public SelfBeneficiariesTPTData mapRow(final ResultSet rs, + @SuppressWarnings("unused") final int rowNum) + throws SQLException { + + final Long id = rs.getLong("id"); + final String name = rs.getString("name"); + final String officeName = rs.getString("officeName"); + final String clientName = rs.getString("clientName"); + final Integer accountTypeId = rs.getInt("accountType"); + final EnumOptionData accountType = AccountTransferEnumerations + .accountType(PortfolioAccountType.fromInt(accountTypeId)); + final String accountNumber = rs.getString("accountNumber"); + final Long transferLimit = rs.getLong("transferLimit"); + + return new SelfBeneficiariesTPTData(id, name, officeName, + clientName, accountType, accountNumber, transferLimit); + } + } + + private static final class AccountTemplateMapper implements + RowMapper { + + private final String schemaSql; + + public AccountTemplateMapper() { + final StringBuilder sqlBuilder = new StringBuilder( + "(select o.name as officeName, "); + sqlBuilder.append(" o.id as officeId, "); + sqlBuilder.append(" c.display_name as clientName, "); + sqlBuilder.append(" c.id as clientId, "); + sqlBuilder.append(" b.account_type as accountType, "); + sqlBuilder.append(" s.account_no as accountNumber, "); + sqlBuilder.append(" s.id as accountId "); + sqlBuilder.append(" from m_selfservice_beneficiaries_tpt as b "); + sqlBuilder + .append(" inner join m_office as o on b.office_id = o.id "); + sqlBuilder + .append(" inner join m_client as c on b.client_id = c.id "); + sqlBuilder + .append(" inner join m_savings_account as s on b.account_id = s.id "); + sqlBuilder.append(" where b.is_active = 1 "); + sqlBuilder.append(" and b.account_type = 2 "); + sqlBuilder.append(" and b.app_user_id = ?) "); + sqlBuilder.append(" union all "); + sqlBuilder.append(" (select o.name as officeName, "); + sqlBuilder.append(" o.id as officeId, "); + sqlBuilder.append(" c.display_name as clientName, "); + sqlBuilder.append(" c.id as clientId, "); + sqlBuilder.append(" b.account_type as accountType, "); + sqlBuilder.append(" l.account_no as accountNumber, "); + sqlBuilder.append(" l.id as accountId "); + sqlBuilder.append(" from m_selfservice_beneficiaries_tpt as b "); + sqlBuilder + .append(" inner join m_office as o on b.office_id = o.id "); + sqlBuilder + .append(" inner join m_client as c on b.client_id = c.id "); + sqlBuilder + .append(" inner join m_loan as l on b.account_id = l.id "); + sqlBuilder.append(" where b.is_active = 1 "); + sqlBuilder.append(" and b.account_type = 1 "); + sqlBuilder.append(" and b.app_user_id = ?) "); + + this.schemaSql = sqlBuilder.toString(); + } + + public String schema() { + return this.schemaSql; + } + + @Override + public SelfAccountTemplateData mapRow(final ResultSet rs, + @SuppressWarnings("unused") final int rowNum) + throws SQLException { + + final String officeName = rs.getString("officeName"); + final Long officeId = rs.getLong("officeId"); + final String clientName = rs.getString("clientName"); + final Long clientId = rs.getLong("clientId"); + final Integer accountTypeId = rs.getInt("accountType"); + final String accountNumber = rs.getString("accountNumber"); + final Long accountId = rs.getLong("accountId"); + + return new SelfAccountTemplateData(accountId, accountNumber, + accountTypeId, clientId, clientName, officeId, officeName); + } + } + + @Override + public Long getTransferLimit(Long appUserId, Long accountId, Integer accountType) { + final StringBuilder sqlBuilder = new StringBuilder("select b.transfer_limit "); + sqlBuilder.append(" from m_selfservice_beneficiaries_tpt as b "); + sqlBuilder.append(" where b.app_user_id = ? "); + sqlBuilder.append(" and b.account_id = ? "); + sqlBuilder.append(" and b.account_type = ? "); + sqlBuilder.append(" and b.is_active = 1; "); + + return this.jdbcTemplate.queryForObject(sqlBuilder.toString(), + new Object[]{appUserId, accountId, accountType}, Long.class); + } +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformService.java new file mode 100644 index 000000000..5d4519b87 --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformService.java @@ -0,0 +1,32 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.service; + +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; + +public interface SelfBeneficiariesTPTWritePlatformService { + + CommandProcessingResult add(JsonCommand command); + + CommandProcessingResult update(JsonCommand command); + + CommandProcessingResult delete(JsonCommand command); + +} diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformServiceImpl.java new file mode 100644 index 000000000..9a4e8fd2b --- /dev/null +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/self/account/service/SelfBeneficiariesTPTWritePlatformServiceImpl.java @@ -0,0 +1,212 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.portfolio.self.account.service; + +import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.ACCOUNT_NUMBER_PARAM_NAME; +import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.ACCOUNT_TYPE_PARAM_NAME; +import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.NAME_PARAM_NAME; +import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.OFFICE_NAME_PARAM_NAME; +import static org.apache.fineract.portfolio.self.account.api.SelfBeneficiariesTPTApiConstants.TRANSFER_LIMIT_PARAM_NAME; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.fineract.infrastructure.core.api.JsonCommand; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResult; +import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder; +import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException; +import org.apache.fineract.infrastructure.security.service.PlatformSecurityContext; +import org.apache.fineract.portfolio.account.PortfolioAccountType; +import org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.apache.fineract.portfolio.loanaccount.domain.LoanRepository; +import org.apache.fineract.portfolio.savings.domain.SavingsAccount; +import org.apache.fineract.portfolio.savings.domain.SavingsAccountRepository; +import org.apache.fineract.portfolio.self.account.data.SelfBeneficiariesTPTDataValidator; +import org.apache.fineract.portfolio.self.account.domain.SelfBeneficiariesTPT; +import org.apache.fineract.portfolio.self.account.domain.SelfBeneficiariesTPTRepository; +import org.apache.fineract.portfolio.self.account.exception.InvalidAccountInformationException; +import org.apache.fineract.portfolio.self.account.exception.InvalidBeneficiaryException; +import org.apache.fineract.useradministration.domain.AppUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class SelfBeneficiariesTPTWritePlatformServiceImpl implements + SelfBeneficiariesTPTWritePlatformService { + + private final Logger logger; + private final PlatformSecurityContext context; + private final SelfBeneficiariesTPTRepository repository; + private final SelfBeneficiariesTPTDataValidator validator; + private final LoanRepository loanRepo; + private final SavingsAccountRepository savingRepo; + + @Autowired + public SelfBeneficiariesTPTWritePlatformServiceImpl( + final PlatformSecurityContext context, + final SelfBeneficiariesTPTRepository repository, + final SelfBeneficiariesTPTDataValidator validator, + final LoanRepository loanRepo, + final SavingsAccountRepository savingRepo) { + this.context = context; + this.repository = repository; + this.validator = validator; + this.loanRepo = loanRepo; + this.savingRepo = savingRepo; + this.logger = LoggerFactory + .getLogger(SelfBeneficiariesTPTWritePlatformServiceImpl.class); + } + + @Transactional + @Override + public CommandProcessingResult add(JsonCommand command) { + HashMap params = this.validator + .validateForCreate(command.json()); + + String name = (String) params.get(NAME_PARAM_NAME); + Integer accountType = (Integer) params.get(ACCOUNT_TYPE_PARAM_NAME); + String accountNumber = (String) params.get(ACCOUNT_NUMBER_PARAM_NAME); + String officeName = (String) params.get(OFFICE_NAME_PARAM_NAME); + Long transferLimit = (Long) params.get(TRANSFER_LIMIT_PARAM_NAME); + + Long accountId = null; + Long clientId = null; + Long officeId = null; + + boolean validAccountDetails = true; + if (accountType.equals(PortfolioAccountType.LOAN)) { + Loan loan = this.loanRepo + .findNonClosedLoanByAccountNumber(accountNumber); + if (loan != null && loan.getClientId() != null + && loan.getOffice().getName().equals(officeName)) { + accountId = loan.getId(); + officeId = loan.getOfficeId(); + clientId = loan.getClientId(); + } else { + validAccountDetails = false; + } + } else { + SavingsAccount savings = this.savingRepo + .findNonClosedAccountByAccountNumber(accountNumber); + if (savings != null + && savings.getClient() != null + && savings.getClient().getOffice().getName() + .equals(officeName)) { + accountId = savings.getId(); + clientId = savings.getClient().getId(); + officeId = savings.getClient().getOffice().getId(); + } else { + validAccountDetails = false; + } + } + + if (validAccountDetails) { + try { + AppUser user = this.context.authenticatedUser(); + SelfBeneficiariesTPT beneficiary = new SelfBeneficiariesTPT( + user.getId(), name, officeId, clientId, accountId, + accountType, transferLimit); + this.repository.save(beneficiary); + return new CommandProcessingResultBuilder().withEntityId( + beneficiary.getId()).build(); + } catch (DataAccessException dae) { + handleDataIntegrityIssues(command, dae); + } + } + throw new InvalidAccountInformationException(officeName, accountNumber, + PortfolioAccountType.fromInt(accountType).getCode()); + + } + + @Transactional + @Override + public CommandProcessingResult update(JsonCommand command) { + HashMap params = this.validator + .validateForUpdate(command.json()); + AppUser user = this.context.authenticatedUser(); + Long beneficiaryId = command.entityId(); + SelfBeneficiariesTPT beneficiary = this.repository + .findOne(beneficiaryId); + if (beneficiary != null + && beneficiary.getAppUserId().equals(user.getId())) { + String name = (String) params.get(NAME_PARAM_NAME); + Long transferLimit = (Long) params.get(TRANSFER_LIMIT_PARAM_NAME); + + Map changes = beneficiary.update(name, + transferLimit); + if (!changes.isEmpty()) { + try { + this.repository.save(beneficiary); + + return new CommandProcessingResultBuilder() // + .withEntityId(beneficiary.getId()) // + .with(changes).build(); + } catch (DataAccessException dae) { + handleDataIntegrityIssues(command, dae); + } + + } + } + throw new InvalidBeneficiaryException(beneficiaryId); + } + + @Transactional + @Override + public CommandProcessingResult delete(JsonCommand command) { + AppUser user = this.context.authenticatedUser(); + Long beneficiaryId = command.entityId(); + SelfBeneficiariesTPT beneficiary = this.repository + .findOne(beneficiaryId); + if (beneficiary != null + && beneficiary.getAppUserId().equals(user.getId())) { + + beneficiary.setActive(false); + this.repository.save(beneficiary); + + return new CommandProcessingResultBuilder() // + .withEntityId(beneficiary.getId()) // + .build(); + } + throw new InvalidBeneficiaryException(beneficiaryId); + } + + private void handleDataIntegrityIssues(final JsonCommand command, + final DataAccessException dae) { + + final Throwable realCause = dae.getMostSpecificCause(); + if (realCause.getMessage().contains("name")) { + + final String name = command + .stringValueOfParameterNamed(NAME_PARAM_NAME); + throw new PlatformDataIntegrityException( + "error.msg.beneficiary.duplicate.name", + "Beneficiary with name `" + name + "` already exists", + NAME_PARAM_NAME, name); + } + + this.logger.error(dae.getMessage(), dae); + throw new PlatformDataIntegrityException( + "error.msg.beneficiary.unknown.data.integrity.issue", + "Unknown data integrity issue with resource."); + } +} diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V304__customer_self_service_third_party_transfers.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V304__customer_self_service_third_party_transfers.sql new file mode 100644 index 000000000..fc6be7735 --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V304__customer_self_service_third_party_transfers.sql @@ -0,0 +1,24 @@ +INSERT INTO `c_configuration` (`name`, `value`, `date_value`, `enabled`, `is_trap_door`, `description`) VALUES ( 'daily-tpt-limit', 0, NULL, 0, 0, 'Daily limit for third party transfers'); + +CREATE TABLE `m_selfservice_beneficiaries_tpt` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `app_user_id` BIGINT NOT NULL, + `name` VARCHAR(50) NOT NULL, + `office_id` BIGINT NOT NULL, + `client_id` BIGINT NOT NULL, + `account_id` BIGINT NOT NULL, + `account_type` SMALLINT(4) NOT NULL, + `transfer_limit` BIGINT NULL DEFAULT 0, + `is_active` BIT(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE INDEX `name` (`name`, `app_user_id`, `is_active`) +) +COLLATE='utf8_general_ci' +ENGINE=InnoDB +; + +INSERT INTO `m_permission`(`grouping`, `code`, `entity_name`, `action_name`, `can_maker_checker`) VALUES +('SSBENEFICIARYTPT', 'READ_SSBENEFICIARYTPT', 'SSBENEFICIARYTPT', 'READ', 0), +('SSBENEFICIARYTPT', 'CREATE_SSBENEFICIARYTPT', 'SSBENEFICIARYTPT', 'CREATE', 0), +('SSBENEFICIARYTPT', 'UPDATE_SSBENEFICIARYTPT', 'SSBENEFICIARYTPT', 'UPDATE', 0), +('SSBENEFICIARYTPT', 'DELETE_SSBENEFICIARYTPT', 'SSBENEFICIARYTPT', 'DELETE', 0);