This is an automated email from the ASF dual-hosted git repository. adamsaghy pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract.git
commit fcf1fb61261bb3c54a751e16af3ce081e0300c77 Author: mariiaKraievska <[email protected]> AuthorDate: Thu Dec 18 12:54:52 2025 +0200 FINERACT-2354: Re-aging:- Accrual and Accrual Activity handling - Default Handling --- .../test/resources/features/LoanReAging.feature | 24 +++--- .../test/resources/features/LoanRepayment.feature | 1 + .../portfolio/loanaccount/domain/Loan.java | 4 + .../domain/ProgressiveLoanScheduleGenerator.java | 9 ++- .../LoanAccrualActivityProcessingServiceImpl.java | 88 ++++++++++++++++------ .../service/LoanAccrualsProcessingServiceImpl.java | 8 +- 6 files changed, 96 insertions(+), 38 deletions(-) diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature index 08811d80f2..1b429ea50f 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature @@ -4175,7 +4175,7 @@ Then Loan Repayment schedule has 4 periods, with the following data for periods: | 01 February 2024 | Chargeback | 17.01 | 16.43 | 0.58 | 0.0 | 0.0 | 100.0 | false | false | | 15 March 2024 | Re-age | 101.42 | 100.0 | 1.42 | 0.0 | 0.0 | 0.0 | false | false | | 15 March 2024 | Repayment | 101.42 | 100.0 | 1.42 | 0.0 | 0.0 | 0.0 | false | false | - | 15 March 2024 | Accrual | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Accrual | 2.0 | 0.0 | 2.0 | 0.0 | 0.0 | 0.0 | false | false | @TestRailId:C4135 @AdvancedPaymentAllocation Scenario: Verify allowing Re-aging on interest bearing loan - Interest calculation: Default Behavior - Charge-back before re-aging and installment is partially paid - UC3.1 @@ -4274,7 +4274,7 @@ Then Loan Repayment schedule has 4 periods, with the following data for periods: | 01 March 2024 | Repayment | 10.0 | 10.0 | 0.0 | 0.0 | 0.0 | 90.0 | false | false | | 15 March 2024 | Re-age | 91.4 | 90.0 | 1.4 | 0.0 | 0.0 | 0.0 | false | false | | 15 March 2024 | Repayment | 91.4 | 90.0 | 1.4 | 0.0 | 0.0 | 0.0 | false | false | - | 15 March 2024 | Accrual | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Accrual | 1.98 | 0.0 | 1.98 | 0.0 | 0.0 | 0.0 | false | false | @TestRailId:C4356 @AdvancedPaymentAllocation Scenario: Verify Re-aging on interest bearing loan - Interest calculation: Default Behavior - with NEXT_INSTALLMENT allocation rule, backdated re-aging on 1st installment after repay and chargeback on 1st due - UC3.2 @@ -4603,7 +4603,7 @@ Then Loan Repayment schedule has 4 periods, with the following data for periods: | 01 February 2024 | Accrual Activity | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | | 15 March 2024 | Re-age | 76.22 | 75.58 | 0.64 | 0.0 | 0.0 | 0.0 | false | false | | 15 March 2024 | Repayment | 76.22 | 75.58 | 0.64 | 0.0 | 0.0 | 0.0 | false | false | - | 15 March 2024 | Accrual | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Accrual | 1.22 | 0.0 | 1.22 | 0.0 | 0.0 | 0.0 | false | false | | 15 March 2024 | Accrual Activity | 0.64 | 0.0 | 0.64 | 0.0 | 0.0 | 0.0 | false | false | When Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule @@ -4827,7 +4827,7 @@ Then Loan Repayment schedule has 4 periods, with the following data for periods: | 01 March 2024 | Accrual Activity | 0.47 | 0.0 | 0.47 | 0.0 | 0.0 | 0.0 | false | false | | 15 March 2024 | Re-age | 59.21 | 59.05 | 0.16 | 0.0 | 0.0 | 0.0 | false | false | | 15 March 2024 | Repayment | 59.21 | 59.05 | 0.16 | 0.0 | 0.0 | 0.0 | false | false | - | 15 March 2024 | Accrual | 1.05 | 0.0 | 1.05 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Accrual | 1.21 | 0.0 | 1.21 | 0.0 | 0.0 | 0.0 | false | false | | 15 March 2024 | Accrual Activity | 0.16 | 0.0 | 0.16 | 0.0 | 0.0 | 0.0 | false | false | When Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule @@ -4927,7 +4927,7 @@ Then Loan Repayment schedule has 4 periods, with the following data for periods: | 01 March 2024 | Accrual Activity | 0.49 | 0.0 | 0.49 | 0.0 | 0.0 | 0.0 | false | false | | 01 April 2024 | Accrual Activity | 0.39 | 0.0 | 0.39 | 0.0 | 0.0 | 0.0 | false | false | | 01 May 2024 | Repayment | 67.83 | 67.05 | 0.78 | 0.0 | 0.0 | 0.0 | false | false | - | 01 May 2024 | Accrual | 1.07 | 0.0 | 1.07 | 0.0 | 0.0 | 0.0 | false | false | + | 01 May 2024 | Accrual | 1.85 | 0.0 | 1.85 | 0.0 | 0.0 | 0.0 | false | false | | 01 May 2024 | Accrual Activity | 0.39 | 0.0 | 0.39 | 0.0 | 0.0 | 0.0 | false | false | @TestRailId:C4233 @@ -5020,7 +5020,7 @@ Then Loan Repayment schedule has 4 periods, with the following data for periods: | 01 March 2024 | Re-age | 76.02 | 75.58 | 0.44 | 0.0 | 0.0 | 0.0 | false | false | | 01 March 2024 | Accrual Activity | 0.44 | 0.0 | 0.44 | 0.0 | 0.0 | 0.0 | false | false | | 15 March 2024 | Repayment | 76.22 | 75.58 | 0.64 | 0.0 | 0.0 | 0.0 | false | false | - | 15 March 2024 | Accrual | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Accrual | 1.22 | 0.0 | 1.22 | 0.0 | 0.0 | 0.0 | false | false | | 15 March 2024 | Accrual Activity | 0.2 | 0.0 | 0.2 | 0.0 | 0.0 | 0.0 | false | false | @TestRailId:C4085 @AdvancedPaymentAllocation @@ -6498,7 +6498,7 @@ Then Loan Repayment schedule has 4 periods, with the following data for periods: | 01 February 2024 | Re-age | 83.57 | 83.57 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | | 01 March 2024 | Repayment | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | 66.56 | false | true | | 01 June 2024 | Repayment | 68.22 | 66.56 | 1.66 | 0.0 | 0.0 | 0.0 | false | false | - | 01 June 2024 | Accrual | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 01 June 2024 | Accrual | 2.24 | 0.0 | 2.24 | 0.0 | 0.0 | 0.0 | false | false | @TestRailId:С4268 @AdvancedPaymentAllocation Scenario: Verify allowing Re-aging on interest bearing loan - Interest calculation: Default Behavior - with LAST_INSTALLMENT allocation rule and partial repayment, due date and frequency changed - UC5.1 @@ -6591,7 +6591,7 @@ Then Loan Repayment schedule has 4 periods, with the following data for periods: | 01 February 2024 | Accrual Activity | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | | 15 March 2024 | Re-age | 76.22 | 75.58 | 0.64 | 0.0 | 0.0 | 0.0 | false | false | | 15 March 2024 | Repayment | 76.22 | 75.58 | 0.64 | 0.0 | 0.0 | 0.0 | false | false | - | 15 March 2024 | Accrual | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Accrual | 1.22 | 0.0 | 1.22 | 0.0 | 0.0 | 0.0 | false | false | | 15 March 2024 | Accrual Activity | 0.64 | 0.0 | 0.64 | 0.0 | 0.0 | 0.0 | false | false | When Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule @@ -6697,7 +6697,7 @@ Then Loan Repayment schedule has 4 periods, with the following data for periods: | 13 April 2024 | Accrual Activity | 0.18 | 0.0 | 0.18 | 0.0 | 0.0 | 0.0 | false | false | | 27 April 2024 | Accrual Activity | 0.18 | 0.0 | 0.18 | 0.0 | 0.0 | 0.0 | false | false | | 01 May 2024 | Repayment | 67.83 | 67.05 | 0.78 | 0.0 | 0.0 | 0.0 | false | false | - | 01 May 2024 | Accrual | 1.07 | 0.0 | 1.07 | 0.0 | 0.0 | 0.0 | false | false | + | 01 May 2024 | Accrual | 1.85 | 0.0 | 1.85 | 0.0 | 0.0 | 0.0 | false | false | | 01 May 2024 | Accrual Activity | 0.05 | 0.0 | 0.05 | 0.0 | 0.0 | 0.0 | false | false | @TestRailId:С4270 @AdvancedPaymentAllocation @@ -6790,7 +6790,7 @@ Then Loan Repayment schedule has 4 periods, with the following data for periods: | 01 March 2024 | Re-age | 76.02 | 75.58 | 0.44 | 0.0 | 0.0 | 0.0 | false | false | | 01 March 2024 | Accrual Activity | 0.44 | 0.0 | 0.44 | 0.0 | 0.0 | 0.0 | false | false | | 15 March 2024 | Repayment | 76.22 | 75.58 | 0.64 | 0.0 | 0.0 | 0.0 | false | false | - | 15 March 2024 | Accrual | 0.58 | 0.0 | 0.58 | 0.0 | 0.0 | 0.0 | false | false | + | 15 March 2024 | Accrual | 1.22 | 0.0 | 1.22 | 0.0 | 0.0 | 0.0 | false | false | | 15 March 2024 | Accrual Activity | 0.2 | 0.0 | 0.2 | 0.0 | 0.0 | 0.0 | false | false | @TestRailId:С4271 @AdvancedPaymentAllocation @@ -6895,7 +6895,7 @@ Then Loan Repayment schedule has 4 periods, with the following data for periods: | 16 April 2024 | Accrual Activity | 0.18 | 0.0 | 0.18 | 0.0 | 0.0 | 0.0 | false | false | | 30 April 2024 | Accrual Activity | 0.18 | 0.0 | 0.18 | 0.0 | 0.0 | 0.0 | false | false | | 01 May 2024 | Repayment | 67.82 | 67.05 | 0.77 | 0.0 | 0.0 | 0.0 | false | false | - | 01 May 2024 | Accrual | 1.07 | 0.0 | 1.07 | 0.0 | 0.0 | 0.0 | false | false | + | 01 May 2024 | Accrual | 1.84 | 0.0 | 1.84 | 0.0 | 0.0 | 0.0 | false | false | | 01 May 2024 | Accrual Activity | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false | false | @TestRailId:C4249 @AdvancedPaymentAllocation @@ -8520,7 +8520,7 @@ Then Loan Repayment schedule has 4 periods, with the following data for periods: | 16 April 2024 | Payout Refund | 40.0 | 14.52 | 0.48 | 0.0 | 25.0 | 69.05 | false | false | | 16 April 2024 | Interest Refund | 0.52 | 0.39 | 0.13 | 0.0 | 0.0 | 68.66 | false | false | | 17 April 2024 | Repayment | 80.0 | 68.66 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - | 17 April 2024 | Accrual | 25.58 | 0.0 | 0.58 | 0.0 | 25.0 | 0.0 | false | false | + | 17 April 2024 | Accrual | 26.19 | 0.0 | 1.19 | 0.0 | 25.0 | 0.0 | false | false | | 17 April 2024 | Accrual Activity | 25.13 | 0.0 | 0.13 | 0.0 | 25.0 | 0.0 | false | false | | 18 April 2024 | Credit Balance Refund | 11.34 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | # --- Close loan --- diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature index 80db7c67bc..2026b67ac1 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature @@ -5685,6 +5685,7 @@ Feature: LoanRepayment | 16 August 2025 | Accrual | 5.28 | 0.0 | 5.28 | 0.0 | 0.0 | 0.0 | false | false | | 16 August 2025 | Accrual Activity | 1.76 | 0.0 | 1.76 | 0.0 | 0.0 | 0.0 | false | false | | 21 August 2025 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | + | 21 August 2025 | Accrual Activity | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | true | | 22 August 2025 | Repayment | 195.07 | 186.99 | 5.28 | 0.0 | 2.8 | 0.0 | true | false | Then Loan status will be "ACTIVE" Then Loan has 195.07 outstanding amount diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index fa1984c86b..787d9db877 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -1764,6 +1764,10 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> { return getLoanTransaction(e -> e.isNotReversed() && e.isContractTermination()); } + public LoanTransaction findReAgeTransaction() { + return getLoanTransaction(LoanTransaction::isReAge); + } + public void handleMaturityDateActivate() { if (this.expectedMaturityDate != null && !this.expectedMaturityDate.equals(this.actualMaturityDate)) { this.actualMaturityDate = this.expectedMaturityDate; diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java index e67ff4c236..ffb2816ecd 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java @@ -41,6 +41,9 @@ import org.apache.fineract.portfolio.loanaccount.data.OutstandingAmountsDTO; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; +import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeInterestHandlingType; +import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.impl.AdvancedPaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.loanschedule.data.LoanScheduleDTO; @@ -243,10 +246,14 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator { Loan loan = installment.getLoan(); LoanRepaymentScheduleTransactionProcessor transactionProcessor = loanTransactionProcessingService .getTransactionProcessor(loan.getTransactionProcessingStrategyCode()); + final LoanTransaction reAgeTransaction = loan.findReAgeTransaction(); + final LoanReAgeParameter loanReAgeParameter = reAgeTransaction != null ? reAgeTransaction.getLoanReAgeParameter() : null; + if (!(transactionProcessor instanceof AdvancedPaymentScheduleTransactionProcessor processor)) { throw new IllegalStateException("Expected an AdvancedPaymentScheduleTransactionProcessor"); } - if (installment.isAdditional() || installment.isDownPayment() || installment.isReAged()) { + if (installment.isAdditional() || installment.isDownPayment() || (installment.isReAged() && loanReAgeParameter != null + && !LoanReAgeInterestHandlingType.DEFAULT.equals(loanReAgeParameter.getInterestHandlingType()))) { return Money.zero(loan.getCurrency()); } Optional<ProgressiveLoanInterestScheduleModel> savedModel = interestScheduleModelRepositoryWrapper.getSavedModel(loan, targetDate); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java index ff5b44200c..474648b4f8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualActivityProcessingServiceImpl.java @@ -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<>(); + + 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); + } + } - if (!LoanTransaction.transactionAmountsMatch(loan.getCurrency(), accrualActivity, newLoanTransaction)) { - createNewTransaction(accrualActivity, newLoanTransaction, changedTransactionDetail); - } - }); + 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); } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java index 0ba26ade41..f84ac18a45 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java @@ -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());
