FINERACT-2081: Accrual not created for closed or overpaid loans
This commit is contained in:
committed by
Adam Saghy
parent
352d282d7c
commit
4a0c52ea8b
+10
-8
@@ -318,7 +318,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AccrualPeriodsData accrualPeriods = calculateAccrualAmounts(loan, tillDate, periodic);
|
AccrualPeriodsData accrualPeriods = calculateAccrualAmounts(loan, tillDate, periodic, isFinal);
|
||||||
boolean mergeTransactions = isFinal || progressiveAccrual;
|
boolean mergeTransactions = isFinal || progressiveAccrual;
|
||||||
MonetaryCurrency currency = loan.getLoanProductRelatedDetail().getCurrency();
|
MonetaryCurrency currency = loan.getLoanProductRelatedDetail().getCurrency();
|
||||||
List<LoanTransaction> accrualTransactions = new ArrayList<>();
|
List<LoanTransaction> accrualTransactions = new ArrayList<>();
|
||||||
@@ -393,7 +393,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private AccrualPeriodsData calculateAccrualAmounts(@NotNull Loan loan, @NotNull LocalDate tillDate, boolean periodic) {
|
private AccrualPeriodsData calculateAccrualAmounts(@NotNull Loan loan, @NotNull LocalDate tillDate, boolean periodic, boolean isFinal) {
|
||||||
boolean chargeOnDueDate = isChargeOnDueDate();
|
boolean chargeOnDueDate = isChargeOnDueDate();
|
||||||
LoanProductRelatedDetail productDetail = loan.getLoanProductRelatedDetail();
|
LoanProductRelatedDetail productDetail = loan.getLoanProductRelatedDetail();
|
||||||
MonetaryCurrency currency = productDetail.getCurrency();
|
MonetaryCurrency currency = productDetail.getCurrency();
|
||||||
@@ -402,11 +402,12 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
|||||||
int firstInstallmentNumber = fetchFirstNormalInstallmentNumber(loan.getRepaymentScheduleInstallments());
|
int firstInstallmentNumber = fetchFirstNormalInstallmentNumber(loan.getRepaymentScheduleInstallments());
|
||||||
LocalDate interestCalculationTillDate = loan.isProgressiveSchedule()
|
LocalDate interestCalculationTillDate = loan.isProgressiveSchedule()
|
||||||
&& loan.getLoanProductRelatedDetail().isInterestRecognitionOnDisbursementDate() ? tillDate.plusDays(1L) : tillDate;
|
&& loan.getLoanProductRelatedDetail().isInterestRecognitionOnDisbursementDate() ? tillDate.plusDays(1L) : tillDate;
|
||||||
List<LoanRepaymentScheduleInstallment> installments = getInstallmentsToAccrue(loan, interestCalculationTillDate, periodic);
|
List<LoanRepaymentScheduleInstallment> installments = isFinal ? loan.getRepaymentScheduleInstallments()
|
||||||
|
: getInstallmentsToAccrue(loan, interestCalculationTillDate, periodic);
|
||||||
AccrualPeriodsData accrualPeriods = AccrualPeriodsData.create(installments, firstInstallmentNumber, currency);
|
AccrualPeriodsData accrualPeriods = AccrualPeriodsData.create(installments, firstInstallmentNumber, currency);
|
||||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||||
addInterestAccrual(loan, interestCalculationTillDate, scheduleGenerator, installment, accrualPeriods);
|
addInterestAccrual(loan, interestCalculationTillDate, scheduleGenerator, installment, accrualPeriods);
|
||||||
addChargeAccrual(loan, tillDate, chargeOnDueDate, installment, accrualPeriods);
|
addChargeAccrual(loan, tillDate, chargeOnDueDate, installment, accrualPeriods, isFinal);
|
||||||
}
|
}
|
||||||
return accrualPeriods;
|
return accrualPeriods;
|
||||||
}
|
}
|
||||||
@@ -512,12 +513,12 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void addChargeAccrual(@NotNull Loan loan, @NotNull LocalDate tillDate, boolean chargeOnDueDate,
|
private void addChargeAccrual(@NotNull Loan loan, @NotNull LocalDate tillDate, boolean chargeOnDueDate,
|
||||||
@NotNull LoanRepaymentScheduleInstallment installment, @NotNull AccrualPeriodsData accrualPeriods) {
|
@NotNull LoanRepaymentScheduleInstallment installment, @NotNull AccrualPeriodsData accrualPeriods, boolean isFinal) {
|
||||||
AccrualPeriodData period = accrualPeriods.getPeriodByInstallmentNumber(installment.getInstallmentNumber());
|
AccrualPeriodData period = accrualPeriods.getPeriodByInstallmentNumber(installment.getInstallmentNumber());
|
||||||
LocalDate dueDate = installment.getDueDate();
|
LocalDate dueDate = installment.getDueDate();
|
||||||
List<LoanCharge> loanCharges = loan
|
Collection<LoanCharge> loanCharges;
|
||||||
.getLoanCharges(lc -> !lc.isDueAtDisbursement() && (lc.isInstalmentFee() ? !DateUtils.isBefore(tillDate, dueDate)
|
loanCharges = loan.getLoanCharges(lc -> !lc.isDueAtDisbursement() && (lc.isInstalmentFee() ? !DateUtils.isBefore(tillDate, dueDate)
|
||||||
: isChargeDue(lc, tillDate, chargeOnDueDate, installment, period.isFirstPeriod())));
|
: isChargeDue(lc, tillDate, chargeOnDueDate, installment, period.isFirstPeriod())));
|
||||||
for (LoanCharge loanCharge : loanCharges) {
|
for (LoanCharge loanCharge : loanCharges) {
|
||||||
addChargeAccrual(loanCharge, tillDate, chargeOnDueDate, installment, accrualPeriods);
|
addChargeAccrual(loanCharge, tillDate, chargeOnDueDate, installment, accrualPeriods);
|
||||||
}
|
}
|
||||||
@@ -596,6 +597,7 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
|||||||
LocalDate fromDate = installment.getFromDate();
|
LocalDate fromDate = installment.getFromDate();
|
||||||
LocalDate dueDate = installment.getDueDate();
|
LocalDate dueDate = installment.getDueDate();
|
||||||
LocalDate toDate = DateUtils.isBefore(dueDate, tillDate) ? dueDate : tillDate;
|
LocalDate toDate = DateUtils.isBefore(dueDate, tillDate) ? dueDate : tillDate;
|
||||||
|
chargeOnDueDate = chargeOnDueDate || loanCharge.getDueLocalDate().isBefore(loanCharge.getSubmittedOnDate());
|
||||||
return chargeOnDueDate ? loanCharge.isDueInPeriod(fromDate, toDate, isFirstPeriod)
|
return chargeOnDueDate ? loanCharge.isDueInPeriod(fromDate, toDate, isFirstPeriod)
|
||||||
: isInPeriod(loanCharge.getSubmittedOnDate(), fromDate, toDate, isFirstPeriod);
|
: isInPeriod(loanCharge.getSubmittedOnDate(), fromDate, toDate, isFirstPeriod);
|
||||||
}
|
}
|
||||||
|
|||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. The ASF licenses this file
|
||||||
|
* to you under the Apache License, Version 2.0 (the
|
||||||
|
* "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing,
|
||||||
|
* software distributed under the License is distributed on an
|
||||||
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
* KIND, either express or implied. See the License for the
|
||||||
|
* specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.apache.fineract.integrationtests;
|
||||||
|
|
||||||
|
import static org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants.CHARGE_ACCRUAL_DATE;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.time.format.DateTimeFormatterBuilder;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||||
|
import org.apache.fineract.client.models.PostChargesRequest;
|
||||||
|
import org.apache.fineract.client.models.PostChargesResponse;
|
||||||
|
import org.apache.fineract.client.models.PostClientsResponse;
|
||||||
|
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||||
|
import org.apache.fineract.client.models.PostLoansLoanIdChargesRequest;
|
||||||
|
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
|
||||||
|
import org.apache.fineract.client.models.PutGlobalConfigurationsRequest;
|
||||||
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.Utils;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class AccrualsOnLoanClosureTest extends BaseLoanIntegrationTest {
|
||||||
|
|
||||||
|
private DateTimeFormatter dateFormatter = new DateTimeFormatterBuilder().appendPattern("dd MMMM yyyy").toFormatter();
|
||||||
|
|
||||||
|
private static final String startDate = "22 April 2025";
|
||||||
|
private static final String disbursementDate = "22 April 2024";
|
||||||
|
private static final String repaymentDate = "25 April 2024";
|
||||||
|
private static final Double disbursementAmount = 800.0;
|
||||||
|
private static final Double repaymentAmount = 820.0;
|
||||||
|
private static final Double chargeAmount = 20.0;
|
||||||
|
|
||||||
|
private static Long loanId;
|
||||||
|
private static PostChargesResponse penaltyResponse;
|
||||||
|
private static final String penaltyCharge1AddedDate = "24 April 2024";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAccrualCreatedOnLoanClosureWithSubmittedDate() {
|
||||||
|
PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
|
PostLoanProductsResponse loanProduct = loanProductHelper
|
||||||
|
.createLoanProduct(createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct());
|
||||||
|
|
||||||
|
loanId = applyAndApproveLoan(client.getClientId(), loanProduct.getResourceId(), disbursementDate, disbursementAmount);
|
||||||
|
Assertions.assertNotNull(loanId);
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(disbursementAmount), disbursementDate);
|
||||||
|
|
||||||
|
penaltyResponse = chargesHelper.createCharges(new PostChargesRequest().active(true).chargeTimeType(2).chargeAppliesTo(1)
|
||||||
|
.chargeCalculationType(1).penalty(true).amount(20.0).currencyCode("USD").locale("en").chargePaymentMode(0)
|
||||||
|
.name(Utils.randomStringGenerator("PENALTY_", 6)));
|
||||||
|
runAt(startDate, () -> {
|
||||||
|
globalConfigurationHelper.updateGlobalConfiguration(CHARGE_ACCRUAL_DATE,
|
||||||
|
new PutGlobalConfigurationsRequest().stringValue("submitted-date"));
|
||||||
|
|
||||||
|
loanTransactionHelper.addLoanCharge(loanId, new PostLoansLoanIdChargesRequest().dateFormat("dd MMMM yyyy").locale("en")
|
||||||
|
.chargeId(penaltyResponse.getResourceId()).amount(chargeAmount).dueDate(penaltyCharge1AddedDate));
|
||||||
|
|
||||||
|
loanTransactionHelper.makeLoanRepayment(loanId, new PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM yyyy")
|
||||||
|
.transactionDate(repaymentDate).locale("en").transactionAmount(repaymentAmount));
|
||||||
|
|
||||||
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||||
|
|
||||||
|
verifyRepaymentSchedule(loanId, //
|
||||||
|
installment(800.0, null, "22 April 2024"), //
|
||||||
|
installment(800.0, 0.0, 0.0, 20.0, 0.0, true, "22 May 2024"));
|
||||||
|
|
||||||
|
verifyTransactions(loanId, //
|
||||||
|
transaction(800.0, "Disbursement", "22 April 2024", 800.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0),
|
||||||
|
transaction(820.0, "Repayment", "25 April 2024", 0.0, 800.0, 0.0, 0.0, 20.0, 0.0, 0.0),
|
||||||
|
transaction(20.0, "Accrual", "25 April 2024", 0.0, 0.0, 0.0, 0.0, 20.0, 0.0, 0.0));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -163,6 +163,7 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
|
|||||||
protected DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN);
|
protected DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN);
|
||||||
protected GlobalConfigurationHelper globalConfigurationHelper = new GlobalConfigurationHelper();
|
protected GlobalConfigurationHelper globalConfigurationHelper = new GlobalConfigurationHelper();
|
||||||
protected final CodeHelper codeHelper = new CodeHelper();
|
protected final CodeHelper codeHelper = new CodeHelper();
|
||||||
|
protected final ChargesHelper chargesHelper = new ChargesHelper();
|
||||||
|
|
||||||
protected static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, LocalDate dueDate, double principalDue,
|
protected static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, LocalDate dueDate, double principalDue,
|
||||||
double principalPaid, double principalOutstanding, double paidInAdvance, double paidLate) {
|
double principalPaid, double principalOutstanding, double paidInAdvance, double paidLate) {
|
||||||
|
|||||||
Reference in New Issue
Block a user