From 3f1df7780cbb0961730b370f9b3614d636ed5076 Mon Sep 17 00:00:00 2001 From: rrpawar96 Date: Sat, 25 Dec 2021 23:09:53 +0530 Subject: [PATCH] FINERACT-1393:RandomAccountNumber --- .../client/domain/AccountNumberGenerator.java | 80 ++++++++++++++++++- .../loanaccount/domain/LoanRepository.java | 9 ++- .../domain/SavingsAccountRepository.java | 3 + ...onfiguration_for_random_account_number.sql | 20 +++++ .../common/GlobalConfigurationHelper.java | 14 +++- 5 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 fineract-provider/src/main/resources/sql/migrations/core_db/V380__configuration_for_random_account_number.sql diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/AccountNumberGenerator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/AccountNumberGenerator.java index 1195bd69b..0e185fa58 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/AccountNumberGenerator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/client/domain/AccountNumberGenerator.java @@ -18,8 +18,10 @@ */ package org.apache.fineract.portfolio.client.domain; +import java.math.BigInteger; import java.util.HashMap; import java.util.Map; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormat; import org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatEnumerations.AccountNumberPrefixType; @@ -29,7 +31,9 @@ import org.apache.fineract.infrastructure.configuration.data.GlobalConfiguration import org.apache.fineract.infrastructure.configuration.service.ConfigurationReadPlatformService; import org.apache.fineract.portfolio.group.domain.Group; 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.shareaccounts.domain.ShareAccount; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -44,6 +48,7 @@ public class AccountNumberGenerator { private static final int maxLength = 9; private static final String ID = "id"; + private static final String ENTITY_TYPE = "entityType"; private static final String CLIENT_TYPE = "clientType"; private static final String OFFICE_NAME = "officeName"; private static final String LOAN_PRODUCT_SHORT_NAME = "loanProductShortName"; @@ -52,18 +57,26 @@ public class AccountNumberGenerator { private static final String PREFIX_SHORT_NAME = "prefixShortName"; private final AccountNumberFormatRepository accountNumberFormatRepository; private final ConfigurationReadPlatformService configurationReadPlatformService; + private final ClientRepository clientRepository; + private final LoanRepository loanRepository; + private final SavingsAccountRepository savingsAccountRepository; @Autowired public AccountNumberGenerator(final ConfigurationReadPlatformService configurationReadPlatformService, - final AccountNumberFormatRepository accountNumberFormatRepository) { + final AccountNumberFormatRepository accountNumberFormatRepository, final ClientRepository clientRepository, + final LoanRepository loanRepository, final SavingsAccountRepository savingsAccountRepository) { this.configurationReadPlatformService = configurationReadPlatformService; this.accountNumberFormatRepository = accountNumberFormatRepository; + this.clientRepository = clientRepository; + this.loanRepository = loanRepository; + this.savingsAccountRepository = savingsAccountRepository; } public String generate(Client client, AccountNumberFormat accountNumberFormat) { Map propertyMap = new HashMap<>(); propertyMap.put(ID, client.getId().toString()); propertyMap.put(OFFICE_NAME, client.getOffice().getName()); + propertyMap.put(ENTITY_TYPE, "client"); CodeValue clientType = client.clientType(); if (clientType != null) { propertyMap.put(CLIENT_TYPE, clientType.label()); @@ -76,6 +89,7 @@ public class AccountNumberGenerator { propertyMap.put(ID, loan.getId().toString()); propertyMap.put(OFFICE_NAME, loan.getOffice().getName()); propertyMap.put(LOAN_PRODUCT_SHORT_NAME, loan.loanProduct().getShortName()); + propertyMap.put(ENTITY_TYPE, "loan"); return generateAccountNumber(propertyMap, accountNumberFormat); } @@ -84,6 +98,7 @@ public class AccountNumberGenerator { propertyMap.put(ID, savingsAccount.getId().toString()); propertyMap.put(OFFICE_NAME, savingsAccount.office().getName()); propertyMap.put(SAVINGS_PRODUCT_SHORT_NAME, savingsAccount.savingsProduct().getShortName()); + propertyMap.put(ENTITY_TYPE, "savingsAccount"); return generateAccountNumber(propertyMap, accountNumberFormat); } @@ -96,6 +111,7 @@ public class AccountNumberGenerator { private String generateAccountNumber(Map propertyMap, AccountNumberFormat accountNumberFormat) { int accountMaxLength = AccountNumberGenerator.maxLength; + String accountNumber = StringUtils.leftPad(propertyMap.get(ID), accountMaxLength, '0'); // find if the custom length is defined final GlobalConfigurationPropertyData customLength = this.configurationReadPlatformService @@ -108,7 +124,14 @@ public class AccountNumberGenerator { } } - String accountNumber = StringUtils.leftPad(propertyMap.get(ID), accountMaxLength, '0'); + final GlobalConfigurationPropertyData randomAccountNumber = this.configurationReadPlatformService + .retrieveGlobalConfiguration("random-account-number"); + + if (randomAccountNumber.isEnabled()) { + accountNumber = randomNumberGenerator(accountMaxLength, propertyMap); + } + + accountNumber = StringUtils.leftPad(accountNumber, accountMaxLength, '0'); if (accountNumberFormat != null && accountNumberFormat.getPrefixEnum() != null) { AccountNumberPrefixType accountNumberPrefixType = AccountNumberPrefixType.fromInt(accountNumberFormat.getPrefixEnum()); String prefix = null; @@ -143,17 +166,66 @@ public class AccountNumberGenerator { } if (accountNumberPrefixType.getValue().equals(AccountNumberPrefixType.PREFIX_SHORT_NAME.getValue())) { Integer prefixLength = prefix.length(); - Integer numberLength = accountMaxLength - prefixLength; - accountNumber = StringUtils.leftPad(propertyMap.get(ID), numberLength, '0'); + + if (randomAccountNumber.isEnabled()) { + accountNumber = accountNumber.substring(prefixLength); + } else { + Integer numberLength = accountMaxLength - prefixLength; + accountNumber = StringUtils.leftPad(propertyMap.get(ID), numberLength, '0'); + } } else { accountNumber = StringUtils.leftPad(accountNumber, Integer.valueOf(propertyMap.get(ID).length()), '0'); } accountNumber = StringUtils.overlay(accountNumber, prefix, 0, 0); } + + if (randomAccountNumber.isEnabled()) { // calling the main function itself until new randomNo. + Boolean randomNumberConflict = checkAccountNumberConflict(propertyMap, accountNumberFormat, accountNumber); + if (randomNumberConflict) { + accountNumber = generateAccountNumber(propertyMap, accountNumberFormat); + } + } return accountNumber; } + private String randomNumberGenerator(int accountMaxLength, Map propertyMap) { + String randomNumber = RandomStringUtils.random(accountMaxLength, false, true); + + BigInteger accNumber = new BigInteger(randomNumber); + if (accNumber.equals(BigInteger.ZERO)) { // to avoid account no. 00 in randomisation + randomNumber = randomNumberGenerator(accountMaxLength, propertyMap); + } + + String accountNumber = randomNumber.substring(0, accountMaxLength); + return accountNumber; + } + + private Boolean checkAccountNumberConflict(Map propertyMap, AccountNumberFormat accountNumberFormat, + String accountNumber) { + + String entityType = propertyMap.get(ENTITY_TYPE); + Boolean randomNumberConflict = false; + if (entityType.equals("client")) { // avoid duplication it will loop until it finds new random account no. + + Client client = this.clientRepository.getClientByAccountNumber(accountNumber); + if (client != null) { + randomNumberConflict = true; + } + } else if (entityType.equals("loan")) { + Loan loan = this.loanRepository.findLoanAccountByAccountNumber(accountNumber); + if (loan != null) { + randomNumberConflict = true; + } + } else if (entityType.equals("savingsAccount")) { + SavingsAccount savingsAccount = this.savingsAccountRepository.findSavingsAccountByAccountNumber(accountNumber); + if (savingsAccount != null) { + randomNumberConflict = true; + } + } + return randomNumberConflict; + } + private Map generatePrefix(Map propertyMap, String accountNumber, Integer accountMaxLength, AccountNumberFormat accountNumberFormat) { 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 658756040..16c8216cd 100644 --- 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 @@ -67,10 +67,12 @@ public interface LoanRepository extends JpaRepository, JpaSpecificat 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)"; - String FIND_BY_ACCOUNT_NUMBER = "select loan from Loan loan where loan.accountNumber = :accountNumber and loan.loanStatus in (100,200,300,303,304)"; + String FIND_NON_CLOSED_BY_ACCOUNT_NUMBER = "select loan from Loan loan where loan.accountNumber = :accountNumber and loan.loanStatus in (100,200,300,303,304)"; String FIND_NON_CLOSED_LOAN_THAT_BELONGS_TO_CLIENT = "select loan from Loan loan where loan.id = :loanId and loan.loanStatus = 300 and loan.client.id = :clientId"; + String FIND_BY_ACCOUNT_NUMBER = "select loan from Loan loan where loan.accountNumber = :accountNumber"; + @Query(FIND_GROUP_LOANS_DISBURSED_AFTER) List getGroupLoansDisbursedAfter(@Param("disbursementDate") Date disbursementDate, @Param("groupId") Long groupId, @Param("loanType") Integer loanType); @@ -150,10 +152,13 @@ public interface LoanRepository extends JpaRepository, JpaSpecificat @Query(DOES_PRODUCT_HAVE_NON_CLOSED_LOANS) boolean doNonClosedLoanAccountsExistForProduct(@Param("productId") Long productId); - @Query(FIND_BY_ACCOUNT_NUMBER) + @Query(FIND_NON_CLOSED_BY_ACCOUNT_NUMBER) Loan findNonClosedLoanByAccountNumber(@Param("accountNumber") String accountNumber); @Query(FIND_NON_CLOSED_LOAN_THAT_BELONGS_TO_CLIENT) Loan findNonClosedLoanThatBelongsToClient(@Param("loanId") Long loanId, @Param("clientId") Long clientId); + @Query(FIND_BY_ACCOUNT_NUMBER) + Loan findLoanAccountByAccountNumber(@Param("accountNumber") String accountNumber); + } 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 f7454b573..514238a38 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 @@ -60,6 +60,9 @@ public interface SavingsAccountRepository extends JpaRepository findByStatus(Integer status, Pageable pageable); SavingsAccount findByExternalId(String externalId); diff --git a/fineract-provider/src/main/resources/sql/migrations/core_db/V380__configuration_for_random_account_number.sql b/fineract-provider/src/main/resources/sql/migrations/core_db/V380__configuration_for_random_account_number.sql new file mode 100644 index 000000000..034ec4cc1 --- /dev/null +++ b/fineract-provider/src/main/resources/sql/migrations/core_db/V380__configuration_for_random_account_number.sql @@ -0,0 +1,20 @@ +-- +-- 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. +-- + +INSERT INTO c_configuration ( name, description) VALUES ( 'random-account-number', 'if enabled, the client accounts, saving accounts, loan accounts will be created with Random Account Number'); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java index 99f08bf2f..9ff751e48 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/GlobalConfigurationHelper.java @@ -89,9 +89,9 @@ public class GlobalConfigurationHelper { ArrayList expectedGlobalConfigurations = getAllDefaultGlobalConfigurations(); ArrayList actualGlobalConfigurations = getAllGlobalConfigurations(requestSpec, responseSpec); - // There are currently 36 global configurations. - Assertions.assertEquals(36, expectedGlobalConfigurations.size()); - Assertions.assertEquals(36, actualGlobalConfigurations.size()); + // There are currently 37 global configurations. + Assertions.assertEquals(37, expectedGlobalConfigurations.size()); + Assertions.assertEquals(37, actualGlobalConfigurations.size()); for (int i = 0; i < expectedGlobalConfigurations.size(); i++) { @@ -415,6 +415,14 @@ public class GlobalConfigurationHelper { isClientAccountNumberLengthModify.put("trapDoor", false); defaults.add(isClientAccountNumberLengthModify); + HashMap isAccountNumberRandomGenerated = new HashMap<>(); + isAccountNumberRandomGenerated.put("id", 41); + isAccountNumberRandomGenerated.put("name", "random-account-number"); + isAccountNumberRandomGenerated.put("value", 0); + isAccountNumberRandomGenerated.put("enabled", false); + isAccountNumberRandomGenerated.put("trapDoor", false); + defaults.add(isAccountNumberRandomGenerated); + return defaults; }