From 68b35024f66e9b3bc9a2a177a8b9ad0a887559ae Mon Sep 17 00:00:00 2001 From: "mark.vituska" Date: Tue, 22 Jul 2025 15:29:37 +0200 Subject: [PATCH] FINERACT-2326: Fix payment allocation when current period is closed --- .../resources/features/EMICalculation.feature | 22 ++--- .../resources/features/LoanChargeOff.feature | 12 +-- .../features/LoanContractTermination.feature | 12 +-- .../features/LoanInterestPause.feature | 21 ++-- ...edPaymentScheduleTransactionProcessor.java | 97 +++++++++++++------ 5 files changed, 104 insertions(+), 60 deletions(-) diff --git a/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature b/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature index 8ed1a8e9e..94f9652c2 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/EMICalculation.feature @@ -2949,12 +2949,12 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 10.0 | 0.0 | 10.0 | 7.01 | | 3 | 31 | 01 April 2024 | | 50.53 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 4 | 30 | 01 May 2024 | | 33.91 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 5 | 31 | 01 June 2024 | | 17.19 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 6 | 30 | 01 July 2024 | | 0.0 | 17.19 | 0.2 | 0.0 | 0.0 | 17.39 | 0.0 | 0.0 | 0.0 | 17.39 | + | 4 | 30 | 01 May 2024 | | 33.81 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 17.0 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 17.0 | 0.1 | 0.0 | 0.0 | 17.1 | 0.0 | 0.0 | 0.0 | 17.1 | Then Loan Repayment schedule has the following data in Total row: | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 100.0 | 2.44 | 0.0 | 0.0 | 102.44 | 27.01 | 0.0 | 10.0 | 75.43 | + | 100.0 | 2.15 | 0.0 | 0.0 | 102.15 | 27.01 | 0.0 | 10.0 | 75.14 | Then Loan Transactions tab has the following data: | 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 | @@ -2969,12 +2969,12 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | 1 | 31 | 01 February 2024 | 01 February 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | | 2 | 29 | 01 March 2024 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 10.0 | 0.0 | 10.0 | 7.01 | | 3 | 31 | 01 April 2024 | | 50.53 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 4 | 30 | 01 May 2024 | | 33.95 | 16.58 | 0.43 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 5 | 31 | 01 June 2024 | | 17.27 | 16.68 | 0.33 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | - | 6 | 30 | 01 July 2024 | | 0.0 | 17.27 | 0.24 | 0.0 | 0.0 | 17.51 | 0.0 | 0.0 | 0.0 | 17.51 | + | 4 | 30 | 01 May 2024 | | 33.81 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2024 | | 17.0 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2024 | | 0.0 | 17.0 | 0.1 | 0.0 | 0.0 | 17.1 | 0.0 | 0.0 | 0.0 | 17.1 | Then Loan Repayment schedule has the following data in Total row: | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 100.0 | 2.56 | 0.0 | 0.0 | 102.56 | 27.01 | 0.0 | 10.0 | 75.55 | + | 100.0 | 2.15 | 0.0 | 0.0 | 102.15 | 27.01 | 0.0 | 10.0 | 75.14 | Then Loan Transactions tab has the following data: | 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 | @@ -7890,10 +7890,10 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | 3 | 31 | 01 April 2025 | | 4661.9 | 1112.7 | 576.13 | 0.0 | 0.0 | 1688.83 | 0.0 | 0.0 | 0.0 | 1688.83 | | 4 | 30 | 01 May 2025 | | 3549.2 | 1112.7 | 576.13 | 0.0 | 0.0 | 1688.83 | 0.0 | 0.0 | 0.0 | 1688.83 | | 5 | 31 | 01 June 2025 | | 2436.5 | 1112.7 | 576.13 | 0.0 | 0.0 | 1688.83 | 0.0 | 0.0 | 0.0 | 1688.83 | - | 6 | 30 | 01 July 2025 | | 0.0 | 2436.5 | 896.66 | 0.0 | 0.0 | 3333.16 | 0.0 | 0.0 | 0.0 | 3333.16 | + | 6 | 30 | 01 July 2025 | | 0.0 | 2436.5 | 576.13 | 0.0 | 0.0 | 3012.63 | 0.0 | 0.0 | 0.0 | 3012.63 | And Loan Repayment schedule has the following data in Total row: | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 8000.0 | 3777.31 | 0.0 | 0.0 | 11777.31 | 0.05 | 0.0 | 0.04 | 11777.26 | + | 8000.0 | 3456.78 | 0.0 | 0.0 | 11456.78 | 0.05 | 0.0 | 0.04 | 11456.73 | @TestRailId:C3657 Scenario: Verify tranche interest bearing progressive loan that expects two tranches with repayment and undo last disbursement - UC1 @@ -9631,4 +9631,4 @@ Feature: EMI calculation and repayment schedule checks for interest bearing loan | Type | Account code | Account name | Debit | Credit | | ASSET | 112601 | Loans Receivable | | 906.25 | | ASSET | 112603 | Interest/Fee Receivable | | 16.87 | - | LIABILITY | 145023 | Suspense/Clearing account | 923.12 | | \ No newline at end of file + | LIABILITY | 145023 | Suspense/Clearing account | 923.12 | | diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeOff.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeOff.feature index 777d384c1..50f13bff1 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeOff.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeOff.feature @@ -6005,18 +6005,18 @@ Feature: Charge-off | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | | 1 | 31 | 01 February 2024 | 01 March 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 17.01 | 0.0 | - | 2 | 29 | 01 March 2024 | 01 March 2024 | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | - | 3 | 30 | 31 March 2024 | | 0.0 | 67.05 | 0.18 | 0.0 | 0.0 | 67.23 | 35.98 | 35.98 | 0.0 | 31.25 | + | 2 | 29 | 01 March 2024 | 01 March 2024 | 67.14 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 3 | 30 | 31 March 2024 | | 0.0 | 67.14 | 0.0 | 0.0 | 0.0 | 67.14 | 35.98 | 35.98 | 0.0 | 31.16 | Then Loan Repayment schedule has the following data in Total row: | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 100.0 | 1.25 | 0.0 | 0.0 | 101.25 | 70.0 | 35.98 | 17.01 | 31.25 | + | 100.0 | 1.16 | 0.0 | 0.0 | 101.16 | 70.0 | 35.98 | 17.01 | 31.16 | Then Loan Transactions tab has the following data: | 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 March 2024 | Repayment | 70.0 | 68.93 | 1.07 | 0.0 | 0.0 | 31.07 | false | false | + | 01 March 2024 | Repayment | 70.0 | 68.84 | 1.16 | 0.0 | 0.0 | 31.16 | false | false | | 31 March 2024 | Accrual | 1.72 | 0.0 | 1.72 | 0.0 | 0.0 | 0.0 | false | false | - | 31 March 2024 | Accrual Adjustment | 0.47 | 0.0 | 0.47 | 0.0 | 0.0 | 0.0 | false | false | - | 31 March 2024 | Charge-off | 31.25 | 31.07 | 0.18 | 0.0 | 0.0 | 0.0 | false | true | + | 31 March 2024 | Accrual Adjustment | 0.56 | 0.0 | 0.56 | 0.0 | 0.0 | 0.0 | false | false | + | 31 March 2024 | Charge-off | 31.16 | 31.16 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | And Global configuration "is-principal-compounding-disabled-for-overdue-loans" is disabled @TestRailId:C3499 @AdvancedPaymentAllocation diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature index 979c70436..65cded5e7 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature @@ -1035,18 +1035,18 @@ Feature: Contract Termination | Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | | | 01 January 2024 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | | 1 | 31 | 01 February 2024 | 01 March 2024 | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 17.01 | 0.0 | - | 2 | 29 | 01 March 2024 | 01 March 2024 | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | - | 3 | 30 | 31 March 2024 | | 0.0 | 67.05 | 0.18 | 0.0 | 0.0 | 67.23 | 35.98 | 35.98 | 0.0 | 31.25 | + | 2 | 29 | 01 March 2024 | 01 March 2024 | 67.14 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 17.01 | 0.0 | 0.0 | 0.0 | + | 3 | 30 | 31 March 2024 | | 0.0 | 67.14 | 0.0 | 0.0 | 0.0 | 67.14 | 35.98 | 35.98 | 0.0 | 31.16 | Then Loan Repayment schedule has the following data in Total row: | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | - | 100.0 | 1.25 | 0.0 | 0.0 | 101.25 | 70.0 | 35.98 | 17.01 | 31.25 | + | 100.0 | 1.16 | 0.0 | 0.0 | 101.16 | 70.0 | 35.98 | 17.01 | 31.16 | Then Loan Transactions tab has the following data: | 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 March 2024 | Repayment | 70.0 | 68.93 | 1.07 | 0.0 | 0.0 | 31.07 | false | false | + | 01 March 2024 | Repayment | 70.0 | 68.84 | 1.16 | 0.0 | 0.0 | 31.16 | false | false | | 31 March 2024 | Accrual | 1.72 | 0.0 | 1.72 | 0.0 | 0.0 | 0.0 | false | false | - | 31 March 2024 | Accrual Adjustment | 0.47 | 0.0 | 0.47 | 0.0 | 0.0 | 0.0 | false | false | - | 31 March 2024 | Contract Termination | 31.25 | 31.07 | 0.18 | 0.0 | 0.0 | 0.0 | false | true | + | 31 March 2024 | Accrual Adjustment | 0.56 | 0.0 | 0.56 | 0.0 | 0.0 | 0.0 | false | false | + | 31 March 2024 | Contract Termination | 31.16 | 31.16 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | And Global configuration "is-principal-compounding-disabled-for-overdue-loans" is disabled @TestRailId:C3738 diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanInterestPause.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanInterestPause.feature index e4a936805..ca9806375 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanInterestPause.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanInterestPause.feature @@ -639,21 +639,21 @@ Feature: Loan interest pause on repayment schedule And Customer makes "PAYOUT_REFUND" transaction with "AUTOPAY" payment type on "22 April 2025" with 400 EUR transaction amount and self-generated Idempotency key When Admin sets the business date to "23 April 2025" And Admin runs inline COB job for Loan - And Admin makes Credit Balance Refund transaction on "23 April 2025" with 165.25 EUR transaction amount + And Admin makes Credit Balance Refund transaction on "23 April 2025" with 164.19 EUR transaction amount When Admin sets the business date to "01 May 2025" And Admin runs inline COB job for Loan Then Loan Repayment schedule has 6 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 April 2025 | | 1000.0 | | | 0.0 | | 0.0 | 0.0 | | | | - | 1 | 30 | 01 May 2025 | 21 April 2025 | 829.71 | 170.29 | 9.75 | 0.0 | 25.0 | 205.04 | 205.04 | 205.04 | 0.0 | 0.0 | - | 2 | 31 | 01 June 2025 | 21 April 2025 | 649.67 | 180.04 | 0.0 | 0.0 | 0.0 | 180.04 | 180.04 | 180.04 | 0.0 | 0.0 | - | 3 | 30 | 01 July 2025 | 21 April 2025 | 469.63 | 180.04 | 0.0 | 0.0 | 0.0 | 180.04 | 180.04 | 180.04 | 0.0 | 0.0 | - | 4 | 31 | 01 August 2025 | 21 April 2025 | 289.59 | 180.04 | 0.0 | 0.0 | 0.0 | 180.04 | 180.04 | 180.04 | 0.0 | 0.0 | - | 5 | 31 | 01 September 2025 | 22 April 2025 | 109.55 | 180.04 | 0.0 | 0.0 | 0.0 | 180.04 | 180.04 | 180.04 | 0.0 | 0.0 | - | 6 | 30 | 01 October 2025 | 22 April 2025 | 0.0 | 109.55 | 0.0 | 0.0 | 0.0 | 109.55 | 109.55 | 109.55 | 0.0 | 0.0 | + | 1 | 30 | 01 May 2025 | 22 April 2025 | 828.65 | 171.35 | 9.75 | 0.0 | 25.0 | 206.1 | 206.1 | 206.1 | 0.0 | 0.0 | + | 2 | 31 | 01 June 2025 | 21 April 2025 | 648.61 | 180.04 | 0.0 | 0.0 | 0.0 | 180.04 | 180.04 | 180.04 | 0.0 | 0.0 | + | 3 | 30 | 01 July 2025 | 21 April 2025 | 468.57 | 180.04 | 0.0 | 0.0 | 0.0 | 180.04 | 180.04 | 180.04 | 0.0 | 0.0 | + | 4 | 31 | 01 August 2025 | 21 April 2025 | 288.53 | 180.04 | 0.0 | 0.0 | 0.0 | 180.04 | 180.04 | 180.04 | 0.0 | 0.0 | + | 5 | 31 | 01 September 2025 | 22 April 2025 | 108.49 | 180.04 | 0.0 | 0.0 | 0.0 | 180.04 | 180.04 | 180.04 | 0.0 | 0.0 | + | 6 | 30 | 01 October 2025 | 22 April 2025 | 0.0 | 108.49 | 1.06 | 0.0 | 0.0 | 109.55 | 109.55 | 109.55 | 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 | - | 1000.0 | 9.75 | 0.0 | 25.0 | 1034.75 | 1034.75 | 1034.75 | 0.0 | 0.0 | + | 1000.0 | 10.81 | 0.0 | 25.0 | 1035.81 | 1035.81 | 1035.81 | 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 | | 01 April 2025 | Disbursement | 1000.0 | 0.0 | 0.0 | 0.0 | 0.0 | 1000.0 | false | false | @@ -672,8 +672,9 @@ Feature: Loan interest pause on repayment schedule | 14 April 2025 | Accrual | 0.75 | 0.0 | 0.75 | 0.0 | 0.0 | 0.0 | false | false | | 20 April 2025 | Accrual | 25.0 | 0.0 | 0.0 | 0.0 | 25.0 | 0.0 | false | false | | 21 April 2025 | Repayment | 800.0 | 765.25 | 9.75 | 0.0 | 25.0 | 234.75 | false | false | - | 22 April 2025 | Payout Refund | 400.0 | 234.75 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | - | 23 April 2025 | Credit Balance Refund | 165.25 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 22 April 2025 | Payout Refund | 400.0 | 234.75 | 1.06 | 0.0 | 0.0 | 0.0 | false | false | + | 22 April 2025 | Accrual | 1.06 | 0.0 | 1.06 | 0.0 | 0.0 | 0.0 | false | false | + | 23 April 2025 | Credit Balance Refund | 164.19 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | Then Loan status will be "CLOSED_OBLIGATIONS_MET" @TestRailId:C3627 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 1802981c1..f6e934adb 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 @@ -1570,10 +1570,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep } else { lastOverdueBalanceChange = currentDate; } - if (DateUtils.isBefore(ctx.getModel().lastOverdueBalanceChange(), toDate)) { - emiCalculator.addBalanceCorrection(ctx.getModel(), lastOverdueBalanceChange, aggregatedOverDuePrincipal.negated()); - ctx.getModel().lastOverdueBalanceChange(lastOverdueBalanceChange); - } + emiCalculator.addBalanceCorrection(ctx.getModel(), lastOverdueBalanceChange, aggregatedOverDuePrincipal.negated()); + ctx.getModel().lastOverdueBalanceChange(lastOverdueBalanceChange); } } @@ -1599,10 +1597,32 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep if (loanTransaction.isRepaymentLikeType() || loanTransaction.isInterestWaiver() || loanTransaction.isRecoveryRepayment()) { loanTransaction.resetDerivedComponents(); } + calculateUnrecognizedInterestForClosedPeriodByInterestRecalculationStrategy(loanTransaction, transactionCtx); + Money transactionAmountUnprocessed = loanTransaction.getAmount(transactionCtx.getCurrency()); processTransaction(loanTransaction, transactionCtx, transactionAmountUnprocessed); } + private void calculateUnrecognizedInterestForClosedPeriodByInterestRecalculationStrategy(LoanTransaction loanTransaction, + TransactionCtx transactionCtx) { + if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx && progressiveTransactionCtx.isPrepayAttempt() + && loanTransaction.isRepaymentLikeType() && loanTransaction.getLoan().getLoanInterestRecalculationDetails() + .getPreCloseInterestCalculationStrategy().calculateTillRestFrequencyEnabled()) { + Optional oCurrentRepaymentPeriod = progressiveTransactionCtx.getModel().repaymentPeriods().stream() + .filter(rm -> DateUtils.isDateInRangeFromInclusiveToExclusive(rm.getFromDate(), rm.getDueDate(), + loanTransaction.getTransactionDate())) + .findFirst(); + if (oCurrentRepaymentPeriod.isPresent() && oCurrentRepaymentPeriod.get().isFullyPaid()) { + RepaymentPeriod currentRepaymentPeriod = oCurrentRepaymentPeriod.get(); + OutstandingDetails outstandingAmountsTillDate = emiCalculator + .getOutstandingAmountsTillDate(progressiveTransactionCtx.getModel(), currentRepaymentPeriod.getDueDate()); + if (outstandingAmountsTillDate.getOutstandingInterest().isGreaterThanZero()) { + currentRepaymentPeriod.setFutureUnrecognizedInterest(outstandingAmountsTillDate.getOutstandingInterest()); + } + } + } + } + private LoanTransactionToRepaymentScheduleMapping getTransactionMapping( List transactionMappings, LoanTransaction loanTransaction, LoanRepaymentScheduleInstallment currentInstallment, MonetaryCurrency currency) { @@ -1751,26 +1771,28 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep final LoanRepaymentScheduleInstallment currentInstallment = loan.getRelatedRepaymentScheduleInstallment(transactionDate); if (!installments.isEmpty() && transactionDate.isBefore(loan.getMaturityDate())) { - if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx - && loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) { - final BigDecimal interestOutstanding = currentInstallment.getInterestOutstanding(loan.getCurrency()).getAmount(); - final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(), - currentInstallment.getDueDate(), transactionDate, true).getAmount(); - if (interestOutstanding.compareTo(BigDecimal.ZERO) > 0 || newInterest.compareTo(BigDecimal.ZERO) > 0) { - currentInstallment.updateInterestCharged(newInterest); - } - } else { - final BigDecimal totalInterest = currentInstallment.getInterestOutstanding(transactionCtx.getCurrency()).getAmount(); - if (totalInterest.compareTo(BigDecimal.ZERO) > 0) { - final long totalDaysInPeriod = ChronoUnit.DAYS.between(currentInstallment.getFromDate(), - currentInstallment.getDueDate()); - final long daysTillChargeOff = ChronoUnit.DAYS.between(currentInstallment.getFromDate(), transactionDate); + if (currentInstallment.isNotFullyPaidOff()) { + if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx + && loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) { + final BigDecimal interestOutstanding = currentInstallment.getInterestOutstanding(loan.getCurrency()).getAmount(); + final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(), + currentInstallment.getDueDate(), transactionDate, true).getAmount(); + if (interestOutstanding.compareTo(BigDecimal.ZERO) > 0 || newInterest.compareTo(BigDecimal.ZERO) > 0) { + currentInstallment.updateInterestCharged(newInterest); + } + } else { + final BigDecimal totalInterest = currentInstallment.getInterestOutstanding(transactionCtx.getCurrency()).getAmount(); + if (totalInterest.compareTo(BigDecimal.ZERO) > 0) { + final long totalDaysInPeriod = ChronoUnit.DAYS.between(currentInstallment.getFromDate(), + currentInstallment.getDueDate()); + final long daysTillChargeOff = ChronoUnit.DAYS.between(currentInstallment.getFromDate(), transactionDate); - final MathContext mc = MoneyHelper.getMathContext(); - final Money interestTillChargeOff = Money.of(transactionCtx.getCurrency(), totalInterest - .divide(BigDecimal.valueOf(totalDaysInPeriod), mc).multiply(BigDecimal.valueOf(daysTillChargeOff), mc), mc); + final MathContext mc = MoneyHelper.getMathContext(); + final Money interestTillChargeOff = Money.of(transactionCtx.getCurrency(), totalInterest + .divide(BigDecimal.valueOf(totalDaysInPeriod), mc).multiply(BigDecimal.valueOf(daysTillChargeOff), mc), mc); - currentInstallment.updateInterestCharged(interestTillChargeOff.getAmount()); + currentInstallment.updateInterestCharged(interestTillChargeOff.getAmount()); + } } } @@ -1790,6 +1812,21 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep currentInstallment.updatePrincipal(MathUtil.nullToZero(currentInstallment.getPrincipal()).add(futurePrincipal)); + if (currentInstallment.isObligationsMet()) { + final BigDecimal futureOutstandingPrincipal = futureInstallments.stream() + .map(installment -> installment.getPrincipalOutstanding(transactionCtx.getCurrency()).getAmount()) + .filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add); + + final BigDecimal futureTotalPaidInAdvance = futureInstallments.stream() + .map(LoanRepaymentScheduleInstallment::getTotalPaidInAdvance).filter(Objects::nonNull) + .reduce(ZERO, BigDecimal::add); + + currentInstallment + .setPrincipalCompleted(MathUtil.nullToZero(currentInstallment.getPrincipal()).subtract(futureOutstandingPrincipal)); + currentInstallment.setTotalPaidInAdvance( + MathUtil.nullToZero(currentInstallment.getTotalPaidInAdvance()).add(futureTotalPaidInAdvance)); + } + final List installmentsUpToTransactionDate = installments.stream() .filter(installment -> transactionDate.isAfter(installment.getFromDate())) .collect(Collectors.toCollection(ArrayList::new)); @@ -1817,7 +1854,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep } } - transactionCtx.getInstallments().stream().filter(installment -> installment.getFromDate().isBefore(transactionDate)) + transactionCtx.getInstallments().stream() + .filter(installment -> installment.getFromDate().isBefore(transactionDate) && installment.isNotFullyPaidOff()) .filter(installment -> transactionsToBeReprocessed.stream() .anyMatch(transaction -> transaction.getLoanTransactionToRepaymentScheduleMappings().stream().anyMatch( mapping -> mapping.getInstallment().getInstallmentNumber().equals(installment.getInstallmentNumber())))) @@ -1835,8 +1873,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep newTransaction.updateLoan(loan); loan.getLoanTransactions().add(newTransaction); } - loanBalanceService.updateLoanSummaryDerivedFields(loan); } + loanBalanceService.updateLoanSummaryDerivedFields(loan); } } @@ -2870,9 +2908,14 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep .filter(repaymentPeriod -> previousInstallmentsMapping.stream() .anyMatch(installment -> installment.getFromDate().equals(repaymentPeriod.getFromDate()))) .forEach(RepaymentPeriod::resetDerivedComponents); - emiCalculator.addBalanceCorrection(transactionCtx.getModel(), processTransaction.getTransactionDate(), - processTransaction.getPrincipalPortion(transactionCtx.getCurrency())); - processSingleTransaction(processTransaction, transactionCtx); + + LoanRepaymentScheduleInstallment installment = processTransaction.getLoan() + .getRelatedRepaymentScheduleInstallment(processTransaction.getTransactionDate()); + if (installment == null || installment.isNotFullyPaidOff()) { + emiCalculator.addBalanceCorrection(transactionCtx.getModel(), processTransaction.getTransactionDate(), + processTransaction.getPrincipalPortion(transactionCtx.getCurrency())); + processSingleTransaction(processTransaction, transactionCtx); + } } }