Skip to content

Commit ef80634

Browse files
committed
FINERACT-2381: Fix Accrual Activity calculation
1 parent e063081 commit ef80634

File tree

5 files changed

+83
-56
lines changed

5 files changed

+83
-56
lines changed

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -223,63 +223,12 @@ public ChangedTransactionDetail reprocessLoanTransactions(final LocalDate disbur
223223
reprocessChargebackTransactionRelation(changedTransactionDetail, transactionsToBeProcessed);
224224
} else if (loanTransaction.isChargeOff()) {
225225
recalculateChargeOffTransaction(changedTransactionDetail, loanTransaction, currency, installments);
226-
} else if (loanTransaction.isAccrualActivity()) {
227-
recalculateAccrualActivityTransaction(changedTransactionDetail, loanTransaction, currency, installments);
228226
}
229227
}
230228
reprocessInstallments(disbursementDate, transactionsToBeProcessed, installments, currency);
231229
return changedTransactionDetail;
232230
}
233231

234-
protected void calculateAccrualActivity(LoanTransaction loanTransaction, MonetaryCurrency currency,
235-
List<LoanRepaymentScheduleInstallment> installments) {
236-
237-
final int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
238-
239-
final Optional<LoanRepaymentScheduleInstallment> currentInstallmentOpt = installments.stream()
240-
.filter(installment -> LoanRepaymentScheduleProcessingWrapper.isInPeriod(loanTransaction.getTransactionDate(), installment,
241-
installment.getInstallmentNumber().equals(firstNormalInstallmentNumber)))
242-
.findFirst();
243-
244-
if (currentInstallmentOpt.isEmpty()) {
245-
return;
246-
}
247-
248-
final LoanRepaymentScheduleInstallment currentInstallment = currentInstallmentOpt.get();
249-
if (currentInstallment.isNotFullyPaidOff() && (currentInstallment.getDueDate().isAfter(loanTransaction.getTransactionDate())
250-
|| (currentInstallment.getDueDate().isEqual(loanTransaction.getTransactionDate())
251-
&& loanTransaction.getTransactionDate().equals(DateUtils.getBusinessLocalDate())))) {
252-
loanTransaction.reverse();
253-
} else {
254-
loanTransaction.resetDerivedComponents();
255-
final Money principalPortion = Money.zero(currency);
256-
Money interestPortion = currentInstallment.getInterestCharged(currency);
257-
Money feeChargesPortion = currentInstallment.getFeeChargesCharged(currency);
258-
Money penaltyChargesPortion = currentInstallment.getPenaltyChargesCharged(currency);
259-
if (interestPortion.plus(feeChargesPortion).plus(penaltyChargesPortion).isZero()) {
260-
loanTransaction.reverse();
261-
} else {
262-
loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
263-
final Loan loan = loanTransaction.getLoan();
264-
if ((loan.isClosedObligationsMet() || loanBalanceService.isOverPaid(loan)) && currentInstallment.isObligationsMet()
265-
&& currentInstallment.isTransactionDateWithinPeriod(currentInstallment.getObligationsMetOnDate())) {
266-
loanTransaction.updateTransactionDate(currentInstallment.getObligationsMetOnDate());
267-
}
268-
}
269-
}
270-
}
271-
272-
private void recalculateAccrualActivityTransaction(ChangedTransactionDetail changedTransactionDetail, LoanTransaction loanTransaction,
273-
MonetaryCurrency currency, List<LoanRepaymentScheduleInstallment> installments) {
274-
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(loanTransaction);
275-
276-
calculateAccrualActivity(newLoanTransaction, currency, installments);
277-
278-
if (!LoanTransaction.transactionAmountsMatch(currency, loanTransaction, newLoanTransaction)) {
279-
createNewTransaction(loanTransaction, newLoanTransaction, changedTransactionDetail);
280-
}
281-
}
282-
283232
@Override
284233
public ChangedTransactionDetail processLatestTransaction(final LoanTransaction loanTransaction, final TransactionCtx ctx) {
285234
switch (loanTransaction.getTypeOf()) {

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package org.apache.fineract.portfolio.loanaccount.service;
2020

2121
import java.time.LocalDate;
22+
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
2223
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
2324
import org.springframework.lang.NonNull;
2425
import org.springframework.transaction.annotation.Transactional;
@@ -30,6 +31,8 @@ public interface LoanAccrualActivityProcessingService {
3031

3132
void makeAccrualActivityTransaction(@NonNull Loan loan, @NonNull LocalDate currentDate);
3233

34+
void recalculateAccrualActivityTransaction(Loan loan, ChangedTransactionDetail changedTransactionDetail);
35+
3336
@Transactional
3437
void processAccrualActivityForLoanClosure(@NonNull Loan loan);
3538

fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -412,7 +412,6 @@ public ChangedTransactionDetail processLatestTransaction(LoanTransaction loanTra
412412
case WAIVE_CHARGES -> log.debug("WAIVE_CHARGES transaction will not be processed.");
413413
case REAMORTIZE -> handleReAmortization(loanTransaction, ctx);
414414
case REAGE -> handleReAge(loanTransaction, ctx);
415-
case ACCRUAL_ACTIVITY -> calculateAccrualActivity(loanTransaction, ctx);
416415
case CAPITALIZED_INCOME -> handleCapitalizedIncome(loanTransaction, ctx);
417416
case CONTRACT_TERMINATION -> handleContractTermination(loanTransaction, ctx);
418417
// TODO: Cover rest of the transaction types
@@ -2903,10 +2902,6 @@ private void handleReAge(LoanTransaction loanTransaction, TransactionCtx ctx) {
29032902
reprocessInstallments(installments);
29042903
}
29052904

2906-
protected void calculateAccrualActivity(LoanTransaction transaction, TransactionCtx ctx) {
2907-
super.calculateAccrualActivity(transaction, ctx.getCurrency(), ctx.getInstallments());
2908-
}
2909-
29102905
private void reprocessInstallments(final List<LoanRepaymentScheduleInstallment> installments) {
29112906
final AtomicInteger counter = new AtomicInteger(1);
29122907
final AtomicReference<LocalDate> previousDueDate = new AtomicReference<>(null);

fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.Map;
2727
import java.util.Optional;
2828
import java.util.Set;
29+
import java.util.concurrent.atomic.AtomicBoolean;
2930
import java.util.stream.Collectors;
3031
import lombok.RequiredArgsConstructor;
3132
import lombok.extern.slf4j.Slf4j;
@@ -36,9 +37,14 @@
3637
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPostBusinessEvent;
3738
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionAccrualActivityPreBusinessEvent;
3839
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
40+
import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
41+
import org.apache.fineract.organisation.monetary.domain.Money;
42+
import org.apache.fineract.portfolio.loanaccount.data.TransactionChangeData;
43+
import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail;
3944
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
4045
import org.apache.fineract.portfolio.loanaccount.domain.LoanAccountService;
4146
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
47+
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleProcessingWrapper;
4248
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
4349
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
4450
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
@@ -101,6 +107,32 @@ public void makeAccrualActivityTransaction(final @NonNull Loan loan, final @NonN
101107
});
102108
}
103109

110+
@Override
111+
public void recalculateAccrualActivityTransaction(Loan loan, ChangedTransactionDetail changedTransactionDetail) {
112+
List<LoanTransaction> accrualActivities = loanTransactionRepository.findNonReversedByLoanAndType(loan,
113+
LoanTransactionType.ACCRUAL_ACTIVITY);
114+
accrualActivities.forEach(accrualActivity -> {
115+
final LoanTransaction newLoanTransaction = LoanTransaction.copyTransactionProperties(accrualActivity);
116+
117+
calculateAccrualActivity(newLoanTransaction, loan.getCurrency(), loan.getRepaymentScheduleInstallments());
118+
119+
if (!LoanTransaction.transactionAmountsMatch(loan.getCurrency(), accrualActivity, newLoanTransaction)) {
120+
createNewTransaction(accrualActivity, newLoanTransaction, changedTransactionDetail);
121+
}
122+
});
123+
}
124+
125+
protected void createNewTransaction(LoanTransaction loanTransaction, LoanTransaction newLoanTransaction,
126+
ChangedTransactionDetail changedTransactionDetail) {
127+
loanTransaction.reverse();
128+
loanTransaction.updateExternalId(null);
129+
newLoanTransaction.copyLoanTransactionRelations(loanTransaction.getLoanTransactionRelations());
130+
// Adding Replayed relation from newly created transaction to reversed transaction
131+
newLoanTransaction.getLoanTransactionRelations().add(
132+
LoanTransactionRelation.linkToTransaction(newLoanTransaction, loanTransaction, LoanTransactionRelationTypeEnum.REPLAYED));
133+
changedTransactionDetail.addTransactionChange(new TransactionChangeData(loanTransaction, newLoanTransaction));
134+
}
135+
104136
@Override
105137
@Transactional
106138
public void processAccrualActivityForLoanClosure(final @NonNull Loan loan) {
@@ -206,6 +238,52 @@ public void processAccrualActivityForLoanReopen(final @NonNull Loan loan) {
206238
}
207239
}
208240

241+
private void calculateAccrualActivity(LoanTransaction loanTransaction, MonetaryCurrency currency,
242+
List<LoanRepaymentScheduleInstallment> installments) {
243+
244+
final int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
245+
246+
final List<LoanRepaymentScheduleInstallment> targetInstallments = installments.stream()
247+
.filter(installment -> LoanRepaymentScheduleProcessingWrapper.isInPeriod(loanTransaction.getTransactionDate(), installment,
248+
installment.getInstallmentNumber().equals(firstNormalInstallmentNumber))
249+
|| (DateUtils.isEqual(installment.getObligationsMetOnDate(), loanTransaction.getTransactionDate())
250+
&& installment.getDueDate().isAfter(loanTransaction.getTransactionDate())))
251+
.toList();
252+
253+
if (targetInstallments.isEmpty()) {
254+
return;
255+
}
256+
257+
AtomicBoolean isReset = new AtomicBoolean(false);
258+
targetInstallments.forEach(currentInstallment -> {
259+
if (currentInstallment.isNotFullyPaidOff() && (currentInstallment.getDueDate().isAfter(loanTransaction.getTransactionDate())
260+
|| (currentInstallment.getDueDate().isEqual(loanTransaction.getTransactionDate())
261+
&& loanTransaction.getTransactionDate().equals(DateUtils.getBusinessLocalDate())))) {
262+
loanTransaction.reverse();
263+
} else {
264+
if (!isReset.get()) {
265+
loanTransaction.resetDerivedComponents();
266+
isReset.set(true);
267+
}
268+
final Money principalPortion = Money.zero(currency);
269+
Money interestPortion = currentInstallment.getInterestCharged(currency);
270+
Money feeChargesPortion = currentInstallment.getFeeChargesCharged(currency);
271+
Money penaltyChargesPortion = currentInstallment.getPenaltyChargesCharged(currency);
272+
273+
loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltyChargesPortion);
274+
final Loan loan = loanTransaction.getLoan();
275+
if ((loan.isClosedObligationsMet() || loanBalanceService.isOverPaid(loan)) && currentInstallment.isObligationsMet()
276+
&& currentInstallment.isTransactionDateWithinPeriod(currentInstallment.getObligationsMetOnDate())) {
277+
loanTransaction.updateTransactionDate(currentInstallment.getObligationsMetOnDate());
278+
}
279+
}
280+
});
281+
if (MathUtil.isZero(MathUtil.nullToZero(MathUtil.add(loanTransaction.getInterestPortion(), loanTransaction.getFeeChargesPortion(),
282+
loanTransaction.getPenaltyChargesPortion())))) {
283+
loanTransaction.reverse();
284+
}
285+
}
286+
209287
private Map<LocalDate, List<LoanTransaction>> loadExistingAccrualActivitiesByDate(final @NonNull Loan loan,
210288
final List<LoanRepaymentScheduleInstallment> installments) {
211289
final Set<LocalDate> dueDates = installments.stream().map(LoanRepaymentScheduleInstallment::getDueDate).collect(Collectors.toSet());

fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa
6262
private final LoanTransactionService loanTransactionService;
6363
private final LoanJournalEntryPoster loanJournalEntryPoster;
6464
private final BusinessEventNotifierService businessEventNotifierService;
65+
private final LoanAccrualActivityProcessingService loanAccrualActivityProcessingService;
6566

6667
@Override
6768
public void reprocessTransactions(final Loan loan) {
@@ -227,6 +228,7 @@ private ChangedTransactionDetail reprocessTransactionsAndFetchChangedTransaction
227228
.map(TransactionChangeData::getNewTransaction).toList();
228229
loan.getLoanTransactions().addAll(newTransactions);
229230
loanBalanceService.updateLoanSummaryDerivedFields(loan);
231+
loanAccrualActivityProcessingService.recalculateAccrualActivityTransaction(loan, changedTransactionDetail);
230232
return changedTransactionDetail;
231233
}
232234
}

0 commit comments

Comments
 (0)