From ff2e6cf4af04e401735c083141413e15cb89351a Mon Sep 17 00:00:00 2001 From: Dhaval Maniyar Date: Mon, 20 Jun 2022 14:09:01 +0530 Subject: [PATCH] FINERACT-1642:Bulk reversal of related Transactions --- ...SavingsAccountTransactionsApiResource.java | 8 ++ ...AccountTransactionsApiResourceSwagger.java | 9 ++ .../data/SavingsAccountTransactionData.java | 5 ++ .../savings/domain/FixedDepositAccount.java | 4 +- .../domain/RecurringDepositAccount.java | 4 +- .../savings/domain/SavingsAccount.java | 65 ++++++++------ .../domain/SavingsAccountDomainService.java | 3 +- .../SavingsAccountDomainServiceJpa.java | 53 +++++++----- .../savings/domain/SavingsAccountSummary.java | 6 +- .../domain/SavingsAccountTransaction.java | 75 +++++++++++------ .../SavingsAccountTransactionRepository.java | 2 + ...vingsAccountTransactionSummaryWrapper.java | 42 +++++----- ...ansactionSavingsAccountCommandHandler.java | 2 +- ...WritePlatformServiceJpaRepositoryImpl.java | 17 ++-- .../SavingsAccountWritePlatformService.java | 3 +- ...WritePlatformServiceJpaRepositoryImpl.java | 46 ++++++---- .../db/changelog/tenant/changelog-tenant.xml | 1 + ...16_changed_unique_constraint_of_ref_no.xml | 31 +++++++ .../ClientSavingsIntegrationTest.java | 84 +++++++++++++++++++ .../common/savings/SavingsAccountHelper.java | 18 ++++ 20 files changed, 353 insertions(+), 125 deletions(-) create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant/parts/0016_changed_unique_constraint_of_ref_no.xml diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java index a9c8161a2..582837aa3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResource.java @@ -18,6 +18,8 @@ */ package org.apache.fineract.portfolio.savings.api; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.parameters.RequestBody; @@ -182,6 +184,12 @@ public class SavingsAccountTransactionsApiResource { @Path("{transactionId}") @Consumes({ MediaType.APPLICATION_JSON }) @Produces({ MediaType.APPLICATION_JSON }) + @Operation(summary = "Undo/Reverse/Modify/Release Amount transaction API", description = "Undo/Reverse/Modify/Release Amount transaction API\n\n" + + "Example Requests:\n" + "\n" + "\n" + "savingsaccounts/{savingsId}/transactions/{transactionId}?command=reverse\n" + "\n" + + "Accepted command = undo, reverse, modify, releaseAmount") + @RequestBody(required = true, content = @Content(schema = @Schema(implementation = SavingsAccountTransactionsApiResourceSwagger.PostSavingsAccountBulkReversalTransactionsRequest.class))) + @ApiResponses({ + @ApiResponse(responseCode = "200", description = "OK", content = @Content(array = @ArraySchema(schema = @Schema(implementation = SavingsAccountTransactionsApiResourceSwagger.PostSavingsAccountBulkReversalTransactionsRequest.class)))) }) public String adjustTransaction(@PathParam("savingsId") final Long savingsId, @PathParam("transactionId") final Long transactionId, @QueryParam("command") final String commandParam, final String apiRequestBodyAsJson) { diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResourceSwagger.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResourceSwagger.java index 96e2a5347..a2ab98316 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResourceSwagger.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/api/SavingsAccountTransactionsApiResourceSwagger.java @@ -57,4 +57,13 @@ final class SavingsAccountTransactionsApiResourceSwagger { @Schema(example = "1") public Integer resourceId; } + + @Schema(description = "PostSavingsAccountBulkReversalTransactionsRequest") + public static final class PostSavingsAccountBulkReversalTransactionsRequest { + + private PostSavingsAccountBulkReversalTransactionsRequest() {} + + @Schema(example = "true") + public String isBulk; + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java index 5afb3fb71..c96077aa5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/data/SavingsAccountTransactionData.java @@ -948,4 +948,9 @@ public final class SavingsAccountTransactionData implements Serializable { this.id = id; this.modifiedId = id; } + + public boolean isReversalTransaction() { + return Boolean.TRUE.equals(this.isReversal); + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java index c8385ca0e..d0b43b28a 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/FixedDepositAccount.java @@ -639,13 +639,13 @@ public class FixedDepositAccount extends SavingsAccount { return interestOnMaturity; } - @Override public void postInterest(final MathContext mc, final LocalDate postingDate, boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill) { final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); + boolean postReversals = false; super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill); + financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); } @Override diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java index 90d078ae2..b228fef3b 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/RecurringDepositAccount.java @@ -752,13 +752,13 @@ public class RecurringDepositAccount extends SavingsAccount { return interestOnMaturity; } - @Override public void postInterest(final MathContext mc, final LocalDate postingDate, final boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, final LocalDate postInterestAson, final boolean backdatedTxnsAllowedTill) { final LocalDate interestPostingUpToDate = interestPostingUpToDate(postingDate); + boolean postReversals = false; super.postInterest(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestAson, backdatedTxnsAllowedTill); + financialYearBeginningMonth, postInterestAson, backdatedTxnsAllowedTill, postReversals); } @Override 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 ee20f96d2..ce6ece216 100644 --- 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 @@ -53,6 +53,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.UUID; import java.util.concurrent.TimeUnit; import javax.persistence.CascadeType; import javax.persistence.Column; @@ -511,7 +512,7 @@ public class SavingsAccount extends AbstractPersistableCustom { public void postInterest(final MathContext mc, final LocalDate interestPostingUpToDate, final boolean isInterestTransfer, final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, - final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill) { + final LocalDate postInterestOnDate, final boolean backdatedTxnsAllowedTill, final boolean postReversals) { final List postingPeriods = calculateInterestUsing(mc, interestPostingUpToDate, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill); @@ -570,6 +571,10 @@ public class SavingsAccount extends AbstractPersistableCustom { if (correctionRequired) { boolean applyWithHoldTaxForOldTransaction = false; postingTransaction.reverse(); + SavingsAccountTransaction reversal = null; + if (postReversals) { + reversal = SavingsAccountTransaction.reversal(postingTransaction); + } final SavingsAccountTransaction withholdTransaction = findTransactionFor(interestPostingTransactionDate, withholdTransactions); if (withholdTransaction != null) { @@ -588,8 +593,14 @@ public class SavingsAccount extends AbstractPersistableCustom { } if (backdatedTxnsAllowedTill) { addTransactionToExisting(newPostingTransaction); + if (reversal != null) { + addTransactionToExisting(reversal); + } } else { addTransaction(newPostingTransaction); + if (reversal != null) { + addTransaction(reversal); + } } if (applyWithHoldTaxForOldTransaction) { createWithHoldTransaction(interestEarnedToBePostedForPeriod.getAmount(), interestPostingTransactionDate, @@ -1166,7 +1177,7 @@ public class SavingsAccount extends AbstractPersistableCustom { } public SavingsAccountTransaction withdraw(final SavingsAccountTransactionDTO transactionDTO, final boolean applyWithdrawFee, - final boolean backdatedTxnsAllowedTill) { + final boolean backdatedTxnsAllowedTill, String refNo) { if (!isTransactionsAllowed()) { @@ -1222,13 +1233,13 @@ public class SavingsAccount extends AbstractPersistableCustom { if (applyWithdrawFee) { // auto pay withdrawal fee payWithdrawalFee(transactionDTO.getTransactionAmount(), transactionDTO.getTransactionDate(), transactionDTO.getAppUser(), - transactionDTO.getPaymentDetail(), backdatedTxnsAllowedTill); + transactionDTO.getPaymentDetail(), backdatedTxnsAllowedTill, refNo); } final Money transactionAmountMoney = Money.of(this.currency, transactionDTO.getTransactionAmount()); final SavingsAccountTransaction transaction = SavingsAccountTransaction.withdrawal(this, office(), transactionDTO.getPaymentDetail(), transactionDTO.getTransactionDate(), transactionAmountMoney, - transactionDTO.getCreatedDate(), transactionDTO.getAppUser()); + transactionDTO.getCreatedDate(), transactionDTO.getAppUser(), refNo); if (backdatedTxnsAllowedTill) { addTransactionToExisting(transaction); @@ -1256,7 +1267,7 @@ public class SavingsAccount extends AbstractPersistableCustom { } private void payWithdrawalFee(final BigDecimal transactionAmount, final LocalDate transactionDate, final AppUser user, - final PaymentDetail paymentDetail, final boolean backdatedTxnsAllowedTill) { + final PaymentDetail paymentDetail, final boolean backdatedTxnsAllowedTill, final String refNo) { for (SavingsAccountCharge charge : this.charges()) { if (charge.isWithdrawalFee() && charge.isActive()) { @@ -1268,23 +1279,23 @@ public class SavingsAccount extends AbstractPersistableCustom { if (charge.isEnablePaymentType() && charge.isEnableFreeWithdrawal()) { // discount transaction to // specific paymentType if (paymentDetail.getPaymentType().getPaymentName().equals(charge.getCharge().getPaymentType().getPaymentName())) { - resetFreeChargeDaysCount(charge, transactionAmount, transactionDate, user); + resetFreeChargeDaysCount(charge, transactionAmount, transactionDate, user, refNo); } } else if (charge.isEnablePaymentType()) { // normal charge-transaction to specific paymentType if (paymentDetail.getPaymentType().getPaymentName().equals(charge.getCharge().getPaymentType().getPaymentName())) { charge.updateWithdralFeeAmount(transactionAmount); this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user, - backdatedTxnsAllowedTill); + backdatedTxnsAllowedTill, refNo); } } else if (!charge.isEnablePaymentType() && charge.isEnableFreeWithdrawal()) { // discount transaction // irrespective of // PaymentTypes. - resetFreeChargeDaysCount(charge, transactionAmount, transactionDate, user); + resetFreeChargeDaysCount(charge, transactionAmount, transactionDate, user, refNo); } else { // normal-withdraw charge.updateWithdralFeeAmount(transactionAmount); - this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user, - backdatedTxnsAllowedTill); + this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user, backdatedTxnsAllowedTill, + refNo); } } @@ -1293,7 +1304,7 @@ public class SavingsAccount extends AbstractPersistableCustom { } private void resetFreeChargeDaysCount(SavingsAccountCharge charge, final BigDecimal transactionAmount, final LocalDate transactionDate, - final AppUser user) { + final AppUser user, final String refNo) { Date resetDate = charge.getResetChargeDate(); Integer restartPeriod = charge.getRestartFrequency(); @@ -1319,7 +1330,7 @@ public class SavingsAccount extends AbstractPersistableCustom { YearMonth gapYearMonth = YearMonth.from(gapIntervalMonth); YearMonth localYearMonth = YearMonth.from(localDate); if (localYearMonth.isBefore(gapYearMonth)) { - countValidation(charge, transactionAmount, transactionDate, user); + countValidation(charge, transactionAmount, transactionDate, user, refNo); } else { discountCharge(1, charge); } @@ -1338,7 +1349,7 @@ public class SavingsAccount extends AbstractPersistableCustom { Integer totalDays = days.intValue(); if (totalDays < restartPeriod) { - countValidation(charge, transactionAmount, transactionDate, user); + countValidation(charge, transactionAmount, transactionDate, user, refNo); } else { discountCharge(1, charge); } @@ -1346,7 +1357,7 @@ public class SavingsAccount extends AbstractPersistableCustom { } private void countValidation(SavingsAccountCharge charge, final BigDecimal transactionAmount, final LocalDate transactionDate, - final AppUser user) { + final AppUser user, final String refNo) { boolean backdatedTxnsAllowedTill = false; if (charge.getFreeWithdrawalCount() < charge.getFrequencyFreeWithdrawalCharge()) { final Integer count = charge.getFreeWithdrawalCount() + 1; @@ -1354,7 +1365,7 @@ public class SavingsAccount extends AbstractPersistableCustom { charge.updateNoWithdrawalFee(); } else { charge.updateWithdralFeeAmount(transactionAmount); - this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user, backdatedTxnsAllowedTill); + this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, user, backdatedTxnsAllowedTill, refNo); } } @@ -2743,11 +2754,12 @@ public class SavingsAccount extends AbstractPersistableCustom { private void payActivationCharges(final boolean isSavingsInterestPostingAtCurrentPeriodEnd, final Integer financialYearBeginningMonth, final AppUser user, final boolean backdatedTxnsAllowedTill) { boolean isSavingsChargeApplied = false; + UUID refNo = UUID.randomUUID(); for (SavingsAccountCharge savingsAccountCharge : this.charges()) { if (savingsAccountCharge.isSavingsActivation()) { isSavingsChargeApplied = true; payCharge(savingsAccountCharge, savingsAccountCharge.getAmountOutstanding(getCurrency()), getActivationLocalDate(), user, - backdatedTxnsAllowedTill); + backdatedTxnsAllowedTill, refNo.toString()); } } @@ -2757,8 +2769,9 @@ public class SavingsAccount extends AbstractPersistableCustom { LocalDate postInterestAsOnDate = null; if (this.isBeforeLastPostingPeriod(getActivationLocalDate(), backdatedTxnsAllowedTill)) { final LocalDate today = DateUtils.getLocalDateOfTenant(); + boolean postReversals = false; this.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestAsOnDate, backdatedTxnsAllowedTill); + postInterestAsOnDate, backdatedTxnsAllowedTill, postReversals); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); this.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, @@ -3106,8 +3119,8 @@ public class SavingsAccount extends AbstractPersistableCustom { } public SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAccountCharge, final BigDecimal amountPaid, - final LocalDate transactionDate, final DateTimeFormatter formatter, final AppUser user, - final boolean backdatedTxnsAllowedTill) { + final LocalDate transactionDate, final DateTimeFormatter formatter, final AppUser user, final boolean backdatedTxnsAllowedTill, + final String refNo) { final List dataValidationErrors = new ArrayList<>(); final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors) @@ -3195,21 +3208,21 @@ public class SavingsAccount extends AbstractPersistableCustom { } } - return this.payCharge(savingsAccountCharge, chargePaid, transactionDate, user, backdatedTxnsAllowedTill); + return this.payCharge(savingsAccountCharge, chargePaid, transactionDate, user, backdatedTxnsAllowedTill, refNo); } public SavingsAccountTransaction payCharge(final SavingsAccountCharge savingsAccountCharge, final Money amountPaid, - final LocalDate transactionDate, final AppUser user, final boolean backdatedTxnsAllowedTill) { + final LocalDate transactionDate, final AppUser user, final boolean backdatedTxnsAllowedTill, String refNo) { savingsAccountCharge.pay(getCurrency(), amountPaid); - return handlePayChargeTransactions(savingsAccountCharge, amountPaid, transactionDate, user, backdatedTxnsAllowedTill); + return handlePayChargeTransactions(savingsAccountCharge, amountPaid, transactionDate, user, backdatedTxnsAllowedTill, refNo); } private SavingsAccountTransaction handlePayChargeTransactions(SavingsAccountCharge savingsAccountCharge, Money transactionAmount, - final LocalDate transactionDate, final AppUser user, final boolean backdatedTxnsAllowedTill) { + final LocalDate transactionDate, final AppUser user, final boolean backdatedTxnsAllowedTill, final String refNo) { SavingsAccountTransaction chargeTransaction = null; if (savingsAccountCharge.isWithdrawalFee()) { - chargeTransaction = SavingsAccountTransaction.withdrawalFee(this, office(), transactionDate, transactionAmount, user); + chargeTransaction = SavingsAccountTransaction.withdrawalFee(this, office(), transactionDate, transactionAmount, user, refNo); } else if (savingsAccountCharge.isAnnualFee()) { chargeTransaction = SavingsAccountTransaction.annualFee(this, office(), transactionDate, transactionAmount, user); } else { @@ -3502,7 +3515,9 @@ public class SavingsAccount extends AbstractPersistableCustom { for (SavingsAccountCharge charge : this.charges()) { if (charge.isSavingsNoActivity() && charge.isActive()) { charge.updateWithdralFeeAmount(this.getAccountBalance()); - this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, appUser, backdatedTxnsAllowedTill); + UUID refNo = UUID.randomUUID(); + this.payCharge(charge, charge.getAmountOutstanding(this.getCurrency()), transactionDate, appUser, backdatedTxnsAllowedTill, + refNo.toString()); } } recalculateDailyBalances(Money.zero(this.currency), transactionDate, backdatedTxnsAllowedTill); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainService.java index 58e9ff875..06f0db995 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainService.java @@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.savings.domain; import java.math.BigDecimal; import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.util.List; import java.util.Set; import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail; import org.apache.fineract.portfolio.savings.SavingsTransactionBooleanValues; @@ -42,7 +43,7 @@ public interface SavingsAccountDomainService { SavingsAccountTransaction handleDividendPayout(SavingsAccount account, LocalDate transactionDate, BigDecimal transactionAmount, boolean backdatedTxnsAllowedTill); - SavingsAccountTransaction handleReversal(SavingsAccount account, SavingsAccountTransaction savingsAccountTransaction, + SavingsAccountTransaction handleReversal(SavingsAccount account, List savingsAccountTransactions, boolean backdatedTxnsAllowedTill); SavingsAccountTransaction handleHold(SavingsAccount account, AppUser createdUser, BigDecimal amount, LocalDate transactionDate, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java index d25b94566..17f979c62 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountDomainServiceJpa.java @@ -28,6 +28,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.UUID; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.service.DateUtils; @@ -108,14 +109,16 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi Integer accountType = null; final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, paymentDetail, new Date(), user, accountType); + UUID refNo = UUID.randomUUID(); final SavingsAccountTransaction withdrawal = account.withdraw(transactionDTO, transactionBooleanValues.isApplyWithdrawFee(), - backdatedTxnsAllowedTill); + backdatedTxnsAllowedTill, refNo.toString()); final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) { final LocalDate today = DateUtils.getLocalDateOfTenant(); + boolean postReversals = false; account.postInterest(mc, today, transactionBooleanValues.isInterestTransfer(), isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill); + financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill, postReversals); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today, transactionBooleanValues.isInterestTransfer(), @@ -195,8 +198,9 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) { final LocalDate today = DateUtils.getLocalDateOfTenant(); + boolean postReversals = false; account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, backdatedTxnsAllowedTill); + postInterestOnDate, backdatedTxnsAllowedTill, postReversals); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, @@ -283,15 +287,12 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi } @Override - public SavingsAccountTransaction handleReversal(SavingsAccount account, SavingsAccountTransaction savingsAccountTransaction, + public SavingsAccountTransaction handleReversal(SavingsAccount account, List savingsAccountTransactions, boolean backdatedTxnsAllowedTill) { final boolean isSavingsInterestPostingAtCurrentPeriodEnd = this.configurationDomainService .isSavingsInterestPostingAtCurrentPeriodEnd(); final Integer financialYearBeginningMonth = this.configurationDomainService.retrieveFinancialYearBeginningMonth(); - - final Set chargePaidBySet = savingsAccountTransaction.getSavingsAccountChargesPaid(); - final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); @@ -300,30 +301,36 @@ public class SavingsAccountDomainServiceJpa implements SavingsAccountDomainServi } else { updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); } + List newTransactions = new ArrayList<>(); + SavingsAccountTransaction reversal = null; - SavingsAccountTransaction reversal = SavingsAccountTransaction.reversal(savingsAccountTransaction); - reversal.getSavingsAccountChargesPaid().addAll(chargePaidBySet); - account.undoTransaction(savingsAccountTransaction); + Set chargePaidBySet = null; + for (SavingsAccountTransaction savingsAccountTransaction : savingsAccountTransactions) { + reversal = SavingsAccountTransaction.reversal(savingsAccountTransaction); + chargePaidBySet = savingsAccountTransaction.getSavingsAccountChargesPaid(); + reversal.getSavingsAccountChargesPaid().addAll(chargePaidBySet); + account.undoTransaction(savingsAccountTransaction); + newTransactions.add(reversal); + } boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; final LocalDate today = DateUtils.getLocalDateOfTenant(); final MathContext mc = new MathContext(15, MoneyHelper.getRoundingMode()); + for (SavingsAccountTransaction savingsAccountTransaction : savingsAccountTransactions) { + if (savingsAccountTransaction.isPostInterestCalculationRequired() + && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), backdatedTxnsAllowedTill)) { + boolean postReversals = true; + account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, + postInterestOnDate, backdatedTxnsAllowedTill, postReversals); + } else { + account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, + financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill); + } - if (savingsAccountTransaction.isPostInterestCalculationRequired() - && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), backdatedTxnsAllowedTill)) { - account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, backdatedTxnsAllowedTill); - } else { - account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, - financialYearBeginningMonth, postInterestOnDate, backdatedTxnsAllowedTill); + account.validateAccountBalanceDoesNotBecomeNegativeMinimal(savingsAccountTransaction.getAmount(), false); + account.activateAccountBasedOnBalance(); } - - List newTransactions = new ArrayList<>(); - newTransactions.add(reversal); - - account.validateAccountBalanceDoesNotBecomeNegativeMinimal(savingsAccountTransaction.getAmount(), false); - account.activateAccountBasedOnBalance(); this.savingsAccountRepository.save(account); this.savingsAccountTransactionRepository.saveAll(newTransactions); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds, false, backdatedTxnsAllowedTill); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java index 8eb54625c..a32afb6b7 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountSummary.java @@ -211,7 +211,8 @@ public final class SavingsAccountSummary { HashMap map = new HashMap<>(); for (int i = savingsAccountTransactions.size() - 1; i >= 0; i--) { final SavingsAccountTransaction savingsAccountTransaction = savingsAccountTransactions.get(i); - if (savingsAccountTransaction.isInterestPostingAndNotReversed() && savingsAccountTransaction.isNotReversed() && !isUpdated) { + if (savingsAccountTransaction.isInterestPostingAndNotReversed() && savingsAccountTransaction.isNotReversed() + && !savingsAccountTransaction.isReversalTransaction() && !isUpdated) { setRunningBalanceOnPivotDate(savingsAccountTransaction.getRunningBalance(currency).getAmount()); setInterestPostedTillDate(savingsAccountTransaction.getLastTransactionDate()); isUpdated = true; @@ -220,7 +221,8 @@ public final class SavingsAccountSummary { } } if (backdatedTxnsAllowedTill) { - if (savingsAccountTransaction.isInterestPostingAndNotReversed() && savingsAccountTransaction.isNotReversed()) { + if (savingsAccountTransaction.isInterestPostingAndNotReversed() && savingsAccountTransaction.isNotReversed() + && !savingsAccountTransaction.isReversalTransaction()) { interestTotal = interestTotal.plus(savingsAccountTransaction.getAmount(currency)); } 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 b6e420fe8..3362d4ebe 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 @@ -143,6 +143,9 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { @Column(name = "is_lien_transaction") private Boolean lienTransaction; + @Column(name = "ref_no", nullable = true) + private String refNo; + SavingsAccountTransaction() { this.dateOf = null; this.typeOf = null; @@ -154,8 +157,9 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final boolean isReversed = false; final boolean isManualTransaction = false; final Boolean lienTransaction = false; + final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.DEPOSIT.getValue(), date, - createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction); + createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction deposit(final SavingsAccount savingsAccount, final Office office, @@ -164,42 +168,46 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final boolean isReversed = false; final boolean isManualTransaction = false; final Boolean lienTransaction = false; + final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, savingsAccountTransactionType.getValue(), date, - createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction); + createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction withdrawal(final SavingsAccount savingsAccount, final Office office, - final PaymentDetail paymentDetail, final LocalDate date, final Money amount, Date createdDate, final AppUser appUser) { + final PaymentDetail paymentDetail, final LocalDate date, final Money amount, Date createdDate, final AppUser appUser, + final String refNo) { final boolean isReversed = false; final boolean isManualTransaction = false; final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.WITHDRAWAL.getValue(), - date, createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction); + date, createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction interestPosting(final SavingsAccount savingsAccount, final Office office, final LocalDate date, final Money amount, final boolean isManualTransaction) { final boolean isReversed = false; final Boolean lienTransaction = false; + final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.INTEREST_POSTING.getValue(), date, - amount, isReversed, null, isManualTransaction, lienTransaction); + amount, isReversed, null, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction overdraftInterest(final SavingsAccount savingsAccount, final Office office, final LocalDate date, final Money amount, final boolean isManualTransaction) { final boolean isReversed = false; final Boolean lienTransaction = false; + final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.OVERDRAFT_INTEREST.getValue(), date, - amount, isReversed, null, isManualTransaction, lienTransaction); + amount, isReversed, null, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction withdrawalFee(final SavingsAccount savingsAccount, final Office office, final LocalDate date, - final Money amount, final AppUser appUser) { + final Money amount, final AppUser appUser, final String refNo) { final boolean isReversed = false; final boolean isManualTransaction = false; final Boolean lienTransaction = false; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.WITHDRAWAL_FEE.getValue(), date, amount, - isReversed, appUser, isManualTransaction, lienTransaction); + isReversed, appUser, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction annualFee(final SavingsAccount savingsAccount, final Office office, final LocalDate date, @@ -207,8 +215,9 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final boolean isReversed = false; final boolean isManualTransaction = false; final Boolean lienTransaction = false; + final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.ANNUAL_FEE.getValue(), date, amount, - isReversed, appUser, isManualTransaction, lienTransaction); + isReversed, appUser, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction charge(final SavingsAccount savingsAccount, final Office office, final LocalDate date, @@ -216,8 +225,9 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final boolean isReversed = false; final boolean isManualTransaction = false; final Boolean lienTransaction = false; + final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.PAY_CHARGE.getValue(), date, amount, - isReversed, appUser, isManualTransaction, lienTransaction); + isReversed, appUser, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction from(final Integer transactionTypeEnum, final LocalDate transactionDate, @@ -233,8 +243,9 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final boolean isReversed = false; final boolean isManualTransaction = false; final Boolean lienTransaction = false; + final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, SavingsAccountTransactionType.WAIVE_CHARGES.getValue(), date, amount, - isReversed, appUser, isManualTransaction, lienTransaction); + isReversed, appUser, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction initiateTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date, @@ -243,9 +254,10 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final boolean isManualTransaction = false; final PaymentDetail paymentDetail = null; final Boolean lienTransaction = false; + final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.INITIATE_TRANSFER.getValue(), date, new Date(), - savingsAccount.getSummary().getAccountBalance(), isReversed, appUser, isManualTransaction, lienTransaction); + savingsAccount.getSummary().getAccountBalance(), isReversed, appUser, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction approveTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date, @@ -254,9 +266,10 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final boolean isManualTransaction = false; final PaymentDetail paymentDetail = null; final Boolean lienTransaction = false; + final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.APPROVE_TRANSFER.getValue(), date, new Date(), - savingsAccount.getSummary().getAccountBalance(), isReversed, appUser, isManualTransaction, lienTransaction); + savingsAccount.getSummary().getAccountBalance(), isReversed, appUser, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction withdrawTransfer(final SavingsAccount savingsAccount, final Office office, final LocalDate date, @@ -265,9 +278,10 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final boolean isManualTransaction = false; final PaymentDetail paymentDetail = null; final Boolean lienTransaction = false; + final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.WITHDRAW_TRANSFER.getValue(), date, new Date(), - savingsAccount.getSummary().getAccountBalance(), isReversed, appUser, isManualTransaction, lienTransaction); + savingsAccount.getSummary().getAccountBalance(), isReversed, appUser, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction withHoldTax(final SavingsAccount savingsAccount, final Office office, final LocalDate date, @@ -275,9 +289,10 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final boolean isReversed = false; final boolean isManualTransaction = false; final Boolean lienTransaction = false; + final String refNo = null; SavingsAccountTransaction accountTransaction = new SavingsAccountTransaction(savingsAccount, office, - SavingsAccountTransactionType.WITHHOLD_TAX.getValue(), date, amount, isReversed, null, isManualTransaction, - lienTransaction); + SavingsAccountTransactionType.WITHHOLD_TAX.getValue(), date, amount, isReversed, null, isManualTransaction, lienTransaction, + refNo); updateTaxDetails(taxDetails, accountTransaction); return accountTransaction; } @@ -287,9 +302,10 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final boolean isReversed = false; final PaymentDetail paymentDetail = null; final Boolean lienTransaction = false; + final String refNo = null; return new SavingsAccountTransaction(savingsAccount, savingsAccount.office(), paymentDetail, SavingsAccountTransactionType.ESCHEAT.getValue(), date, new Date(), savingsAccount.getSummary().getAccountBalance(), - isReversed, appUser, accountTransaction, lienTransaction); + isReversed, appUser, accountTransaction, lienTransaction, refNo); } public static void updateTaxDetails(final Map taxDetails, @@ -306,26 +322,28 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { return new SavingsAccountTransaction(accountTransaction.savingsAccount, accountTransaction.office, accountTransaction.paymentDetail, accountTransaction.typeOf, accountTransaction.transactionLocalDate(), DateUtils.getDateOfTenant(), accountTransaction.amount, accountTransaction.reversed, accountTransaction.appUser, accountTransaction.isManualTransaction, - accountTransaction.lienTransaction); + accountTransaction.lienTransaction, accountTransaction.refNo); } private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final Integer typeOf, final LocalDate transactionLocalDate, final Money amount, final boolean isReversed, final AppUser appUser, - final boolean isManualTransaction, final Boolean lienTransaction) { + final boolean isManualTransaction, final Boolean lienTransaction, final String refNo) { this(savingsAccount, office, null, typeOf, transactionLocalDate, new Date(), amount, isReversed, appUser, isManualTransaction, - lienTransaction); + lienTransaction, refNo); } private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final PaymentDetail paymentDetail, final Integer typeOf, final LocalDate transactionLocalDate, final Date createdDate, final Money amount, - final boolean isReversed, final AppUser appUser, final boolean isManualTransaction, final Boolean lienTransaction) { + final boolean isReversed, final AppUser appUser, final boolean isManualTransaction, final Boolean lienTransaction, + final String refNo) { this(savingsAccount, office, paymentDetail, typeOf, transactionLocalDate, createdDate, amount.getAmount(), isReversed, appUser, - isManualTransaction, lienTransaction); + isManualTransaction, lienTransaction, refNo); } private SavingsAccountTransaction(final SavingsAccount savingsAccount, final Office office, final PaymentDetail paymentDetail, final Integer typeOf, final LocalDate transactionLocalDate, final Date createdDate, final BigDecimal amount, - final boolean isReversed, final AppUser appUser, final boolean isManualTransaction, final Boolean lienTransaction) { + final boolean isReversed, final AppUser appUser, final boolean isManualTransaction, final Boolean lienTransaction, + final String refNo) { this.savingsAccount = savingsAccount; this.office = office; this.typeOf = typeOf; @@ -337,6 +355,7 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { this.appUser = appUser; this.isManualTransaction = isManualTransaction; this.lienTransaction = lienTransaction; + this.refNo = refNo; } private SavingsAccountTransaction(final Integer transactionTypeEnum, final LocalDate transactionDate, final BigDecimal amount, @@ -362,15 +381,17 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { final Boolean lienTransaction) { final boolean isReversed = false; final boolean isManualTransaction = false; + final String refNo = null; return new SavingsAccountTransaction(savingsAccount, office, paymentDetail, SavingsAccountTransactionType.AMOUNT_HOLD.getValue(), - date, createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction); + date, createdDate, amount, isReversed, appUser, isManualTransaction, lienTransaction, refNo); } public static SavingsAccountTransaction releaseAmount(SavingsAccountTransaction accountTransaction, LocalDate transactionDate, Date createdDate, final AppUser appUser) { return new SavingsAccountTransaction(accountTransaction.savingsAccount, accountTransaction.office, accountTransaction.paymentDetail, SavingsAccountTransactionType.AMOUNT_RELEASE.getValue(), transactionDate, createdDate, accountTransaction.amount, - accountTransaction.reversed, appUser, accountTransaction.isManualTransaction, accountTransaction.lienTransaction); + accountTransaction.reversed, appUser, accountTransaction.isManualTransaction, accountTransaction.lienTransaction, + accountTransaction.refNo); } public static SavingsAccountTransaction reversal(SavingsAccountTransaction accountTransaction) { @@ -860,6 +881,10 @@ public final class SavingsAccountTransaction extends AbstractPersistableCustom { return this.dateOf; } + public String getRefNo() { + return this.refNo; + } + public PaymentDetail getPaymentDetail() { return this.paymentDetail; } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionRepository.java index cdb1d9337..a7e1b3bfc 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionRepository.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionRepository.java @@ -42,4 +42,6 @@ public interface SavingsAccountTransactionRepository @Lock(LockModeType.PESSIMISTIC_WRITE) List findBySavingsAccount(@Param("savingsAccount") SavingsAccount savingsAccount); + @Query("select sat from SavingsAccountTransaction sat where sat.refNo = :refNo") + List findAllTransactionByRefNo(@Param("refNo") String refNo); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java index 1498c92cc..82e9a2de5 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/domain/SavingsAccountTransactionSummaryWrapper.java @@ -67,7 +67,7 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalWithdrawals(final CurrencyData currency, final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransactionData transaction : transactions) { - if (transaction.isWithdrawal() && transaction.isNotReversed()) { + if (transaction.isWithdrawal() && transaction.isNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount()); } } @@ -77,7 +77,7 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalInterestPosted(final MonetaryCurrency currency, final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransaction transaction : transactions) { - if (transaction.isInterestPostingAndNotReversed() && transaction.isNotReversed()) { + if (transaction.isInterestPostingAndNotReversed() && transaction.isNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount(currency)); } } @@ -87,7 +87,7 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalInterestPosted(final CurrencyData currency, final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransactionData transaction : transactions) { - if (transaction.isInterestPostingAndNotReversed() && transaction.isNotReversed()) { + if (transaction.isInterestPostingAndNotReversed() && transaction.isNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount()); } } @@ -98,7 +98,7 @@ public final class SavingsAccountTransactionSummaryWrapper { final List savingsAccountTransactions) { Money total = Money.of(currency, currentInterestPosted); for (final SavingsAccountTransaction transaction : savingsAccountTransactions) { - if (transaction.isInterestPostingAndNotReversed() && transaction.isNotReversed()) { + if (transaction.isInterestPostingAndNotReversed() && transaction.isNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount(currency)); } } @@ -108,7 +108,7 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalWithdrawalFees(final MonetaryCurrency currency, final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransaction transaction : transactions) { - if (transaction.isWithdrawalFeeAndNotReversed() && transaction.isNotReversed()) { + if (transaction.isWithdrawalFeeAndNotReversed() && transaction.isNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount(currency)); } } @@ -118,7 +118,7 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalWithdrawalFees(final CurrencyData currency, final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransactionData transaction : transactions) { - if (transaction.isWithdrawalFeeAndNotReversed() && transaction.isNotReversed()) { + if (transaction.isWithdrawalFeeAndNotReversed() && transaction.isNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount()); } } @@ -128,7 +128,7 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalAnnualFees(final MonetaryCurrency currency, final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransaction transaction : transactions) { - if (transaction.isAnnualFeeAndNotReversed() && transaction.isNotReversed()) { + if (transaction.isAnnualFeeAndNotReversed() && transaction.isNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount(currency)); } } @@ -138,7 +138,7 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalAnnualFees(final CurrencyData currency, final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransactionData transaction : transactions) { - if (transaction.isAnnualFeeAndNotReversed() && transaction.isNotReversed()) { + if (transaction.isAnnualFeeAndNotReversed() && transaction.isNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount()); } } @@ -148,7 +148,7 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalFeesCharge(final MonetaryCurrency currency, final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransaction transaction : transactions) { - if (transaction.isFeeChargeAndNotReversed()) { + if (transaction.isFeeChargeAndNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount(currency)); } } @@ -158,7 +158,7 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalFeesCharge(final CurrencyData currency, final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransactionData transaction : transactions) { - if (transaction.isFeeChargeAndNotReversed()) { + if (transaction.isFeeChargeAndNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount()); } } @@ -168,7 +168,7 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalFeesChargeWaived(final MonetaryCurrency currency, final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransaction transaction : transactions) { - if (transaction.isWaiveFeeChargeAndNotReversed()) { + if (transaction.isWaiveFeeChargeAndNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount(currency)); } } @@ -178,7 +178,7 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalFeesChargeWaived(final CurrencyData currency, final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransactionData transaction : transactions) { - if (transaction.isWaiveFeeChargeAndNotReversed()) { + if (transaction.isWaiveFeeChargeAndNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount()); } } @@ -188,7 +188,7 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalPenaltyCharge(final MonetaryCurrency currency, final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransaction transaction : transactions) { - if (transaction.isPenaltyChargeAndNotReversed()) { + if (transaction.isPenaltyChargeAndNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount(currency)); } } @@ -198,7 +198,7 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalPenaltyCharge(final CurrencyData currency, final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransactionData transaction : transactions) { - if (transaction.isPenaltyChargeAndNotReversed()) { + if (transaction.isPenaltyChargeAndNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount()); } } @@ -209,7 +209,7 @@ public final class SavingsAccountTransactionSummaryWrapper { final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransaction transaction : transactions) { - if (transaction.isWaivePenaltyChargeAndNotReversed()) { + if (transaction.isWaivePenaltyChargeAndNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount(currency)); } } @@ -220,7 +220,7 @@ public final class SavingsAccountTransactionSummaryWrapper { final List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransactionData transaction : transactions) { - if (transaction.isWaivePenaltyChargeAndNotReversed()) { + if (transaction.isWaivePenaltyChargeAndNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount()); } } @@ -231,7 +231,7 @@ public final class SavingsAccountTransactionSummaryWrapper { List transactions) { Money total = Money.of(currency, overdraftPosted); for (final SavingsAccountTransaction transaction : transactions) { - if (transaction.isOverdraftInterestAndNotReversed()) { + if (transaction.isOverdraftInterestAndNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount(currency)); } } @@ -241,7 +241,7 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalOverdraftInterest(MonetaryCurrency currency, List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransaction transaction : transactions) { - if (transaction.isOverdraftInterestAndNotReversed()) { + if (transaction.isOverdraftInterestAndNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount(currency)); } } @@ -251,7 +251,7 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalOverdraftInterest(CurrencyData currency, List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransactionData transaction : transactions) { - if (transaction.isOverdraftInterestAndNotReversed()) { + if (transaction.isOverdraftInterestAndNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount()); } } @@ -261,7 +261,7 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalWithholdTaxWithdrawal(MonetaryCurrency currency, List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransaction transaction : transactions) { - if (transaction.isWithHoldTaxAndNotReversed()) { + if (transaction.isWithHoldTaxAndNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount(currency)); } } @@ -271,7 +271,7 @@ public final class SavingsAccountTransactionSummaryWrapper { public BigDecimal calculateTotalWithholdTaxWithdrawal(CurrencyData currency, List transactions) { Money total = Money.zero(currency); for (final SavingsAccountTransactionData transaction : transactions) { - if (transaction.isWithHoldTaxAndNotReversed()) { + if (transaction.isWithHoldTaxAndNotReversed() && !transaction.isReversalTransaction()) { total = total.plus(transaction.getAmount()); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ReverseTransactionSavingsAccountCommandHandler.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ReverseTransactionSavingsAccountCommandHandler.java index 1932791ee..ec09d9804 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ReverseTransactionSavingsAccountCommandHandler.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/handler/ReverseTransactionSavingsAccountCommandHandler.java @@ -45,6 +45,6 @@ public class ReverseTransactionSavingsAccountCommandHandler implements NewComman @Override public CommandProcessingResult processCommand(final JsonCommand command) { final Long transactionId = Long.valueOf(command.getTransactionId()); - return this.writePlatformService.reverseTransaction(command.getSavingsId(), transactionId, false); + return this.writePlatformService.reverseTransaction(command.getSavingsId(), transactionId, false, command); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java index 75dafe97b..be46494e3 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/DepositAccountWritePlatformServiceJpaRepositoryImpl.java @@ -37,6 +37,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; @@ -296,7 +297,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo for (SavingsAccountCharge savingsAccountCharge : account.charges()) { if (savingsAccountCharge.isSavingsActivation()) { account.payCharge(savingsAccountCharge, savingsAccountCharge.getAmount(account.getCurrency()), - account.getActivationLocalDate(), user, false); + account.getActivationLocalDate(), user, false, null); } } } @@ -571,8 +572,9 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo final MathContext mc = new MathContext(10, MoneyHelper.getRoundingMode()); boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; + boolean postReversals = false; account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, false); + postInterestOnDate, false, postReversals); this.savingAccountRepositoryWrapper.saveAndFlush(account); postJournalEntries(account, existingTransactionIds, existingReversedTransactionIds); @@ -744,6 +746,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo SavingsAccountTransaction transaction = null; Integer accountType = null; + UUID refNo = UUID.randomUUID(); if (savingsAccountTransaction.isDeposit()) { final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, paymentDetail, savingsAccountTransaction.createdDate(), user, accountType); @@ -751,7 +754,7 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo } else { final SavingsAccountTransactionDTO transactionDTO = new SavingsAccountTransactionDTO(fmt, transactionDate, transactionAmount, paymentDetail, savingsAccountTransaction.createdDate(), user, accountType); - transaction = account.withdraw(transactionDTO, true, false); + transaction = account.withdraw(transactionDTO, true, false, refNo.toString()); } final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction); boolean isInterestTransfer = false; @@ -1218,8 +1221,9 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate(), false)) { final LocalDate today = DateUtils.getLocalDateOfTenant(); + boolean postReversals = false; account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, false); + postInterestOnDate, false, postReversals); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, @@ -1351,14 +1355,15 @@ public class DepositAccountWritePlatformServiceJpaRepositoryImpl implements Depo final Set existingTransactionIds = new HashSet<>(); final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); - account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user, false); + account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user, false, null); boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(transactionDate, false)) { final LocalDate today = DateUtils.getLocalDateOfTenant(); + boolean postReversals = false; account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, false); + postInterestOnDate, false, postReversals); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java index dab91c282..8b2979591 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformService.java @@ -41,7 +41,8 @@ public interface SavingsAccountWritePlatformService { CommandProcessingResult calculateInterest(Long savingsId); - CommandProcessingResult reverseTransaction(Long savingsId, Long transactionId, boolean allowAccountTransferModification); + CommandProcessingResult reverseTransaction(Long savingsId, Long transactionId, boolean allowAccountTransferModification, + JsonCommand command); CommandProcessingResult undoTransaction(Long savingsId, Long transactionId, boolean allowAccountTransferModification); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java index 1bd5291e4..1f700a748 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/savings/service/SavingsAccountWritePlatformServiceJpaRepositoryImpl.java @@ -38,6 +38,7 @@ import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -46,6 +47,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.UUID; import org.apache.commons.lang3.StringUtils; import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; @@ -614,9 +616,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi if (postInterestAs) { postInterestOnDate = transactionDate; } - + boolean postReversals = false; account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, backdatedTxnsAllowedTill); + postInterestOnDate, backdatedTxnsAllowedTill, postReversals); if (!backdatedTxnsAllowedTill) { List transactions = account.getTransactions(); @@ -685,9 +687,10 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi @Override public CommandProcessingResult reverseTransaction(final Long savingsId, final Long transactionId, - final boolean allowAccountTransferModification) { + final boolean allowAccountTransferModification, final JsonCommand command) { final boolean backdatedTxnsAllowedTill = this.savingAccountAssembler.getPivotConfigStatus(); + final boolean isBulk = command.booleanPrimitiveValueOfParameterNamed("isBulk"); final SavingsAccount account = this.savingAccountAssembler.assembleFrom(savingsId, backdatedTxnsAllowedTill); final SavingsAccountTransaction savingsAccountTransaction = this.savingsAccountTransactionRepository @@ -707,18 +710,24 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi throw new PlatformServiceUnavailableException("error.msg.saving.account.transaction.update.not.allowed", "Savings account transaction:" + transactionId + " update not allowed for this savings type", transactionId); } - if (account.isNotActive()) { throwValidationForActiveStatus(SavingsApiConstants.undoTransactionAction); } - + SavingsAccountTransaction reversal = null; + Long reversalId = null; checkClientOrGroupActive(account); - - final SavingsAccountTransaction reversal = this.savingsAccountDomainService.handleReversal(account, savingsAccountTransaction, - backdatedTxnsAllowedTill); - + List savingsAccountTransactions = null; + if (isBulk) { + String transactionRefNo = savingsAccountTransaction.getRefNo(); + savingsAccountTransactions = this.savingsAccountTransactionRepository.findAllTransactionByRefNo(transactionRefNo); + reversal = this.savingsAccountDomainService.handleReversal(account, savingsAccountTransactions, backdatedTxnsAllowedTill); + } else { + reversal = this.savingsAccountDomainService.handleReversal(account, Collections.singletonList(savingsAccountTransaction), + backdatedTxnsAllowedTill); + } + reversalId = reversal.getId(); return new CommandProcessingResultBuilder() // - .withEntityId(reversal.getId()) // + .withEntityId(reversalId) // .withOfficeId(account.officeId()) // .withClientId(account.clientId()) // .withGroupId(account.groupId()) // @@ -781,8 +790,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi checkClientOrGroupActive(account); if (savingsAccountTransaction.isPostInterestCalculationRequired() && account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) { + boolean postReversals = false; account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, false); + postInterestOnDate, false, postReversals); } else { account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, false); @@ -875,14 +885,16 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi if (savingsAccountTransaction.isDeposit()) { transaction = account.deposit(transactionDTO, false); } else { - transaction = account.withdraw(transactionDTO, true, false); + UUID refNo = UUID.randomUUID(); + transaction = account.withdraw(transactionDTO, true, false, refNo.toString()); } final Long newtransactionId = saveTransactionToGenerateTransactionId(transaction); final LocalDate postInterestOnDate = null; if (account.isBeforeLastPostingPeriod(transactionDate, false) || account.isBeforeLastPostingPeriod(savingsAccountTransaction.transactionLocalDate(), false)) { + boolean postReversals = false; account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, false); + postInterestOnDate, false, postReversals); } else { account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, postInterestOnDate, false); @@ -1312,8 +1324,9 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(savingsAccountCharge.getDueLocalDate(), backdatedTxnsAllowedTill)) { final LocalDate today = DateUtils.getLocalDateOfTenant(); + boolean postReversals = false; account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, backdatedTxnsAllowedTill); + postInterestOnDate, backdatedTxnsAllowedTill, postReversals); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, @@ -1458,14 +1471,15 @@ public class SavingsAccountWritePlatformServiceJpaRepositoryImpl implements Savi final Set existingReversedTransactionIds = new HashSet<>(); updateExistingTransactionsDetails(account, existingTransactionIds, existingReversedTransactionIds); SavingsAccountTransaction chargeTransaction = account.payCharge(savingsAccountCharge, amountPaid, transactionDate, formatter, user, - backdatedTxnsAllowedTill); + backdatedTxnsAllowedTill, null); boolean isInterestTransfer = false; LocalDate postInterestOnDate = null; final MathContext mc = MathContext.DECIMAL64; if (account.isBeforeLastPostingPeriod(transactionDate, backdatedTxnsAllowedTill)) { final LocalDate today = DateUtils.getLocalDateOfTenant(); + boolean postReversals = false; account.postInterest(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, financialYearBeginningMonth, - postInterestOnDate, isInterestTransfer); + postInterestOnDate, isInterestTransfer, postReversals); } else { final LocalDate today = DateUtils.getLocalDateOfTenant(); account.calculateInterestUsing(mc, today, isInterestTransfer, isSavingsInterestPostingAtCurrentPeriodEnd, diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index 00372f771..c5d7ef7f5 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -35,4 +35,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0016_changed_unique_constraint_of_ref_no.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0016_changed_unique_constraint_of_ref_no.xml new file mode 100644 index 000000000..ec83a13c7 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0016_changed_unique_constraint_of_ref_no.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java index 32a7484d2..9cc08a415 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientSavingsIntegrationTest.java @@ -2731,4 +2731,88 @@ public class ClientSavingsIntegrationTest { assertEquals(balance, summary.get("accountBalance"), "Verifying opening Balance is 500"); } + + @Test + public void testReversalWhenIsBulkIsTrue() { + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + final String minBalanceForInterestCalculation = null; + final String minRequiredBalance = "0"; + final String enforceMinRequiredBalance = "false"; + final boolean allowOverdraft = false; + final boolean isBulk = true; + + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, + minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft); + Assertions.assertNotNull(savingsProductID); + + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); + Assertions.assertNotNull(savingsId); + final Integer withdrawalChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec, + ChargesHelper.getSavingsWithdrawalFeeJSON()); + Assertions.assertNotNull(withdrawalChargeId); + + this.savingsAccountHelper.addChargesForSavings(savingsId, withdrawalChargeId, false); + + HashMap savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId); + SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap); + + savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId); + SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap); + + Integer withdrawalTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "500", + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId); + Float balance = Float.parseFloat("400.0"); + assertEquals(balance, summary.get("accountBalance"), "Verifying account balance is 400"); + LOG.info("------------------------When Bulk transaction is true------------------------"); + this.savingsAccountHelper.reverseSavingsAccountTransaction(savingsId, withdrawalTransactionId, isBulk); + summary = this.savingsAccountHelper.getSavingsSummary(savingsId); + balance = Float.parseFloat("1000.0"); + assertEquals(balance, summary.get("accountBalance"), "Verifying account balance is 1000"); + } + + @Test + public void testReversalWhenIsBulkIsFalse() { + this.savingsAccountHelper = new SavingsAccountHelper(this.requestSpec, this.responseSpec); + + final Integer clientID = ClientHelper.createClient(this.requestSpec, this.responseSpec); + ClientHelper.verifyClientCreatedOnServer(this.requestSpec, this.responseSpec, clientID); + final String minBalanceForInterestCalculation = null; + final String minRequiredBalance = "0"; + final String enforceMinRequiredBalance = "false"; + final boolean allowOverdraft = false; + final boolean isBulk = false; + + final Integer savingsProductID = createSavingsProduct(this.requestSpec, this.responseSpec, MINIMUM_OPENING_BALANCE, + minBalanceForInterestCalculation, minRequiredBalance, enforceMinRequiredBalance, allowOverdraft); + Assertions.assertNotNull(savingsProductID); + + final Integer savingsId = this.savingsAccountHelper.applyForSavingsApplication(clientID, savingsProductID, ACCOUNT_TYPE_INDIVIDUAL); + Assertions.assertNotNull(savingsId); + final Integer withdrawalChargeId = ChargesHelper.createCharges(this.requestSpec, this.responseSpec, + ChargesHelper.getSavingsWithdrawalFeeJSON()); + Assertions.assertNotNull(withdrawalChargeId); + + this.savingsAccountHelper.addChargesForSavings(savingsId, withdrawalChargeId, false); + + HashMap savingsStatusHashMap = this.savingsAccountHelper.approveSavings(savingsId); + SavingsStatusChecker.verifySavingsIsApproved(savingsStatusHashMap); + + savingsStatusHashMap = this.savingsAccountHelper.activateSavings(savingsId); + SavingsStatusChecker.verifySavingsIsActive(savingsStatusHashMap); + + Integer withdrawalTransactionId = (Integer) this.savingsAccountHelper.withdrawalFromSavingsAccount(savingsId, "500", + SavingsAccountHelper.TRANSACTION_DATE, CommonConstants.RESPONSE_RESOURCE_ID); + HashMap summary = this.savingsAccountHelper.getSavingsSummary(savingsId); + Float balance = Float.parseFloat("400.0"); + assertEquals(balance, summary.get("accountBalance"), "Verifying account balance is 400"); + LOG.info("------------------------When Bulk transaction is false------------------------"); + this.savingsAccountHelper.reverseSavingsAccountTransaction(savingsId, withdrawalTransactionId, isBulk); + summary = this.savingsAccountHelper.getSavingsSummary(savingsId); + balance = Float.parseFloat("900.0"); + assertEquals(balance, summary.get("accountBalance"), "Verifying account balance is 900"); + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java index 90127462e..5f3016e2b 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/savings/SavingsAccountHelper.java @@ -280,6 +280,12 @@ public class SavingsAccountHelper { getSavingsTransactionJSON("0", LAST_TRANSACTION_DATE), CommonConstants.RESPONSE_RESOURCE_ID); } + public Integer reverseSavingsAccountTransaction(final Integer savingsId, final Integer transactionId, final boolean isBulk) { + LOG.info("\n--------------------------------- REVERSE SAVINGS TRANSACTION --------------------------------"); + return (Integer) performSavingActions(createAdjustTransactionURL(REVERSE_TRASACTION_COMMAND, savingsId, transactionId), + getSavingsTransactionJSON("0", LAST_TRANSACTION_DATE, isBulk), CommonConstants.RESPONSE_RESOURCE_ID); + } + public void calculateInterestForSavings(final Integer savingsId) { LOG.info("--------------------------------- CALCULATING INTEREST FOR SAVINGS --------------------------------"); performSavingActions(createSavingsCalculateInterestURL(CALCULATE_INTEREST_SAVINGS_COMMAND, savingsId), @@ -460,6 +466,18 @@ public class SavingsAccountHelper { return savingsAccountWithdrawalJson; } + private String getSavingsTransactionJSON(final String amount, final String transactionDate, final boolean isBulk) { + final HashMap map = new HashMap<>(); + map.put("locale", CommonConstants.LOCALE); + map.put("dateFormat", CommonConstants.DATE_FORMAT); + map.put("transactionDate", transactionDate); + map.put("transactionAmount", amount); + map.put("isBulk", String.valueOf(isBulk)); + String savingsAccountWithdrawalJson = new Gson().toJson(map); + LOG.info(savingsAccountWithdrawalJson); + return savingsAccountWithdrawalJson; + } + private String getLienSavingsTransactionJSON(final String amount, final String transactionDate, final Boolean lienAllowed) { final HashMap map = new HashMap<>(); map.put("locale", CommonConstants.LOCALE);