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 27d875a38..0f67b5a1a 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature @@ -4449,4 +4449,122 @@ Feature: LoanRepayment | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | | 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false | | 01 February 2024 | Repayment | 20.0 | 19.42 | 0.58 | 0.0 | 0.0 | 80.58 | 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 \ No newline at end of file + When Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_INSTALLMENT" future installment allocation rule + + @TestRailId:C3569 + Scenario: Verify Loan is fully paid and closed after partial repayments, Merchant issued refund which overpays the loan, partial CBR and reversal of 1st repayment + When Admin sets the business date to "28 March 2025" + When Admin creates a client with random data + When Admin creates a fully customized loan with the following data: + | LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy | + | LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL | 26 March 2025 | 120 | 35.29 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 3 | MONTHS | 1 | MONTHS | 3 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "26 March 2025" with "120" amount and expected disbursement date on "26 March 2025" + When Admin successfully disburse the loan on "26 March 2025" with "120" EUR transaction amount + Then Loan Repayment schedule has 3 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 | + | | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 26 April 2025 | | 81.15 | 38.85 | 3.53 | 0.0 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 | + | 2 | 30 | 26 May 2025 | | 41.16 | 39.99 | 2.39 | 0.0 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 | + | 3 | 31 | 26 June 2025 | | 0.0 | 41.16 | 1.21 | 0.0 | 0.0 | 42.37 | 0.0 | 0.0 | 0.0 | 42.37 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 120.0 | 7.13 | 0.0 | 0.0 | 127.13 | 0.0 | 0.0 | 0.0 | 127.13 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false | + And Customer makes "AUTOPAY" repayment on "27 March 2025" with 20 EUR transaction amount + Then Loan Repayment schedule has 3 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 | + | | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 26 April 2025 | | 80.58 | 39.42 | 2.96 | 0.0 | 0.0 | 42.38 | 20.0 | 20.0 | 0.0 | 22.38 | + | 2 | 30 | 26 May 2025 | | 40.57 | 40.01 | 2.37 | 0.0 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 | + | 3 | 31 | 26 June 2025 | | 0.0 | 40.57 | 1.19 | 0.0 | 0.0 | 41.76 | 0.0 | 0.0 | 0.0 | 41.76 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 120.0 | 6.52 | 0.0 | 0.0 | 126.52 | 20.0 | 20.0 | 0.0 | 106.52 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false | + | 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | false | false | + And Customer makes "AUTOPAY" repayment on "27 March 2025" with 20 EUR transaction amount + Then Loan Repayment schedule has 3 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 | + | | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 26 April 2025 | | 80.01 | 39.99 | 2.39 | 0.0 | 0.0 | 42.38 | 40.0 | 40.0 | 0.0 | 2.38 | + | 2 | 30 | 26 May 2025 | | 39.98 | 40.03 | 2.35 | 0.0 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 | + | 3 | 31 | 26 June 2025 | | 0.0 | 39.98 | 1.18 | 0.0 | 0.0 | 41.16 | 0.0 | 0.0 | 0.0 | 41.16 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 120.0 | 5.92 | 0.0 | 0.0 | 125.92 | 40.0 | 40.0 | 0.0 | 85.92 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false | + | 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | false | false | + | 27 March 2025 | Repayment | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 80.11 | false | false | + And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "27 March 2025" with 120 EUR transaction amount and self-generated Idempotency key + Then Loan Repayment schedule has 3 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 | + | | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 26 April 2025 | 27 March 2025 | 80.11 | 39.89 | 0.11 | 0.0 | 0.0 | 40.0 | 40.0 | 40.0 | 0.0 | 0.0 | + | 2 | 30 | 26 May 2025 | 27 March 2025 | 42.38 | 37.73 | 0.0 | 0.0 | 0.0 | 37.73 | 37.73 | 37.73 | 0.0 | 0.0 | + | 3 | 31 | 26 June 2025 | 27 March 2025 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 | 42.38 | 42.38 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 120.0 | 0.11 | 0.0 | 0.0 | 120.11 | 120.11 | 120.11 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false | + | 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | false | false | + | 27 March 2025 | Repayment | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 80.11 | false | false | + | 27 March 2025 | Merchant Issued Refund | 120.0 | 80.11 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 27 March 2025 | Interest Refund | 0.11 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 27 March 2025 | Accrual Activity | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | + | 28 March 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan status will be "OVERPAID" + Then Loan has 0 outstanding amount + Then Loan has 40 overpaid amount + And Admin makes Credit Balance Refund transaction on "28 March 2025" with 20 EUR transaction amount + Then Loan Repayment schedule has 3 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 | + | | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 26 April 2025 | 27 March 2025 | 80.11 | 39.89 | 0.11 | 0.0 | 0.0 | 40.0 | 40.0 | 40.0 | 0.0 | 0.0 | + | 2 | 30 | 26 May 2025 | 27 March 2025 | 42.38 | 37.73 | 0.0 | 0.0 | 0.0 | 37.73 | 37.73 | 37.73 | 0.0 | 0.0 | + | 3 | 31 | 26 June 2025 | 27 March 2025 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 | 42.38 | 42.38 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 120.0 | 0.11 | 0.0 | 0.0 | 120.11 | 120.11 | 120.11 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false | + | 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | false | false | + | 27 March 2025 | Repayment | 20.0 | 20.0 | 0.0 | 0.0 | 0.0 | 80.11 | false | false | + | 27 March 2025 | Merchant Issued Refund | 120.0 | 80.11 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 27 March 2025 | Interest Refund | 0.11 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 27 March 2025 | Accrual Activity | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | + | 28 March 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | + | 28 March 2025 | Credit Balance Refund | 20.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan status will be "OVERPAID" + Then Loan has 0 outstanding amount + Then Loan has 20 overpaid amount + When Customer undo "1"th "Repayment" transaction made on "27 March 2025" + Then Loan Repayment schedule has 3 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 | + | | | 26 March 2025 | | 120.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 26 April 2025 | 27 March 2025 | 84.76 | 35.24 | 0.11 | 0.0 | 0.0 | 35.35 | 35.35 | 35.35 | 0.0 | 0.0 | + | 2 | 30 | 26 May 2025 | 27 March 2025 | 42.38 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 | 42.38 | 42.38 | 0.0 | 0.0 | + | 3 | 31 | 26 June 2025 | 27 March 2025 | 0.0 | 42.38 | 0.0 | 0.0 | 0.0 | 42.38 | 42.38 | 42.38 | 0.0 | 0.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 120.0 | 0.11 | 0.0 | 0.0 | 120.11 | 120.11 | 120.11 | 0.0 | 0.0 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 26 March 2025 | Disbursement | 120.0 | 0.0 | 0.0 | 0.0 | 0.0 | 120.0 | false | false | + | 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | true | false | + | 27 March 2025 | Repayment | 20.0 | 19.89 | 0.11 | 0.0 | 0.0 | 100.11 | false | true | + | 27 March 2025 | Merchant Issued Refund | 120.0 | 100.11 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | + | 27 March 2025 | Interest Refund | 0.11 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | + | 27 March 2025 | Accrual Activity | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | true | + | 28 March 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | + | 28 March 2025 | Credit Balance Refund | 20.0 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + Then Loan status will be "CLOSED_OBLIGATIONS_MET" + Then Loan has 0 outstanding amount diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java index 9d023d729..afc9c0ed7 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ReprocessLoanTransactionsServiceImpl.java @@ -60,7 +60,6 @@ public class ReprocessLoanTransactionsServiceImpl implements ReprocessLoanTransa public void reprocessTransactionsWithPostTransactionChecks(final Loan loan, final LocalDate transactionDate) { final ChangedTransactionDetail changedTransactionDetail = reprocessTransactionsAndFetchChangedTransactions(loan, loan.retrieveListOfTransactionsForReprocessing()); - loan.doPostLoanTransactionChecks(transactionDate, loan.getLoanLifecycleStateMachine()); handleChangedDetail(changedTransactionDetail); } 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 b495af02f..6673d77a8 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 @@ -275,8 +275,7 @@ public class ProgressiveLoanScheduleGenerator implements LoanScheduleGenerator { continue; } - Money outstandingBalance = emiCalculator.getOutstandingLoanBalanceOfPeriod(interestScheduleModel, periodDueDate, - disbursementDate); + Money outstandingBalance = emiCalculator.getOutstandingLoanBalanceOfPeriod(interestScheduleModel, disbursementDate); final Money disbursedAmount = Money.of(loanApplicationTerms.getCurrency(), disbursementData.getPrincipal(), mc); final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java index 312a116b7..24487c499 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/EMICalculator.java @@ -119,8 +119,7 @@ public interface EMICalculator { Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate, @NotNull LocalDate targetDate, boolean includeChargebackInterest); - Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel interestScheduleModel, LocalDate repaymentPeriodDueDate, - LocalDate targetDate); + Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel interestScheduleModel, LocalDate targetDate); OutstandingDetails getOutstandingAmountsTillDate(ProgressiveLoanInterestScheduleModel model, LocalDate targetDate); diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java index 03be2b04c..35c1b5c47 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/ProgressiveEMICalculator.java @@ -256,7 +256,7 @@ public final class ProgressiveEMICalculator implements EMICalculator { public PeriodDueDetails getDueAmounts(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate, @NotNull LocalDate targetDate) { ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel, - periodDueDate, targetDate); + targetDate); RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow(); long notFullyRepaidRepaymentPeriodCount = recalculatedScheduleModelTillDate.repaymentPeriods().stream() .filter(rp -> !rp.isFullyPaid()).count(); @@ -265,6 +265,11 @@ public final class ProgressiveEMICalculator implements EMICalculator { if (!targetDate.isAfter(repaymentPeriod.getFromDate())) { if (multiplePeriodIsUnpaid) { repaymentPeriod.setEmi(repaymentPeriod.getOriginalEmi()); + Money totalOutstandingPrincipal = recalculatedScheduleModelTillDate.getTotalOutstandingPrincipal(); + Money outstandingPrincipal = repaymentPeriod.getOutstandingPrincipal(); + // If there are less outstanding principal than anticipated + Money emiAdjustment = MathUtil.negativeToZero(outstandingPrincipal.minus(totalOutstandingPrincipal)); + repaymentPeriod.setEmi(repaymentPeriod.getEmi().minus(emiAdjustment)); } else if (repaymentPeriod.isFullyPaid() && onePeriodIsUnpaid) { repaymentPeriod.setEmi(MathUtil.min(repaymentPeriod.getOriginalEmi(), // recalculatedScheduleModelTillDate.getTotalDuePrincipal() // @@ -285,7 +290,7 @@ public final class ProgressiveEMICalculator implements EMICalculator { public Money getPeriodInterestTillDate(@NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate, @NotNull LocalDate targetDate, boolean includeChargebackInterest) { ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel, - periodDueDate, targetDate); + targetDate); RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow(); return includeChargebackInterest ? repaymentPeriod.getCalculatedDueInterest() : repaymentPeriod.getCalculatedDueInterest().minus(repaymentPeriod.getChargebackInterest(), @@ -293,11 +298,18 @@ public final class ProgressiveEMICalculator implements EMICalculator { } @Override - public Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate periodDueDate, - LocalDate targetDate) { + public Money getOutstandingLoanBalanceOfPeriod(ProgressiveLoanInterestScheduleModel scheduleModel, LocalDate targetDate) { ProgressiveLoanInterestScheduleModel recalculatedScheduleModelTillDate = recalculateScheduleModelTillDate(scheduleModel, - periodDueDate, targetDate); - RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow(); + targetDate); + RepaymentPeriod repaymentPeriod = recalculatedScheduleModelTillDate.findRepaymentPeriod(targetDate).orElseGet(() -> { + // If target date is after maturity date + if (targetDate.isAfter(recalculatedScheduleModelTillDate.getLastRepaymentPeriod().getDueDate())) { + return recalculatedScheduleModelTillDate.getLastRepaymentPeriod(); + } else { + // if target date is before 1st disbursement date, we use 1st repayment period + return recalculatedScheduleModelTillDate.repaymentPeriods().get(0); + } + }); return repaymentPeriod.getOutstandingLoanBalance(); } @@ -343,43 +355,58 @@ public final class ProgressiveEMICalculator implements EMICalculator { @NotNull private ProgressiveLoanInterestScheduleModel recalculateScheduleModelTillDate( - @NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate periodDueDate, @NotNull LocalDate targetDate) { + @NotNull ProgressiveLoanInterestScheduleModel scheduleModel, @NotNull LocalDate targetDate) { MathContext mc = scheduleModel.mc(); ProgressiveLoanInterestScheduleModel scheduleModelCopy = scheduleModel.deepCopy(mc); - RepaymentPeriod repaymentPeriod = scheduleModelCopy.findRepaymentPeriodByDueDate(periodDueDate).orElseThrow(); - - LocalDate adjustedTargetDate = targetDate; - InterestPeriod interestPeriod; - if (!targetDate.isAfter(repaymentPeriod.getFromDate())) { - interestPeriod = repaymentPeriod.getFirstInterestPeriod(); - adjustedTargetDate = repaymentPeriod.getFromDate(); - } else if (targetDate.isAfter(repaymentPeriod.getDueDate())) { - interestPeriod = repaymentPeriod.getLastInterestPeriod(); - adjustedTargetDate = repaymentPeriod.getDueDate(); + boolean isBeforeFirstDisbursement = targetDate.isBefore(scheduleModelCopy.repaymentPeriods().get(0).getFromDate()); + boolean isAfterMaturityDate = !targetDate.isBefore(scheduleModelCopy.getLastRepaymentPeriod().getDueDate()); + if (isBeforeFirstDisbursement) { + scheduleModelCopy.repaymentPeriods().forEach(rp -> rp.getInterestPeriods().clear()); + return scheduleModelCopy; + } else if (isAfterMaturityDate) { + return scheduleModelCopy; } else { - interestPeriod = repaymentPeriod.findInterestPeriod(targetDate).orElseThrow(); - } - // TODO use findInterestPeriod - scheduleModelCopy.findRepaymentPeriod(targetDate)// - .flatMap(rp -> rp.findInterestPeriod(targetDate)).ifPresent(ip -> ip.setDueDate(targetDate)); // - interestPeriod.setDueDate(adjustedTargetDate); - int index = repaymentPeriod.getInterestPeriods().indexOf(interestPeriod); - int nextIdx = index + 1; - boolean thereIsInterestPeriodFromDateOnTargetDate = repaymentPeriod.getInterestPeriods().size() > nextIdx - && repaymentPeriod.getInterestPeriods().get(nextIdx).getFromDate().isEqual(targetDate); - if (thereIsInterestPeriodFromDateOnTargetDate) { - // NOTE: If there is a next interest period with fromDate on the target date - // then the related chargeback amounts comes from the next interest period too. - InterestPeriod nextInterestPeriod = repaymentPeriod.getInterestPeriods().get(nextIdx); - interestPeriod.addChargebackPrincipalAmount(nextInterestPeriod.getChargebackPrincipal()); - interestPeriod.addChargebackInterestAmount(nextInterestPeriod.getChargebackInterest()); - } - repaymentPeriod.getInterestPeriods().subList(nextIdx, repaymentPeriod.getInterestPeriods().size()).clear(); - scheduleModelCopy.repaymentPeriods().forEach(rp -> rp.getInterestPeriods().removeIf(ip -> ip.getDueDate().isAfter(targetDate))); - calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(), scheduleModelCopy); - calculateOutstandingBalance(scheduleModelCopy); - calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy); + RepaymentPeriod repaymentPeriod = scheduleModelCopy.findRepaymentPeriod(targetDate).orElseThrow(); + scheduleModelCopy.repaymentPeriods().forEach(rp -> { + if (rp.getDueDate().isAfter(targetDate)) { + if (rp.equals(repaymentPeriod)) { + rp.findInterestPeriod(targetDate).ifPresent(ip -> { + ip.setDueDate(targetDate); + int index = rp.getInterestPeriods().indexOf(ip); + int nextIdx = index + 1; + boolean thereIsInterestPeriodFromDateOnTargetDate = ip.getRepaymentPeriod().getInterestPeriods() + .size() > nextIdx; + if (thereIsInterestPeriodFromDateOnTargetDate) { + // NOTE: If there is a next interest period with fromDate on the target date + // then the related chargeback amounts comes from the next interest period too. + InterestPeriod nextInterestPeriod = ip.getRepaymentPeriod().getInterestPeriods().get(nextIdx); + ip.addChargebackPrincipalAmount(nextInterestPeriod.getChargebackPrincipal()); + ip.addChargebackInterestAmount(nextInterestPeriod.getChargebackInterest()); + } + ip.getRepaymentPeriod().getInterestPeriods() + .subList(nextIdx, ip.getRepaymentPeriod().getInterestPeriods().size()).clear(); + }); + } else if (rp.getPrevious().isPresent() && rp.getPrevious().get().equals(repaymentPeriod) + && (rp.getInterestPeriods().get(0).getChargebackInterest().isGreaterThanZero() + || rp.getInterestPeriods().get(0).getChargebackPrincipal().isGreaterThanZero())) { + // NOTE: we need to check whether there is chargeback on the 1st interest period of the next + // period + // if so, we need to retain that interest period, but need to update due date to match with from + // date -> 0 interest + rp.getInterestPeriods().get(0).setDueDate(rp.getInterestPeriods().get(0).getFromDate()); + if (rp.getInterestPeriods().size() > 1) { + rp.getInterestPeriods().subList(1, rp.getInterestPeriods().size()).clear(); + } + } else { + rp.getInterestPeriods().clear(); + } + } + }); + calculateRateFactorForPeriods(scheduleModelCopy.repaymentPeriods(), scheduleModelCopy); + calculateOutstandingBalance(scheduleModelCopy); + calculateLastUnpaidRepaymentPeriodEMI(scheduleModelCopy); + } return scheduleModelCopy; } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java index e29bfc51c..3a74d32f2 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/ProgressiveLoanInterestScheduleModel.java @@ -42,6 +42,7 @@ import java.util.stream.Collectors; import lombok.Data; import lombok.experimental.Accessors; import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.core.service.MathUtil; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.data.LoanTermVariationsData; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType; @@ -338,6 +339,14 @@ public class ProgressiveLoanInterestScheduleModel { return repaymentPeriods().stream().map(RepaymentPeriod::getChargebackPrincipal).reduce(zero, Money::plus); } + public Money getTotalOutstandingPrincipal() { + return MathUtil.negativeToZero(getTotalDuePrincipal().minus(getTotalPaidPrincipal())); + } + + public Money getTotalOutstandingInterest() { + return MathUtil.negativeToZero(getTotalDueInterest().minus(getTotalPaidInterest())); + } + public Optional findRepaymentPeriod(@NotNull LocalDate transactionDate) { return repaymentPeriods.stream() // .filter(period -> isInPeriod(transactionDate, period.getFromDate(), period.getDueDate(), period.isFirstRepaymentPeriod()))// diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java index 8aa2e595d..5390c1705 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/calc/data/RepaymentPeriod.java @@ -318,4 +318,8 @@ public final class RepaymentPeriod { public boolean isFirstRepaymentPeriod() { return previous == null; } + + public Money getOutstandingPrincipal() { + return getDuePrincipal().minus(getPaidPrincipal()); + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java index cfb030d17..d9b10d53c 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanChargeWritePlatformServiceImpl.java @@ -302,7 +302,7 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo } else { reprocessLoanTransactionsService.reprocessTransactions(loan); } - + loan.doPostLoanTransactionChecks(transactionDate, loan.getLoanLifecycleStateMachine()); loan = loanAccountService.saveAndFlushLoanWithDataIntegrityViolationChecks(loan); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java index a57a3954c..3d1215353 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java @@ -184,8 +184,8 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService { if (!transactionIds.isEmpty()) { this.accountTransfersWritePlatformService.reverseTransfersWithFromAccountTransactions(transactionIds, PortfolioAccountType.LOAN); - loan.updateLoanSummaryAndStatus(); } + loan.updateLoanSummaryAndStatus(); loanAccrualsProcessingService.processAccrualsOnInterestRecalculation(loan, loan.isInterestBearingAndInterestRecalculationEnabled(), false); @@ -267,8 +267,6 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService { writeOffTransaction.reverse(); } - loan.updateLoanSummaryAndStatus(); - if (newTransactionDetail.isRepaymentLikeType() || newTransactionDetail.isInterestWaiver()) { loanDownPaymentHandlerService.handleRepaymentOrRecoveryOrWaiverTransaction(loan, newTransactionDetail, loanLifecycleStateMachine, transactionForAdjustment, scheduleGeneratorDTO);