FINERACT-2381: Fix Accrual Activity calculation
This commit is contained in:
-51
@@ -223,63 +223,12 @@ public abstract class AbstractLoanRepaymentScheduleTransactionProcessor implemen
|
|||||||
reprocessChargebackTransactionRelation(changedTransactionDetail, transactionsToBeProcessed);
|
reprocessChargebackTransactionRelation(changedTransactionDetail, transactionsToBeProcessed);
|
||||||
} else if (loanTransaction.isChargeOff()) {
|
} else if (loanTransaction.isChargeOff()) {
|
||||||
recalculateChargeOffTransaction(changedTransactionDetail, loanTransaction, currency, installments);
|
recalculateChargeOffTransaction(changedTransactionDetail, loanTransaction, currency, installments);
|
||||||
} else if (loanTransaction.isAccrualActivity()) {
|
|
||||||
recalculateAccrualActivityTransaction(changedTransactionDetail, loanTransaction, currency, installments);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reprocessInstallments(disbursementDate, transactionsToBeProcessed, installments, currency);
|
reprocessInstallments(disbursementDate, transactionsToBeProcessed, installments, currency);
|
||||||
return changedTransactionDetail;
|
return changedTransactionDetail;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void calculateAccrualActivity(LoanTransaction loanTransaction, MonetaryCurrency currency,
|
|
||||||
List<LoanRepaymentScheduleInstallment> installments) {
|
|
||||||
|
|
||||||
final int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
|
|
||||||
|
|
||||||
final Optional<LoanRepaymentScheduleInstallment> currentInstallmentOpt = installments.stream()
|
|
||||||
.filter(installment -> LoanRepaymentScheduleProcessingWrapper.isInPeriod(loanTransaction.getTransactionDate(), installment,
|
|
||||||
installment.getInstallmentNumber().equals(firstNormalInstallmentNumber)))
|
|
||||||
.findFirst();
|
|
||||||
|
|
||||||
if (currentInstallmentOpt.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final LoanRepaymentScheduleInstallment currentInstallment = currentInstallmentOpt.get();
|
|
||||||
if (currentInstallment.isNotFullyPaidOff() && (currentInstallment.getDueDate().isAfter(loanTransaction.getTransactionDate())
|
|
||||||
|| (currentInstallment.getDueDate().isEqual(loanTransaction.getTransactionDate())
|
|
||||||
&& loanTransaction.getTransactionDate().equals(DateUtils.getBusinessLocalDate())))) {
|
|
||||||
loanTransaction.reverse();
|
|
||||||
} else {
|
|
||||||
loanTransaction.resetDerivedComponents();
|
|
||||||
final Money principalPortion = Money.zero(currency);
|
|
||||||
Money interestPortion = currentInstallment.getInterestCharged(currency);
|
|
||||||
Money feeChargesPortion = currentInstallment.getFeeChargesCharged(currency);
|
|
||||||
Money penaltyChargesPortion = currentInstallment.getPenaltyChargesCharged(currency);
|
|
||||||
if (interestPortion.plus(feeChargesPortion).plus(penaltyChargesPortion).isZero()) {
|
|
||||||
loanTransaction.reverse();
|
|
||||||
} else {
|
|
||||||
loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
|
||||||
final Loan loan = loanTransaction.getLoan();
|
|
||||||
if ((loan.isClosedObligationsMet() || loanBalanceService.isOverPaid(loan)) && currentInstallment.isObligationsMet()
|
|
||||||
&& currentInstallment.isTransactionDateWithinPeriod(currentInstallment.getObligationsMetOnDate())) {
|
|
||||||
loanTransaction.updateTransactionDate(currentInstallment.getObligationsMetOnDate());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void recalculateAccrualActivityTransaction(ChangedTransactionDetail changedTransactionDetail, LoanTransaction loanTransaction,
|
|
||||||
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments) {
|
|
||||||
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction);
|
|
||||||
|
|
||||||
calculateAccrualActivity(newLoanTransaction, currency, installments);
|
|
||||||
|
|
||||||
if (!LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) {
|
|
||||||
createNewTransaction(loanTransaction, newLoanTransaction, changedTransactionDetail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ChangedTransactionDetail processLatestTransaction(final LoanTransaction loanTransaction, final TransactionCtx ctx) {
|
public ChangedTransactionDetail processLatestTransaction(final LoanTransaction loanTransaction, final TransactionCtx ctx) {
|
||||||
switch (loanTransaction.getTypeOf()) {
|
switch (loanTransaction.getTypeOf()) {
|
||||||
|
|||||||
+3
@@ -19,6 +19,7 @@
|
|||||||
package org.apache.fineract.portfolio.loanaccount.service;
|
package org.apache.fineract.portfolio.loanaccount.service;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@@ -30,6 +31,8 @@ public interface LoanAccrualActivityProcessingService {
|
|||||||
|
|
||||||
void makeAccrualActivityTransaction(@NonNull Loan loan, @NonNull LocalDate currentDate);
|
void makeAccrualActivityTransaction(@NonNull Loan loan, @NonNull LocalDate currentDate);
|
||||||
|
|
||||||
|
void recalculateAccrualActivityTransaction(Loan loan, ChangedTransactionDetail changedTransactionDetail);
|
||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
void processAccrualActivityForLoanClosure(@NonNull Loan loan);
|
void processAccrualActivityForLoanClosure(@NonNull Loan loan);
|
||||||
|
|
||||||
|
|||||||
-5
@@ -412,7 +412,6 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
case WAIVE_CHARGES -> log.debug("WAIVE_CHARGES transaction will not be processed.");
|
case WAIVE_CHARGES -> log.debug("WAIVE_CHARGES transaction will not be processed.");
|
||||||
case REAMORTIZE -> handleReAmortization(loanTransaction, ctx);
|
case REAMORTIZE -> handleReAmortization(loanTransaction, ctx);
|
||||||
case REAGE -> handleReAge(loanTransaction, ctx);
|
case REAGE -> handleReAge(loanTransaction, ctx);
|
||||||
case ACCRUAL_ACTIVITY -> calculateAccrualActivity(loanTransaction, ctx);
|
|
||||||
case CAPITALIZED_INCOME -> handleCapitalizedIncome(loanTransaction, ctx);
|
case CAPITALIZED_INCOME -> handleCapitalizedIncome(loanTransaction, ctx);
|
||||||
case CONTRACT_TERMINATION -> handleContractTermination(loanTransaction, ctx);
|
case CONTRACT_TERMINATION -> handleContractTermination(loanTransaction, ctx);
|
||||||
// TODO: Cover rest of the transaction types
|
// TODO: Cover rest of the transaction types
|
||||||
@@ -2903,10 +2902,6 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
reprocessInstallments(installments);
|
reprocessInstallments(installments);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void calculateAccrualActivity(LoanTransaction transaction, TransactionCtx ctx) {
|
|
||||||
super.calculateAccrualActivity(transaction, ctx.getCurrency(), ctx.getInstallments());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reprocessInstallments(final List<LoanRepaymentScheduleInstallment> installments) {
|
private void reprocessInstallments(final List<LoanRepaymentScheduleInstallment> installments) {
|
||||||
final AtomicInteger counter = new AtomicInteger(1);
|
final AtomicInteger counter = new AtomicInteger(1);
|
||||||
final AtomicReference<LocalDate> previousDueDate = new AtomicReference<>(null);
|
final AtomicReference<LocalDate> previousDueDate = new AtomicReference<>(null);
|
||||||
|
|||||||
+78
@@ -26,6 +26,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -36,9 +37,14 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustT
|
|||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPostBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPostBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPreBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPreBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
|
||||||
|
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||||
|
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.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
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.LoanRepositoryWrapper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
||||||
@@ -101,6 +107,32 @@ public class LoanAccrualActivityProcessingServiceImpl implements LoanAccrualActi
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recalculateAccrualActivityTransaction(Loan loan, ChangedTransactionDetail changedTransactionDetail) {
|
||||||
|
List<LoanTransaction> accrualActivities = loanTransactionRepository.findNonReversedByLoanAndType(loan,
|
||||||
|
LoanTransactionType.ACCRUAL_ACTIVITY);
|
||||||
|
accrualActivities.forEach(accrualActivity -> {
|
||||||
|
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(accrualActivity);
|
||||||
|
|
||||||
|
calculateAccrualActivity(newLoanTransaction, loan.getCurrency(), loan.getRepaymentScheduleInstallments());
|
||||||
|
|
||||||
|
if (!LoanTransaction.transactionAmountsMatch(loan.getCurrency(), accrualActivity, newLoanTransaction)) {
|
||||||
|
createNewTransaction(accrualActivity, newLoanTransaction, changedTransactionDetail);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void createNewTransaction(LoanTransaction loanTransaction, LoanTransaction newLoanTransaction,
|
||||||
|
ChangedTransactionDetail changedTransactionDetail) {
|
||||||
|
loanTransaction.reverse();
|
||||||
|
loanTransaction.updateExternalId(null);
|
||||||
|
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
|
||||||
|
// Adding Replayed relation from newly created transaction to reversed transaction
|
||||||
|
newLoanTransaction.getLoanTransactionRelations().add(
|
||||||
|
LoanTransactionRelation.linkToTransaction(newLoanTransaction, loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
|
||||||
|
changedTransactionDetail.addTransactionChange(new TransactionChangeData(loanTransaction, newLoanTransaction));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void processAccrualActivityForLoanClosure(final @NonNull Loan loan) {
|
public void processAccrualActivityForLoanClosure(final @NonNull Loan loan) {
|
||||||
@@ -206,6 +238,52 @@ 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();
|
||||||
|
|
||||||
|
if (targetInstallments.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AtomicBoolean isReset = new AtomicBoolean(false);
|
||||||
|
targetInstallments.forEach(currentInstallment -> {
|
||||||
|
if (currentInstallment.isNotFullyPaidOff() && (currentInstallment.getDueDate().isAfter(loanTransaction.getTransactionDate())
|
||||||
|
|| (currentInstallment.getDueDate().isEqual(loanTransaction.getTransactionDate())
|
||||||
|
&& loanTransaction.getTransactionDate().equals(DateUtils.getBusinessLocalDate())))) {
|
||||||
|
loanTransaction.reverse();
|
||||||
|
} else {
|
||||||
|
if (!isReset.get()) {
|
||||||
|
loanTransaction.resetDerivedComponents();
|
||||||
|
isReset.set(true);
|
||||||
|
}
|
||||||
|
final Money principalPortion = Money.zero(currency);
|
||||||
|
Money interestPortion = currentInstallment.getInterestCharged(currency);
|
||||||
|
Money feeChargesPortion = currentInstallment.getFeeChargesCharged(currency);
|
||||||
|
Money penaltyChargesPortion = currentInstallment.getPenaltyChargesCharged(currency);
|
||||||
|
|
||||||
|
loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
|
||||||
|
final Loan loan = loanTransaction.getLoan();
|
||||||
|
if ((loan.isClosedObligationsMet() || loanBalanceService.isOverPaid(loan)) && currentInstallment.isObligationsMet()
|
||||||
|
&& currentInstallment.isTransactionDateWithinPeriod(currentInstallment.getObligationsMetOnDate())) {
|
||||||
|
loanTransaction.updateTransactionDate(currentInstallment.getObligationsMetOnDate());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (MathUtil.isZero(MathUtil.nullToZero(MathUtil.add(loanTransaction.getInterestPortion(), loanTransaction.getFeeChargesPortion(),
|
||||||
|
loanTransaction.getPenaltyChargesPortion())))) {
|
||||||
|
loanTransaction.reverse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Map<LocalDate, List<LoanTransaction>> loadExistingAccrualActivitiesByDate(final @NonNull Loan loan,
|
private Map<LocalDate, List<LoanTransaction>> loadExistingAccrualActivitiesByDate(final @NonNull Loan loan,
|
||||||
final List<LoanRepaymentScheduleInstallment> installments) {
|
final List<LoanRepaymentScheduleInstallment> installments) {
|
||||||
final Set<LocalDate> dueDates = installments.stream().map(LoanRepaymentScheduleInstallment::getDueDate).collect(Collectors.toSet());
|
final Set<LocalDate> dueDates = installments.stream().map(LoanRepaymentScheduleInstallment::getDueDate).collect(Collectors.toSet());
|
||||||
|
|||||||
+2
@@ -62,6 +62,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
private final LoanTransactionService loanTransactionService;
|
private final LoanTransactionService loanTransactionService;
|
||||||
private final LoanJournalEntryPoster loanJournalEntryPoster;
|
private final LoanJournalEntryPoster loanJournalEntryPoster;
|
||||||
private final BusinessEventNotifierService businessEventNotifierService;
|
private final BusinessEventNotifierService businessEventNotifierService;
|
||||||
|
private final LoanAccrualActivityProcessingService loanAccrualActivityProcessingService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reprocessTransactions(final Loan loan) {
|
public void reprocessTransactions(final Loan loan) {
|
||||||
@@ -227,6 +228,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
|
|||||||
.map(TransactionChangeData::getNewTransaction).toList();
|
.map(TransactionChangeData::getNewTransaction).toList();
|
||||||
loan.getLoanTransactions().addAll(newTransactions);
|
loan.getLoanTransactions().addAll(newTransactions);
|
||||||
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
loanBalanceService.updateLoanSummaryDerivedFields(loan);
|
||||||
|
loanAccrualActivityProcessingService.recalculateAccrualActivityTransaction(loan, changedTransactionDetail);
|
||||||
return changedTransactionDetail;
|
return changedTransactionDetail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user