FINERACT-2333: Accrual handling in case of Charged-off loan
This commit is contained in:
committed by
Adam Saghy
parent
843b27926e
commit
f0cc697c67
+80
-3
@@ -21,6 +21,7 @@ package org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.im
|
||||
import static java.math.BigDecimal.ZERO;
|
||||
import static java.util.stream.Collectors.mapping;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction.accrueTransaction;
|
||||
import static org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum.CHARGEBACK;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.FEE;
|
||||
import static org.apache.fineract.portfolio.loanproduct.domain.AllocationType.INTEREST;
|
||||
@@ -60,6 +61,7 @@ import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.fineract.infrastructure.core.domain.AbstractPersistableCustom;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
@@ -81,6 +83,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparator;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor;
|
||||
@@ -109,9 +112,11 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY = "advanced-payment-allocation-strategy";
|
||||
public static final String ADVANCED_PAYMENT_ALLOCATION_STRATEGY_NAME = "Advanced payment allocation strategy";
|
||||
|
||||
public final EMICalculator emiCalculator;
|
||||
public final LoanRepositoryWrapper loanRepositoryWrapper;
|
||||
public final InterestRefundService interestRefundService;
|
||||
private final EMICalculator emiCalculator;
|
||||
private final LoanRepositoryWrapper loanRepositoryWrapper;
|
||||
private final InterestRefundService interestRefundService;
|
||||
private final LoanTransactionRepository loanTransactionRepository;
|
||||
private final ExternalIdFactory externalIdFactory;
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
@@ -1342,6 +1347,11 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
}
|
||||
}
|
||||
|
||||
final BigDecimal newInterest = getInterestTillChargeOffForPeriod(loanTransaction.getLoan(), loanTransaction.getTransactionDate(),
|
||||
transactionCtx);
|
||||
createMissingAccrualTransactionDuringChargeOffIfNeeded(newInterest, loanTransaction.getLoan(),
|
||||
loanTransaction.getTransactionDate());
|
||||
|
||||
loanTransaction.resetDerivedComponents();
|
||||
// determine how much is outstanding total and breakdown for principal, interest and charges
|
||||
Money principalPortion = Money.zero(transactionCtx.getCurrency());
|
||||
@@ -2267,4 +2277,71 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
||||
processSingleTransaction(processTransaction, transactionCtx);
|
||||
}
|
||||
}
|
||||
|
||||
private BigDecimal getInterestTillChargeOffForPeriod(final Loan loan, final LocalDate chargeOffDate,
|
||||
final TransactionCtx transactionCtx) {
|
||||
BigDecimal interestTillChargeOff = BigDecimal.ZERO;
|
||||
final MonetaryCurrency currency = loan.getCurrency();
|
||||
|
||||
final List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments().stream()
|
||||
.filter(i -> !i.isAdditional()).toList();
|
||||
|
||||
for (LoanRepaymentScheduleInstallment installment : installments) {
|
||||
final boolean isPastPeriod = !installment.getDueDate().isAfter(chargeOffDate);
|
||||
final boolean isInPeriod = !installment.getFromDate().isAfter(chargeOffDate) && installment.getDueDate().isAfter(chargeOffDate);
|
||||
|
||||
BigDecimal interest = BigDecimal.ZERO;
|
||||
|
||||
if (isPastPeriod) {
|
||||
interest = installment.getInterestCharged(currency).minus(installment.getCreditedInterest()).getAmount();
|
||||
} else if (isInPeriod) {
|
||||
if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx
|
||||
&& loan.isInterestBearingAndInterestRecalculationEnabled()) {
|
||||
interest = emiCalculator
|
||||
.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(), installment.getDueDate(), chargeOffDate, true)
|
||||
.getAmount();
|
||||
} else {
|
||||
final BigDecimal totalInterest = installment.getInterestOutstanding(currency).getAmount();
|
||||
if (LoanChargeOffBehaviour.ZERO_INTEREST.equals(loan.getLoanProductRelatedDetail().getChargeOffBehaviour())
|
||||
|| LoanChargeOffBehaviour.ACCELERATE_MATURITY
|
||||
.equals(loan.getLoanProductRelatedDetail().getChargeOffBehaviour())) {
|
||||
interest = totalInterest;
|
||||
} else {
|
||||
final long totalDaysInPeriod = ChronoUnit.DAYS.between(installment.getFromDate(), installment.getDueDate());
|
||||
final long daysTillChargeOff = ChronoUnit.DAYS.between(installment.getFromDate(), chargeOffDate);
|
||||
final MathContext mc = MoneyHelper.getMathContext();
|
||||
|
||||
interest = Money.of(currency, totalInterest.divide(BigDecimal.valueOf(totalDaysInPeriod), mc)
|
||||
.multiply(BigDecimal.valueOf(daysTillChargeOff), mc), mc).getAmount();
|
||||
}
|
||||
}
|
||||
}
|
||||
interestTillChargeOff = interestTillChargeOff.add(interest);
|
||||
}
|
||||
|
||||
return interestTillChargeOff;
|
||||
}
|
||||
|
||||
private void createMissingAccrualTransactionDuringChargeOffIfNeeded(final BigDecimal newInterest, final Loan loan,
|
||||
final LocalDate chargeOffDate) {
|
||||
final List<LoanRepaymentScheduleInstallment> relevantInstallments = loan.getRepaymentScheduleInstallments().stream()
|
||||
.filter(i -> !i.getFromDate().isAfter(chargeOffDate)).toList();
|
||||
|
||||
if (relevantInstallments.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final BigDecimal sumOfAccrualsTillChargeOff = loan.getLoanTransactions().stream()
|
||||
.filter(lt -> lt.isAccrual() && !lt.getTransactionDate().isAfter(chargeOffDate))
|
||||
.map(lt -> Optional.ofNullable(lt.getInterestPortion()).orElse(BigDecimal.ZERO)).reduce(BigDecimal.ZERO, BigDecimal::add);
|
||||
|
||||
final BigDecimal missingAccrualAmount = newInterest.subtract(sumOfAccrualsTillChargeOff);
|
||||
|
||||
if (missingAccrualAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
final LoanTransaction newAccrualTransaction = accrueTransaction(loan, loan.getOffice(), chargeOffDate, missingAccrualAmount,
|
||||
missingAccrualAmount, ZERO, ZERO, externalIdFactory.create());
|
||||
loan.addLoanTransaction(newAccrualTransaction);
|
||||
loanTransactionRepository.saveAndFlush(newAccrualTransaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ class AdvancedPaymentScheduleTransactionProcessorTest {
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
underTest = new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, null);
|
||||
underTest = new AdvancedPaymentScheduleTransactionProcessor(emiCalculator, loanRepositoryWrapper, null, null, null);
|
||||
|
||||
ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
|
||||
ThreadLocalContextUtil.setActionContext(ActionContext.DEFAULT);
|
||||
|
||||
Reference in New Issue
Block a user