FINERACT-2220: Use stored ProgressiveLoanInterestScheduleModel for Periodic Accrual Calculation

This commit is contained in:
Soma Sörös
2025-04-30 13:29:29 +02:00
committed by Adam Saghy
parent 0563311251
commit 071592eceb
4 changed files with 63 additions and 6 deletions
@@ -131,6 +131,11 @@ public class LoanDownPaymentHandlerServiceImpl implements LoanDownPaymentHandler
|| !DateUtils.isEqualBusinessDate(loanTransaction.getTransactionDate()) || currentInstallment == null
|| !currentInstallment.getTotalOutstanding(loan.getCurrency()).isEqualTo(loanTransaction.getAmount(loan.getCurrency()));
// TODO FINERACT-2220 fix processLatestTransaction to save model.
if (loan.isProgressiveSchedule() && loan.isInterestBearing()) {
reprocess = true;
}
if (isTransactionChronologicallyLatest && adjustedTransaction == null
&& (!reprocess || !loan.isInterestBearingAndInterestRecalculationEnabled()) && !loan.isForeclosure()) {
loanTransactionProcessingService.processLatestTransaction(loan.getTransactionProcessingStrategyCode(), loanTransaction,
@@ -19,9 +19,16 @@
package org.apache.fineract.portfolio.loanaccount.loanschedule.domain;
import java.math.MathContext;
import java.time.LocalDate;
import java.util.Optional;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.ProgressiveLoanModel;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlan;
import org.apache.fineract.portfolio.loanaccount.service.InterestScheduleModelRepositoryWrapper;
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
import org.apache.fineract.portfolio.loanproduct.calc.ProgressiveEMICalculator;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
@SuppressWarnings("unused")
public class EmbeddableProgressiveLoanScheduleGenerator {
@@ -33,10 +40,40 @@ public class EmbeddableProgressiveLoanScheduleGenerator {
public EmbeddableProgressiveLoanScheduleGenerator() {
this.emiCalculator = new ProgressiveEMICalculator();
this.scheduledDateGenerator = new DefaultScheduledDateGenerator();
this.scheduleGenerator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator);
this.scheduleGenerator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator,
new NoopInterestScheduleModelRepositoryWrapper());
}
public LoanSchedulePlan generate(final MathContext mc, final LoanRepaymentScheduleModelData modelData) {
return scheduleGenerator.generate(mc, modelData);
}
private static final class NoopInterestScheduleModelRepositoryWrapper implements InterestScheduleModelRepositoryWrapper {
@Override
public Optional<ProgressiveLoanModel> findOneByLoanId(Long loanId) {
return Optional.empty();
}
@Override
public Optional<ProgressiveLoanInterestScheduleModel> extractModel(Optional<ProgressiveLoanModel> progressiveLoanModel) {
return Optional.empty();
}
@Override
public String writeInterestScheduleModel(Loan loan, ProgressiveLoanInterestScheduleModel model) {
return "";
}
@Override
public Optional<ProgressiveLoanInterestScheduleModel> readProgressiveLoanInterestScheduleModel(Long loanId,
LoanProductMinimumRepaymentScheduleRelatedDetail detail, Integer installmentAmountInMultipliesOf) {
return Optional.empty();
}
@Override
public Optional<ProgressiveLoanInterestScheduleModel> getSavedModel(Loan loan, LocalDate businessDate) {
return Optional.empty();
}
}
}
@@ -29,6 +29,7 @@ import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.apache.fineract.infrastructure.core.service.MathUtil;
import org.apache.fineract.organisation.monetary.data.CurrencyData;
@@ -48,6 +49,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleM
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleParams;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlan;
import org.apache.fineract.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
import org.apache.fineract.portfolio.loanaccount.service.InterestScheduleModelRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService;
import org.apache.fineract.portfolio.loanproduct.calc.EMICalculator;
import org.apache.fineract.portfolio.loanproduct.calc.data.OutstandingDetails;
@@ -61,12 +63,15 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
private final ScheduledDateGenerator scheduledDateGenerator;
private final EMICalculator emiCalculator;
private final InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper;
private LoanTransactionProcessingService loanTransactionProcessingService;
public ProgressiveLoanScheduleGenerator(ScheduledDateGenerator scheduledDateGenerator, EMICalculator emiCalculator) {
public ProgressiveLoanScheduleGenerator(ScheduledDateGenerator scheduledDateGenerator, EMICalculator emiCalculator,
InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapper) {
this.scheduledDateGenerator = scheduledDateGenerator;
this.emiCalculator = emiCalculator;
this.interestScheduleModelRepositoryWrapper = interestScheduleModelRepositoryWrapper;
}
@Autowired(required = false)
@@ -187,7 +192,10 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
case NONE -> throw new IllegalStateException("Unexpected PreClosureInterestCalculationStrategy: NONE");
};
ProgressiveLoanInterestScheduleModel model = processor.calculateInterestScheduleModel(loan.getId(), onDate);
Optional<ProgressiveLoanInterestScheduleModel> savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan,
transactionDate);
ProgressiveLoanInterestScheduleModel model = savedModel
.orElseGet(() -> processor.calculateInterestScheduleModel(loan.getId(), onDate));
OutstandingDetails outstandingAmounts = emiCalculator.getOutstandingAmountsTillDate(model, transactionDate);
// TODO: We should add all the past due outstanding amounts as well
OutstandingAmountsDTO result = new OutstandingAmountsDTO(currency) //
@@ -228,7 +236,9 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator {
if (installment.isAdditional() || installment.isDownPayment() || installment.isReAged()) {
return Money.zero(loan.getCurrency());
}
ProgressiveLoanInterestScheduleModel model = processor.calculateInterestScheduleModel(loan.getId(), targetDate);
Optional<ProgressiveLoanInterestScheduleModel> savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan, targetDate);
ProgressiveLoanInterestScheduleModel model = savedModel
.orElseGet(() -> processor.calculateInterestScheduleModel(loan.getId(), targetDate));
return emiCalculator.getPeriodInterestTillDate(model, installment.getDueDate(), targetDate, false);
}
@@ -33,6 +33,7 @@ import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleP
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlanDisbursementPeriod;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlanDownPaymentPeriod;
import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanSchedulePlanRepaymentPeriod;
import org.apache.fineract.portfolio.loanaccount.service.InterestScheduleModelRepositoryWrapper;
import org.apache.fineract.portfolio.loanaccount.service.LoanTransactionProcessingService;
import org.apache.fineract.portfolio.loanproduct.calc.ProgressiveEMICalculator;
import org.junit.jupiter.api.Test;
@@ -55,6 +56,8 @@ class LoanScheduleGeneratorTest {
private static final MathContext mc = new MathContext(12, RoundingMode.HALF_EVEN);
private static final BigDecimal DOWN_PAYMENT_PORTION = BigDecimal.valueOf(25);
private static final LoanTransactionProcessingService loanTransactionProcessingService = mock(LoanTransactionProcessingService.class);
private static final InterestScheduleModelRepositoryWrapper interestScheduleModelRepositoryWrapperMock = mock(
InterestScheduleModelRepositoryWrapper.class);
@Test
void testGenerateLoanSchedule() {
@@ -63,7 +66,8 @@ class LoanScheduleGeneratorTest {
NOMINAL_INTEREST_RATE, false, DaysInMonthType.DAYS_30, DaysInYearType.DAYS_360, null, null, null, false, null);
ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
ProgressiveLoanScheduleGenerator generator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator);
ProgressiveLoanScheduleGenerator generator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator,
interestScheduleModelRepositoryWrapperMock);
generator.setLoanTransactionProcessingService(loanTransactionProcessingService);
LoanSchedulePlan loanSchedule = generator.generate(mc, modelData);
@@ -100,7 +104,8 @@ class LoanScheduleGeneratorTest {
null);
ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
ProgressiveLoanScheduleGenerator generator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator);
ProgressiveLoanScheduleGenerator generator = new ProgressiveLoanScheduleGenerator(scheduledDateGenerator, emiCalculator,
interestScheduleModelRepositoryWrapperMock);
generator.setLoanTransactionProcessingService(loanTransactionProcessingService);
LoanSchedulePlan loanSchedule = generator.generate(mc, modelData);