From 2151b19e27ab5f9a7c2ccf72beaa8fac9304d667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Soma=20S=C3=B6r=C3=B6s?= Date: Mon, 13 Jan 2025 12:06:28 +0100 Subject: [PATCH] FINERACT-1971: Fix EMI Calculator: Last Unpaid Repayment Period Handling. call calculateLastUnpaidRepaymentPeriodEMI recursively when emi would be less than already paid to adjust remaining emi correction for previous period. Fixing AdvancedPaymentScheduleTransactionProcessor to handle division by installment count adjustment part in case of IN_ADVANCED payment --- ...edPaymentScheduleTransactionProcessor.java | 8 +- .../calc/ProgressiveEMICalculator.java | 4 +- .../LoanInterestRefundTest.java | 115 ++++++++++++++++++ 3 files changed, 121 insertions(+), 6 deletions(-) diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index ce5493c4c..4edebfc0a 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -1689,16 +1689,16 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep transactionMappings, loanTransaction, inAdvanceInstallment, currency); Loan loan = loanTransaction.getLoan(); + // Adjust the portion for the last installment + if (inAdvanceInstallment.equals(inAdvanceInstallments.get(numberOfInstallments - 1))) { + evenPortion = evenPortion.add(balanceAdjustment); + } if (transactionCtx instanceof ProgressiveTransactionCtx ctx && loan.isInterestBearing() && loan.getLoanProductRelatedDetail().isInterestRecalculationEnabled() && !ctx.isChargedOff()) { paidPortion = handlingPaymentAllocationForInterestBearingProgressiveLoan(loanTransaction, evenPortion, balances, paymentAllocationType, inAdvanceInstallment, ctx, loanTransactionToRepaymentScheduleMapping, inAdvanceInstallmentCharges); } else { - // Adjust the portion for the last installment - if (inAdvanceInstallment.equals(inAdvanceInstallments.get(numberOfInstallments - 1))) { - evenPortion = evenPortion.add(balanceAdjustment); - } paidPortion = processPaymentAllocation(paymentAllocationType, inAdvanceInstallment, loanTransaction, evenPortion, loanTransactionToRepaymentScheduleMapping, inAdvanceInstallmentCharges, balances, LoanRepaymentScheduleInstallment.PaymentAction.PAY); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java index 521cbc9d8..b5adfcc5e 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java @@ -309,8 +309,8 @@ public final class ProgressiveEMICalculator implements EMICalculator { .reduce((first, second) -> second); findLastUnpaidRepaymentPeriod.ifPresent(repaymentPeriod -> { repaymentPeriod.setEmi(repaymentPeriod.getEmi().add(diff, mc)); - if (repaymentPeriod.getEmi().isLessThanZero()) { - repaymentPeriod.setEmi(repaymentPeriod.getEmi().zero()); + if (repaymentPeriod.getEmi().isLessThan(repaymentPeriod.getTotalPaidAmount())) { + repaymentPeriod.setEmi(repaymentPeriod.getTotalPaidAmount()); calculateLastUnpaidRepaymentPeriodEMI(scheduleModel); } }); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java index 3c68589cb..2ebad0581 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanInterestRefundTest.java @@ -30,6 +30,7 @@ import io.restassured.specification.RequestSpecification; import io.restassured.specification.ResponseSpecification; import java.math.BigDecimal; import java.time.format.DateTimeFormatter; +import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.Optional; @@ -128,6 +129,120 @@ public class LoanInterestRefundTest extends BaseLoanIntegrationTest { }); } + @Test + public void verifyFullMerchantIssuedRefundWithReAmortizationOnDay0HighInterest6month() { + runAt("1 January 2021", () -> { + PostLoanProductsResponse loanProduct = loanProductHelper + .createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) // + .daysInYearType(DaysInYearType.ACTUAL) // + .addSupportedInterestRefundTypesItem(SupportedInterestRefundTypesItem.MERCHANT_ISSUED_REFUND) // + .paymentAllocation(List.of(createDefaultPaymentAllocation("REAMORTIZATION")))// + .recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) // + ); + Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 600.0, 60.0, + 6, null); + Assertions.assertNotNull(loanId); + disburseLoan(loanId, BigDecimal.valueOf(600), "1 January 2021"); + PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper + .makeLoanRepayment("MerchantIssuedRefund", "1 January 2021", 600F, loanId.intValue()); + Assertions.assertNotNull(postLoansLoanIdTransactionsResponse); + Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId()); + + verifyTransactions(loanId, transaction(600.0, "Disbursement", "01 January 2021"), // + transaction(600.0, "Merchant Issued Refund", "01 January 2021") // + ); + verifyRepaymentSchedule(loanId, installment(600.0, null, "01 January 2021"), // + fullyRepaidInstallment(100.0, 0.0, "01 February 2021"), // + fullyRepaidInstallment(100.0, 0.0, "01 March 2021"), // + fullyRepaidInstallment(100.0, 0.0, "01 April 2021"), // + fullyRepaidInstallment(100.0, 0.0, "01 May 2021"), // + fullyRepaidInstallment(100.0, 0.0, "01 June 2021"), // + fullyRepaidInstallment(100.0, 0.0, "01 July 2021") // + ); + }); + } + + @Test + public void verifyAlmostFullMerchantIssuedRefundWithReAmortizationOnDay0HighInterest12month() { + runAt("1 January 2021", () -> { + PostLoanProductsResponse loanProduct = loanProductHelper + .createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) // + .daysInYearType(DaysInYearType.ACTUAL) // + .addSupportedInterestRefundTypesItem(SupportedInterestRefundTypesItem.MERCHANT_ISSUED_REFUND) // + .paymentAllocation(List.of(createDefaultPaymentAllocation("REAMORTIZATION")))// + .recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) // + ); + Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 26.0, + 12, null); + Assertions.assertNotNull(loanId); + disburseLoan(loanId, BigDecimal.valueOf(1000), "1 January 2021"); + + PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper + .makeLoanRepayment("MerchantIssuedRefund", "1 January 2021", 980F, loanId.intValue()); + Assertions.assertNotNull(postLoansLoanIdTransactionsResponse); + Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId()); + + verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), // + transaction(980.0, "Merchant Issued Refund", "01 January 2021") // + ); + + verifyRepaymentSchedule(loanId, installment(1000.0, null, "01 January 2021"), // + installment(95.04, 0.44, 13.81, false, "01 February 2021"), // + installment(88.30, 0.13, 6.76, false, "01 March 2021"), // + fullyRepaidInstallment(81.67, 0.0, "01 April 2021"), // + fullyRepaidInstallment(81.67, 0.0, "01 May 2021"), // + fullyRepaidInstallment(81.67, 0.0, "01 June 2021"), // + fullyRepaidInstallment(81.67, 0.0, "01 July 2021"), // + fullyRepaidInstallment(81.67, 0.0, "01 August 2021"), // + fullyRepaidInstallment(81.67, 0.0, "01 September 2021"), // + fullyRepaidInstallment(81.67, 0.0, "01 October 2021"), // + fullyRepaidInstallment(81.67, 0.0, "01 November 2021"), // + fullyRepaidInstallment(81.67, 0.0, "01 December 2021"), // + fullyRepaidInstallment(81.63, 0.0, "01 January 2022") // + ); + }); + } + + @Test + public void verifyFullMerchantIssuedRefundWithReAmortizationOnDay0HighInterest12month() { + runAt("1 January 2021", () -> { + PostLoanProductsResponse loanProduct = loanProductHelper + .createLoanProduct(create4IProgressive().daysInMonthType(DaysInMonthType.ACTUAL) // + .daysInYearType(DaysInYearType.ACTUAL) // + .addSupportedInterestRefundTypesItem(SupportedInterestRefundTypesItem.MERCHANT_ISSUED_REFUND) // + .paymentAllocation(List.of(createDefaultPaymentAllocation("REAMORTIZATION")))// + .recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY) // + ); + Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProduct.getResourceId(), "1 January 2021", 1000.0, 26.0, + 12, null); + Assertions.assertNotNull(loanId); + disburseLoan(loanId, BigDecimal.valueOf(1000), "1 January 2021"); + + PostLoansLoanIdTransactionsResponse postLoansLoanIdTransactionsResponse = loanTransactionHelper + .makeLoanRepayment("MerchantIssuedRefund", "1 January 2021", 1000F, loanId.intValue()); + Assertions.assertNotNull(postLoansLoanIdTransactionsResponse); + Assertions.assertNotNull(postLoansLoanIdTransactionsResponse.getResourceId()); + + verifyTransactions(loanId, transaction(1000.0, "Disbursement", "01 January 2021"), // + transaction(1000.0, "Merchant Issued Refund", "01 January 2021") // + ); + verifyRepaymentSchedule(loanId, installment(1000.0, null, "01 January 2021"), // + fullyRepaidInstallment(83.33, 0.0, "01 February 2021"), // + fullyRepaidInstallment(83.33, 0.0, "01 March 2021"), // + fullyRepaidInstallment(83.33, 0.0, "01 April 2021"), // + fullyRepaidInstallment(83.33, 0.0, "01 May 2021"), // + fullyRepaidInstallment(83.33, 0.0, "01 June 2021"), // + fullyRepaidInstallment(83.33, 0.0, "01 July 2021"), // + fullyRepaidInstallment(83.33, 0.0, "01 August 2021"), // + fullyRepaidInstallment(83.33, 0.0, "01 September 2021"), // + fullyRepaidInstallment(83.33, 0.0, "01 October 2021"), // + fullyRepaidInstallment(83.33, 0.0, "01 November 2021"), // + fullyRepaidInstallment(83.33, 0.0, "01 December 2021"), // + fullyRepaidInstallment(83.37, 0.0, "01 January 2022") // + ); + }); + } + @Test public void verifyInterestRefundCreatedForPayoutRefund() { AtomicReference loanIdRef = new AtomicReference<>();