You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ad...@apache.org on 2025/03/03 15:40:03 UTC

(fineract) branch develop updated (f0cc697c6 -> 262f1d9e1)

This is an automated email from the ASF dual-hosted git repository.

adamsaghy pushed a change to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


    from f0cc697c6 FINERACT-2333: Accrual handling in case of Charged-off loan
     new b546a43ce FINERACT-2179: Introduce Next/Last in future allocation rule for progressive loans
     new 262f1d9e1 FINERACT-2179: Added use-cases to cover next/last installment payment allocation

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../test/resources/features/LoanRepayment.feature  |  58 ++++++++++
 .../domain/FutureInstallmentAllocationRule.java    |   1 +
 ...dvancedPaymentScheduleTransactionProcessor.java |  39 +++++++
 .../integrationtests/BaseLoanIntegrationTest.java  |   1 +
 .../integrationtests/LoanProductTemplateTest.java  |   8 +-
 ...essiveLoanTransactionProcessorNextLastTest.java | 120 +++++++++++++++++++++
 .../common/loans/LoanTransactionHelper.java        |  19 +++-
 7 files changed, 243 insertions(+), 3 deletions(-)
 create mode 100644 integration-tests/src/test/java/org/apache/fineract/integrationtests/ProgressiveLoanTransactionProcessorNextLastTest.java


(fineract) 02/02: FINERACT-2179: Added use-cases to cover next/last installment payment allocation

Posted by ad...@apache.org.
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 262f1d9e10c516f164281b6134d8ee6348026424
Author: tamasszabo-dpc <ta...@dpc.hu>
AuthorDate: Fri Feb 28 14:56:44 2025 +0100

    FINERACT-2179: Added use-cases to cover next/last installment payment allocation
---
 .../test/resources/features/LoanRepayment.feature  | 58 ++++++++++++++++++++++
 1 file changed, 58 insertions(+)

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 52872eacf..27d875a38 100644
--- a/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
+++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanRepayment.feature
@@ -4391,4 +4391,62 @@ Feature: LoanRepayment
       | 25 January 2025  | Accrual          | 0.15   | 0.0       | 0.15     | 0.0  | 0.0       |  0.0         | false    | false    |
       | 25 January 2025  | Goodwill Credit  | 200.0  | 200.0     | 0.0      | 0.0  | 0.0       |  600.0       | false    | true     |
 
+  @TestRailId:C3520
+  Scenario: Verify the next/last installament payment allocation in case the repayment is before the installment date
+    When Admin sets the business date to "01 January 2024"
+    When Admin creates a client with random data
+    When Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_LAST_INSTALLMENT" future installment allocation rule
+    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 | 01 January 2024   | 100            | 7                      | DECLINING_BALANCE | DAILY                       | EQUAL_INSTALLMENTS | 6                 | MONTHS                | 1              | MONTHS                 | 6                  | 0                       | 0                      | 0                    | ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024"
+    When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount
+    When Admin sets the business date to "15 January 2024"
+    And Customer makes "AUTOPAY" repayment on "15 January 2024" with 20 EUR transaction amount
+    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 January 2024  |                 | 100.0           |               |          | 0.0  |           | 0.0   | 0.0   |            |      |             |
+      | 1  | 31   | 01 February 2024 | 15 January 2024 | 83.25           | 16.75         | 0.26     | 0.0  | 0.0       | 17.01 | 17.01 | 17.01      | 0.0  | 0.0         |
+      | 2  | 29   | 01 March 2024    |                 | 66.97           | 16.28         | 0.73     | 0.0  | 0.0       | 17.01 | 0.0   | 0.0        | 0.0  | 17.01       |
+      | 3  | 31   | 01 April 2024    |                 | 50.33           | 16.64         | 0.37     | 0.0  | 0.0       | 17.01 | 0.0   | 0.0        | 0.0  | 17.01       |
+      | 4  | 30   | 01 May 2024      |                 | 33.6            | 16.73         | 0.28     | 0.0  | 0.0       | 17.01 | 0.0   | 0.0        | 0.0  | 17.01       |
+      | 5  | 31   | 01 June 2024     |                 | 16.77           | 16.83         | 0.18     | 0.0  | 0.0       | 17.01 | 0.0   | 0.0        | 0.0  | 17.01       |
+      | 6  | 30   | 01 July 2024     |                 | 0.0             | 16.77         | 0.08     | 0.0  | 0.0       | 16.85 | 2.99  | 2.99       | 0.0  | 13.86       |
+    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.9      | 0.0  | 0.0       | 101.9 | 20.0 | 20.0       | 0.0  | 81.9        |
+    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    |
+      | 15 January 2024  | Repayment        | 20.0   | 19.74     | 0.26     | 0.0  | 0.0       | 80.26        | 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
 
+  @TestRailId:C3521
+  Scenario: Verify the next/last installament payment allocation in case the repayment is on the installment date
+    When Admin sets the business date to "01 January 2024"
+    When Admin creates a client with random data
+    When Admin set "LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL" loan product "DEFAULT" transaction type to "NEXT_LAST_INSTALLMENT" future installment allocation rule
+    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 | 01 January 2024   | 100            | 7                      | DECLINING_BALANCE | DAILY                       | EQUAL_INSTALLMENTS | 6                 | MONTHS                | 1              | MONTHS                 | 6                  | 0                       | 0                      | 0                    | ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "01 January 2024" with "100" amount and expected disbursement date on "01 January 2024"
+    When Admin successfully disburse the loan on "01 January 2024" with "100" EUR transaction amount
+    When Admin sets the business date to "01 February 2024"
+    And Customer makes "AUTOPAY" repayment on "01 February 2024" with 20 EUR transaction amount
+    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 January 2024  |                  | 100.0           |               |          | 0.0  |           | 0.0   | 0.0   |            |      |             |
+      | 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.03           | 16.54         | 0.47     | 0.0  | 0.0       | 17.01 | 0.0   | 0.0        | 0.0  | 17.01       |
+      | 3  | 31   | 01 April 2024    |                  | 50.39           | 16.64         | 0.37     | 0.0  | 0.0       | 17.01 | 0.0   | 0.0        | 0.0  | 17.01       |
+      | 4  | 30   | 01 May 2024      |                  | 33.66           | 16.73         | 0.28     | 0.0  | 0.0       | 17.01 | 0.0   | 0.0        | 0.0  | 17.01       |
+      | 5  | 31   | 01 June 2024     |                  | 16.83           | 16.83         | 0.18     | 0.0  | 0.0       | 17.01 | 0.0   | 0.0        | 0.0  | 17.01       |
+      | 6  | 30   | 01 July 2024     |                  | 0.0             | 16.83         | 0.08     | 0.0  | 0.0       | 16.91 | 2.99  | 2.99       | 0.0  | 13.92       |
+    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.96     | 0.0  | 0.0       | 101.96 | 20.0 | 2.99       | 0.0  | 81.96       |
+    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 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


(fineract) 01/02: FINERACT-2179: Introduce Next/Last in future allocation rule for progressive loans

Posted by ad...@apache.org.
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 b546a43ce9736cbaeba1348fcdda6ae84f0e4059
Author: Soma Sörös <so...@dpc.hu>
AuthorDate: Mon Feb 24 20:28:24 2025 +0100

    FINERACT-2179: Introduce Next/Last in future allocation rule for progressive loans
---
 .../domain/FutureInstallmentAllocationRule.java    |   1 +
 ...dvancedPaymentScheduleTransactionProcessor.java |  39 +++++++
 .../integrationtests/BaseLoanIntegrationTest.java  |   1 +
 .../integrationtests/LoanProductTemplateTest.java  |   8 +-
 ...essiveLoanTransactionProcessorNextLastTest.java | 120 +++++++++++++++++++++
 .../common/loans/LoanTransactionHelper.java        |  19 +++-
 6 files changed, 185 insertions(+), 3 deletions(-)

diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/FutureInstallmentAllocationRule.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/FutureInstallmentAllocationRule.java
index 854ac2b73..806c67478 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/FutureInstallmentAllocationRule.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/domain/FutureInstallmentAllocationRule.java
@@ -31,6 +31,7 @@ public enum FutureInstallmentAllocationRule {
 
     NEXT_INSTALLMENT("Next installment"), //
     LAST_INSTALLMENT("Last installment"), //
+    NEXT_LAST_INSTALLMENT("Next/Last installment"), //
     REAMORTIZATION("Reamortization"); //
 
     private final String humanReadableName;
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 8d1855b7f..53adf3d5b 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
@@ -1764,6 +1764,18 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
             inAdvanceInstallments = installments.stream().filter(installment -> installment.getTotalPaid(currency).isGreaterThan(zero))
                     .filter(e -> loanTransaction.isBefore(e.getDueDate()))
                     .max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList();
+        } else if (FutureInstallmentAllocationRule.NEXT_LAST_INSTALLMENT.equals(futureInstallmentAllocationRule)) {
+            // try to resolve as current installment ( not due )
+            inAdvanceInstallments = installments.stream().filter(installment -> installment.getTotalPaid(currency).isGreaterThan(zero))
+                    .filter(e -> loanTransaction.isBefore(e.getDueDate())).filter(f -> loanTransaction.isAfter(f.getFromDate())
+                            || (loanTransaction.isOn(f.getFromDate()) && f.getInstallmentNumber() == 1))
+                    .toList();
+            // if there is no current installment, resolve similar to LAST_INSTALLMENT
+            if (inAdvanceInstallments.isEmpty()) {
+                inAdvanceInstallments = installments.stream().filter(installment -> installment.getTotalPaid(currency).isGreaterThan(zero))
+                        .filter(e -> loanTransaction.isBefore(e.getDueDate()))
+                        .max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList();
+            }
         }
         return inAdvanceInstallments;
     }
@@ -1853,6 +1865,18 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
                 inAdvanceInstallments = installments.stream().filter(LoanRepaymentScheduleInstallment::isNotFullyPaidOff)
                         .filter(e -> loanTransaction.isBefore(e.getDueDate()))
                         .max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList();
+            } else if (FutureInstallmentAllocationRule.NEXT_LAST_INSTALLMENT.equals(futureInstallmentAllocationRule)) {
+                // try to resolve as current installment ( not due )
+                inAdvanceInstallments = installments.stream().filter(LoanRepaymentScheduleInstallment::isNotFullyPaidOff)
+                        .filter(e -> loanTransaction.isBefore(e.getDueDate())).filter(f -> loanTransaction.isAfter(f.getFromDate())
+                                || (loanTransaction.isOn(f.getFromDate()) && f.getInstallmentNumber() == 1))
+                        .toList();
+                // if there is no current installment, resolve similar to LAST_INSTALLMENT
+                if (inAdvanceInstallments.isEmpty()) {
+                    inAdvanceInstallments = installments.stream().filter(LoanRepaymentScheduleInstallment::isNotFullyPaidOff)
+                            .filter(e -> loanTransaction.isBefore(e.getDueDate()))
+                            .max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList();
+                }
             }
 
             int firstNormalInstallmentNumber = LoanRepaymentScheduleProcessingWrapper.fetchFirstNormalInstallmentNumber(installments);
@@ -2089,6 +2113,21 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
                             currentInstallments = installments.stream().filter(predicate)
                                     .filter(e -> loanTransaction.isBefore(e.getDueDate()))
                                     .max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream().toList();
+                        } else if (FutureInstallmentAllocationRule.NEXT_LAST_INSTALLMENT.equals(futureInstallmentAllocationRule)) {
+                            // get current installment where from date < transaction date < to date OR transaction date
+                            // is on first installment's first day ( from day )
+                            currentInstallments = installments.stream().filter(predicate)
+                                    .filter(e -> loanTransaction.isBefore(e.getDueDate()))
+                                    .filter(f -> loanTransaction.isAfter(f.getFromDate())
+                                            || (loanTransaction.isOn(f.getFromDate()) && f.getInstallmentNumber() == 1))
+                                    .toList();
+                            // if there is no current in advance installment resolve similar to LAST_INSTALLMENT
+                            if (currentInstallments.isEmpty()) {
+                                currentInstallments = installments.stream().filter(predicate)
+                                        .filter(e -> loanTransaction.isBefore(e.getDueDate()))
+                                        .max(Comparator.comparing(LoanRepaymentScheduleInstallment::getInstallmentNumber)).stream()
+                                        .toList();
+                            }
                         }
                         int numberOfInstallments = currentInstallments.size();
                         paidPortion = Money.zero(currency);
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
index f1d3f15fc..414180cc4 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java
@@ -1427,6 +1427,7 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest {
 
         public static final String LAST_INSTALLMENT = "LAST_INSTALLMENT";
         public static final String NEXT_INSTALLMENT = "NEXT_INSTALLMENT";
+        public static final String NEXT_LAST_INSTALLMENT = "NEXT_LAST_INSTALLMENT";
 
     }
 
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductTemplateTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductTemplateTest.java
index 269244416..b159809f0 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductTemplateTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanProductTemplateTest.java
@@ -93,9 +93,13 @@ public class LoanProductTemplateTest {
                 loanProductsTemplateResponse.getAdvancedPaymentAllocationFutureInstallmentAllocationRules().get(1).getCode());
         assertEquals("Last installment",
                 loanProductsTemplateResponse.getAdvancedPaymentAllocationFutureInstallmentAllocationRules().get(1).getValue());
-        assertEquals("REAMORTIZATION",
+        assertEquals("NEXT_LAST_INSTALLMENT",
                 loanProductsTemplateResponse.getAdvancedPaymentAllocationFutureInstallmentAllocationRules().get(2).getCode());
-        assertEquals("Reamortization",
+        assertEquals("Next/Last installment",
                 loanProductsTemplateResponse.getAdvancedPaymentAllocationFutureInstallmentAllocationRules().get(2).getValue());
+        assertEquals("REAMORTIZATION",
+                loanProductsTemplateResponse.getAdvancedPaymentAllocationFutureInstallmentAllocationRules().get(3).getCode());
+        assertEquals("Reamortization",
+                loanProductsTemplateResponse.getAdvancedPaymentAllocationFutureInstallmentAllocationRules().get(3).getValue());
     }
 }
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ProgressiveLoanTransactionProcessorNextLastTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ProgressiveLoanTransactionProcessorNextLastTest.java
new file mode 100644
index 000000000..73edb99ab
--- /dev/null
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ProgressiveLoanTransactionProcessorNextLastTest.java
@@ -0,0 +1,120 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
+import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.junit.jupiter.api.Test;
+
+public class ProgressiveLoanTransactionProcessorNextLastTest extends BaseLoanIntegrationTest {
+
+    private final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
+
+    @Test
+    public void testPartialEarlyRepaymentWithNextLast() {
+        AtomicReference<Long> loanIdRef = new AtomicReference<>();
+        runAt("1 January 2024", () -> {
+            Long progressiveLoanInterestRecalculationNextLastId = loanProductHelper
+                    .createLoanProduct(create4IProgressive().isInterestRecalculationEnabled(true).loanScheduleProcessingType("HORIZONTAL")
+                            .paymentAllocation(
+                                    List.of(createPaymentAllocation("DEFAULT", FuturePaymentAllocationRule.NEXT_LAST_INSTALLMENT))))
+                    .getResourceId();
+            Long loanId = applyAndApproveProgressiveLoan(clientId, progressiveLoanInterestRecalculationNextLastId, "1 January 2024", 100.0,
+                    65.7, 6, null);
+            loanIdRef.set(loanId);
+
+            loanTransactionHelper.disburseLoan(loanId, "1 January 2024", 100.0);
+            verifyRepaymentSchedule(loanId, installment(100.0, null, "01 January 2024"),
+                    installment(14.52, 5.48, 20.0, false, "01 February 2024"), //
+                    installment(15.32, 4.68, 20.0, false, "01 March 2024"), //
+                    installment(16.16, 3.84, 20.0, false, "01 April 2024"), //
+                    installment(17.04, 2.96, 20.0, false, "01 May 2024"), //
+                    installment(17.98, 2.02, 20.0, false, "01 June 2024"), //
+                    installment(18.98, 1.04, 20.02, false, "01 July 2024"));
+
+            // should pay to first installment - edge case coming from implementation
+            loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "1 January 2024", 5.0);
+            verifyRepaymentSchedule(loanId, installment(100.0, null, "01 January 2024"), //
+                    installment(14.8, 5.2, 15.0, false, "01 February 2024"), //
+                    installment(15.34, 4.66, 20.0, false, "01 March 2024"), //
+                    installment(16.18, 3.82, 20.0, false, "01 April 2024"), //
+                    installment(17.06, 2.94, 20.0, false, "01 May 2024"), //
+                    installment(18.0, 2.0, 20.0, false, "01 June 2024"), //
+                    installment(18.62, 1.02, 19.64, false, "01 July 2024"));
+        });
+        runAt("31 January 2024", () -> {
+            Long loanId = loanIdRef.get();
+
+            // test the repayment before the due date. Should go to 1st installment.
+            loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "31 January 2024", 4.0);
+            verifyRepaymentSchedule(loanId, installment(100.0, null, "01 January 2024"), //
+                    installment(14.81, 5.19, 11.0, false, "01 February 2024"), //
+                    installment(15.34, 4.66, 20.0, false, "01 March 2024"), //
+                    installment(16.18, 3.82, 20.0, false, "01 April 2024"), //
+                    installment(17.06, 2.94, 20.0, false, "01 May 2024"), //
+                    installment(18.0, 2.0, 20.0, false, "01 June 2024"), //
+                    installment(18.61, 1.02, 19.63, false, "01 July 2024"));
+
+            // test the repayment before the due date. Should go to 1st installment, and rest to last installment.
+            loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "31 January 2024", 20.0);
+            verifyRepaymentSchedule(loanId, installment(100.0, null, "01 January 2024"),
+                    installment(14.97, 5.03, 0.0, true, "01 February 2024"), installment(15.7, 4.3, 20.0, false, "01 March 2024"),
+                    installment(16.7, 3.3, 20.0, false, "01 April 2024"), installment(17.61, 2.39, 20.0, false, "01 May 2024"),
+                    installment(18.58, 1.42, 20.0, false, "01 June 2024"), installment(16.44, 0.41, 7.85, false, "01 July 2024"));
+        });
+        runAt("1 March 2024", () -> {
+            Long loanId = loanIdRef.get();
+            // test repayment on due date. should repay 2nd installment normally and rest should go to last installment.
+            loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "1 March 2024", 26.0);
+            verifyRepaymentSchedule(loanId, installment(100.0, null, "01 January 2024"),
+                    installment(14.97, 5.03, 0.0, true, "01 February 2024"), installment(15.7, 4.3, 0.0, true, "01 March 2024"),
+                    installment(17.03, 2.97, 20.0, false, "01 April 2024"), installment(17.96, 2.04, 20.0, false, "01 May 2024"),
+                    installment(18.94, 1.06, 20.0, false, "01 June 2024"), installment(15.4, 0.02, 0.42, false, "01 July 2024"));
+        });
+        runAt("2 March 2024", () -> {
+            Long loanId = loanIdRef.get();
+            // verify multiple partial repayment for "current" installment
+            loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "2 March 2024", 7.0);
+            verifyRepaymentSchedule(loanId, installment(100.0, null, "01 January 2024"),
+                    installment(14.97, 5.03, 0.0, true, "01 February 2024"), installment(15.7, 4.3, 0.0, true, "01 March 2024"),
+                    installment(17.4, 2.6, 13.0, false, "01 April 2024"), installment(17.98, 2.02, 20.0, false, "01 May 2024"),
+                    installment(18.95, 1.04, 19.99, false, "01 June 2024"), installment(15.0, 0.0, 0.0, true, "01 July 2024"));
+            // verify multiple partial repayment for "current" installment
+            loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "2 March 2024", 7.0);
+            verifyRepaymentSchedule(loanId, installment(100.0, null, "01 January 2024"),
+                    installment(14.97, 5.03, 0.0, true, "01 February 2024"), installment(15.7, 4.3, 0.0, true, "01 March 2024"),
+                    installment(17.77, 2.23, 6.0, false, "01 April 2024"), installment(18.0, 2.0, 20.0, false, "01 May 2024"),
+                    installment(18.56, 1.02, 19.58, false, "01 June 2024"), installment(15.0, 0.0, 0.0, true, "01 July 2024"));
+            // verify next then last installment logic.
+            loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "2 March 2024", 22.0);
+            verifyRepaymentSchedule(loanId, installment(100.0, null, "01 January 2024"),
+                    installment(14.97, 5.03, 0.0, true, "01 February 2024"), installment(15.7, 4.3, 0.0, true, "01 March 2024"),
+                    installment(19.9, 0.1, 0.0, true, "01 April 2024"), installment(18.02, 1.98, 20.0, false, "01 May 2024"),
+                    installment(16.41, 0.02, 0.43, false, "01 June 2024"), installment(15.0, 0.0, 0.0, true, "01 July 2024"));
+            // verify last installment logic.
+            loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "2 March 2024", 22.0);
+            verifyRepaymentSchedule(loanId, installment(100.0, null, "01 January 2024"),
+                    installment(14.97, 5.03, 0.0, true, "01 February 2024"), installment(15.7, 4.3, 0.0, true, "01 March 2024"),
+                    installment(19.9, 0.1, 0.0, true, "01 April 2024"), installment(14.43, 0.0, 0.0, true, "01 May 2024"),
+                    installment(20.0, 0.0, 0.0, true, "01 June 2024"), installment(15.0, 0.0, 0.0, true, "01 July 2024"));
+        });
+    }
+
+}
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index 41fb81bc3..58b4236c5 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -871,12 +871,13 @@ public class LoanTransactionHelper {
     @Deprecated(forRemoval = true)
     public PostLoansLoanIdTransactionsResponse makeLoanRepayment(final String repaymentTypeCommand, final String date,
             final Float amountToBePaid, final Integer loanID) {
-        log.info("Repayment with amount {} in {} for Loan {}", amountToBePaid, date, loanID);
+        log.info("{} with amount {} in {} for Loan {}", repaymentTypeCommand, amountToBePaid, date, loanID);
         return postLoanTransaction(createLoanTransactionURL(repaymentTypeCommand, loanID), getRepaymentBodyAsJSON(date, amountToBePaid));
     }
 
     public PostLoansLoanIdTransactionsResponse makeLoanRepayment(final Long loanId, final String command, final String date,
             final Double amountToBePaid) {
+        log.info("Make loan transaction. Command - {} with amount {} in {} for Loan {}", command, amountToBePaid, date, loanId);
         return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction(loanId,
                 new PostLoansLoanIdTransactionsRequest().transactionAmount(amountToBePaid).transactionDate(date).dateFormat("dd MMMM yyyy")
                         .locale("en"),
@@ -2792,6 +2793,22 @@ public class LoanTransactionHelper {
         return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitions(loanId, request, "disburse"));
     }
 
+    /**
+     * Disburse loan on provided date and amount.
+     *
+     * @param loanId
+     *            loan Id
+     * @param date
+     *            formatted to "d MMMM yyyy"
+     * @param amount
+     *            amount to disburse
+     * @return Post Loans Loan Id Response
+     */
+    public PostLoansLoanIdResponse disburseLoan(Long loanId, String date, Double amount) {
+        return disburseLoan(loanId, new PostLoansLoanIdRequest().actualDisbursementDate(date).dateFormat(DATE_FORMAT)
+                .transactionAmount(BigDecimal.valueOf(amount)).locale("en"));
+    }
+
     public PostLoansLoanIdResponse disburseToSavingsLoan(String loanExternalId, PostLoansLoanIdRequest request) {
         return Calls.ok(FineractClientHelper.getFineractClient().loans.stateTransitions1(loanExternalId, request, "disburseToSavings"));
     }