FINERACT-2220: Use stored ProgressiveLoanInterestScheduleModel for calculation

This commit is contained in:
Soma Sörös
2025-04-29 13:12:51 +02:00
committed by Adam Saghy
parent d7f321bc65
commit 2d17fcd478
14 changed files with 408 additions and 162 deletions
@@ -1187,7 +1187,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
.sorted(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).toList();
}
private void recalculateInterestForDate(LocalDate targetDate, ProgressiveTransactionCtx ctx) {
public void recalculateInterestForDate(LocalDate targetDate, ProgressiveTransactionCtx ctx) {
if (ctx.getInstallments() != null && !ctx.getInstallments().isEmpty()) {
Loan loan = ctx.getInstallments().get(0).getLoan();
if (loan.isInterestBearingAndInterestRecalculationEnabled() && !loan.isNpa() && !ctx.isChargedOff()) {
@@ -1240,17 +1240,18 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
LocalDate toDate = currentInstallment.getDueDate();
boolean hasUpdate = false;
if (!currentDate.equals(ctx.getLastOverdueBalanceChange())) {
if (!currentDate.equals(ctx.getModel().lastOverdueBalanceChange())) {
// if we have same date for fromDate & last overdue balance change then it means we have the up-to-date
// model.
if (ctx.getLastOverdueBalanceChange() == null || currentInstallment.getFromDate().isAfter(ctx.getLastOverdueBalanceChange())) {
if (ctx.getModel().lastOverdueBalanceChange() == null
|| currentInstallment.getFromDate().isAfter(ctx.getModel().lastOverdueBalanceChange())) {
// first overdue hit for installment. setting overdue balance correction from instalment from date.
emiCalculator.addBalanceCorrection(ctx.getModel(), fromDate, overduePrincipal);
} else {
// not the first balance correction on installment period, then setting overdue balance correction from
// last balance change's current date. previous interest period already has the correct balance
// correction.
emiCalculator.addBalanceCorrection(ctx.getModel(), ctx.getLastOverdueBalanceChange(), overduePrincipal);
emiCalculator.addBalanceCorrection(ctx.getModel(), ctx.getModel().lastOverdueBalanceChange(), overduePrincipal);
}
hasUpdate = true;
@@ -1263,7 +1264,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
lastOverdueBalanceChange = currentDate;
}
emiCalculator.addBalanceCorrection(ctx.getModel(), lastOverdueBalanceChange, aggregatedOverDuePrincipal.negated());
ctx.setLastOverdueBalanceChange(lastOverdueBalanceChange);
ctx.getModel().lastOverdueBalanceChange(lastOverdueBalanceChange);
}
}
@@ -18,7 +18,6 @@
*/
package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
@@ -38,8 +37,6 @@ import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanIntere
public class ProgressiveTransactionCtx extends TransactionCtx {
private final ProgressiveLoanInterestScheduleModel model;
@Setter
private LocalDate lastOverdueBalanceChange = null;
private List<LoanTransaction> alreadyProcessedTransactions = new ArrayList<>();
@Setter
private Money sumOfInterestRefundAmount;
@@ -18,16 +18,23 @@
*/
package org.apache.fineract.portfolio.loanaccount.service;
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.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
public interface InterestScheduleModelRepositoryWrapper {
Optional<ProgressiveLoanModel> findOneByLoanId(Long loanId);
Optional<ProgressiveLoanInterestScheduleModel> extractModel(Optional<ProgressiveLoanModel> progressiveLoanModel);
String writeInterestScheduleModel(Loan loan, ProgressiveLoanInterestScheduleModel model);
Optional<ProgressiveLoanInterestScheduleModel> readProgressiveLoanInterestScheduleModel(Long loanId,
LoanProductMinimumRepaymentScheduleRelatedDetail detail, Integer installmentAmountInMultipliesOf);
Optional<ProgressiveLoanInterestScheduleModel> getSavedModel(Loan loan, LocalDate businessDate);
}
@@ -18,14 +18,23 @@
*/
package org.apache.fineract.portfolio.loanaccount.service;
import jakarta.persistence.EntityManager;
import jakarta.persistence.FlushModeType;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import java.time.LocalDate;
import java.util.Optional;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.ProgressiveLoanModel;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.ProgressiveTransactionCtx;
import org.apache.fineract.portfolio.loanaccount.repository.ProgressiveLoanModelRepository;
import org.apache.fineract.portfolio.loanproduct.calc.data.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;
@@ -37,6 +46,9 @@ public class InterestScheduleModelRepositoryWrapperImpl implements InterestSched
private final ProgressiveLoanModelRepository loanModelRepository;
private final ProgressiveLoanInterestScheduleModelParserService progressiveLoanInterestScheduleModelParserService;
private final AdvancedPaymentScheduleTransactionProcessor advancedPaymentScheduleTransactionProcessor;
@PersistenceContext
private final EntityManager entityManager;
@Transactional
@Override
@@ -45,18 +57,54 @@ public class InterestScheduleModelRepositoryWrapperImpl implements InterestSched
return null;
}
String jsonModel = progressiveLoanInterestScheduleModelParserService.toJson(model);
ProgressiveLoanModel progressiveLoanModel = loanModelRepository.findOneByLoanId(loan.getId()).orElseGet(() -> {
ProgressiveLoanModel plm = new ProgressiveLoanModel();
plm.setLoan(loan);
return plm;
});
progressiveLoanModel.setBusinessDate(ThreadLocalContextUtil.getBusinessDate());
progressiveLoanModel.setLastModifiedDate(DateUtils.getAuditOffsetDateTime());
progressiveLoanModel.setJsonModel(jsonModel);
loanModelRepository.save(progressiveLoanModel);
try {
entityManager.setFlushMode(FlushModeType.COMMIT);
ProgressiveLoanModel progressiveLoanModel = loanModelRepository.findOneByLoanId(loan.getId()).orElseGet(() -> {
ProgressiveLoanModel plm = new ProgressiveLoanModel();
plm.setLoan(loan);
return plm;
});
progressiveLoanModel.setBusinessDate(ThreadLocalContextUtil.getBusinessDate());
progressiveLoanModel.setLastModifiedDate(DateUtils.getAuditOffsetDateTime());
progressiveLoanModel.setJsonModel(jsonModel);
loanModelRepository.save(progressiveLoanModel);
} finally {
entityManager.setFlushMode(FlushModeType.AUTO);
}
return jsonModel;
}
@Override
public Optional<ProgressiveLoanModel> findOneByLoanId(Long loanId) {
return loanModelRepository.findOneByLoanId(loanId);
}
@Override
public Optional<ProgressiveLoanInterestScheduleModel> extractModel(Optional<ProgressiveLoanModel> progressiveLoanModel) {
return progressiveLoanModel.map(ProgressiveLoanModel::getJsonModel) //
.map(jsonModel -> progressiveLoanInterestScheduleModelParserService.fromJson(jsonModel,
progressiveLoanModel.get().getLoan().getLoanProductRelatedDetail(), MoneyHelper.getMathContext(),
progressiveLoanModel.get().getLoan().getLoanProduct().getInstallmentAmountInMultiplesOf()));
}
@Override
public Optional<ProgressiveLoanInterestScheduleModel> getSavedModel(Loan loan, LocalDate businessDate) {
Optional<ProgressiveLoanModel> progressiveLoanModel = findOneByLoanId(loan.getId());
Optional<ProgressiveLoanInterestScheduleModel> savedModel;
if (progressiveLoanModel.isPresent() && !progressiveLoanModel.get().getBusinessDate().isAfter(businessDate)) {
savedModel = extractModel(progressiveLoanModel);
if (progressiveLoanModel.get().getBusinessDate().isBefore(businessDate) && savedModel.isPresent()) {
ProgressiveTransactionCtx ctx = new ProgressiveTransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(),
Set.of(), new MoneyHolder(loan.getTotalOverpaidAsMoney()), new ChangedTransactionDetail(), savedModel.get());
ctx.setChargedOff(loan.isChargedOff());
advancedPaymentScheduleTransactionProcessor.recalculateInterestForDate(businessDate, ctx);
}
} else {
savedModel = Optional.empty();
}
return savedModel;
}
@Override
public Optional<ProgressiveLoanInterestScheduleModel> readProgressiveLoanInterestScheduleModel(final Long loanId,
final LoanProductMinimumRepaymentScheduleRelatedDetail detail, final Integer installmentAmountInMultipliesOf) {
@@ -41,6 +41,7 @@ import java.util.function.Consumer;
import java.util.stream.Collectors;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Setter;
import lombok.experimental.Accessors;
import org.apache.fineract.infrastructure.core.serialization.gson.JsonExclude;
import org.apache.fineract.infrastructure.core.service.DateUtils;
@@ -67,6 +68,9 @@ public class ProgressiveLoanInterestScheduleModel {
private final Money zero;
private final Map<LoanInterestScheduleModelModifiers, Boolean> modifiers;
@Setter
private LocalDate lastOverdueBalanceChange;
public ProgressiveLoanInterestScheduleModel(final List<RepaymentPeriod> repaymentPeriods,
final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail,
final List<LoanTermVariationsData> loanTermVariations, final Integer installmentAmountInMultiplesOf, final MathContext mc) {