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 ce5493c4c2..4edebfc0aa 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 @@ private Money processAllocationsHorizontally(LoanTransaction loanTransaction, Tr 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 521cbc9d8b..b5adfcc5e7 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 @@ private void calculateLastUnpaidRepaymentPeriodEMI(ProgressiveLoanInterestSchedu .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 3c68589cba..2ebad05818 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.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 void verifyInterestRefundNotCreatedForMerchantIssuedRefundWhenTypesAreEmp }); } + @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<>();