From 7368c3f11add84ddf8dcee086eccc7b8f69d518a Mon Sep 17 00:00:00 2001 From: ShruthiRajaram Date: Wed, 12 Jul 2017 17:59:18 +0530 Subject: [PATCH] FINERACT-482 block account changes --- .../ClientSavingsIntegrationTest.java | 12 ++++-- .../common/savings/SavingsAccountHelper.java | 1 + .../savings/SavingsApiConstants.java | 1 + .../savings/api/SavingsApiSetConstants.java | 2 +- .../savings/data/SavingsAccountData.java | 21 ++++++---- ...avingsAccountTransactionDataValidator.java | 38 +++++++++++------ .../savings/domain/SavingsAccount.java | 41 +++++++++++++++---- .../domain/SavingsAccountTransaction.java | 4 ++ ...SavingsAccountReadPlatformServiceImpl.java | 15 +++++-- ...WritePlatformServiceJpaRepositoryImpl.java | 5 ++- ..._savings_account_transaction_releaseId.sql | 4 ++ 11 files changed, 106 insertions(+), 38 deletions(-) diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java index b279163e1..1ae2609f0 100755 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java @@ -28,10 +28,12 @@ import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Locale; +import org.apache.fineract.infrastructure.core.service.DateUtils; import org.apache.fineract.integrationtests.common.ClientHelper; import org.apache.fineract.integrationtests.common.CommonConstants; import org.apache.fineract.integrationtests.common.SchedulerJobHelper; @@ -2214,13 +2216,17 @@ public class ClientSavingsIntegrationTest { Integer holdTransactionId = (Integer) this.savingsAccountHelper.holdAmountInSavingsAccount(savingsId, String.valueOf(balance - 100), SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); error = (List) savingsAccountHelperValidationError.withdrawalFromSavingsAccount(savingsId, "300", - SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_ERROR); + SavingsAccountHelper.TRANSACTION_DATE_PLUS_ONE, CommonConstants.RESPONSE_ERROR); assertEquals("error.msg.savingsaccount.transaction.insufficient.account.balance", error.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE)); Integer releaseTransactionId = this.savingsAccountHelper.releaseAmount(savingsId, holdTransactionId); - withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "300", - SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + Date today = DateUtils.getDateOfTenant(); + String todayDate = today.toString(); + SimpleDateFormat dt1 = new SimpleDateFormat("dd MMM yyyy"); + todayDate = dt1.format(today).toString(); + withdrawTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "300", todayDate, + CommonConstants.RESPONSE_RESOURCE_ID); withdrawTransaction = this.savingsAccountHelper.getSavingsTransaction(savingsId, withdrawTransactionId); balance -= new Float("300"); assertEquals("Verifying Withdrawal Amount", new Float("300"), withdrawTransaction.get("amount")); diff --git a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java index c2cd4f92a..d0752a327 100644 --- a/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java +++ b/fineract-provider/src/integrationTest/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java @@ -69,6 +69,7 @@ public class SavingsAccountHelper { public static final String CREATED_DATE_PLUS_ONE = "09 January 2013"; public static final String CREATED_DATE_MINUS_ONE = "07 January 2013"; public static final String TRANSACTION_DATE = "01 March 2013"; + public static final String TRANSACTION_DATE_PLUS_ONE = "02 March 2013"; public static final String LAST_TRANSACTION_DATE = "01 March 2013"; public static final String ACCOUNT_TYPE_INDIVIDUAL = "INDIVIDUAL"; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java index 1b8aa53aa..a2826ab66 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/SavingsApiConstants.java @@ -133,6 +133,7 @@ public class SavingsApiConstants { public static final String minBalanceForInterestCalculationParamName = "minBalanceForInterestCalculation"; public static final String withdrawBalanceParamName = "withdrawBalance"; public static final String onHoldFundsParamName = "onHoldFunds"; + public static final String savingsAmountOnHold = "savingsAmountOnHold"; public static final String withHoldTaxParamName = "withHoldTax"; public static final String taxGroupIdParamName = "taxGroupId"; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsApiSetConstants.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsApiSetConstants.java index 70ac7b487..36d319a95 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsApiSetConstants.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsApiSetConstants.java @@ -65,7 +65,7 @@ public class SavingsApiSetConstants extends SavingsApiConstants { "interestCalculationTypeOptions", "interestCalculationDaysInYearTypeOptions", "lockinPeriodFrequencyTypeOptions", "withdrawalFeeTypeOptions", "withdrawalFee", "annualFee", onHoldFundsParamName, nominalAnnualInterestRateOverdraftParamName, - minOverdraftForInterestCalculationParamName, datatables)); + minOverdraftForInterestCalculationParamName, datatables, savingsAmountOnHold)); protected static final Set SAVINGS_TRANSACTION_RESPONSE_DATA_PARAMETERS = new HashSet<>( Arrays.asList(idParamName, "accountId", accountNoParamName, "currency", "amount", dateParamName, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java index d06bbe934..32a0630fe 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountData.java @@ -76,6 +76,7 @@ public class SavingsAccountData { private final Integer daysToInactive; private final Integer daysToDormancy; private final Integer daysToEscheat; + private final BigDecimal savingsAmountOnHold; // associations private final SavingsAccountSummaryData summary; @@ -118,7 +119,7 @@ public class SavingsAccountData { final BigDecimal onHoldFunds, final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup, final LocalDate lastActiveTransactionDate, final boolean isDormancyTrackingActive, final Integer daysToInactive, - final Integer daysToDormancy, final Integer daysToEscheat) { + final Integer daysToDormancy, final Integer daysToEscheat, final BigDecimal savingsAmountOnHold) { final Collection productOptions = null; final Collection fieldOfficerOptions = null; @@ -142,7 +143,7 @@ public class SavingsAccountData { lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, - lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat); + lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold); } public static SavingsAccountData lookup(final Long accountId, final String accountNo, final EnumOptionData depositType) { @@ -204,6 +205,7 @@ public class SavingsAccountData { final Integer daysToInactive = null; final Integer daysToDormancy = null; final Integer daysToEscheat = null; + final BigDecimal savingsAmountOnHold = null; return new SavingsAccountData(accountId, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, nominalAnnualInterestRate, @@ -214,7 +216,7 @@ public class SavingsAccountData { lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, - lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat); + lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold); } public static SavingsAccountData lookupWithProductDetails(final Long accountId, final String accountNo, @@ -274,6 +276,7 @@ public class SavingsAccountData { final Integer daysToInactive = null; final Integer daysToDormancy = null; final Integer daysToEscheat = null; + final BigDecimal savingsAmountOnHold = null; return new SavingsAccountData(accountId, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, nominalAnnualInterestRate, @@ -284,7 +287,7 @@ public class SavingsAccountData { lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, - lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat); + lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold); } public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, final SavingsAccountData template, @@ -320,7 +323,7 @@ public class SavingsAccountData { account.minBalanceForInterestCalculation, account.onHoldFunds, account.nominalAnnualInterestRateOverdraft, account.minOverdraftForInterestCalculation, account.withHoldTax, account.taxGroup, account.lastActiveTransactionDate, account.isDormancyTrackingActive, account.daysToInactive, - account.daysToDormancy, account.daysToEscheat); + account.daysToDormancy, account.daysToEscheat, account.savingsAmountOnHold); } public static SavingsAccountData withTemplateOptions(final SavingsAccountData account, @@ -345,7 +348,7 @@ public class SavingsAccountData { account.overdraftLimit, account.minRequiredBalance, account.enforceMinRequiredBalance, account.minBalanceForInterestCalculation, account.onHoldFunds, account.nominalAnnualInterestRateOverdraft, account.minOverdraftForInterestCalculation, account.withHoldTax, account.taxGroup, account.lastActiveTransactionDate, - account.isDormancyTrackingActive, account.daysToInactive, account.daysToDormancy, account.daysToEscheat); + account.isDormancyTrackingActive, account.daysToInactive, account.daysToDormancy, account.daysToEscheat, account.savingsAmountOnHold); } public static SavingsAccountData withClientTemplate(final Long clientId, final String clientName, final Long groupId, @@ -407,6 +410,7 @@ public class SavingsAccountData { final Integer daysToInactive = null; final Integer daysToDormancy = null; final Integer daysToEscheat = null; + final BigDecimal savingsAmountOnHold = null; return new SavingsAccountData(id, accountNo, depositType, externalId, groupId, groupName, clientId, clientName, productId, productName, fieldOfficerId, fieldOfficerName, status, subStatus, timeline, currency, nominalAnnualInterestRate, @@ -417,7 +421,7 @@ public class SavingsAccountData { lockinPeriodFrequencyTypeOptions, withdrawalFeeTypeOptions, charges, chargeOptions, allowOverdraft, overdraftLimit, minRequiredBalance, enforceMinRequiredBalance, minBalanceForInterestCalculation, onHoldFunds, nominalAnnualInterestRateOverdraft, minOverdraftForInterestCalculation, withHoldTax, taxGroup, - lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat); + lastActiveTransactionDate, isDormancyTrackingActive, daysToInactive, daysToDormancy, daysToEscheat, savingsAmountOnHold); } private SavingsAccountData(final Long id, final String accountNo, final EnumOptionData depositType, final String externalId, @@ -441,7 +445,7 @@ public class SavingsAccountData { final BigDecimal onHoldFunds, final BigDecimal nominalAnnualInterestRateOverdraft, final BigDecimal minOverdraftForInterestCalculation, final boolean withHoldTax, final TaxGroupData taxGroup, final LocalDate lastActiveTransactionDate, final boolean isDormancyTrackingActive, final Integer daysToInactive, - final Integer daysToDormancy, final Integer daysToEscheat) { + final Integer daysToDormancy, final Integer daysToEscheat, final BigDecimal savingsAmountOnHold) { this.id = id; this.accountNo = accountNo; this.depositType = depositType; @@ -507,6 +511,7 @@ public class SavingsAccountData { this.daysToInactive = daysToInactive; this.daysToDormancy = daysToDormancy; this.daysToEscheat = daysToEscheat; + this.savingsAmountOnHold = savingsAmountOnHold; } private SavingsAccountChargeData getWithdrawalFee() { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java index fcd7a8878..02ae1026b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionDataValidator.java @@ -121,7 +121,7 @@ public class SavingsAccountTransactionDataValidator { throwExceptionIfValidationWarningsExist(dataValidationErrors); } - public void validateClosing(final JsonCommand command) { + public void validateClosing(final JsonCommand command, final SavingsAccount account) { final String json = command.json(); if (StringUtils.isBlank(json)) { throw new InvalidJsonException(); } @@ -144,6 +144,11 @@ public class SavingsAccountTransactionDataValidator { baseDataValidator.reset().parameter(withdrawBalanceParamName).value(withdrawBalance).isOneOfTheseValues(true, false); } + if (account.getSavingsHoldAmount().compareTo(BigDecimal.ZERO) == 1) { + baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( + "amount.is.on.hold.release.the.amount.to.continue", account.getId()); + } + validatePaymentTypeDetails(baseDataValidator, element); throwExceptionIfValidationWarningsExist(dataValidationErrors); @@ -192,10 +197,9 @@ public class SavingsAccountTransactionDataValidator { baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName) .failWithCodeNoParameterAddedToErrorCode(SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE); } - account.holdFunds(amount); - if (account.getWithdrawableBalance().compareTo(BigDecimal.ZERO) == -1) { + account.holdAmount(amount); + if (account.getWithdrawableBalance().compareTo(BigDecimal.ZERO)==-1){ baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("insufficient balance", account.getId()); - baseDataValidator.failWithCode("validation.msg.savingsaccount.insufficient balance", "Insufficient balance"); } LocalDate lastTransactionDate = account.retrieveLastTransactionDate(); // compare two dates now @@ -206,7 +210,8 @@ public class SavingsAccountTransactionDataValidator { throwExceptionIfValidationWarningsExist(dataValidationErrors); final PaymentDetail paymentDetails = null; - Date createdDate = DateUtils.getDateOfTenant(); + Date createdDate = new Date(); + SavingsAccountTransaction transaction = SavingsAccountTransaction.holdAmount(account, account.office(), paymentDetails, transactionDate, Money.of(account.getCurrency(), amount), createdDate, createdUser); return transaction; @@ -217,15 +222,24 @@ public class SavingsAccountTransactionDataValidator { final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) .resource(SAVINGS_ACCOUNT_RESOURCE_NAME); - if (holdTransaction == null) { - baseDataValidator.failWithCode("validation.msg.validation.errors.exist", "Transaction not found"); - } else if (holdTransaction.getReleaseIdOfHoldAmountTransaction() != null) { - baseDataValidator.parameter(SavingsApiConstants.amountParamName).value(holdTransaction.getAmount()).failWithCode("validation.msg.amount.is.not.on.hold", - "Transaction amount is not on hold"); - } + if (holdTransaction == null) { + baseDataValidator.failWithCode("validation.msg.validation.errors.exist", "Transaction not found"); + } else if (holdTransaction.getReleaseIdOfHoldAmountTransaction() != null) { + baseDataValidator.parameter(SavingsApiConstants.amountParamName).value(holdTransaction.getAmount()) + .failWithCode("validation.msg.amount.is.not.on.hold", "Transaction amount is not on hold"); + } + + if (holdTransaction != null) { + boolean isActive = holdTransaction.getSavingsAccount().isActive(); + if (!isActive) { + baseDataValidator.reset().parameter(SavingsApiConstants.statusParamName) + .failWithCodeNoParameterAddedToErrorCode( + SavingsApiConstants.ERROR_MSG_SAVINGS_ACCOUNT_NOT_ACTIVE); + } + } throwExceptionIfValidationWarningsExist(dataValidationErrors); - Date createdDate = DateUtils.getDateOfTenant(); + Date createdDate = new Date(); LocalDate transactionDate = DateUtils.getLocalDateOfTenant(); SavingsAccountTransaction transaction = SavingsAccountTransaction.releaseAmount(holdTransaction, transactionDate, createdDate, createdUser); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java index f904b0950..0acd0969f 100755 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccount.java @@ -325,6 +325,8 @@ public class SavingsAccount extends AbstractPersistableCustom { @JoinColumn(name = "tax_group_id") private TaxGroup taxGroup; + @Column(name = "total_savings_amount_on_hold", scale = 6, precision = 19, nullable = true) + private BigDecimal savingsOnHoldAmount; protected SavingsAccount() { // } @@ -1051,19 +1053,16 @@ public class SavingsAccount extends AbstractPersistableCustom { Money runningBalance = Money.zero(this.currency); Money minRequiredBalance = minRequiredBalanceDerived(getCurrency()); LocalDate lastSavingsDate = null; + final BigDecimal withdrawalFee = null; for (final SavingsAccountTransaction transaction : transactionsSortedByDate) { if (transaction.isNotReversed() && transaction.isCredit()) { runningBalance = runningBalance.plus(transaction.getAmount(this.currency)); } else if (transaction.isNotReversed() && transaction.isDebit()) { runningBalance = runningBalance.minus(transaction.getAmount(this.currency)); - } else if(transaction.isAmountOnHold() && transaction.getReleaseIdOfHoldAmountTransaction() == null){ - runningBalance = runningBalance.minus(transaction.getAmount(this.currency)); }else { continue; } - final BigDecimal withdrawalFee = null; - /* * Loop through the onHold funds and see if we need to deduct or add * to minimum required balance and the point in time the transaction @@ -1093,6 +1092,12 @@ public class SavingsAccount extends AbstractPersistableCustom { lastSavingsDate = transaction.transactionLocalDate(); } + if (this.getSavingsHoldAmount().compareTo(BigDecimal.ZERO) == 1) { + if (runningBalance.minus(this.getSavingsHoldAmount()).isLessThanZero()) { + throw new InsufficientAccountBalanceException("transactionAmount", getAccountBalance(), withdrawalFee, + transactionAmount); + } + } } public void validateAccountBalanceDoesNotBecomeNegative(final String transactionAction, @@ -2753,7 +2758,7 @@ public class SavingsAccount extends AbstractPersistableCustom { } public BigDecimal getWithdrawableBalance() { - return getAccountBalance().subtract(minRequiredBalanceDerived(getCurrency()).getAmount()).subtract(this.getOnHoldFunds()); + return getAccountBalance().subtract(minRequiredBalanceDerived(getCurrency()).getAmount()).subtract(this.getOnHoldFunds()).subtract(this.getSavingsHoldAmount()); } public TaxGroup getTaxGroup() { @@ -3006,9 +3011,27 @@ public class SavingsAccount extends AbstractPersistableCustom { public LocalDate retrieveLastTransactionDate() { final List transactionsSortedByDate = retreiveListOfTransactions(); - SavingsAccountTransaction lastTransaction = transactionsSortedByDate.get(transactionsSortedByDate.size() - 1); - return lastTransaction.transactionLocalDate(); + SavingsAccountTransaction lastTransaction = null; + if (transactionsSortedByDate.size() > 0) { + lastTransaction = transactionsSortedByDate.get(transactionsSortedByDate.size() - 1); + } + LocalDate lastransactionDate = null; + if (lastTransaction != null) { + lastransactionDate = lastTransaction.transactionLocalDate(); + } + return lastransactionDate; } - - + + public BigDecimal getSavingsHoldAmount() { + return this.savingsOnHoldAmount == null ? BigDecimal.ZERO : this.savingsOnHoldAmount; + } + + public void holdAmount(BigDecimal amount) { + this.savingsOnHoldAmount = getSavingsHoldAmount().add(amount); + } + + public void releaseAmount(BigDecimal amount) { + this.savingsOnHoldAmount = getSavingsHoldAmount().subtract(amount); + } + } \ No newline at end of file diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java index 12de52762..cf5ae0354 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransaction.java @@ -787,4 +787,8 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom