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

arnold 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 2543e6f6a FINERACT-1784: Support undo waive charge for specific due 
date charges
2543e6f6a is described below

commit 2543e6f6a3e57a737a9323ce554e3fab59087e17
Author: Adam Saghy <[email protected]>
AuthorDate: Tue Oct 25 19:41:58 2022 +0200

    FINERACT-1784: Support undo waive charge for specific due date charges
---
 .../jobs/service/JobRegisterServiceImpl.java       |   8 +-
 .../jobs/service/SchedulerStopListener.java        |   8 +-
 .../exception/LoanChargeNotFoundException.java     |   4 +
 .../LoanChargeWaiveCannotBeReversedException.java  |  30 +-
 .../loanaccount/api/LoansApiResource.java          |  50 ++--
 .../portfolio/loanaccount/domain/Loan.java         |  25 +-
 .../portfolio/loanaccount/domain/LoanCharge.java   |  20 +-
 .../loanaccount/domain/LoanInstallmentCharge.java  |   8 +-
 .../domain/LoanRepaymentScheduleInstallment.java   |  22 +-
 .../portfolio/loanaccount/domain/LoanSummary.java  |  22 +-
 .../domain/LoanTransactionRepository.java          |   4 +-
 .../LoanWritePlatformServiceJpaRepositoryImpl.java | 285 +++++++++---------
 .../ClientLoanIntegrationTest.java                 | 326 ++++++++++++++++++++-
 .../common/loans/LoanTransactionHelper.java        |   9 +-
 14 files changed, 586 insertions(+), 235 deletions(-)

diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java
index dadd2a81a..bd627cc92 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/JobRegisterServiceImpl.java
@@ -45,7 +45,6 @@ import org.springframework.batch.core.Job;
 import org.springframework.batch.core.configuration.JobLocator;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationListener;
-import org.springframework.context.annotation.Lazy;
 import org.springframework.context.event.ContextClosedEvent;
 import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
 import 
org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
@@ -71,10 +70,6 @@ public class JobRegisterServiceImpl implements 
JobRegisterService, ApplicationLi
 
     private static final HashMap<String, Scheduler> SCHEDULERS = new 
HashMap<>(4);
 
-    @Autowired
-    @Lazy
-    private SchedulerStopListener schedulerStopListener;
-
     @Autowired
     private FineractProperties fineractProperties;
 
@@ -99,6 +94,7 @@ public class JobRegisterServiceImpl implements 
JobRegisterService, ApplicationLi
             final JobDetail jobDetail = createJobDetail(scheduledJobDetail);
             JobKey jobKey = jobDetail.getKey();
             if (scheduler == null || !scheduler.checkExists(jobKey)) {
+                SchedulerStopListener schedulerStopListener = new 
SchedulerStopListener(this);
                 final String tempSchedulerName = "temp" + 
scheduledJobDetail.getId();
                 final Scheduler tempScheduler = 
createScheduler(tempSchedulerName, 1, schedulerJobListener, 
schedulerStopListener);
                 jobDataMap.put(SchedulerServiceConstants.SCHEDULER_NAME, 
tempSchedulerName);
@@ -112,7 +108,7 @@ public class JobRegisterServiceImpl implements 
JobRegisterService, ApplicationLi
         } catch (final Exception e) {
             final String msg = "Job execution failed for job with id:" + 
scheduledJobDetail.getId();
             log.error("{}", msg, e);
-            throw new 
PlatformInternalServerException("error.msg.sheduler.job.execution.failed", msg, 
scheduledJobDetail.getId(), e);
+            throw new 
PlatformInternalServerException("error.msg.scheduler.job.execution.failed", 
msg, scheduledJobDetail.getId(), e);
         }
 
     }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerStopListener.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerStopListener.java
index e016393aa..c4488e995 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerStopListener.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/jobs/service/SchedulerStopListener.java
@@ -18,20 +18,16 @@
  */
 package org.apache.fineract.infrastructure.jobs.service;
 
-import lombok.RequiredArgsConstructor;
 import org.quartz.JobExecutionContext;
 import org.quartz.JobExecutionException;
 import org.quartz.JobListener;
-import org.springframework.stereotype.Component;
 
 /**
  * Global job Listener class to Stop the temporary scheduler once job 
execution completes
  */
-@Component
-@RequiredArgsConstructor
 public class SchedulerStopListener implements JobListener {
 
-    private static final String name = "Singlr Trigger Global Listener";
+    private static final String SINGLE_TRIGGER_GLOBAL_LISTENER = "Single 
Trigger Global Listener";
 
     // MIFOSX-1184: This class cannot use constructor injection, because one of
     // its dependencies (SchedulerStopListener) has a circular dependency to
@@ -47,7 +43,7 @@ public class SchedulerStopListener implements JobListener {
 
     @Override
     public String getName() {
-        return name;
+        return SINGLE_TRIGGER_GLOBAL_LISTENER;
     }
 
     @Override
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeNotFoundException.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeNotFoundException.java
index 2db934591..582c66d69 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeNotFoundException.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeNotFoundException.java
@@ -26,6 +26,10 @@ import 
org.apache.fineract.infrastructure.core.exception.AbstractPlatformResourc
  */
 public class LoanChargeNotFoundException extends 
AbstractPlatformResourceNotFoundException {
 
+    public LoanChargeNotFoundException() {
+        super("error.msg.loanCharge.invalid", "Loan charge cannot be found");
+    }
+
     public LoanChargeNotFoundException(final Long id) {
         super("error.msg.loanCharge.id.invalid", "Loan charge with identifier 
" + id + " does not exist", id);
     }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeWaiveCannotBeReversedException.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeWaiveCannotBeReversedException.java
index 1ed8e194e..32a9d557b 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeWaiveCannotBeReversedException.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/charge/exception/LoanChargeWaiveCannotBeReversedException.java
@@ -29,38 +29,38 @@ public class LoanChargeWaiveCannotBeReversedException 
extends AbstractPlatformDo
 
         public String errorMessage() {
 
-            if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
+            if (name().equalsIgnoreCase("ALREADY_PAID")) {
                 return "This loan charge has been completely paid";
-            } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
+            } else if (name().equalsIgnoreCase("ALREADY_WAIVED")) {
                 return "This loan charge has already been waived";
-            } else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) {
+            } else if (name().equalsIgnoreCase("LOAN_INACTIVE")) {
                 return "This loan charge can be waived as the loan associated 
with it is currently inactive";
-            } else if 
(name().toString().equalsIgnoreCase("WAIVE_NOT_ALLOWED_FOR_CHARGE")) {
+            } else if 
(name().equalsIgnoreCase("WAIVE_NOT_ALLOWED_FOR_CHARGE")) {
                 return "This loan charge can be waived";
-            } else if (name().toString().equalsIgnoreCase("NOT_WAIVED")) {
+            } else if (name().equalsIgnoreCase("NOT_WAIVED")) {
                 return "This loan charge waive cannot be reversed as this 
charge is not waived";
-            } else if (name().toString().equalsIgnoreCase("ALREADY_REVERSED")) 
{
-                return "This loan charge waive cannot be reversed as this 
transaction is not reversed";
+            } else if (name().equalsIgnoreCase("ALREADY_REVERSED")) {
+                return "This loan charge waive cannot be reversed as this 
transaction is already reversed";
             }
 
-            return name().toString();
+            return name();
         }
 
         public String errorCode() {
-            if (name().toString().equalsIgnoreCase("ALREADY_PAID")) {
+            if (name().equalsIgnoreCase("ALREADY_PAID")) {
                 return "error.msg.loan.charge.already.paid";
-            } else if (name().toString().equalsIgnoreCase("ALREADY_WAIVED")) {
+            } else if (name().equalsIgnoreCase("ALREADY_WAIVED")) {
                 return "error.msg.loan.charge.already.waived";
-            } else if (name().toString().equalsIgnoreCase("LOAN_INACTIVE")) {
+            } else if (name().equalsIgnoreCase("LOAN_INACTIVE")) {
                 return "error.msg.loan.charge.associated.loan.inactive";
-            } else if 
(name().toString().equalsIgnoreCase("WAIVE_NOT_ALLOWED_FOR_CHARGE")) {
+            } else if 
(name().equalsIgnoreCase("WAIVE_NOT_ALLOWED_FOR_CHARGE")) {
                 return "error.msg.loan.charge.waive.not.allowed";
-            } else if (name().toString().equalsIgnoreCase("NOT_WAIVED")) {
+            } else if (name().equalsIgnoreCase("NOT_WAIVED")) {
                 return "error.msg.loan.charge.waive.cannot.undo";
-            } else if (name().toString().equalsIgnoreCase("ALREADY_REVERSED")) 
{
+            } else if (name().equalsIgnoreCase("ALREADY_REVERSED")) {
                 return "error.msg.transaction.cannot.reverse";
             }
-            return name().toString();
+            return name();
         }
     }
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
index ca4fc1639..87be8ea40 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResource.java
@@ -35,6 +35,7 @@ import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -208,10 +209,10 @@ import org.springframework.util.CollectionUtils;
 @RequiredArgsConstructor
 public class LoansApiResource {
 
-    private final Set<String> loanDataParameters = new 
HashSet<>(Arrays.asList("id", "accountNo", "status", "externalId", "clientId",
-            "group", "loanProductId", "loanProductName", 
"loanProductDescription", "isLoanProductLinkedToFloatingRate", "fundId",
-            "fundName", "loanPurposeId", "loanPurposeName", "loanOfficerId", 
"loanOfficerName", "currency", "principal", "totalOverpaid",
-            "inArrearsTolerance", "termFrequency", "termPeriodFrequencyType", 
"numberOfRepayments", "repaymentEvery",
+    private static final Set<String> LOAN_DATA_PARAMETERS = new 
HashSet<>(Arrays.asList("id", "accountNo", "status", "externalId",
+            "clientId", "group", "loanProductId", "loanProductName", 
"loanProductDescription", "isLoanProductLinkedToFloatingRate",
+            "fundId", "fundName", "loanPurposeId", "loanPurposeName", 
"loanOfficerId", "loanOfficerName", "currency", "principal",
+            "totalOverpaid", "inArrearsTolerance", "termFrequency", 
"termPeriodFrequencyType", "numberOfRepayments", "repaymentEvery",
             "interestRatePerPeriod", "annualInterestRate", 
"repaymentFrequencyType", "transactionProcessingStrategyId",
             "transactionProcessingStrategyName", "interestRateFrequencyType", 
"amortizationType", "interestType",
             "interestCalculationPeriodType", 
LoanProductConstants.ALLOW_PARTIAL_PERIOD_INTEREST_CALCUALTION_PARAM_NAME,
@@ -228,10 +229,10 @@ public class LoansApiResource {
             LoanApiConstants.datatables, 
LoanProductConstants.RATES_PARAM_NAME, 
LoanApiConstants.MULTIDISBURSE_DETAILS_PARAMNAME,
             LoanApiConstants.EMI_AMOUNT_VARIATIONS_PARAMNAME, 
LoanApiConstants.COLLECTION_PARAMNAME));
 
-    private final Set<String> loanApprovalDataParameters = new 
HashSet<>(Arrays.asList("approvalDate", "approvalAmount"));
-    final Set<String> glimAccountsDataParameters = new 
HashSet<>(Arrays.asList("glimId", "groupId", "clientId", "parentLoanAccountNo",
-            "parentPrincipalAmount", "childLoanAccountNo", 
"childPrincipalAmount", "clientName"));
-    private final String resourceNameForPermissions = "LOAN";
+    private static final Set<String> LOAN_APPROVAL_DATA_PARAMETERS = new 
HashSet<>(Arrays.asList("approvalDate", "approvalAmount"));
+    private static final Set<String> GLIM_ACCOUNTS_DATA_PARAMETERS = new 
HashSet<>(Arrays.asList("glimId", "groupId", "clientId",
+            "parentLoanAccountNo", "parentPrincipalAmount", 
"childLoanAccountNo", "childPrincipalAmount", "clientName"));
+    private static final String RESOURCE_NAME_FOR_PERMISSIONS = "LOAN";
 
     private final PlatformSecurityContext context;
     private final LoanReadPlatformService loanReadPlatformService;
@@ -281,7 +282,7 @@ public class LoansApiResource {
             @QueryParam("templateType") @Parameter(description = 
"templateType") final String templateType,
             @Context final UriInfo uriInfo) {
 
-        
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+        
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
 
         LoanApprovalData loanApprovalTemplate = null;
 
@@ -293,7 +294,7 @@ public class LoansApiResource {
         }
 
         final ApiRequestJsonSerializationSettings settings = 
this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
-        return this.loanApprovalDataToApiJsonSerializer.serialize(settings, 
loanApprovalTemplate, this.loanApprovalDataParameters);
+        return this.loanApprovalDataToApiJsonSerializer.serialize(settings, 
loanApprovalTemplate, LOAN_APPROVAL_DATA_PARAMETERS);
 
     }
 
@@ -315,7 +316,7 @@ public class LoansApiResource {
             @DefaultValue("false") @QueryParam("activeOnly") 
@Parameter(description = "activeOnly") final boolean onlyActive,
             @Context final UriInfo uriInfo) {
 
-        
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+        
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
 
         // template
         final Collection<LoanProductData> productOptions = 
this.loanProductReadPlatformService.retrieveAllLoanProductsForLookup(onlyActive);
@@ -412,7 +413,7 @@ public class LoansApiResource {
         newLoanAccount.setDatatables(datatableTemplates);
 
         final ApiRequestJsonSerializationSettings settings = 
this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
-        return this.toApiJsonSerializer.serialize(settings, newLoanAccount, 
this.loanDataParameters);
+        return this.toApiJsonSerializer.serialize(settings, newLoanAccount, 
LOAN_DATA_PARAMETERS);
     }
 
     private Collection<PortfolioAccountData> getaccountLinkingOptions(final 
LoanAccountData newLoanAccount, final Long clientId,
@@ -448,7 +449,7 @@ public class LoansApiResource {
             @QueryParam("exclude") @Parameter(in = ParameterIn.QUERY, name = 
"exclude", description = "Optional Loan object relation list to be filtered in 
the response", required = false, example = "guarantors,futureSchedule") final 
String exclude,
             @QueryParam("fields") @Parameter(in = ParameterIn.QUERY, name = 
"fields", description = "Optional Loan attribute list to be in the response", 
required = false, example = "id,principal,annualInterestRate") final String 
fields,
             @Context final UriInfo uriInfo) {
-        
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+        
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
 
         LoanAccountData loanBasicDetails = 
this.loanReadPlatformService.retrieveOne(loanId);
         if (loanBasicDetails.isInterestRecalculationEnabled()) {
@@ -492,7 +493,7 @@ public class LoansApiResource {
         PortfolioAccountData linkedAccount = null;
         Collection<DisbursementData> disbursementData = null;
         Collection<LoanTermVariationsData> emiAmountVariations = null;
-        Collection<LoanCollateralResponseData> loanCollateralManagements = 
null;
+        Collection<LoanCollateralResponseData> loanCollateralManagements;
         Collection<LoanCollateralManagementData> loanCollateralManagementData 
= new ArrayList<>();
         CollectionData collectionData = CollectionData.template();
 
@@ -574,9 +575,6 @@ public class LoansApiResource {
                 for (LoanCollateralResponseData loanCollateralManagement : 
loanCollateralManagements) {
                     
loanCollateralManagementData.add(loanCollateralManagement.toCommand());
                 }
-                if (CollectionUtils.isEmpty(loanCollateralManagements)) {
-                    loanCollateralManagements = null;
-                }
             }
 
             if 
(associationParameters.contains(DataTableApiConstant.meetingAssociateParamName))
 {
@@ -606,7 +604,7 @@ public class LoansApiResource {
         }
 
         Collection<LoanProductData> productOptions = null;
-        LoanProductData product = null;
+        LoanProductData product;
         Collection<EnumOptionData> loanTermFrequencyTypeOptions = null;
         Collection<EnumOptionData> repaymentFrequencyTypeOptions = null;
         Collection<EnumOptionData> repaymentFrequencyNthDayTypeOptions = null;
@@ -624,7 +622,7 @@ public class LoansApiResource {
         Collection<CodeValueData> loanCollateralOptions = null;
         Collection<CalendarData> calendarOptions = null;
         Collection<PortfolioAccountData> accountLinkingOptions = null;
-        PaidInAdvanceData paidInAdvanceTemplate = null;
+        PaidInAdvanceData paidInAdvanceTemplate;
         Collection<LoanAccountSummaryData> clientActiveLoanOptions = null;
 
         final boolean template = 
ApiParameterHelper.template(uriInfo.getQueryParameters());
@@ -640,7 +638,7 @@ public class LoansApiResource {
 
             amortizationTypeOptions = 
this.dropdownReadPlatformService.retrieveLoanAmortizationTypeOptions();
             if (product.isLinkedToFloatingInterestRates()) {
-                interestTypeOptions = 
Arrays.asList(interestType(InterestMethod.DECLINING_BALANCE));
+                interestTypeOptions = 
Collections.singletonList(interestType(InterestMethod.DECLINING_BALANCE));
             } else {
                 interestTypeOptions = 
this.dropdownReadPlatformService.retrieveLoanInterestTypeOptions();
             }
@@ -710,7 +708,7 @@ public class LoansApiResource {
 
         final ApiRequestJsonSerializationSettings settings = 
this.apiRequestParameterHelper.process(uriInfo.getQueryParameters(),
                 mandatoryResponseParameters);
-        return this.toApiJsonSerializer.serialize(settings, loanAccount, 
this.loanDataParameters);
+        return this.toApiJsonSerializer.serialize(settings, loanAccount, 
LOAN_DATA_PARAMETERS);
     }
 
     @GET
@@ -731,7 +729,7 @@ public class LoansApiResource {
             @QueryParam("sortOrder") @Parameter(description = "sortOrder") 
final String sortOrder,
             @QueryParam("accountNo") @Parameter(description = "accountNo") 
final String accountNo) {
 
-        
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+        
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
 
         final SearchParameters searchParameters = 
SearchParameters.forLoans(sqlSearch, externalId, offset, limit, orderBy, 
sortOrder,
                 accountNo);
@@ -739,7 +737,7 @@ public class LoansApiResource {
         final Page<LoanAccountData> loanBasicDetails = 
this.loanReadPlatformService.retrieveAll(searchParameters);
 
         final ApiRequestJsonSerializationSettings settings = 
this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
-        return this.toApiJsonSerializer.serialize(settings, loanBasicDetails, 
this.loanDataParameters);
+        return this.toApiJsonSerializer.serialize(settings, loanBasicDetails, 
LOAN_DATA_PARAMETERS);
     }
 
     @POST
@@ -767,7 +765,7 @@ public class LoansApiResource {
             final LoanScheduleModel loanSchedule = 
this.calculationPlatformService.calculateLoanSchedule(query, true);
 
             final ApiRequestJsonSerializationSettings settings = 
this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
-            return this.loanScheduleToApiJsonSerializer.serialize(settings, 
loanSchedule.toData(), new HashSet<String>());
+            return this.loanScheduleToApiJsonSerializer.serialize(settings, 
loanSchedule.toData(), new HashSet<>());
         }
 
         final CommandWrapper commandRequest = new 
CommandWrapperBuilder().createLoanApplication().withJson(apiRequestBodyAsJson).build();
@@ -889,10 +887,10 @@ public class LoansApiResource {
     @Consumes({ MediaType.APPLICATION_JSON })
     @Produces({ MediaType.APPLICATION_JSON })
     public String getGlimRepaymentTemplate(@PathParam("glimId") final Long 
glimId, @Context final UriInfo uriInfo) {
-        
this.context.authenticatedUser().validateHasReadPermission(this.resourceNameForPermissions);
+        
this.context.authenticatedUser().validateHasReadPermission(RESOURCE_NAME_FOR_PERMISSIONS);
         Collection<GlimRepaymentTemplate> glimRepaymentTemplate = 
this.glimAccountInfoReadPlatformService.findglimRepaymentTemplate(glimId);
         final ApiRequestJsonSerializationSettings settings = 
this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
-        return this.glimTemplateToApiJsonSerializer.serialize(settings, 
glimRepaymentTemplate, this.glimAccountsDataParameters);
+        return this.glimTemplateToApiJsonSerializer.serialize(settings, 
glimRepaymentTemplate, GLIM_ACCOUNTS_DATA_PARAMETERS);
     }
 
     @POST
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index 21728bfc7..f7dc17113 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -567,9 +567,16 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
         return this.summary;
     }
 
-    public void updateLoanSummaryForUndoWaiveCharge(final BigDecimal 
amountWaived) {
-        
this.summary.updateFeeChargesWaived(this.summary.getTotalFeeChargesWaived().subtract(amountWaived));
-        
this.summary.updateFeeChargeOutstanding(this.summary.getTotalFeeChargesOutstanding().add(amountWaived));
+    public void updateLoanSummaryForUndoWaiveCharge(final BigDecimal 
amountWaived, final boolean isPenalty) {
+        if (isPenalty) {
+            
this.summary.updatePenaltyChargesWaived(this.summary.getTotalPenaltyChargesWaived().subtract(amountWaived));
+            
this.summary.updatePenaltyChargeOutstanding(this.summary.getTotalPenaltyChargesOutstanding().add(amountWaived));
+        } else {
+            
this.summary.updateFeeChargesWaived(this.summary.getTotalFeeChargesWaived().subtract(amountWaived));
+            
this.summary.updateFeeChargeOutstanding(this.summary.getTotalFeeChargesOutstanding().add(amountWaived));
+        }
+        
this.summary.updateTotalOutstanding(this.summary.getTotalOutstanding().add(amountWaived));
+        
this.summary.updateTotalWaived(this.summary.getTotalWaived().subtract(amountWaived));
     }
 
     private BigDecimal deriveSumTotalOfChargesDueAtDisbursement() {
@@ -1377,7 +1384,7 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
 
             final Money principal = 
this.loanRepaymentScheduleDetail.getPrincipal();
             this.summary.updateSummary(loanCurrency(), principal, 
getRepaymentScheduleInstallments(), this.loanSummaryWrapper,
-                    isDisbursed(), this.charges);
+                    this.charges);
             updateLoanOutstandingBalances();
         }
     }
@@ -2051,20 +2058,22 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom {
 
         if (submittedOn.isAfter(DateUtils.getBusinessLocalDate())) {
             final String errorMessage = "The date on which a loan is submitted 
cannot be in the future.";
-            throw new InvalidLoanStateTransitionException("submittal", 
"cannot.be.a.future.date", errorMessage, submittedOn);
+            throw new InvalidLoanStateTransitionException("submittal", 
"cannot.be.a.future.date", errorMessage, submittedOn,
+                    DateUtils.getBusinessLocalDate());
         }
 
         if (this.client != null && this.client.isActivatedAfter(submittedOn)) {
             final String errorMessage = "The date on which a loan is submitted 
cannot be earlier than client's activation date.";
-            throw new InvalidLoanStateTransitionException("submittal", 
"cannot.be.before.client.activation.date", errorMessage,
-                    submittedOn);
+            throw new InvalidLoanStateTransitionException("submittal", 
"cannot.be.before.client.activation.date", errorMessage, submittedOn,
+                    client.getActivationLocalDate());
         }
 
         
validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_CREATED, 
submittedOn);
 
         if (this.group != null && this.group.isActivatedAfter(submittedOn)) {
             final String errorMessage = "The date on which a loan is submitted 
cannot be earlier than groups's activation date.";
-            throw new InvalidLoanStateTransitionException("submittal", 
"cannot.be.before.group.activation.date", errorMessage, submittedOn);
+            throw new InvalidLoanStateTransitionException("submittal", 
"cannot.be.before.group.activation.date", errorMessage, submittedOn,
+                    group.getActivationLocalDate());
         }
 
         if (submittedOn.isAfter(getExpectedDisbursedOnLocalDate())) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java
index 294966ce1..92f2b0d70 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanCharge.java
@@ -358,7 +358,7 @@ public class LoanCharge extends AbstractPersistableCustom {
         }
     }
 
-    public void resetOutstandingAmount(final BigDecimal amountOutstanding) {
+    public void setOutstandingAmount(final BigDecimal amountOutstanding) {
         this.amountOutstanding = amountOutstanding;
     }
 
@@ -659,7 +659,7 @@ public class LoanCharge extends AbstractPersistableCustom {
      *          amount is within min and max cap
      */
     private BigDecimal minimumAndMaximumCap(final BigDecimal percentageOf) {
-        BigDecimal minMaxCap = BigDecimal.ZERO;
+        BigDecimal minMaxCap;
         if (this.minCap != null) {
             final int minimumCap = percentageOf.compareTo(this.minCap);
             if (minimumCap == -1) {
@@ -789,7 +789,7 @@ public class LoanCharge extends AbstractPersistableCustom {
      * @return Actual amount paid on this charge
      */
     public Money updatePaidAmountBy(final Money incrementBy, final Integer 
installmentNumber, final Money feeAmount) {
-        Money processAmount = Money.zero(incrementBy.getCurrency());
+        Money processAmount;
         if (isInstalmentFee()) {
             if (installmentNumber == null) {
                 processAmount = 
getUnpaidInstallmentLoanCharge().updatePaidAmountBy(incrementBy, feeAmount);
@@ -802,7 +802,7 @@ public class LoanCharge extends AbstractPersistableCustom {
         Money amountPaidToDate = Money.of(processAmount.getCurrency(), 
this.amountPaid);
         final Money amountOutstanding = Money.of(processAmount.getCurrency(), 
this.amountOutstanding);
 
-        Money amountPaidOnThisCharge = Money.zero(processAmount.getCurrency());
+        Money amountPaidOnThisCharge;
         if (processAmount.isGreaterThanOrEqualTo(amountOutstanding)) {
             amountPaidOnThisCharge = amountOutstanding;
             amountPaidToDate = amountPaidToDate.plus(amountOutstanding);
@@ -890,7 +890,7 @@ public class LoanCharge extends AbstractPersistableCustom {
 
     public LoanInstallmentCharge getInstallmentLoanCharge(final Integer 
installmentNumber) {
         for (final LoanInstallmentCharge loanChargePerInstallment : 
this.loanInstallmentCharge) {
-            if 
(installmentNumber.equals(loanChargePerInstallment.getRepaymentInstallment().getInstallmentNumber().intValue()))
 {
+            if 
(installmentNumber.equals(loanChargePerInstallment.getRepaymentInstallment().getInstallmentNumber()))
 {
                 return loanChargePerInstallment;
             }
         }
@@ -900,7 +900,7 @@ public class LoanCharge extends AbstractPersistableCustom {
     public void setInstallmentLoanCharge(final LoanInstallmentCharge 
loanInstallmentCharge, final Integer installmentNumber) {
         LoanInstallmentCharge loanInstallmentChargeToBeRemoved = null;
         for (final LoanInstallmentCharge loanChargePerInstallment : 
this.loanInstallmentCharge) {
-            if 
(installmentNumber.equals(loanChargePerInstallment.getRepaymentInstallment().getInstallmentNumber().intValue()))
 {
+            if 
(installmentNumber.equals(loanChargePerInstallment.getRepaymentInstallment().getInstallmentNumber()))
 {
                 loanInstallmentChargeToBeRemoved = loanChargePerInstallment;
                 break;
             }
@@ -1017,7 +1017,7 @@ public class LoanCharge extends AbstractPersistableCustom 
{
     }
 
     public Money undoPaidOrPartiallyAmountBy(final Money incrementBy, final 
Integer installmentNumber, final Money feeAmount) {
-        Money processAmount = Money.zero(incrementBy.getCurrency());
+        Money processAmount;
         if (isInstalmentFee()) {
             if (installmentNumber == null) {
                 processAmount = 
getLastPaidOrPartiallyPaidInstallmentLoanCharge(incrementBy.getCurrency()).undoPaidAmountBy(incrementBy,
@@ -1030,7 +1030,7 @@ public class LoanCharge extends AbstractPersistableCustom 
{
         }
         Money amountPaidToDate = Money.of(processAmount.getCurrency(), 
this.amountPaid);
 
-        Money amountDeductedOnThisCharge = 
Money.zero(processAmount.getCurrency());
+        Money amountDeductedOnThisCharge;
         if (processAmount.isGreaterThanOrEqualTo(amountPaidToDate)) {
             amountDeductedOnThisCharge = amountPaidToDate;
             amountPaidToDate = Money.zero(processAmount.getCurrency());
@@ -1097,4 +1097,8 @@ public class LoanCharge extends AbstractPersistableCustom 
{
         this.externalId = externalId;
     }
 
+    public ChargeTimeType getChargeTimeType() {
+        return ChargeTimeType.fromInt(this.chargeTime);
+    }
+
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java
index b690795de..06ad91843 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanInstallmentCharge.java
@@ -185,7 +185,7 @@ public class LoanInstallmentCharge extends 
AbstractPersistableCustom implements
         Money amountPaidToDate = Money.of(incrementBy.getCurrency(), 
this.amountPaid);
         final Money amountOutstanding = Money.of(incrementBy.getCurrency(), 
this.amountOutstanding);
         Money amountPaidPreviously = amountPaidToDate;
-        Money amountPaidOnThisCharge = Money.zero(incrementBy.getCurrency());
+        Money amountPaidOnThisCharge;
         if (incrementBy.isGreaterThanOrEqualTo(amountOutstanding)) {
             amountPaidOnThisCharge = amountOutstanding;
             amountPaidToDate = amountPaidToDate.plus(amountOutstanding);
@@ -226,7 +226,7 @@ public class LoanInstallmentCharge extends 
AbstractPersistableCustom implements
         this.paid = false;
     }
 
-    public void resetAmountWaived(final BigDecimal amountWaived) {
+    public void setAmountWaived(final BigDecimal amountWaived) {
         this.amountWaived = amountWaived;
     }
 
@@ -234,7 +234,7 @@ public class LoanInstallmentCharge extends 
AbstractPersistableCustom implements
         this.waived = false;
     }
 
-    public void resetOutstandingAmount(final BigDecimal amountOutstanding) {
+    public void setOutstandingAmount(final BigDecimal amountOutstanding) {
         this.amountOutstanding = amountOutstanding;
     }
 
@@ -293,7 +293,7 @@ public class LoanInstallmentCharge extends 
AbstractPersistableCustom implements
 
         Money amountPaidToDate = Money.of(incrementBy.getCurrency(), 
this.amountPaid);
 
-        Money amountToDeductOnThisCharge = 
Money.zero(incrementBy.getCurrency());
+        Money amountToDeductOnThisCharge;
         if (incrementBy.isGreaterThanOrEqualTo(amountPaidToDate)) {
             amountToDeductOnThisCharge = amountPaidToDate;
             amountPaidToDate = Money.zero(incrementBy.getCurrency());
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
index 7aea6ea41..071dcf377 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java
@@ -20,7 +20,6 @@ package org.apache.fineract.portfolio.loanaccount.domain;
 
 import java.math.BigDecimal;
 import java.time.LocalDate;
-import java.util.Comparator;
 import java.util.HashSet;
 import java.util.Set;
 import javax.persistence.CascadeType;
@@ -716,15 +715,6 @@ public class LoanRepaymentScheduleInstallment extends 
AbstractAuditableWithUTCDa
         }
     }
 
-    public static Comparator<LoanRepaymentScheduleInstallment> 
installmentNumberComparator = new 
Comparator<LoanRepaymentScheduleInstallment>() {
-
-        @Override
-        public int compare(LoanRepaymentScheduleInstallment arg0, 
LoanRepaymentScheduleInstallment arg1) {
-
-            return 
arg0.getInstallmentNumber().compareTo(arg1.getInstallmentNumber());
-        }
-    };
-
     public BigDecimal getTotalPaidInAdvance() {
         return this.totalPaidInAdvance;
     }
@@ -742,7 +732,7 @@ public class LoanRepaymentScheduleInstallment extends 
AbstractAuditableWithUTCDa
     public Money unpayPenaltyChargesComponent(final LocalDate transactionDate, 
final Money transactionAmountRemaining) {
 
         final MonetaryCurrency currency = 
transactionAmountRemaining.getCurrency();
-        Money penaltyPortionOfTransactionDeducted = Money.zero(currency);
+        Money penaltyPortionOfTransactionDeducted;
 
         final Money penaltyChargesCompleted = getPenaltyChargesPaid(currency);
         if 
(transactionAmountRemaining.isGreaterThanOrEqualTo(penaltyChargesCompleted)) {
@@ -761,7 +751,7 @@ public class LoanRepaymentScheduleInstallment extends 
AbstractAuditableWithUTCDa
     public Money unpayFeeChargesComponent(final LocalDate transactionDate, 
final Money transactionAmountRemaining) {
 
         final MonetaryCurrency currency = 
transactionAmountRemaining.getCurrency();
-        Money feePortionOfTransactionDeducted = Money.zero(currency);
+        Money feePortionOfTransactionDeducted;
 
         final Money feeChargesCompleted = getFeeChargesPaid(currency);
         if 
(transactionAmountRemaining.isGreaterThanOrEqualTo(feeChargesCompleted)) {
@@ -782,7 +772,7 @@ public class LoanRepaymentScheduleInstallment extends 
AbstractAuditableWithUTCDa
     public Money unpayInterestComponent(final LocalDate transactionDate, final 
Money transactionAmountRemaining) {
 
         final MonetaryCurrency currency = 
transactionAmountRemaining.getCurrency();
-        Money interestPortionOfTransactionDeducted = Money.zero(currency);
+        Money interestPortionOfTransactionDeducted;
 
         final Money interestCompleted = getInterestPaid(currency);
         if 
(transactionAmountRemaining.isGreaterThanOrEqualTo(interestCompleted)) {
@@ -803,7 +793,7 @@ public class LoanRepaymentScheduleInstallment extends 
AbstractAuditableWithUTCDa
     public Money unpayPrincipalComponent(final LocalDate transactionDate, 
final Money transactionAmountRemaining) {
 
         final MonetaryCurrency currency = 
transactionAmountRemaining.getCurrency();
-        Money principalPortionOfTransactionDeducted = Money.zero(currency);
+        Money principalPortionOfTransactionDeducted;
 
         final Money principalCompleted = getPrincipalCompleted(currency);
         if 
(transactionAmountRemaining.isGreaterThanOrEqualTo(principalCompleted)) {
@@ -882,6 +872,10 @@ public class LoanRepaymentScheduleInstallment extends 
AbstractAuditableWithUTCDa
         this.feeChargesWaived = newFeeChargesCharged;
     }
 
+    public void setPenaltyChargesWaived(final BigDecimal 
newPenaltyChargesCharged) {
+        this.penaltyChargesWaived = newPenaltyChargesCharged;
+    }
+
     public Set<LoanInstallmentCharge> getInstallmentCharges() {
         return installmentCharges;
     }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java
index 902094070..9296dc06d 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanSummary.java
@@ -35,7 +35,7 @@ import org.apache.fineract.organisation.monetary.domain.Money;
  */
 @Embeddable
 @Getter
-public final class LoanSummary {
+public class LoanSummary {
 
     // derived totals fields
     @Column(name = "principal_disbursed_derived", scale = 6, precision = 19)
@@ -126,7 +126,7 @@ public final class LoanSummary {
         return new LoanSummary(totalFeeChargesDueAtDisbursement);
     }
 
-    LoanSummary() {
+    protected LoanSummary() {
         //
     }
 
@@ -150,10 +150,18 @@ public final class LoanSummary {
         this.totalFeeChargesOutstanding = totalFeeChargesOutstanding;
     }
 
+    public void updatePenaltyChargeOutstanding(final BigDecimal 
totalPenaltyChargesOutstanding) {
+        this.totalPenaltyChargesOutstanding = totalPenaltyChargesOutstanding;
+    }
+
     public void updateFeeChargesWaived(final BigDecimal totalFeeChargesWaived) 
{
         this.totalFeeChargesWaived = totalFeeChargesWaived;
     }
 
+    public void updatePenaltyChargesWaived(final BigDecimal 
totalPenaltyChargesWaived) {
+        this.totalPenaltyChargesWaived = totalPenaltyChargesWaived;
+    }
+
     public boolean isRepaidInFull(final MonetaryCurrency currency) {
         return getTotalOutstanding(currency).isZero();
     }
@@ -182,6 +190,14 @@ public final class LoanSummary {
         return this.totalOutstanding;
     }
 
+    public void updateTotalOutstanding(final BigDecimal newTotalOutstanding) {
+        this.totalOutstanding = newTotalOutstanding;
+    }
+
+    public void updateTotalWaived(final BigDecimal totalWaived) {
+        this.totalWaived = totalWaived;
+    }
+
     /**
      * All fields but <code>totalFeeChargesDueAtDisbursement</code> should be 
reset.
      */
@@ -217,7 +233,7 @@ public final class LoanSummary {
 
     public void updateSummary(final MonetaryCurrency currency, final Money 
principal,
             final List<LoanRepaymentScheduleInstallment> 
repaymentScheduleInstallments, final LoanSummaryWrapper summaryWrapper,
-            final Boolean disbursed, Set<LoanCharge> charges) {
+            Set<LoanCharge> charges) {
 
         this.totalPrincipalDisbursed = principal.getAmount();
         this.totalPrincipalAdjustments = 
summaryWrapper.calculateTotalPrincipalAdjusted(repaymentScheduleInstallments, 
currency)
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
index bdd343013..16f6268cf 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
@@ -18,9 +18,11 @@
  */
 package org.apache.fineract.portfolio.loanaccount.domain;
 
+import java.util.Optional;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 
 public interface LoanTransactionRepository extends 
JpaRepository<LoanTransaction, Long>, JpaSpecificationExecutor<LoanTransaction> 
{
-    // no added behaviour
+
+    Optional<LoanTransaction> findByIdAndLoanId(Long transactionId, Long 
loanId);
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index b8ebd1293..349546f1c 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -234,6 +234,7 @@ import 
org.springframework.transaction.annotation.Transactional;
 @RequiredArgsConstructor
 public class LoanWritePlatformServiceJpaRepositoryImpl implements 
LoanWritePlatformService {
 
+    public static final String AMOUNT = "amount";
     private final PlatformSecurityContext context;
     private final LoanEventApiJsonValidator loanEventApiJsonValidator;
     private final LoanUpdateCommandFromApiJsonDeserializer 
loanUpdateCommandFromApiJsonDeserializer;
@@ -278,6 +279,10 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
     private final LoanDisbursementDetailsRepository 
loanDisbursementDetailsRepository;
     private final LoanRepaymentScheduleInstallmentRepository 
loanRepaymentScheduleInstallmentRepository;
 
+    private static boolean isPartOfThisInstallment(LoanCharge loanCharge, 
LoanRepaymentScheduleInstallment e) {
+        return e.getFromDate().isBefore(loanCharge.getDueDate()) && 
!loanCharge.getDueDate().isAfter(e.getDueDate());
+    }
+
     private LoanLifecycleStateMachine defaultLoanLifecycleStateMachine() {
         return new DefaultLoanLifecycleStateMachine(LoanStatus.values(), 
businessEventNotifierService);
     }
@@ -343,7 +348,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
         final Set<LoanCollateralManagement> loanCollateralManagements = 
loan.getLoanCollateralManagements();
 
         // Get relevant loan collateral modules
-        if ((loanCollateralManagements != null && 
loanCollateralManagements.size() != 0)
+        if ((loanCollateralManagements != null && 
!loanCollateralManagements.isEmpty())
                 && 
AccountType.fromInt(loan.getLoanType()).isIndividualAccount()) {
 
             BigDecimal totalCollateral = BigDecimal.valueOf(0);
@@ -463,7 +468,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
                 loan.addLoanTransaction(disbursementTransaction);
             }
 
-            if (loan.getRepaymentScheduleInstallments().size() == 0) {
+            if (loan.getRepaymentScheduleInstallments().isEmpty()) {
                 /*
                  * If no schedule, generate one (applicable to non-tranche 
multi-disbursal loans)
                  */
@@ -578,7 +583,6 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
      *
      * @param loan
      *            the disbursed loan
-     *
      **/
     private void createStandingInstruction(Loan loan) {
 
@@ -877,15 +881,13 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
             
journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
 
             // Remove All the Disbursement Details If the Loan Product is 
disabled and exists one
-            if (loan.loanProduct().isDisallowExpectedDisbursements()) {
-                if (!loan.getDisbursementDetails().isEmpty()) {
-                    List<LoanDisbursementDetails> reversedDisbursementDetails 
= new ArrayList<LoanDisbursementDetails>();
-                    for (LoanDisbursementDetails disbursementDetail : 
loan.getAllDisbursementDetails()) {
-                        disbursementDetail.reverse();
-                        reversedDisbursementDetails.add(disbursementDetail);
-                    }
-                    
this.loanDisbursementDetailsRepository.saveAllAndFlush(reversedDisbursementDetails);
+            if (loan.loanProduct().isDisallowExpectedDisbursements() && 
!loan.getDisbursementDetails().isEmpty()) {
+                List<LoanDisbursementDetails> reversedDisbursementDetails = 
new ArrayList<>();
+                for (LoanDisbursementDetails disbursementDetail : 
loan.getAllDisbursementDetails()) {
+                    disbursementDetail.reverse();
+                    reversedDisbursementDetails.add(disbursementDetail);
                 }
+                
this.loanDisbursementDetailsRepository.saveAllAndFlush(reversedDisbursementDetails);
             }
 
             businessEventNotifierService.notifyPostBusinessEvent(new 
LoanUndoDisbursalBusinessEvent(loan));
@@ -1924,10 +1926,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
             final Integer installmentNumber = 
installmentChargeEntry.getRepaymentInstallment().getInstallmentNumber();
             chargeAmountPaid = 
installmentChargeEntry.getAmountPaid(loanCurrency).getAmount();
             for (LoanChargePaidBy loanChargePaidBy : 
loanCharge.getLoanChargePaidBySet()) {
-                if 
(installmentNumber.equals(loanChargePaidBy.getInstallmentNumber())) {
-                    if (isRefundElementOfChargeRefund(loanChargePaidBy)) {
-                        chargeAmountRefunded = 
chargeAmountRefunded.add(loanChargePaidBy.getAmount());
-                    }
+                if 
(installmentNumber.equals(loanChargePaidBy.getInstallmentNumber()) && 
isRefundElementOfChargeRefund(loanChargePaidBy)) {
+                    chargeAmountRefunded = 
chargeAmountRefunded.add(loanChargePaidBy.getAmount());
                 }
             }
         } else {
@@ -1947,12 +1947,11 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
         }
 
         BigDecimal refundableAmount = 
chargeAmountPaid.subtract(chargeAmountRefunded);
-        if (transactionAmount != null) { // refund amount was provided.
-            if (transactionAmount.compareTo(refundableAmount) > 0) {
-                final String errorMessage = 
"loan.charge.transaction.amount.is.more.than.is.refundable";
-                final String details = "transactionAmount: " + 
transactionAmount + "  Refundable: " + refundableAmount;
-                throw new LoanChargeRefundException(errorMessage, details);
-            }
+        // refund amount was provided.
+        if (transactionAmount != null && 
transactionAmount.compareTo(refundableAmount) > 0) {
+            final String errorMessage = 
"loan.charge.transaction.amount.is.more.than.is.refundable";
+            final String details = "transactionAmount: " + transactionAmount + 
"  Refundable: " + refundableAmount;
+            throw new LoanChargeRefundException(errorMessage, details);
         }
 
         return refundableAmount;
@@ -2022,143 +2021,153 @@ public class 
LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
     @Override
     public CommandProcessingResult undoWaiveLoanCharge(final JsonCommand 
command) {
 
-        LoanTransaction loanTransaction = 
this.loanTransactionRepository.findById(command.entityId())
-                .orElseThrow(() -> new 
LoanTransactionNotFoundException(command.entityId()));
-
+        LoanTransaction loanTransaction = 
this.loanTransactionRepository.findByIdAndLoanId(command.entityId(), 
command.getLoanId())
+                .orElseThrow(() -> new 
LoanTransactionNotFoundException(command.entityId(), command.getLoanId()));
         if 
(!loanTransaction.getTypeOf().getCode().equals(LoanTransactionType.WAIVE_CHARGES.getCode()))
 {
-            throw new InvalidLoanTransactionTypeException("Undo Waive Charge", 
"Waive an Installment Charge First",
-                    "Transaction is not a waive charge type.");
+            throw new InvalidLoanTransactionTypeException("transaction", 
"undo.waive.charge", "Transaction is not a waive charge type.");
+        }
+        if (!loanTransaction.isNotReversed()) {
+            throw new 
LoanChargeWaiveCannotBeReversedException(LoanChargeWaiveCannotUndoReason.ALREADY_REVERSED,
 loanTransaction.getId());
         }
 
         Set<LoanChargePaidBy> loanChargePaidBySet = 
loanTransaction.getLoanChargesPaid();
-        Integer installmentNumber = null;
-        Long loanChargeId = null;
-        final Long loanId = loanTransaction.getLoan().getId();
-
-        for (LoanChargePaidBy loanChargePaidBy : loanChargePaidBySet) {
-            installmentNumber = loanChargePaidBy.getInstallmentNumber();
-            loanChargeId = loanChargePaidBy.getLoanCharge().getId();
-            break;
+        LoanChargePaidBy loanChargePaidBy = 
loanChargePaidBySet.stream().findFirst().orElseThrow(LoanChargeNotFoundException::new);
+        final LoanCharge loanCharge = loanChargePaidBy.getLoanCharge();
+        // Validate loan charge is not already paid
+        if (loanCharge.isPaid()) {
+            throw new 
LoanChargeWaiveCannotBeReversedException(LoanChargeWaiveCannotUndoReason.ALREADY_PAID,
 loanCharge.getId());
         }
 
+        final Long loanId = loanTransaction.getLoan().getId();
         final Loan loan = this.loanAssembler.assembleFrom(loanId);
         checkClientOrGroupActive(loan);
-        final LoanCharge loanCharge = retrieveLoanChargeBy(loanId, 
loanChargeId);
-
         // Charges may be waived only when the loan associated with them are
         // active
         if (!loan.getStatus().isActive()) {
             throw new 
LoanChargeWaiveCannotBeReversedException(LoanChargeWaiveCannotUndoReason.LOAN_INACTIVE,
 loanCharge.getId());
         }
 
-        // Validate loan charge is not already paid
-        if (loanCharge.isPaid()) {
-            throw new 
LoanChargeWaiveCannotBeReversedException(LoanChargeWaiveCannotUndoReason.ALREADY_PAID,
 loanCharge.getId());
-        }
-
         final Map<String, Object> changes = new LinkedHashMap<>(3);
 
         businessEventNotifierService.notifyPreBusinessEvent(new 
LoanWaiveChargeUndoBusinessEvent(loanCharge));
 
-        if (loanCharge.isInstalmentFee()) {
-            LoanInstallmentCharge chargePerInstallment;
-
-            // final Integer installmentNumber = 
command.integerValueOfParameterNamed("installmentNumber");
-            if (installmentNumber != null) {
-
-                // Get installment charge.
-                chargePerInstallment = 
loanCharge.getInstallmentLoanCharge(installmentNumber);
-
-                if (!loanTransaction.isNotReversed()) {
-                    throw new 
LoanChargeWaiveCannotBeReversedException(LoanChargeWaiveCannotUndoReason.ALREADY_REVERSED,
-                            loanTransaction.getId());
-                }
-
-                // Reverse waived transaction
-                loanTransaction.setReversed();
-
-                // Get installment amount waived.
-                BigDecimal amountWaived = 
chargePerInstallment.getAmountWaived(loan.getCurrency()).getAmount();
-
-                // Set manually adjusted value to `1`
-                loanTransaction.setManuallyAdjustedOrReversed();
-
-                // Save updated data
-                this.loanTransactionRepository.saveAndFlush(loanTransaction);
-
-                // Get installment outstanding amount
-                BigDecimal amountOutstandingPerInstallment = 
chargePerInstallment.getAmountOutstanding();
-
-                // Check whether the installment charge is not waived. If so 
throw new error
-                if (!chargePerInstallment.isWaived() || amountWaived == null) {
-                    throw new 
LoanChargeWaiveCannotBeReversedException(LoanChargeWaiveCannotUndoReason.NOT_WAIVED,
 loanChargeId);
-                }
-
-                // Get loan charge total amount waived
-                BigDecimal totalAmountWaved = 
loanCharge.getAmountWaived(loan.getCurrency()).getAmount();
-
-                // Get loan charge outstanding amount
-                BigDecimal amountOutstanding = 
loanCharge.getAmountOutstanding(loan.getCurrency()).getAmount();
-
-                // Add the amount waived to outstanding amount
-                
loanCharge.resetOutstandingAmount(amountOutstanding.add(amountWaived));
-
-                // Subtract the amount waived from the existing amount waived.
-                
loanCharge.setAmountWaived(totalAmountWaved.subtract(amountWaived));
+        undoWaivedCharge(changes, loan, loanTransaction, loanChargePaidBy);
 
-                // Add the amount waived to the outstanding amount of the 
installment
-                
chargePerInstallment.resetOutstandingAmount(amountOutstandingPerInstallment.add(amountWaived));
-
-                // Set the amount waived value to ZERO
-                chargePerInstallment.resetAmountWaived(BigDecimal.ZERO);
-
-                // Reset waived flag
-                chargePerInstallment.undoWaiveFlag();
-
-                // Get the fee charges waived amount per installment
-                BigDecimal feeChargesWaivedAmount = 
chargePerInstallment.getInstallment().getFeeChargesWaived(loan.getCurrency())
-                        .getAmount();
-
-                // Subtract the amount waived from the existing fee charges 
waived amount.
-                
chargePerInstallment.getInstallment().setFeeChargesWaived(feeChargesWaivedAmount.subtract(amountWaived));
-
-                // Update loan charge.
-                loanCharge.setInstallmentLoanCharge(chargePerInstallment, 
chargePerInstallment.getInstallment().getInstallmentNumber());
-
-                if 
(loanCharge.getAmount(loan.getCurrency()).compareTo(loanCharge.getAmountOutstanding(loan.getCurrency()))
 == 0
-                        && loanCharge.isWaived()) {
-                    loanCharge.undoWaived();
-                }
-
-                this.loanChargeRepository.saveAndFlush(loanCharge);
+        businessEventNotifierService.notifyPostBusinessEvent(new 
LoanWaiveChargeUndoBusinessEvent(loanCharge));
 
-                loan.updateLoanSummaryForUndoWaiveCharge(amountWaived);
+        changes.put("principalPortion", loanTransaction.getPrincipalPortion());
+        changes.put("interestPortion", 
loanTransaction.getInterestPortion(loan.getCurrency()));
+        changes.put("feeChargesPortion", 
loanTransaction.getFeeChargesPortion(loan.getCurrency()));
+        changes.put("penaltyChargesPortion", 
loanTransaction.getPenaltyChargesPortion(loan.getCurrency()));
+        changes.put("outstandingLoanBalance", 
loanTransaction.getOutstandingLoanBalance());
+        changes.put("id", loanTransaction.getId());
+        changes.put("date", loanTransaction.getTransactionDate());
 
-                changes.put("amount", amountWaived);
+        return new CommandProcessingResultBuilder() //
+                .withCommandId(command.commandId()) //
+                .withEntityId(loanCharge.getId()) //
+                .withLoanId(loanId) //
+                .with(changes).build();
+    }
 
-            } else {
-                throw new InstallmentNotFoundException(command.entityId());
-            }
+    private void undoWaivedCharge(final Map<String, Object> changes, final 
Loan loan, final LoanTransaction loanTransaction,
+            final LoanChargePaidBy loanChargePaidBy) {
+        switch (loanChargePaidBy.getLoanCharge().getChargeTimeType()) {
+            case SPECIFIED_DUE_DATE -> undoSpecifiedDueDateCharge(changes, 
loan, loanTransaction, loanChargePaidBy);
+            case INSTALMENT_FEE -> undoInstalmentFee(changes, loan, 
loanTransaction, loanChargePaidBy);
+            default -> throw new UnsupportedOperationException(
+                    "Undo waive charge is not support for this charge: " + 
loanChargePaidBy.getLoanCharge().getChargeTimeType());
         }
+    }
 
-        saveLoanWithDataIntegrityViolationChecks(loan);
+    private void undoInstalmentFee(Map<String, Object> changes, Loan loan, 
LoanTransaction loanTransaction,
+            LoanChargePaidBy loanChargePaidBy) {
+        final List<Long> existingTransactionIds = 
loan.findExistingTransactionIds();
+        final List<Long> existingReversedTransactionIds = 
loan.findExistingReversedTransactionIds();
+        LoanCharge loanCharge = loanChargePaidBy.getLoanCharge();
+        final Integer installmentNumber = 
loanChargePaidBy.getInstallmentNumber();
+        LoanInstallmentCharge chargePerInstallment;
+        // final Integer installmentNumber = 
command.integerValueOfParameterNamed("installmentNumber");
+        if (installmentNumber != null) {
+            // Get installment charge.
+            chargePerInstallment = 
loanCharge.getInstallmentLoanCharge(installmentNumber);
+            // Get installment amount waived.
+            BigDecimal amountWaived = 
chargePerInstallment.getAmountWaived(loan.getCurrency()).getAmount();
+            // Check whether the installment charge is not waived. If so throw 
new error
+            if (!chargePerInstallment.isWaived() || amountWaived == null) {
+                throw new 
LoanChargeWaiveCannotBeReversedException(LoanChargeWaiveCannotUndoReason.NOT_WAIVED,
 loanCharge.getId());
+            }
+            // Reverse waived transaction
+            loanTransaction.reverse();
+            // Set manually adjusted value to `1`
+            loanTransaction.setManuallyAdjustedOrReversed();
+            // Get loan charge outstanding amount
+            BigDecimal amountOutstanding = 
loanCharge.getAmountOutstanding(loan.getCurrency()).getAmount();
+            // Add the amount waived to outstanding amount
+            
loanCharge.setOutstandingAmount(amountOutstanding.add(amountWaived));
+            // Get loan charge total amount waived
+            BigDecimal totalAmountWaved = 
loanCharge.getAmountWaived(loan.getCurrency()).getAmount();
+            // Subtract the amount waived from the existing amount waived.
+            
loanCharge.setAmountWaived(totalAmountWaved.subtract(amountWaived));
+            // Get installment outstanding amount
+            BigDecimal amountOutstandingPerInstallment = 
chargePerInstallment.getAmountOutstanding();
+            // Add the amount waived to the outstanding amount of the 
installment
+            
chargePerInstallment.setOutstandingAmount(amountOutstandingPerInstallment.add(amountWaived));
+            // Set the amount waived value to ZERO
+            chargePerInstallment.setAmountWaived(null);
+            // Reset waived flag
+            chargePerInstallment.undoWaiveFlag();
+            // Update installment balances
+            updateRepaymentInstalmentWithWaivedAmount(loanCharge, 
chargePerInstallment.getInstallment(), amountWaived);
+            // Update loan charge.
+            loanCharge.setInstallmentLoanCharge(chargePerInstallment, 
chargePerInstallment.getInstallment().getInstallmentNumber());
+            if (loanCharge.amount().compareTo(loanCharge.amountOutstanding()) 
== 0 && loanCharge.isWaived()) {
+                loanCharge.undoWaived();
+            }
+            loan.updateLoanSummaryForUndoWaiveCharge(amountWaived, 
loanCharge.isPenaltyCharge());
+            postJournalEntries(loan, existingTransactionIds, 
existingReversedTransactionIds);
+            changes.put(AMOUNT, amountWaived);
+        } else {
+            throw new InstallmentNotFoundException(loanTransaction.getId());
+        }
+    }
 
-        businessEventNotifierService.notifyPostBusinessEvent(new 
LoanWaiveChargeUndoBusinessEvent(loanCharge));
+    private void undoSpecifiedDueDateCharge(final Map<String, Object> changes, 
final Loan loan, final LoanTransaction loanTransaction,
+            final LoanChargePaidBy loanChargePaidBy) {
 
-        LoanTransaction loanTransactionData = 
this.loanTransactionRepository.getReferenceById(command.entityId());
-        changes.put("principalPortion", 
loanTransactionData.getPrincipalPortion());
-        changes.put("interestPortion", 
loanTransactionData.getInterestPortion(loan.getCurrency()));
-        changes.put("feeChargesPortion", 
loanTransactionData.getFeeChargesPortion(loan.getCurrency()));
-        changes.put("penaltyChargesPortion", 
loanTransactionData.getPenaltyChargesPortion(loan.getCurrency()));
-        changes.put("outstandingLoanBalance", 
loanTransactionData.getOutstandingLoanBalance());
-        changes.put("id", loanTransactionData.getId());
-        changes.put("date", loanTransactionData.getTransactionDate());
+        final List<Long> existingTransactionIds = 
loan.findExistingTransactionIds();
+        final List<Long> existingReversedTransactionIds = 
loan.findExistingReversedTransactionIds();
+        LoanCharge loanCharge = loanChargePaidBy.getLoanCharge();
+        BigDecimal amountWaived = 
loanCharge.getAmountWaived(loan.getCurrency()).getAmount();
+        if (!loanCharge.isWaived() || amountWaived == null) {
+            throw new 
LoanChargeWaiveCannotBeReversedException(LoanChargeWaiveCannotUndoReason.NOT_WAIVED,
 loanCharge.getId());
+        }
+        loanTransaction.reverse();
+        loanTransaction.setManuallyAdjustedOrReversed();
+        
loanCharge.setOutstandingAmount(loanCharge.amountOutstanding().add(amountWaived));
+        loanCharge.setAmountWaived(null);
+        loanCharge.undoWaived();
+        LoanRepaymentScheduleInstallment installment = 
loan.getRepaymentScheduleInstallments().stream()
+                .filter(e -> isPartOfThisInstallment(loanCharge, 
e)).findFirst().orElseThrow();
+        updateRepaymentInstalmentWithWaivedAmount(loanCharge, installment, 
amountWaived);
+        loan.updateLoanSummaryForUndoWaiveCharge(amountWaived, 
loanCharge.isPenaltyCharge());
+        postJournalEntries(loan, existingTransactionIds, 
existingReversedTransactionIds);
+        changes.put(AMOUNT, amountWaived);
+    }
 
-        return new CommandProcessingResultBuilder() //
-                .withCommandId(command.commandId()) //
-                .withEntityId(loanChargeId) //
-                .withLoanId(loanId) //
-                .with(changes).build();
+    private void updateRepaymentInstalmentWithWaivedAmount(final LoanCharge 
loanCharge, final LoanRepaymentScheduleInstallment installment,
+            final BigDecimal amountWaived) {
+        if (loanCharge.isPenaltyCharge()) {
+            // Get the penalty charges waived amount per installment
+            BigDecimal penaltyChargesWaivedAmount = 
installment.getPenaltyChargesWaived(loanCharge.getLoan().getCurrency()).getAmount();
+            // Subtract the amount waived from the existing fee charges waived 
amount.
+            
installment.setPenaltyChargesWaived(penaltyChargesWaivedAmount.subtract(amountWaived));
+        } else {
+            // Get the fee charges waived amount per installment
+            BigDecimal feeChargesWaivedAmount = 
installment.getFeeChargesWaived(loanCharge.getLoan().getCurrency()).getAmount();
+            // Subtract the amount waived from the existing fee charges waived 
amount.
+            
installment.setFeeChargesWaived(feeChargesWaivedAmount.subtract(amountWaived));
+        }
     }
 
     @Transactional
@@ -2829,16 +2838,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
 
     private void checkClientOrGroupActive(final Loan loan) {
         final Client client = loan.client();
-        if (client != null) {
-            if (client.isNotActive()) {
-                throw new ClientNotActiveException(client.getId());
-            }
+        if (client != null && client.isNotActive()) {
+            throw new ClientNotActiveException(client.getId());
         }
         final Group group = loan.group();
-        if (group != null) {
-            if (group.isNotActive()) {
-                throw new GroupNotActiveException(group.getId());
-            }
+        if (group != null && group.isNotActive()) {
+            throw new GroupNotActiveException(group.getId());
         }
     }
 
@@ -2950,7 +2955,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
         if (diff < 1) {
             diff = 1L;
         }
-        LocalDate startDate = 
dueDate.plusDays(penaltyWaitPeriodValue.intValue() + 1);
+        LocalDate startDate = dueDate.plusDays(penaltyWaitPeriodValue + 1L);
         int frequencyNumber = 1;
         if (feeFrequency == null) {
             scheduleDates.put(frequencyNumber++, startDate.minusDays(diff));
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
index 26273f3aa..ac4913248 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ClientLoanIntegrationTest.java
@@ -35,6 +35,8 @@ import java.text.DateFormat;
 import java.text.DecimalFormat;
 import java.text.SimpleDateFormat;
 import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -94,6 +96,8 @@ public class ClientLoanIntegrationTest {
     private SavingsAccountHelper savingsAccountHelper;
     private AccountTransferHelper accountTransferHelper;
 
+    private DateTimeFormatter dateFormatter = new 
DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter();
+
     @BeforeEach
     public void setup() {
         Utils.initializeRESTAssured();
@@ -104,6 +108,9 @@ public class ClientLoanIntegrationTest {
         this.loanTransactionHelper = new 
LoanTransactionHelper(this.requestSpec, this.responseSpec);
         this.accountHelper = new AccountHelper(this.requestSpec, 
this.responseSpec);
         this.schedulerJobHelper = new SchedulerJobHelper(this.requestSpec);
+        this.loanTransactionHelper = new 
LoanTransactionHelper(this.requestSpec, this.responseSpec);
+        this.periodicAccrualAccountingHelper = new 
PeriodicAccrualAccountingHelper(this.requestSpec, this.responseSpec);
+        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, 
this.responseSpec);
     }
 
     @Test
@@ -815,7 +822,7 @@ public class ClientLoanIntegrationTest {
                 map.put("id", transId.toString());
                 map.put("loanId", loanID.toString());
                 final String putBody = gson.toJson(map);
-                chargeId = 
this.loanTransactionHelper.undoWaiveChargesForLoan(loanID, transId, putBody);
+                chargeId = 
this.loanTransactionHelper.undoWaiveChargesForLoanReturnResourceId(loanID, 
transId, putBody);
                 break;
             }
         }
@@ -1551,7 +1558,6 @@ public class ClientLoanIntegrationTest {
      */
     @Test
     public void loanWithFlatCahargesAndCashBasedAccountingEnabled() {
-        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, 
this.responseSpec);
 
         final Integer clientID = ClientHelper.createClient(this.requestSpec, 
this.responseSpec);
         ClientHelper.verifyClientCreatedOnServer(this.requestSpec, 
this.responseSpec, clientID);
@@ -1739,7 +1745,6 @@ public class ClientLoanIntegrationTest {
      */
     @Test
     public void 
loanWithCahargesOfTypeAmountPercentageAndCashBasedAccountingEnabled() {
-        this.journalEntryHelper = new JournalEntryHelper(this.requestSpec, 
this.responseSpec);
 
         final Integer clientID = ClientHelper.createClient(this.requestSpec, 
this.responseSpec);
         ClientHelper.verifyClientCreatedOnServer(this.requestSpec, 
this.responseSpec, clientID);
@@ -5976,6 +5981,321 @@ public class ClientLoanIntegrationTest {
         assertEquals(clientCollateralId, clientCollateralIdResult);
     }
 
+    @Test
+    public void undoWaivedChargeTransactionDoesNotExist() {
+        ResponseSpecification responseSpec = new 
ResponseSpecBuilder().expectStatusCode(404).build();
+        LoanTransactionHelper loanTransactionHelper = new 
LoanTransactionHelper(this.requestSpec, responseSpec);
+        HashMap response = loanTransactionHelper.undoWaiveChargesForLoan(-1, 
-2, "");
+        assertEquals("error.msg.loan.id.invalid", ((Map) ((List) 
response.get("errors")).get(0)).get("userMessageGlobalisationCode"));
+        assertEquals("Transaction with identifier -2 does not exist for loan 
with identifier -1.",
+                ((Map) ((List) 
response.get("errors")).get(0)).get("defaultUserMessage"));
+    }
+
+    @Test
+    public void undoWaivedChargeWaiveTransactionDoesNotExist() {
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = 
this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = 
this.accountHelper.createLiabilityAccount();
+
+        final Integer loanProductID = 
createLoanProductWithPeriodicAccrualAccountingNoInterest(assetAccount, 
incomeAccount, expenseAccount,
+                overpaymentAccount);
+
+        final Integer clientID = ClientHelper.createClient(requestSpec, 
responseSpec, "01 January 2011");
+
+        final Integer loanID = applyForLoanApplication(clientID, 
loanProductID);
+
+        HashMap<String, Object> loanStatusHashMap = 
LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("02 
September 2022", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        loanStatusHashMap = 
this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("03 September 
2022", loanID, "1000");
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+        String loanDetails = 
this.loanTransactionHelper.getLoanDetails(requestSpec, responseSpec, loanID);
+        final Integer loanTransactionId = (Integer) ((Map) ((List) 
JsonPath.from(loanDetails).get("transactions")).get(0)).get("id");
+
+        ResponseSpecification responseSpec = new 
ResponseSpecBuilder().expectStatusCode(403).build();
+        LoanTransactionHelper loanTransactionHelper = new 
LoanTransactionHelper(this.requestSpec, responseSpec);
+        HashMap response = 
loanTransactionHelper.undoWaiveChargesForLoan(loanID, loanTransactionId, "");
+        assertEquals("error.msg.loan.transaction.undo.waive.charge",
+                ((Map) ((List) 
response.get("errors")).get(0)).get("userMessageGlobalisationCode"));
+        assertEquals("Transaction is not a waive charge type.", ((Map) ((List) 
response.get("errors")).get(0)).get("defaultUserMessage"));
+    }
+
+    @Test
+    public void undoWaivedCharge() {
+        final Account assetAccount = this.accountHelper.createAssetAccount();
+        final Account incomeAccount = this.accountHelper.createIncomeAccount();
+        final Account expenseAccount = 
this.accountHelper.createExpenseAccount();
+        final Account overpaymentAccount = 
this.accountHelper.createLiabilityAccount();
+
+        Integer penalty = ChargesHelper.createCharges(requestSpec, 
responseSpec,
+                
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT,
 "10", true));
+        final Integer loanProductID = 
createLoanProductWithPeriodicAccrualAccountingNoInterest(assetAccount, 
incomeAccount, expenseAccount,
+                overpaymentAccount);
+
+        final Integer clientID = ClientHelper.createClient(requestSpec, 
responseSpec, "01 January 2011");
+
+        final Integer loanID = applyForLoanApplication(clientID, 
loanProductID);
+
+        HashMap<String, Object> loanStatusHashMap = 
LoanStatusChecker.getStatusOfLoan(requestSpec, responseSpec, loanID);
+        LoanStatusChecker.verifyLoanIsPending(loanStatusHashMap);
+
+        loanStatusHashMap = this.loanTransactionHelper.approveLoan("02 
September 2022", loanID);
+        LoanStatusChecker.verifyLoanIsApproved(loanStatusHashMap);
+        LoanStatusChecker.verifyLoanIsWaitingForDisbursal(loanStatusHashMap);
+
+        loanStatusHashMap = 
this.loanTransactionHelper.disburseLoanWithNetDisbursalAmount("03 September 
2022", loanID, "1000");
+        LoanStatusChecker.verifyLoanIsActive(loanStatusHashMap);
+
+        ArrayList<HashMap> loanSchedule = 
this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, 
loanID);
+        assertEquals(2, loanSchedule.size());
+        assertEquals(0, loanSchedule.get(1).get("feeChargesDue"));
+        assertEquals(0, loanSchedule.get(1).get("feeChargesOutstanding"));
+        assertEquals(0, loanSchedule.get(1).get("penaltyChargesDue"));
+        assertEquals(0, loanSchedule.get(1).get("penaltyChargesOutstanding"));
+        assertEquals(1000.0f, loanSchedule.get(1).get("totalDueForPeriod"));
+        assertEquals(1000.0f, 
loanSchedule.get(1).get("totalOutstandingForPeriod"));
+        LocalDate targetDate = LocalDate.of(2022, 9, 7);
+        final String penaltyCharge1AddedDate = 
dateFormatter.format(targetDate);
+        Integer penalty1LoanChargeId = 
this.loanTransactionHelper.addChargesForLoan(loanID,
+                
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(penalty),
 penaltyCharge1AddedDate, "10"));
+
+        this.loanTransactionHelper.noAccrualTransactionForRepayment(loanID);
+
+        loanSchedule = 
this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, 
loanID);
+        assertEquals(2, loanSchedule.size());
+        assertEquals(0, loanSchedule.get(1).get("feeChargesDue"));
+        assertEquals(0, loanSchedule.get(1).get("feeChargesOutstanding"));
+        assertEquals(10.0f, loanSchedule.get(1).get("penaltyChargesDue"));
+        assertEquals(10.0f, 
loanSchedule.get(1).get("penaltyChargesOutstanding"));
+        assertEquals(1010.0f, loanSchedule.get(1).get("totalDueForPeriod"));
+        assertEquals(1010.0f, 
loanSchedule.get(1).get("totalOutstandingForPeriod"));
+        assertEquals(0, loanSchedule.get(1).get("totalWaivedForPeriod"));
+
+        HashMap loanSummary = 
this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, 
loanID, "summary");
+        assertEquals(10.0f, loanSummary.get("penaltyChargesCharged"));
+        assertEquals(10.0f, loanSummary.get("penaltyChargesOutstanding"));
+        assertEquals(0.0f, loanSummary.get("penaltyChargesWaived"));
+        assertEquals(0.0f, loanSummary.get("feeChargesCharged"));
+        assertEquals(0.0f, loanSummary.get("feeChargesOutstanding"));
+        assertEquals(0.0f, loanSummary.get("feeChargesWaived"));
+        assertEquals(1010.0f, loanSummary.get("totalOutstanding"));
+        assertEquals(0.0f, loanSummary.get("totalWaived"));
+
+        this.loanTransactionHelper.waiveChargesForLoan(loanID, 
penalty1LoanChargeId, "");
+
+        loanSchedule = 
this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, 
loanID);
+        assertEquals(2, loanSchedule.size());
+        assertEquals(0, loanSchedule.get(1).get("feeChargesDue"));
+        assertEquals(0, loanSchedule.get(1).get("feeChargesOutstanding"));
+        assertEquals(0, loanSchedule.get(1).get("feeChargesWaived"));
+        assertEquals(10.0f, loanSchedule.get(1).get("penaltyChargesDue"));
+        assertEquals(10.0f, loanSchedule.get(1).get("penaltyChargesWaived"));
+        assertEquals(0.0f, 
loanSchedule.get(1).get("penaltyChargesOutstanding"));
+        assertEquals(1010.0f, loanSchedule.get(1).get("totalDueForPeriod"));
+        assertEquals(1000.0f, 
loanSchedule.get(1).get("totalOutstandingForPeriod"));
+        assertEquals(10.0f, loanSchedule.get(1).get("totalWaivedForPeriod"));
+
+        loanSummary = 
this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, 
loanID, "summary");
+        assertEquals(10.0f, loanSummary.get("penaltyChargesCharged"));
+        assertEquals(0.0f, loanSummary.get("penaltyChargesOutstanding"));
+        assertEquals(10.0f, loanSummary.get("penaltyChargesWaived"));
+        assertEquals(0.0f, loanSummary.get("feeChargesCharged"));
+        assertEquals(0.0f, loanSummary.get("feeChargesOutstanding"));
+        assertEquals(0.0f, loanSummary.get("feeChargesWaived"));
+        assertEquals(1000.0f, loanSummary.get("totalOutstanding"));
+        assertEquals(10.0f, loanSummary.get("totalWaived"));
+
+        List<HashMap> transactions = 
this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, 
loanID, "transactions");
+        assertEquals(10.0f, (float) transactions.get(1).get("amount"));
+        assertEquals(9, (int) ((HashMap) 
transactions.get(1).get("type")).get("id"));
+        Integer waiveTransactionId = (int) transactions.get(1).get("id");
+
+        this.loanTransactionHelper.undoWaiveChargesForLoan(loanID, 
waiveTransactionId, "");
+
+        loanSchedule = 
this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, 
loanID);
+        assertEquals(2, loanSchedule.size());
+        assertEquals(0, loanSchedule.get(1).get("feeChargesDue"));
+        assertEquals(0, loanSchedule.get(1).get("feeChargesOutstanding"));
+        assertEquals(0, loanSchedule.get(1).get("feeChargesWaived"));
+        assertEquals(10.0f, loanSchedule.get(1).get("penaltyChargesDue"));
+        assertEquals(0.0f, loanSchedule.get(1).get("penaltyChargesWaived"));
+        assertEquals(10.0f, 
loanSchedule.get(1).get("penaltyChargesOutstanding"));
+        assertEquals(1010.0f, loanSchedule.get(1).get("totalDueForPeriod"));
+        assertEquals(1010.0f, 
loanSchedule.get(1).get("totalOutstandingForPeriod"));
+        assertEquals(0.0f, loanSchedule.get(1).get("totalWaivedForPeriod"));
+
+        loanSummary = 
this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, 
loanID, "summary");
+        assertEquals(10.0f, loanSummary.get("penaltyChargesCharged"));
+        assertEquals(10.0f, loanSummary.get("penaltyChargesOutstanding"));
+        assertEquals(0.0f, loanSummary.get("penaltyChargesWaived"));
+        assertEquals(0.0f, loanSummary.get("feeChargesCharged"));
+        assertEquals(0.0f, loanSummary.get("feeChargesOutstanding"));
+        assertEquals(0.0f, loanSummary.get("feeChargesWaived"));
+        assertEquals(1010.0f, loanSummary.get("totalOutstanding"));
+        assertEquals(0.0f, loanSummary.get("totalWaived"));
+
+        transactions = 
this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, 
loanID, "transactions");
+        assertEquals(10.0f, (float) transactions.get(1).get("amount"));
+        assertEquals(9, (int) ((HashMap) 
transactions.get(1).get("type")).get("id"));
+        assertEquals(true, transactions.get(1).get("manuallyReversed"));
+
+        Integer fee = ChargesHelper.createCharges(requestSpec, responseSpec,
+                
ChargesHelper.getLoanSpecifiedDueDateJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT,
 "10", false));
+
+        final String feeCharge1AddedDate = dateFormatter.format(targetDate);
+        Integer fee1LoanChargeId = 
this.loanTransactionHelper.addChargesForLoan(loanID,
+                
LoanTransactionHelper.getSpecifiedDueDateChargesForLoanAsJSON(String.valueOf(fee),
 feeCharge1AddedDate, "10"));
+
+        
this.periodicAccrualAccountingHelper.runPeriodicAccrualAccounting(feeCharge1AddedDate);
+
+        transactions = 
this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, 
loanID, "transactions");
+        assertEquals(20.0f, (float) transactions.get(2).get("amount"));
+        assertEquals(10, (int) ((HashMap) 
transactions.get(2).get("type")).get("id"));
+        Integer accrualTransactionId = (int) transactions.get(2).get("id");
+
+        List<HashMap> journalEntries = 
this.journalEntryHelper.getJournalEntriesByTransactionId("L" + 
accrualTransactionId);
+        assertEquals(10.0f, (float) journalEntries.get(0).get("amount"));
+        assertEquals(incomeAccount.getAccountID(), (int) 
journalEntries.get(0).get("glAccountId"));
+        assertEquals("CREDIT", ((HashMap) 
journalEntries.get(0).get("entryType")).get("value"));
+        assertEquals(10.0f, (float) journalEntries.get(1).get("amount"));
+        assertEquals(assetAccount.getAccountID(), (int) 
journalEntries.get(1).get("glAccountId"));
+        assertEquals("DEBIT", ((HashMap) 
journalEntries.get(1).get("entryType")).get("value"));
+        assertEquals(10.0f, (float) journalEntries.get(2).get("amount"));
+        assertEquals(incomeAccount.getAccountID(), (int) 
journalEntries.get(2).get("glAccountId"));
+        assertEquals("CREDIT", ((HashMap) 
journalEntries.get(2).get("entryType")).get("value"));
+        assertEquals(10.0f, (float) journalEntries.get(3).get("amount"));
+        assertEquals(assetAccount.getAccountID(), (int) 
journalEntries.get(3).get("glAccountId"));
+        assertEquals("DEBIT", ((HashMap) 
journalEntries.get(3).get("entryType")).get("value"));
+
+        loanSchedule = 
this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, 
loanID);
+        assertEquals(2, loanSchedule.size());
+        assertEquals(10.0f, loanSchedule.get(1).get("feeChargesDue"));
+        assertEquals(10.0f, loanSchedule.get(1).get("feeChargesOutstanding"));
+        assertEquals(0, loanSchedule.get(1).get("feeChargesWaived"));
+        assertEquals(10.0f, loanSchedule.get(1).get("penaltyChargesDue"));
+        assertEquals(0, loanSchedule.get(1).get("penaltyChargesWaived"));
+        assertEquals(10.0f, 
loanSchedule.get(1).get("penaltyChargesOutstanding"));
+        assertEquals(1020.0f, loanSchedule.get(1).get("totalDueForPeriod"));
+        assertEquals(1020.0f, 
loanSchedule.get(1).get("totalOutstandingForPeriod"));
+        assertEquals(0, loanSchedule.get(1).get("totalWaivedForPeriod"));
+
+        loanSummary = 
this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, 
loanID, "summary");
+        assertEquals(10.0f, loanSummary.get("penaltyChargesCharged"));
+        assertEquals(10.0f, loanSummary.get("penaltyChargesOutstanding"));
+        assertEquals(0.0f, loanSummary.get("penaltyChargesWaived"));
+        assertEquals(10.0f, loanSummary.get("feeChargesCharged"));
+        assertEquals(10.0f, loanSummary.get("feeChargesOutstanding"));
+        assertEquals(0.0f, loanSummary.get("feeChargesWaived"));
+        assertEquals(1020.0f, loanSummary.get("totalOutstanding"));
+        assertEquals(0.0f, loanSummary.get("totalWaived"));
+
+        this.loanTransactionHelper.waiveChargesForLoan(loanID, 
fee1LoanChargeId, "");
+
+        transactions = 
this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, 
loanID, "transactions");
+        assertEquals(10.0f, (float) transactions.get(3).get("amount"));
+        assertEquals(9, (int) ((HashMap) 
transactions.get(3).get("type")).get("id"));
+        Integer waive2TransactionId = (int) transactions.get(3).get("id");
+
+        journalEntries = 
this.journalEntryHelper.getJournalEntriesByTransactionId("L" + 
waive2TransactionId);
+        assertEquals(10.0f, (float) journalEntries.get(0).get("amount"));
+        assertEquals(expenseAccount.getAccountID(), (int) 
journalEntries.get(0).get("glAccountId"));
+        assertEquals("DEBIT", ((HashMap) 
journalEntries.get(0).get("entryType")).get("value"));
+        assertEquals(10.0f, (float) journalEntries.get(1).get("amount"));
+        assertEquals(assetAccount.getAccountID(), (int) 
journalEntries.get(1).get("glAccountId"));
+        assertEquals("CREDIT", ((HashMap) 
journalEntries.get(1).get("entryType")).get("value"));
+
+        loanSchedule = 
this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, 
loanID);
+        assertEquals(2, loanSchedule.size());
+        assertEquals(10.0f, loanSchedule.get(1).get("feeChargesDue"));
+        assertEquals(0.0f, loanSchedule.get(1).get("feeChargesOutstanding"));
+        assertEquals(10.0f, loanSchedule.get(1).get("feeChargesWaived"));
+        assertEquals(10.0f, loanSchedule.get(1).get("penaltyChargesDue"));
+        assertEquals(0, loanSchedule.get(1).get("penaltyChargesWaived"));
+        assertEquals(10.0f, 
loanSchedule.get(1).get("penaltyChargesOutstanding"));
+        assertEquals(1020.0f, loanSchedule.get(1).get("totalDueForPeriod"));
+        assertEquals(1010.0f, 
loanSchedule.get(1).get("totalOutstandingForPeriod"));
+        assertEquals(10.0f, loanSchedule.get(1).get("totalWaivedForPeriod"));
+
+        loanSummary = 
this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, 
loanID, "summary");
+        assertEquals(10.0f, loanSummary.get("penaltyChargesCharged"));
+        assertEquals(10.0f, loanSummary.get("penaltyChargesOutstanding"));
+        assertEquals(0.0f, loanSummary.get("penaltyChargesWaived"));
+        assertEquals(10.0f, loanSummary.get("feeChargesCharged"));
+        assertEquals(0.0f, loanSummary.get("feeChargesOutstanding"));
+        assertEquals(10.0f, loanSummary.get("feeChargesWaived"));
+        assertEquals(1010.0f, loanSummary.get("totalOutstanding"));
+        assertEquals(10.0f, loanSummary.get("totalWaived"));
+
+        this.loanTransactionHelper.undoWaiveChargesForLoan(loanID, 
waive2TransactionId, "");
+
+        transactions = 
this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, 
loanID, "transactions");
+        assertEquals(10.0f, (float) transactions.get(3).get("amount"));
+        assertEquals(9, (int) ((HashMap) 
transactions.get(3).get("type")).get("id"));
+        assertEquals(true, transactions.get(3).get("manuallyReversed"));
+
+        journalEntries = 
this.journalEntryHelper.getJournalEntriesByTransactionId("L" + 
waive2TransactionId);
+        assertEquals(10.0f, (float) journalEntries.get(0).get("amount"));
+        assertEquals(expenseAccount.getAccountID(), (int) 
journalEntries.get(0).get("glAccountId"));
+        assertEquals("CREDIT", ((HashMap) 
journalEntries.get(0).get("entryType")).get("value"));
+        assertEquals(10.0f, (float) journalEntries.get(1).get("amount"));
+        assertEquals(assetAccount.getAccountID(), (int) 
journalEntries.get(1).get("glAccountId"));
+        assertEquals("DEBIT", ((HashMap) 
journalEntries.get(1).get("entryType")).get("value"));
+        assertEquals(10.0f, (float) journalEntries.get(2).get("amount"));
+        assertEquals(expenseAccount.getAccountID(), (int) 
journalEntries.get(2).get("glAccountId"));
+        assertEquals("DEBIT", ((HashMap) 
journalEntries.get(2).get("entryType")).get("value"));
+        assertEquals(10.0f, (float) journalEntries.get(3).get("amount"));
+        assertEquals(assetAccount.getAccountID(), (int) 
journalEntries.get(3).get("glAccountId"));
+        assertEquals("CREDIT", ((HashMap) 
journalEntries.get(3).get("entryType")).get("value"));
+
+        loanSchedule = 
this.loanTransactionHelper.getLoanRepaymentSchedule(requestSpec, responseSpec, 
loanID);
+        assertEquals(2, loanSchedule.size());
+        assertEquals(10.0f, loanSchedule.get(1).get("feeChargesDue"));
+        assertEquals(10.0f, loanSchedule.get(1).get("feeChargesOutstanding"));
+        assertEquals(0.0f, loanSchedule.get(1).get("feeChargesWaived"));
+        assertEquals(10.0f, loanSchedule.get(1).get("penaltyChargesDue"));
+        assertEquals(0, loanSchedule.get(1).get("penaltyChargesWaived"));
+        assertEquals(10.0f, 
loanSchedule.get(1).get("penaltyChargesOutstanding"));
+        assertEquals(1020.0f, loanSchedule.get(1).get("totalDueForPeriod"));
+        assertEquals(1020.0f, 
loanSchedule.get(1).get("totalOutstandingForPeriod"));
+        assertEquals(0.0f, loanSchedule.get(1).get("totalWaivedForPeriod"));
+
+        loanSummary = 
this.loanTransactionHelper.getLoanDetail(this.requestSpec, this.responseSpec, 
loanID, "summary");
+        assertEquals(10.0f, loanSummary.get("penaltyChargesCharged"));
+        assertEquals(10.0f, loanSummary.get("penaltyChargesOutstanding"));
+        assertEquals(0.0f, loanSummary.get("penaltyChargesWaived"));
+        assertEquals(10.0f, loanSummary.get("feeChargesCharged"));
+        assertEquals(10.0f, loanSummary.get("feeChargesOutstanding"));
+        assertEquals(0.0f, loanSummary.get("feeChargesWaived"));
+        assertEquals(1020.0f, loanSummary.get("totalOutstanding"));
+        assertEquals(0.0f, loanSummary.get("totalWaived"));
+    }
+
+    private Integer applyForLoanApplication(final Integer clientID, final 
Integer loanProductID) {
+        LOG.info("--------------------------------APPLYING FOR LOAN 
APPLICATION--------------------------------");
+        final String loanApplicationJSON = new 
LoanApplicationTestBuilder().withPrincipal("1000").withLoanTermFrequency("1")
+                
.withLoanTermFrequencyAsMonths().withNumberOfRepayments("1").withRepaymentEveryAfter("1")
+                
.withRepaymentFrequencyTypeAsMonths().withInterestRatePerPeriod("0").withInterestTypeAsFlatBalance()
+                
.withAmortizationTypeAsEqualPrincipalPayments().withInterestCalculationPeriodTypeSameAsRepaymentPeriod()
+                .withExpectedDisbursementDate("03 September 
2022").withSubmittedOnDate("01 September 2022").withLoanType("individual")
+                .build(clientID.toString(), loanProductID.toString(), null);
+        return this.loanTransactionHelper.getLoanId(loanApplicationJSON);
+    }
+
+    private Integer 
createLoanProductWithPeriodicAccrualAccountingNoInterest(final Account... 
accounts) {
+        LOG.info("------------------------------CREATING NEW LOAN PRODUCT 
---------------------------------------");
+        final String loanProductJSON = new 
LoanProductTestBuilder().withPrincipal("1000").withRepaymentTypeAsMonth()
+                
.withRepaymentAfterEvery("1").withNumberOfRepayments("1").withRepaymentTypeAsMonth().withinterestRatePerPeriod("0")
+                
.withInterestRateFrequencyTypeAsMonths().withAmortizationTypeAsEqualPrincipalPayment().withInterestTypeAsFlat()
+                
.withAccountingRulePeriodicAccrual(accounts).withDaysInMonth("30").withDaysInYear("365").withMoratorium("0",
 "0")
+                .build(null);
+        return this.loanTransactionHelper.getLoanProductId(loanProductJSON);
+    }
+
     private void validateIfValuesAreNotOverridden(Integer loanID, Integer 
loanProductID) {
         String loanProductDetails = 
this.loanTransactionHelper.getLoanProductDetails(this.requestSpec, 
this.responseSpec, loanProductID);
         String loanDetails = 
this.loanTransactionHelper.getLoanDetails(this.requestSpec, this.responseSpec, 
loanID);
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index 1719595e1..15bc47d88 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -529,7 +529,14 @@ public class LoanTransactionHelper {
         return (Integer) response.get("resourceId");
     }
 
-    public Integer undoWaiveChargesForLoan(final Integer loanId, final Integer 
transactionId, final String body) {
+    public HashMap undoWaiveChargesForLoan(final Integer loanId, final Integer 
transactionId, final String body) {
+        log.info("--------------------------------- UNDO WAIVE CHARGES FOR 
LOAN --------------------------------");
+        final String TRANSAC_URL = "/fineract-provider/api/v1/loans/" + loanId 
+ "/transactions/" + transactionId + "?"
+                + Utils.TENANT_IDENTIFIER;
+        return Utils.performServerPut(requestSpec, responseSpec, TRANSAC_URL, 
body, "");
+    }
+
+    public Integer undoWaiveChargesForLoanReturnResourceId(final Integer 
loanId, final Integer transactionId, final String body) {
         log.info("--------------------------------- UNDO WAIVE CHARGES FOR 
LOAN --------------------------------");
         final String TRANSAC_URL = "/fineract-provider/api/v1/loans/" + loanId 
+ "/transactions/" + transactionId + "?"
                 + Utils.TENANT_IDENTIFIER;


Reply via email to