This is an automated email from the ASF dual-hosted git repository.
adamsaghy 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 4f1184f20 FINERACT-2148: Stop recalculating interest if the loan is
charged-off
4f1184f20 is described below
commit 4f1184f20b5378011812c49812dcf2f644f795ce
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Sun Dec 22 11:39:10 2024 -0500
FINERACT-2148: Stop recalculating interest if the loan is charged-off
---
.../api/LoanTransactionsApiResourceSwagger.java | 8 ++
.../portfolio/loanaccount/domain/Loan.java | 8 +-
.../loanaccount/service/LoanScheduleService.java | 4 +-
.../LoanChargeWritePlatformServiceImpl.java | 2 +-
...PaymentAllocationLoanRepaymentScheduleTest.java | 88 ++++++++++++++++++++++
.../common/loans/LoanTransactionHelper.java | 5 ++
6 files changed, 111 insertions(+), 4 deletions(-)
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
index 87fe5f81e..a938039a8 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
@@ -79,6 +79,14 @@ final class LoanTransactionsApiResourceSwagger {
@Schema(example = "200.000000")
public Double amount;
+ @Schema(example = "100.000000")
+ public Double principalPortion;
+ @Schema(example = "80.000000")
+ public Double interestPortion;
+ @Schema(example = "20.000000")
+ public Double feeChargesPortion;
+ @Schema(example = "20.000000")
+ public Double penaltyChargesPortion;
public GetLoanCurrency currency;
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index e50e6822e..3a91044ea 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -2722,7 +2722,7 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom<Long> {
public OutstandingAmountsDTO fetchPrepaymentDetail(final
ScheduleGeneratorDTO scheduleGeneratorDTO, final LocalDate onDate) {
OutstandingAmountsDTO outstandingAmounts;
- if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled())
{
+ if (this.loanRepaymentScheduleDetail.isInterestRecalculationEnabled()
&& !isChargeOffOnDate(onDate)) {
final MathContext mc = MoneyHelper.getMathContext();
final InterestMethod interestMethod =
this.loanRepaymentScheduleDetail.getInterestMethod();
@@ -3571,4 +3571,10 @@ public class Loan extends
AbstractAuditableWithUTCDateTimeCustom<Long> {
public boolean isProgressiveSchedule() {
return getLoanProductRelatedDetail().getLoanScheduleType() ==
PROGRESSIVE;
}
+
+ public boolean isChargeOffOnDate(final LocalDate onDate) {
+ final LoanTransaction chargeOffTransaction =
findChargedOffTransaction();
+ return (chargeOffTransaction == null) ? false :
(chargeOffTransaction.getDateOf().compareTo(onDate) <= 0);
+ }
+
}
diff --git
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java
index a5bf24e66..86cb820a7 100644
---
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java
+++
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanScheduleService.java
@@ -54,7 +54,7 @@ public class LoanScheduleService {
}
public void recalculateScheduleFromLastTransaction(final Loan loan, final
ScheduleGeneratorDTO generatorDTO) {
- if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+ if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() &&
!loan.isChargedOff()) {
regenerateRepaymentScheduleWithInterestRecalculation(loan,
generatorDTO);
} else {
regenerateRepaymentSchedule(loan, generatorDTO);
@@ -73,7 +73,7 @@ public class LoanScheduleService {
* loanTransaction.getTransactionDate().isAfter(recalculateFrom)) {
recalculateFrom =
* loanTransaction.getTransactionDate(); } }
generatorDTO.setRecalculateFrom(recalculateFrom);
*/
- if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+ if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() &&
!loan.isChargedOff()) {
regenerateRepaymentScheduleWithInterestRecalculation(loan,
generatorDTO);
} else {
regenerateRepaymentSchedule(loan, generatorDTO);
diff --git
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
index 7751d18aa..d0e8495bc 100644
---
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
+++
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java
@@ -1154,7 +1154,7 @@ public class LoanChargeWritePlatformServiceImpl
implements LoanChargeWritePlatfo
}
public Loan runScheduleRecalculation(Loan loan, final LocalDate
recalculateFrom) {
- if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
+ if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled() &&
!loan.isChargedOff()) {
final List<Long> existingTransactionIds =
loan.findExistingTransactionIds();
ScheduleGeneratorDTO generatorDTO =
this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
ChangedTransactionDetail changedTransactionDetail =
loanScheduleService
diff --git
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
index 03002d86b..671eed4af 100644
---
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
+++
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java
@@ -52,6 +52,7 @@ import
org.apache.fineract.client.models.GetLoansLoanIdLoanChargeData;
import org.apache.fineract.client.models.GetLoansLoanIdRepaymentPeriod;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
+import
org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse;
import org.apache.fineract.client.models.LoanProduct;
import org.apache.fineract.client.models.PaymentAllocationOrder;
import org.apache.fineract.client.models.PostClientsResponse;
@@ -82,6 +83,7 @@ import
org.apache.fineract.integrationtests.common.charges.ChargesHelper;
import
org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import org.apache.fineract.integrationtests.common.organisation.StaffHelper;
+import org.apache.fineract.integrationtests.common.system.CodeHelper;
import
org.apache.fineract.integrationtests.useradministration.roles.RolesHelper;
import
org.apache.fineract.integrationtests.useradministration.users.UserHelper;
import
org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
@@ -5802,6 +5804,92 @@ public class
AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan
});
}
+ // UC153: Validate Stop recalculating interest if the loan is charged-off
+ // 1. Create a Loan product with Adv. Pment. Alloc. and with Interest
Recalculation enabled
+ // 2. Submit Loan, approve and Disburse
+ // 3. Apply Charge-Off
+ // 4. Prepay the loan to get the same interest amount after Charge-Off
+ @Test
+ public void uc153() {
+ AtomicLong createdLoanId = new AtomicLong();
+ runAt("01 January 2024", () -> {
+ String operationDate = "01 January 2024";
+ Long clientId = client.getClientId();
+ BigDecimal interestRatePerPeriod = BigDecimal.valueOf(7.0);
+
+ final Integer rescheduleStrategyMethod = 4; // Adjust last, unpaid
period
+ PostLoanProductsRequest loanProduct =
createOnePeriod30DaysPeriodicAccrualProductWithAdvancedPaymentAllocationAndInterestRecalculation(
+ (double) 80.0, rescheduleStrategyMethod);
+ final PostLoanProductsResponse loanProductResponse =
loanProductHelper.createLoanProduct(loanProduct);
+ assertNotNull(loanProductResponse);
+
+ PostLoansRequest applicationRequest = applyLoanRequest(clientId,
loanProductResponse.getResourceId(), operationDate, 100.0, 6);
+
+ applicationRequest = applicationRequest.numberOfRepayments(6)//
+ .loanTermFrequency(6)//
+ .loanTermFrequencyType(2)//
+ .interestRatePerPeriod(interestRatePerPeriod)//
+ .interestCalculationPeriodType(DAYS)//
+
.transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)//
+ .repaymentEvery(1)//
+ .repaymentFrequencyType(2)//
+ .maxOutstandingLoanBalance(BigDecimal.valueOf(10000.0))//
+ ;//
+
+ PostLoansResponse loanResponse =
loanTransactionHelper.applyLoan(applicationRequest);
+
+ loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new
PostLoansLoanIdRequest()//
+ .approvedLoanAmount(BigDecimal.valueOf(100))//
+
.approvedOnDate(operationDate).dateFormat(DATETIME_PATTERN).locale("en"));//
+
+ loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new
PostLoansLoanIdRequest()//
+ .transactionAmount(BigDecimal.valueOf(100.0))//
+
.actualDisbursementDate(operationDate).dateFormat(DATETIME_PATTERN).locale("en"));//
+
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(loanResponse.getLoanId());
+ validateLoanSummaryBalances(loanDetails, 125.67, 0.0, 100.0, 0.0,
null);
+ createdLoanId.set(loanResponse.getLoanId());
+ });
+
+ runAt("01 February 2024", () -> {
+
+ loanTransactionHelper.makeLoanRepayment(createdLoanId.get(), new
PostLoansLoanIdTransactionsRequest()
+ .transactionDate("01 February 2024").dateFormat("dd MMMM
yyyy").locale("en").transactionAmount(21.0));
+ GetLoansLoanIdResponse loanDetails =
loanTransactionHelper.getLoanDetails(createdLoanId.get());
+ validateLoanSummaryBalances(loanDetails, 104.67, 21.0, 86.11,
13.89, null);
+ });
+
+ runAt("01 March 2024", () -> {
+ String randomText = Utils.randomStringGenerator("en", 5) +
Utils.randomNumberGenerator(6)
+ + Utils.randomStringGenerator("is", 5);
+ String transactionExternalId = UUID.randomUUID().toString();
+ Integer chargeOffReasonId =
CodeHelper.createChargeOffCodeValue(requestSpec, responseSpec, randomText, 1);
+
+ loanTransactionHelper.chargeOffLoan(createdLoanId.get(),
+ new
PostLoansLoanIdTransactionsRequest().transactionDate("01 March
2024").locale("en").dateFormat("dd MMMM yyyy")
+
.externalId(transactionExternalId).chargeOffReasonId((long) chargeOffReasonId));
+
+ // Loan Prepayment (before) Charge-Off transaction - With Interest
Recalculation
+ GetLoansLoanIdTransactionsTemplateResponse transactionBefore =
loanTransactionHelper
+ .retrieveTransactionTemplate(createdLoanId.get(),
"prepayLoan", "dd MMMM yyyy", "15 February 2024", "en");
+ assertEquals(88.88, transactionBefore.getAmount());
+ assertEquals(86.11, transactionBefore.getPrincipalPortion());
+ assertEquals(2.77, transactionBefore.getInterestPortion());
+ assertEquals(0.00, transactionBefore.getFeeChargesPortion());
+ assertEquals(0.00, transactionBefore.getPenaltyChargesPortion());
+
+ // Loan Prepayment (after) Charge-Off transaction - WithOut
Interest Recalculation
+ GetLoansLoanIdTransactionsTemplateResponse transactionAfter =
loanTransactionHelper
+ .retrieveTransactionTemplate(createdLoanId.get(),
"prepayLoan", "dd MMMM yyyy", "01 March 2024", "en");
+ assertEquals(104.67, transactionAfter.getAmount());
+ assertEquals(86.11, transactionAfter.getPrincipalPortion());
+ assertEquals(18.56, transactionAfter.getInterestPortion());
+ assertEquals(0.00, transactionAfter.getFeeChargesPortion());
+ assertEquals(0.00, transactionAfter.getPenaltyChargesPortion());
+ });
+
+ }
+
private Long
applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long
clientId, Long loanProductId,
Integer numberOfRepayments, String loanDisbursementDate, double
amount) {
LOG.info("------------------------------APPLY AND APPROVE LOAN
---------------------------------------");
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 174886399..8785ec626 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
@@ -1920,6 +1920,11 @@ public class LoanTransactionHelper extends
IntegrationTest {
return chargebackPayload;
}
+ public GetLoansLoanIdTransactionsTemplateResponse
retrieveTransactionTemplate(Long loanId, String command, String dateFormat,
+ String transactionDate, String locale) {
+ return
ok(fineract().loanTransactions.retrieveTransactionTemplate(loanId, command,
dateFormat, transactionDate, locale));
+ }
+
public GetLoansLoanIdTransactionsTemplateResponse
retrieveTransactionTemplate(String loanExternalIdStr, String command,
String dateFormat, String transactionDate, String locale) {
return ok(