FINERACT-2354: Re-aging:- Accrual and Accrual Activity handling - Default Handling
This commit is contained in:
committed by
Adam Saghy
parent
09031aa5a7
commit
fcf1fb6126
+64
-24
@@ -20,8 +20,10 @@ package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -44,7 +46,6 @@ import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
||||
@@ -108,18 +109,60 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recalculateAccrualActivityTransaction(Loan loan, ChangedTransactionDetail changedTransactionDetail) {
|
||||
List<LoanTransaction> accrualActivities = loan.getLoanTransactions().stream()
|
||||
public void recalculateAccrualActivityTransaction(final Loan loan, final ChangedTransactionDetail changedTransactionDetail) {
|
||||
if (!loan.getLoanProductRelatedDetail().isEnableAccrualActivityPosting()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<LoanTransaction> accrualActivities = loan.getLoanTransactions().stream()
|
||||
.filter(lt -> lt.isNotReversed() && lt.isAccrualActivity()).toList();
|
||||
accrualActivities.forEach(accrualActivity -> {
|
||||
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(accrualActivity);
|
||||
|
||||
calculateAccrualActivity(newLoanTransaction, loan.getCurrency(), loan.getRepaymentScheduleInstallments());
|
||||
if (!accrualActivities.isEmpty()) {
|
||||
final Map<LoanRepaymentScheduleInstallment, LoanTransaction> installmentsToAccrualActivities = new HashMap<>();
|
||||
|
||||
if (!LoanTransaction.transactionAmountsMatch(loan.getCurrency(), accrualActivity, newLoanTransaction)) {
|
||||
createNewTransaction(accrualActivity, newLoanTransaction, changedTransactionDetail);
|
||||
}
|
||||
});
|
||||
accrualActivities.forEach(accrualActivity -> {
|
||||
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(accrualActivity);
|
||||
final List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments().stream()
|
||||
.filter(i -> !i.isDownPayment()).toList();
|
||||
final LocalDate transactionDate = newLoanTransaction.getTransactionDate();
|
||||
|
||||
List<LoanRepaymentScheduleInstallment> targetInstallments = new ArrayList<>(installments.stream().filter(i -> i.getDueDate()
|
||||
.isEqual(transactionDate)
|
||||
|| (DateUtils.isEqual(i.getObligationsMetOnDate(), transactionDate) && i.getDueDate().isAfter(transactionDate)))
|
||||
.toList());
|
||||
|
||||
AtomicBoolean transactionShouldBeReplayed = new AtomicBoolean(false);
|
||||
if (targetInstallments.isEmpty()) {
|
||||
final Set<LocalDate> existingAccrualDates = accrualActivities.stream().map(LoanTransaction::getDateOf)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
final Optional<LocalDate> nearestDueDate = installments.stream().map(LoanRepaymentScheduleInstallment::getDueDate)
|
||||
.filter(dueDate -> !existingAccrualDates.contains(dueDate)).min(Comparator.naturalOrder());
|
||||
|
||||
if (nearestDueDate.isPresent()) {
|
||||
targetInstallments = installments.stream().filter(i -> i.getDueDate().equals(nearestDueDate.get()))
|
||||
.collect(Collectors.toList());
|
||||
transactionShouldBeReplayed.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
calculateAccrualActivity(newLoanTransaction, loan.getCurrency(), targetInstallments, transactionShouldBeReplayed);
|
||||
targetInstallments.forEach(installment -> installmentsToAccrualActivities.put(installment, newLoanTransaction));
|
||||
|
||||
if (!LoanTransaction.transactionAmountsMatch(loan.getCurrency(), accrualActivity, newLoanTransaction)
|
||||
|| transactionShouldBeReplayed.get()) {
|
||||
createNewTransaction(accrualActivity, newLoanTransaction, changedTransactionDetail);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
final List<LoanRepaymentScheduleInstallment> installmentsToCreateNewAccrualActivities = loan.getRepaymentScheduleInstallments(
|
||||
i -> !i.isDownPayment() && DateUtils.isAfter(DateUtils.getBusinessLocalDate(), i.getDueDate())
|
||||
&& !(installmentsToAccrualActivities.containsKey(i) && installmentsToAccrualActivities.get(i).isNotReversed()));
|
||||
|
||||
installmentsToCreateNewAccrualActivities
|
||||
.forEach(installment -> makeAccrualActivityTransaction(loan, installment, installment.getDueDate()));
|
||||
}
|
||||
}
|
||||
|
||||
protected void createNewTransaction(LoanTransaction loanTransaction, LoanTransaction newLoanTransaction,
|
||||
@@ -169,7 +212,7 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
|
||||
for (LoanRepaymentScheduleInstallment installment : loan.getRepaymentScheduleInstallments()) {
|
||||
if (!installment.isDownPayment() && !installment.isAdditional() && DateUtils.isBefore(installment.getDueDate(), closureDate)) {
|
||||
List<LoanTransaction> installmentAccruals = accrualActivities.stream()
|
||||
.filter(t -> t.getDateOf().isEqual(installment.getDueDate())).toList();
|
||||
.filter(t -> t.getDateOf().isEqual(installment.getDueDate()) && t.isNotReversed()).toList();
|
||||
|
||||
if (installmentAccruals.isEmpty()) {
|
||||
// No AAT for this installment; create one
|
||||
@@ -242,27 +285,18 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
|
||||
}
|
||||
|
||||
private void calculateAccrualActivity(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
||||
List<LoanRepaymentScheduleInstallment> installments) {
|
||||
|
||||
final int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
|
||||
|
||||
final List<LoanRepaymentScheduleInstallment> targetInstallments = installments.stream()
|
||||
.filter(installment -> LoanRepaymentScheduleProcessingWrapper.isInPeriod(loanTransaction.getTransactionDate(), installment,
|
||||
installment.getInstallmentNumber().equals(firstNormalInstallmentNumber))
|
||||
|| (DateUtils.isEqual(installment.getObligationsMetOnDate(), loanTransaction.getTransactionDate())
|
||||
&& installment.getDueDate().isAfter(loanTransaction.getTransactionDate())))
|
||||
.toList();
|
||||
|
||||
List<LoanRepaymentScheduleInstallment> targetInstallments, AtomicBoolean transactionShouldBeReplayed) {
|
||||
if (targetInstallments.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AtomicBoolean isReset = new AtomicBoolean(false);
|
||||
targetInstallments.forEach(currentInstallment -> {
|
||||
if (currentInstallment.isNotFullyPaidOff() && (currentInstallment.getDueDate().isAfter(loanTransaction.getTransactionDate())
|
||||
if (currentInstallment.isNotFullyPaidOff() && ((currentInstallment.getDueDate().isAfter(loanTransaction.getTransactionDate())
|
||||
&& !currentInstallment.getDueDate().isBefore(DateUtils.getBusinessLocalDate()))
|
||||
|| (currentInstallment.getDueDate().isEqual(loanTransaction.getTransactionDate())
|
||||
&& loanTransaction.getTransactionDate().equals(DateUtils.getBusinessLocalDate())))) {
|
||||
loanTransaction.reverse();
|
||||
transactionShouldBeReplayed.set(false);
|
||||
} else {
|
||||
if (!isReset.get()) {
|
||||
loanTransaction.resetDerivedComponents();
|
||||
@@ -278,12 +312,18 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
|
||||
if ((loan.isClosedObligationsMet() || loanBalanceService.isOverPaid(loan)) && currentInstallment.isObligationsMet()
|
||||
&& currentInstallment.isTransactionDateWithinPeriod(currentInstallment.getObligationsMetOnDate())) {
|
||||
loanTransaction.updateTransactionDate(currentInstallment.getObligationsMetOnDate());
|
||||
transactionShouldBeReplayed.set(false);
|
||||
} else {
|
||||
if (transactionShouldBeReplayed.get()) {
|
||||
loanTransaction.updateTransactionDate(currentInstallment.getDueDate());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
if (MathUtil.isZero(MathUtil.nullToZero(MathUtil.add(loanTransaction.getInterestPortion(), loanTransaction.getFeeChargesPortion(),
|
||||
loanTransaction.getPenaltyChargesPortion())))) {
|
||||
loanTransaction.reverse();
|
||||
transactionShouldBeReplayed.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -80,6 +80,8 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionComparato
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeInterestHandlingType;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter;
|
||||
import org.apache.fineract.portfolio.loanaccount.exception.LoanNotFoundException;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGenerator;
|
||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleGeneratorFactory;
|
||||
@@ -468,7 +470,11 @@ public class LoanAccrualsProcessingServiceImpl implements LoanAccrualsProcessing
|
||||
private void addInterestAccrual(@NonNull final Loan loan, @NonNull final LocalDate tillDate,
|
||||
final LoanScheduleGenerator scheduleGenerator, @NonNull final LoanRepaymentScheduleInstallment installment,
|
||||
@NonNull final AccrualPeriodsData accrualPeriods) {
|
||||
if (installment.isAdditional() || installment.isReAged()) {
|
||||
final LoanTransaction reAgeTransaction = loan.findReAgeTransaction();
|
||||
final LoanReAgeParameter loanReAgeParameter = reAgeTransaction != null ? reAgeTransaction.getLoanReAgeParameter() : null;
|
||||
|
||||
if (installment.isAdditional() || (installment.isReAged() && loanReAgeParameter != null
|
||||
&& !LoanReAgeInterestHandlingType.DEFAULT.equals(loanReAgeParameter.getInterestHandlingType()))) {
|
||||
return;
|
||||
}
|
||||
final AccrualPeriodData period = accrualPeriods.getPeriodByInstallmentNumber(installment.getInstallmentNumber());
|
||||
|
||||
Reference in New Issue
Block a user