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 33c74abeb73f72bdafb7569cf35d2358f6f49a44
Author: Oleksii Novikov <[email protected]>
AuthorDate: Tue Aug 26 16:56:00 2025 +0300

    FINERACT-2355: Fix charge off and loan charge for zero installments
---
 .../src/test/resources/features/LoanCharge.feature | 49 +++++++++++++++++++++-
 .../loanaccount/domain/LoanTransaction.java        |  4 ++
 .../LoanTransactionToRepaymentScheduleMapping.java |  4 ++
 ...dvancedPaymentScheduleTransactionProcessor.java |  7 +++-
 4 files changed, 61 insertions(+), 3 deletions(-)

diff --git 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature
index 6bbb3de045..b4ddda4c2c 100644
--- a/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature
+++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanCharge.feature
@@ -7776,4 +7776,51 @@ Feature: LoanCharge
       | 1125.0        | 0.0      | 30.0 | 0.0       | 1155.0 | 250.0 | 0.0     
   | 0.0  | 905.0       |
     Then Loan Charges tab has the following data:
       | Name                 | isPenalty | Payment due at  | Due as of | 
Calculation type | Due  | Paid | Waived | Outstanding |
-      | Installment flat fee | false     | Installment Fee |           | Flat  
           | 30.0 | 0.0  | 0.0    | 30.0        |
\ No newline at end of file
+      | Installment flat fee | false     | Installment Fee |           | Flat  
           | 30.0 | 0.0  | 0.0    | 30.0        |
+
+  Scenario: Verify Loan Charge together with paid installments with amounts 
zero
+    When Admin sets the business date to "11 April 2025"
+    And Admin creates a client with random data
+    And 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 | 11 
April 2025     | 1001           | 12.25                  | DECLINING_BALANCE | 
DAILY                       | EQUAL_INSTALLMENTS | 24                | MONTHS   
             | 1              | MONTHS                 | 24                 | 0 
                      | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "11 April 2025" with "209.72" 
amount and expected disbursement date on "11 April 2025"
+    And Admin successfully disburse the loan on "11 April 2025" with "209.72" 
EUR transaction amount
+    And Admin sets the business date to "11 May 2025"
+    And Customer makes "AUTOPAY" repayment on "11 May 2025" with 9.9 EUR 
transaction amount
+    And Admin sets the business date to "11 June 2025"
+    And Customer makes "AUTOPAY" repayment on "11 June 2025" with 9.9 EUR 
transaction amount
+    And Admin sets the business date to "11 July 2025"
+    And Customer makes "AUTOPAY" repayment on "11 July 2025" with 9.9 EUR 
transaction amount
+    And Admin sets the business date to "11 August 2025"
+    And Customer makes "AUTOPAY" repayment on "11 August 2025" with 9.9 EUR 
transaction amount
+    And Admin sets the business date to "13 August 2025"
+    And Customer makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" 
payment type on "13 August 2025" with 188.8 EUR transaction amount and 
system-generated Idempotency key
+    Then Loan Repayment schedule has 24 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 |
+      | 1  | 30   | 11 May 2025       | 11 May 2025    | 201.96          | 
7.76          | 2.14     | 0.0  | 0.0       | 9.9  | 9.9   | 0.0        | 0.0  
| 0.0         |
+      | 2  | 31   | 11 June 2025      | 11 June 2025   | 194.12          | 
7.84          | 2.06     | 0.0  | 0.0       | 9.9  | 9.9  | 0.0        | 0.0  | 
0.0         |
+      | 3  | 30   | 11 July 2025      | 11 July 2025   | 186.2           | 
7.92          | 1.98     | 0.0  | 0.0       | 9.9  | 9.9  | 0.0        | 0.0  | 
0.0         |
+      | 4  | 31   | 11 August 2025    | 11 August 2025 | 178.2           | 8.0 
          | 1.9      | 0.0  | 0.0       | 9.9  | 9.9  | 0.0        | 0.0  | 0.0 
        |
+      | 5  | 31   | 11 September 2025 | 13 August 2025 | 178.2           | 0.0 
          | 0.12     | 0.0  | 0.0       | 0.12 | 0.12 | 0.12       | 0.0  | 0.0 
        |
+      | 6  | 30   | 11 October 2025   | 13 August 2025 | 178.2           | 0.0 
          | 0.0      | 0.0  | 0.0       | 0.0  | 0.0  | 0.0        | 0.0  | 0.0 
        |
+      | 7  | 31   | 11 November 2025  | 13 August 2025 | 168.3           | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+      | 8  | 30   | 11 December 2025  | 13 August 2025 | 158.4           | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+      | 9  | 31   | 11 January 2026   | 13 August 2025 | 148.5           | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+      | 10 | 31   | 11 February 2026  | 13 August 2025 | 138.6           | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+      | 11 | 28   | 11 March 2026     | 13 August 2025 | 128.7           | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+      | 12 | 31   | 11 April 2026     | 13 August 2025 | 118.8           | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+      | 13 | 30   | 11 May 2026       | 13 August 2025 | 108.9           | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+      | 14 | 31   | 11 June 2026      | 13 August 2025 | 99.0            | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+      | 15 | 30   | 11 July 2026      | 13 August 2025 | 89.1            | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+      | 16 | 31   | 11 August 2026    | 13 August 2025 | 79.2            | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+      | 17 | 31   | 11 September 2026 | 13 August 2025 | 69.3            | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+      | 18 | 30   | 11 October 2026   | 13 August 2025 | 59.4            | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+      | 19 | 31   | 11 November 2026  | 13 August 2025 | 49.5            | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+      | 20 | 30   | 11 December 2026  | 13 August 2025 | 39.6            | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+      | 21 | 31   | 11 January 2027   | 13 August 2025 | 29.7            | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+      | 22 | 31   | 11 February 2027  | 13 August 2025 | 19.8            | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+      | 23 | 28   | 11 March 2027     | 13 August 2025 | 9.9             | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+      | 24 | 31   | 11 April 2027     | 13 August 2025 | 0.0             | 9.9 
          | 0.0      | 0.0  | 0.0       | 9.9  | 9.9  | 9.9        | 0.0  | 0.0 
        |
+    When Admin sets the business date to "15 August 2025"
+    When Admin adds "LOAN_NSF_FEE" due date charge with "15 August 2025" due 
date and 35 EUR transaction amount
\ No newline at end of file
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index 230d3f3dde..f77a490ae1 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -914,6 +914,10 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom<Long
                 retainMappings.add(newMapping);
             }
         }
+
+        if (retainMappings != null) {
+            
retainMappings.removeIf(LoanTransactionToRepaymentScheduleMapping::isZeroAmount);
+        }
     }
 
     public void updateLoanChargePaidMappings(final 
Collection<LoanChargePaidBy> updatedMappings) {
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionToRepaymentScheduleMapping.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionToRepaymentScheduleMapping.java
index f07d0bfda7..13680708b0 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionToRepaymentScheduleMapping.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionToRepaymentScheduleMapping.java
@@ -141,4 +141,8 @@ public class LoanTransactionToRepaymentScheduleMapping 
extends AbstractPersistab
         return Money.of(currency, this.penaltyChargesPortion);
     }
 
+    public boolean isZeroAmount() {
+        return this.amount == null || this.amount.compareTo(BigDecimal.ZERO) 
== 0;
+    }
+
 }
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 94896dc8ae..974597f897 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
@@ -1973,8 +1973,11 @@ public class AdvancedPaymentScheduleTransactionProcessor 
extends AbstractLoanRep
                         }
                     });
 
-            final Money amountToEditLastInstallment = 
loanTransaction.getLoan().getPrincipal().minus(installments.stream()
-                    .filter(i -> 
!i.isAdditional()).map(LoanRepaymentScheduleInstallment::getPrincipal).reduce(ZERO,
 BigDecimal::add));
+            final Money amountToEditLastInstallment = 
loanTransaction.getLoan().getPrincipal().minus(installments.stream() //
+                    .filter(i -> i.getPrincipal() != null) //
+                    .filter(i -> !i.isAdditional()) //
+                    .map(LoanRepaymentScheduleInstallment::getPrincipal) //
+                    .reduce(ZERO, BigDecimal::add));
 
             BigDecimal principalBalance = 
amountToEditLastInstallment.getAmount();
             for (int i = installments.size() - 1; i > 0 && 
BigDecimal.ZERO.compareTo(principalBalance) != 0; i--) {

Reply via email to