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;