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 27d435e7a17..e92701e8b10 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature @@ -3304,7 +3304,7 @@ Then Loan Repayment schedule has 4 periods, with the following data for periods: When Loan Pay-off is made on "02 January 2025" Then Loan is closed with zero outstanding balance and it's all installments have obligations met - @Skip @TestRailId:C4080 @AdvancedPaymentAllocation + @TestRailId:C4080 @AdvancedPaymentAllocation Scenario: Verify Loan re-aging transaction at 1st installment with charge - before maturity date and removes additional normal installments and not modifies n+1 - UC13 When Admin sets the business date to "01 January 2025" When Admin creates a client with random data @@ -3340,12 +3340,12 @@ Then Loan Repayment schedule has 4 periods, with the following data for periods: | frequencyNumber | frequencyType | startDate | numberOfInstallments | | 1 | MONTHS | 11 January 2025 | 2 | Then Loan Repayment schedule has 4 periods, with the following data for periods: - | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 0 | 01 January 2025 | 01 January 2025 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | - | 2 | 9 | 10 January 2025 | 10 January 2025 | 750.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | - | 3 | 1 | 11 January 2025 | | 375.0 | 375.0 | 0.0 | 0.0 | 20.0 | 395.0 | 0.0 | 0.0 | 0.0 | 395.0 | - | 4 | 31 | 11 February 2025 | | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | | | 01 January 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 0 | 01 January 2025 | 01 January 2025 | 750.0 | 250.0 | 0.0 | 0.0 | 0.0 | 250.0 | 250.0 | 0.0 | 0.0 | 0.0 | + | 2 | 9 | 10 January 2025 | | 750.0 | 0.0 | 0.0 | 0.0 | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 20.0 | + | 3 | 1 | 11 January 2025 | | 375.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | + | 4 | 31 | 11 February 2025 | | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | 0.0 | 0.0 | 0.0 | 375.0 | Then Loan Repayment schedule has the following data in Total row: | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | 1000.0 | 0.0 | 0.0 | 20.0 | 1020.0 | 250.0 | 0.0 | 0.0 | 770.0 | diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java index 482586035b0..1330394cfbd 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java @@ -945,6 +945,48 @@ public void addToCreditedPenalty(final BigDecimal amount) { } } + public void addPenaltyCharges(final Money amount) { + if (amount != null) { + addPenaltyCharges(amount.getAmount()); + } + } + + public void addPenaltyCharges(final BigDecimal amount) { + if (this.penaltyCharges == null) { + setPenaltyCharges(amount); + } else { + setPenaltyCharges(this.penaltyCharges.add(amount)); + } + } + + public void addToPenaltyPaid(final Money amount) { + if (amount != null) { + addToPenaltyPaid(amount.getAmount()); + } + } + + public void addToPenaltyPaid(final BigDecimal amount) { + if (this.penaltyChargesPaid == null) { + setPenaltyChargesPaid(amount); + } else { + setPenaltyChargesPaid(this.penaltyChargesPaid.add(amount)); + } + } + + public void addToFeeChargesPaid(final Money amount) { + if (amount != null) { + addToFeeChargesPaid(amount.getAmount()); + } + } + + public void addToFeeChargesPaid(final BigDecimal amount) { + if (this.feeChargesPaid == null) { + setFeeChargesPaid(amount); + } else { + setFeeChargesPaid(this.feeChargesPaid.add(amount)); + } + } + /********** UNPAY COMPONENTS ****/ public Money unpayPenaltyChargesComponent(final LocalDate transactionDate, final Money transactionAmountRemaining) { diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index 4ed75b60318..018bce06a21 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -3764,10 +3764,18 @@ private void handleReAgeWithCommonStrategy(LoanTransaction loanTransaction, Comm balances.getPrincipal().getAmount(), balances.getInterest().getAmount(), balances.getFee().getAmount(), balances.getPenalty().getAmount(), null, null, null); + if (!settings.isEqualInstallmentForFeesAndPenalties) { + // If charges should NOT reamortize by reage, we should manage to keep them related to the correct + // installment. + LoanRepaymentScheduleInstallment target = earlyRepaidInstallment; + Set charges = ctx.getCharges(); + moveRelatedChargesToInstallment(charges, target, installments, currency); + } + earlyRepaidInstallment.setPrincipalCompleted(balances.getPrincipal().getAmount()); earlyRepaidInstallment.setInterestPaid(balances.getInterest().getAmount()); - earlyRepaidInstallment.setFeeChargesPaid(balances.getFee().getAmount()); - earlyRepaidInstallment.setPenaltyChargesPaid(balances.getPenalty().getAmount()); + earlyRepaidInstallment.addToFeeChargesPaid(balances.getFee()); + earlyRepaidInstallment.addToPenaltyPaid(balances.getPenalty()); earlyRepaidInstallment.setTotalPaidInAdvance(balances.getPaidInAdvance().getAmount()); @@ -3820,6 +3828,38 @@ private void handleReAgeWithCommonStrategy(LoanTransaction loanTransaction, Comm reprocessInstallments(installments); } + private void moveRelatedChargesToInstallment(Set charges, LoanRepaymentScheduleInstallment target, + List installments, MonetaryCurrency currency) { + int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments); + Set chargesOfNewInstallment = getLoanChargesOfInstallment(charges, target, firstNormalInstallmentNumber); + Integer targetInstallmentNumber = target.getInstallmentNumber(); + installments.stream().filter(i -> Objects.equals(i.getInstallmentNumber(), targetInstallmentNumber)).findFirst() + .filter(source -> source != target).ifPresent(source -> { + // move fees + chargesOfNewInstallment.stream().filter(LoanCharge::isNotFullyPaid).filter(LoanCharge::isFeeCharge) + .forEach(loanCharge -> moveFeeCharge(loanCharge, source, target, currency)); + // move penalties + chargesOfNewInstallment.stream().filter(LoanCharge::isNotFullyPaid).filter(LoanCharge::isPenaltyCharge) + .forEach(loanCharge -> movePenaltyCharge(loanCharge, source, target, currency)); + }); + } + + private void movePenaltyCharge(LoanCharge loanCharge, LoanRepaymentScheduleInstallment source, LoanRepaymentScheduleInstallment target, + MonetaryCurrency currency) { + source.setPenaltyCharges(MathUtil.subtract(source.getPenaltyCharges(), loanCharge.chargeAmount())); + source.setPenaltyChargesPaid(MathUtil.subtract(source.getPenaltyChargesPaid(), loanCharge.getAmountPaid())); + target.addPenaltyCharges(loanCharge.getAmount(currency)); + target.addToPenaltyPaid(loanCharge.getAmountPaid()); + } + + private void moveFeeCharge(LoanCharge loanCharge, LoanRepaymentScheduleInstallment source, LoanRepaymentScheduleInstallment target, + MonetaryCurrency currency) { + source.setFeeChargesCharged(MathUtil.subtract(source.getFeeChargesCharged(), loanCharge.chargeAmount())); + source.setFeeChargesPaid(MathUtil.subtract(source.getFeeChargesPaid(), loanCharge.getAmountPaid())); + target.addToFeeCharges(loanCharge.getAmount(currency)); + target.addToFeeChargesPaid(loanCharge.getAmountPaid()); + } + private void createChargeMappingsForInstallment(final LoanRepaymentScheduleInstallment installment, List reAgedChargeEqualAmortizationValues, Integer index) { reAgedChargeEqualAmortizationValues.forEach(amortizationValue -> installment.getInstallmentCharges().add(new LoanInstallmentCharge(