This is an automated email from the ASF dual-hosted git repository.

manojvm pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new ff2e6cf4a FINERACT-1642:Bulk reversal of related Transactions
     new 74e43ab62 Merge pull request #2372 from logoutdhaval/fineract-1642
ff2e6cf4a is described below

commit ff2e6cf4af04e401735c083141413e15cb89351a
Author: Dhaval Maniyar <[email protected]>
AuthorDate: Mon Jun 20 14:09:01 2022 +0530

    FINERACT-1642:Bulk reversal of related Transactions
---
 .../api/SavingsAccountTransactionsApiResource.java |  8 +++
 ...vingsAccountTransactionsApiResourceSwagger.java |  9 +++
 .../data/SavingsAccountTransactionData.java        |  5 ++
 .../savings/domain/FixedDepositAccount.java        |  4 +-
 .../savings/domain/RecurringDepositAccount.java    |  4 +-
 .../portfolio/savings/domain/SavingsAccount.java   | 65 ++++++++++-------
 .../domain/SavingsAccountDomainService.java        |  3 +-
 .../domain/SavingsAccountDomainServiceJpa.java     | 57 ++++++++-------
 .../savings/domain/SavingsAccountSummary.java      |  6 +-
 .../savings/domain/SavingsAccountTransaction.java  | 75 ++++++++++++-------
 .../SavingsAccountTransactionRepository.java       |  2 +
 .../SavingsAccountTransactionSummaryWrapper.java   | 42 +++++------
 ...rseTransactionSavingsAccountCommandHandler.java |  2 +-
 ...countWritePlatformServiceJpaRepositoryImpl.java | 17 +++--
 .../SavingsAccountWritePlatformService.java        |  3 +-
 ...countWritePlatformServiceJpaRepositoryImpl.java | 46 +++++++-----
 .../db/changelog/tenant/changelog-tenant.xml       |  1 +
 .../0016_changed_unique_constraint_of_ref_no.xml   | 31 ++++++++
 .../ClientSavingsIntegrationTest.java              | 84 ++++++++++++++++++++++
 .../common/savings/SavingsAccountHelper.java       | 18 +++++
 20 files changed, 355 insertions(+), 127 deletions(-)

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<PostingPeriod> 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<ApiParameterError> 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<SavingsAccountTransaction> 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<SavingsAccountTransaction> savingsAccountTransactions,
             boolean backdatedTxnsAllowedTill) {
 
         final boolean isSavingsInterestPostingAtCurrentPeriodEnd = 
this.configurationDomainService
                 .isSavingsInterestPostingAtCurrentPeriodEnd();
         final Integer financialYearBeginningMonth = 
this.configurationDomainService.retrieveFinancialYearBeginningMonth();
-
-        final Set<SavingsAccountChargePaidBy> chargePaidBySet = 
savingsAccountTransaction.getSavingsAccountChargesPaid();
-
         final Set<Long> existingTransactionIds = new HashSet<>();
         final Set<Long> existingReversedTransactionIds = new HashSet<>();
 
@@ -300,30 +301,36 @@ public class SavingsAccountDomainServiceJpa implements 
SavingsAccountDomainServi
         } else {
             updateExistingTransactionsDetails(account, existingTransactionIds, 
existingReversedTransactionIds);
         }
-
-        SavingsAccountTransaction reversal = 
SavingsAccountTransaction.reversal(savingsAccountTransaction);
-        reversal.getSavingsAccountChargesPaid().addAll(chargePaidBySet);
-        account.undoTransaction(savingsAccountTransaction);
+        List<SavingsAccountTransaction> newTransactions = new ArrayList<>();
+        SavingsAccountTransaction reversal = null;
+
+        Set<SavingsAccountChargePaidBy> 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());
-
-        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);
+        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);
+            }
+
+            
account.validateAccountBalanceDoesNotBecomeNegativeMinimal(savingsAccountTransaction.getAmount(),
 false);
+            account.activateAccountBasedOnBalance();
         }
-
-        List<SavingsAccountTransaction> 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<String, Money> 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<TaxComponent, BigDecimal> 
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<SavingsAccountTransaction> 
findBySavingsAccount(@Param("savingsAccount") SavingsAccount savingsAccount);
 
+    @Query("select sat from SavingsAccountTransaction sat where sat.refNo = 
:refNo")
+    List<SavingsAccountTransaction> 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<SavingsAccountTransactionData> 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<SavingsAccountTransaction> 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<SavingsAccountTransactionData> 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<SavingsAccountTransaction> 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<SavingsAccountTransaction> 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<SavingsAccountTransactionData> 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<SavingsAccountTransaction> 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<SavingsAccountTransactionData> 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<SavingsAccountTransaction> 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<SavingsAccountTransactionData> 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<SavingsAccountTransaction> 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<SavingsAccountTransactionData> 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<SavingsAccountTransaction> 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<SavingsAccountTransactionData> 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<SavingsAccountTransaction> 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<SavingsAccountTransactionData> 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<SavingsAccountTransaction> 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<SavingsAccountTransaction> 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<SavingsAccountTransactionData> 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<SavingsAccountTransaction> 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<SavingsAccountTransactionData> 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<Long> existingTransactionIds = new HashSet<>();
         final Set<Long> 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<SavingsAccountTransaction> 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<SavingsAccountTransaction> 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<Long> 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 @@
     <include file="parts/0013_remove_topics.xml" 
relativeToChangelogFile="true"/>
     <include file="parts/0014_remove_unused_jobs.xml" 
relativeToChangelogFile="true"/>
     <include file="parts/0015_add_business_date.xml" 
relativeToChangelogFile="true"/>
+    <include file="parts/0016_changed_unique_constraint_of_ref_no.xml" 
relativeToChangelogFile="true"/>
 </databaseChangeLog>
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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd";>
+    <changeSet author="fineract" id="1" context="postgresql">
+        <dropUniqueConstraint tableName="m_savings_account_transaction" 
constraintName="m_savings_account_transaction_ref_no_key"/>
+    </changeSet>
+    <changeSet author="fineract" id="1" context="mysql">
+        <dropUniqueConstraint tableName="m_savings_account_transaction" 
constraintName="ref_no"/>
+    </changeSet>
+</databaseChangeLog>
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<String, String> 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<String, Object> map = new HashMap<>();
         map.put("locale", CommonConstants.LOCALE);

Reply via email to