diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java index 34d8baf6b5c..9f5ce47d828 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java @@ -512,6 +512,7 @@ public Money payPenaltyChargesComponent(final LocalDate transactionDate, final M Money penaltyPortionOfTransaction = Money.zero(currency); if (transactionAmountRemaining.isZero()) { + // checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency); return penaltyPortionOfTransaction; } @@ -538,6 +539,7 @@ public Money payFeeChargesComponent(final LocalDate transactionDate, final Money final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); Money feePortionOfTransaction = Money.zero(currency); if (transactionAmountRemaining.isZero()) { + // checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency); return feePortionOfTransaction; } final Money feeChargesDue = getFeeChargesOutstanding(currency); @@ -563,6 +565,7 @@ public Money payInterestComponent(final LocalDate transactionDate, final Money t final MonetaryCurrency currency = transactionAmountRemaining.getCurrency(); Money interestPortionOfTransaction = Money.zero(currency); if (transactionAmountRemaining.isZero()) { + // checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency); return interestPortionOfTransaction; } final Money interestDue = getInterestOutstanding(currency); @@ -588,6 +591,7 @@ public Money payPrincipalComponent(final LocalDate transactionDate, final Money final MonetaryCurrency currency = transactionAmount.getCurrency(); Money principalPortionOfTransaction = Money.zero(currency); if (transactionAmount.isZero()) { + checkIfRepaymentPeriodObligationsAreMet(transactionDate, currency); return principalPortionOfTransaction; } final Money principalDue = getPrincipalOutstanding(currency); 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 fca941dfca5..b08b055d257 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 @@ -26,6 +26,8 @@ import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.INTEREST; import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PENALTY; import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.PRINCIPAL; +import static org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy.TILL_PRE_CLOSURE_DATE; +import static org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy.TILL_REST_FREQUENCY_DATE; import static org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationTransactionType.DEFAULT; import java.math.BigDecimal; @@ -85,6 +87,7 @@ import org.apache.fineract.portfolio.loanproduct.domain.CreditAllocationTransactionType; import org.apache.fineract.portfolio.loanproduct.domain.DueType; import org.apache.fineract.portfolio.loanproduct.domain.FutureInstallmentAllocationRule; +import org.apache.fineract.portfolio.loanproduct.domain.LoanPreClosureInterestCalculationStrategy; import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail; import org.apache.fineract.portfolio.loanproduct.domain.PaymentAllocationType; import org.jetbrains.annotations.NotNull; @@ -1231,10 +1234,21 @@ private Money processAllocationsHorizontally(LoanTransaction loanTransaction, Tr if (transactionCtx instanceof ProgressiveTransactionCtx ctx && loan.isInterestBearing() && loan.getLoanProductRelatedDetail().isInterestRecalculationEnabled()) { ProgressiveLoanInterestScheduleModel model = ctx.getModel(); - LocalDate transactionDate = loanTransaction.getTransactionDate(); - LocalDate payDate = inAdvanceInstallment.getFromDate().isAfter(transactionDate) - ? inAdvanceInstallment.getFromDate() - : transactionDate; + LoanPreClosureInterestCalculationStrategy strategy = loanTransaction.getLoan().getLoanProduct() + .preCloseInterestCalculationStrategy(); + + LocalDate payDate = switch (strategy) { + case TILL_PRE_CLOSURE_DATE -> { + LocalDate transactionDate = loanTransaction.getTransactionDate(); + yield inAdvanceInstallment.getFromDate().isAfter(transactionDate) + ? inAdvanceInstallment.getFromDate() + : transactionDate; + } + case TILL_REST_FREQUENCY_DATE -> inAdvanceInstallment.getDueDate(); + case NONE -> + throw new IllegalStateException("Unexpected PreClosureInterestCalculationStrategy: NONE"); + }; + ProgressiveLoanInterestRepaymentModel payableDetails = emiCalculator .getPayableDetails(model, inAdvanceInstallment.getDueDate(), payDate).orElseThrow(); @@ -1253,13 +1267,19 @@ private Money processAllocationsHorizontally(LoanTransaction loanTransaction, Tr switch (paymentAllocationType) { case IN_ADVANCE_PRINCIPAL -> { - emiCalculator.addBalanceCorrection(model, payDate, - payableDetails.getOutstandingBalance().multipliedBy(-1)); + Money balance = switch (strategy) { + case TILL_PRE_CLOSURE_DATE -> payableDetails.getOutstandingBalance(); + case TILL_REST_FREQUENCY_DATE -> payableDetails.getRemainingBalance(); + default -> throw new IllegalStateException(); + }; + emiCalculator.addBalanceCorrection(model, payDate, balance.multipliedBy(-1)); emiCalculator.addBalanceCorrection(model, payDate, payableDetails.getPrincipalDue().minus(paidPortion)); } - case IN_ADVANCE_INTEREST -> emiCalculator.addBalanceCorrection(model, payDate, - payableDetails.getInterestDue().minus(paidPortion)); + case IN_ADVANCE_INTEREST -> { + emiCalculator.addBalanceCorrection(model, payDate, + payableDetails.getInterestDue().minus(paidPortion)); + } default -> { } } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java index b1250fd457d..ded468ca2f6 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java @@ -251,7 +251,7 @@ public OutstandingAmountsDTO calculatePrepaymentAmount(MonetaryCurrency currency case TILL_PRE_CLOSURE_DATE -> onDate; case TILL_REST_FREQUENCY_DATE -> // find due date of current installment installments.stream().filter(it -> it.getFromDate().isBefore(onDate) && it.getDueDate().isAfter(onDate)).findFirst() - .orElseThrow(() -> new IllegalStateException("No installment found for transaction date: " + onDate)).getDueDate(); + .orElse(installments.get(0)).getDueDate(); case NONE -> throw new IllegalStateException("Unexpected PreClosureInterestCalculationStrategy: NONE"); };