FINERACT-2354: Re-aging: -Interest Handling Option: Equal amortization

This commit is contained in:
Soma Sörös
2025-11-21 15:02:34 +01:00
committed by Adam Saghy
parent 47e8674fa0
commit 747121bbe2
17 changed files with 3125 additions and 120 deletions
@@ -35,7 +35,6 @@ import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
@@ -62,8 +61,19 @@ public class ProgressiveLoanInterestRefundServiceImpl implements InterestRefundS
private static void simulateRepaymentForDisbursements(LoanTransaction lt, final AtomicReference<BigDecimal> refundFinal,
List<LoanTransaction> collect) {
collect.add(new LoanTransaction(lt.getLoan(), lt.getLoan().getOffice(), lt.getTypeOf(), lt.getDateOf(), lt.getAmount(),
BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, false, null, null));
LoanTransaction copy = new LoanTransaction(lt.getLoan(), lt.getLoan().getOffice(), lt.getTypeOf(), lt.getDateOf(), lt.getAmount(),
BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, false, null, null);
if (LoanTransactionType.CHARGE_PAYMENT.equals(copy.getTypeOf())
|| LoanTransactionType.REPAYMENT_AT_DISBURSEMENT.equals(copy.getTypeOf())) {
copy.getLoanChargesPaid().addAll(copy.getLoanChargesPaid());
}
if (LoanTransactionType.REAGE.equals(copy.getTypeOf())) {
copy.setLoanReAgeParameter(lt.getLoanReAgeParameter().getCopy(copy));
}
if (LoanTransactionType.REAMORTIZE.equals(copy.getTypeOf())) {
copy.setLoanReAmortizationParameter(lt.getLoanReAmortizationParameter());
}
collect.add(copy);
if (lt.getTypeOf().isDisbursement() && MathUtil.isGreaterThanZero(refundFinal.get())) {
if (lt.getAmount().compareTo(refundFinal.get()) <= 0) {
collect.add(new LoanTransaction(lt.getLoan(), lt.getLoan().getOffice(), REPAYMENT, lt.getDateOf(), lt.getAmount(),
@@ -79,15 +89,14 @@ public class ProgressiveLoanInterestRefundServiceImpl implements InterestRefundS
private Money recalculateTotalInterest(AdvancedPaymentScheduleTransactionProcessor processor, Loan loan,
LocalDate relatedRefundTransactionDate, List<LoanTransaction> transactionsToReprocess) {
List<LoanRepaymentScheduleInstallment> installmentsToReprocess = new ArrayList<>(
loan.getRepaymentScheduleInstallments().stream().filter(i -> !i.isReAged() && !i.isAdditional()).toList());
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
Pair<ChangedTransactionDetail, ProgressiveLoanInterestScheduleModel> reprocessResult = processor
.reprocessProgressiveLoanTransactions(loan.getDisbursementDate(), relatedRefundTransactionDate, transactionsToReprocess,
loan.getCurrency(), installmentsToReprocess, loan.getActiveCharges());
loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges());
final List<LoanTransaction> newTransactions = reprocessResult.getLeft().getTransactionChanges().stream()
.map(TransactionChangeData::getNewTransaction).toList().stream().filter(LoanTransaction::isNotReversed).toList();
loan.getLoanTransactions().addAll(newTransactions);
@@ -196,7 +196,11 @@ public class LoanReAgingService {
}
private void processReAgeTransaction(final Loan loan, final LoanTransaction reAgeTransaction, final boolean withPostTransactionChecks) {
if (reAgeTransaction.getTransactionDate().isBefore(reAgeTransaction.getSubmittedOnDate())) {
if (reAgeTransaction.getTransactionDate().isBefore(reAgeTransaction.getSubmittedOnDate())
|| LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_FULL_INTEREST
.equals(reAgeTransaction.getLoanReAgeParameter().getInterestHandlingType())
|| LoanReAgeInterestHandlingType.EQUAL_AMORTIZATION_PAYABLE_INTEREST
.equals(reAgeTransaction.getLoanReAgeParameter().getInterestHandlingType())) {
final ScheduleGeneratorDTO scheduleGeneratorDTO = loanUtilService.buildScheduleGeneratorDTO(loan, null);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
if (withPostTransactionChecks) {
@@ -35,6 +35,7 @@ import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException;
import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException;
import org.apache.fineract.infrastructure.core.serialization.JsonParserHelper;
import org.apache.fineract.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
import org.apache.fineract.portfolio.loanaccount.api.LoanReAgingApiConstants;
import org.apache.fineract.portfolio.loanaccount.api.request.ReAgePreviewRequest;
@@ -113,6 +114,13 @@ public class LoanReAgingValidator {
}
private void validateReAgeBusinessRules(Loan loan) {
// validate reaging shouldn't happen before maturity
// on progressive loans it can
if (!loan.isProgressiveSchedule() && DateUtils.isBefore(getBusinessLocalDate(), loan.getMaturityDate())) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.reage.cannot.be.submitted.before.maturity",
"Loan cannot be re-aged before maturity", loan.getId());
}
// validate reaging is only available for progressive schedule & advanced payment allocation
LoanScheduleType loanScheduleType = LoanScheduleType.valueOf(loan.getLoanProductRelatedDetail().getLoanScheduleType().name());
boolean isProgressiveSchedule = LoanScheduleType.PROGRESSIVE.equals(loanScheduleType);
@@ -32,6 +32,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.imp
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.InterestPrincipalPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.PrincipalInterestPenaltyFeesOrderLoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.RBILoanRepaymentScheduleTransactionProcessor;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.ScheduledDateGenerator;
import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator;
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
import org.apache.fineract.portfolio.loanaccount.service.LoanChargeService;
@@ -137,8 +138,8 @@ public class LoanAccountAutoStarter {
final @Lazy ProgressiveLoanInterestRefundServiceImpl progressiveLoanInterestRefundService,
final ExternalIdFactory externalIdFactory, final LoanScheduleComponent loanSchedule,
final LoanChargeValidator loanChargeValidator, final LoanBalanceService loanBalanceService,
@Lazy final LoanChargeService loanChargeService) {
@Lazy final LoanChargeService loanChargeService, final ScheduledDateGenerator scheduledDateGenerator) {
return new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, progressiveLoanInterestRefundService, externalIdFactory,
loanSchedule, loanChargeValidator, loanBalanceService, loanChargeService);
loanSchedule, loanChargeValidator, loanBalanceService, loanChargeService, scheduledDateGenerator);
}
}