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 1feb1d730fd93dad24a09772173a4dcf0c85a2e6
Author: Oleksii Novikov <[email protected]>
AuthorDate: Wed Feb 11 12:37:54 2026 +0200

    FINERACT-2418: Extend disbursal and reage events with changed terms flag
---
 .../main/avro/loan/v1/LoanTransactionDataV1.avsc   |  9 ++++
 .../avro/loan/v1/LoanTransactionFlagsDataV1.avsc   | 15 ++++++
 .../transaction/LoanReAgeTransactionEvent.java     | 17 ++----
 .../test/stepdef/loan/LoanReAgingStepDef.java      | 18 +++++++
 .../fineract/test/stepdef/loan/LoanStepDef.java    | 33 ++++++++++++
 .../src/test/resources/features/Loan.feature       | 39 +++++++++++++-
 .../test/resources/features/LoanReAging.feature    |  2 +-
 .../LoanDisbursalTransactionBusinessEvent.java     |  4 ++
 .../transaction/LoanTransactionBusinessEvent.java  |  9 ++++
 ...essEvent.java => LoanTransactionFlagsData.java} | 14 +----
 .../reaging/LoanReAgeTransactionBusinessEvent.java |  5 ++
 .../portfolio/loanaccount/domain/Loan.java         | 13 ++---
 .../mapper/loan/LoanTransactionDataMapper.java     |  1 +
 .../LoanTransactionBusinessEventSerializer.java    |  7 +++
 .../LoanWritePlatformServiceJpaRepositoryImpl.java | 21 ++++++--
 .../service/reaging/LoanReAgingService.java        |  8 ++-
 ...LoanTransactionBusinessEventSerializerTest.java | 62 ++++++++++++++++++++++
 17 files changed, 234 insertions(+), 43 deletions(-)

diff --git 
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc 
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc
index f02d6e090a..cca1dab0c8 100644
--- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc
+++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc
@@ -283,6 +283,15 @@
                     "items": 
"org.apache.fineract.avro.loan.v1.OriginatorDetailsV1"
                 }
             ]
+        },
+        {
+            "default": null,
+            "name": "flags",
+            "doc": "Optional flags associated with this transaction event",
+            "type": [
+                "null",
+                "org.apache.fineract.avro.loan.v1.LoanTransactionFlagsDataV1"
+            ]
         }
     ]
 }
diff --git 
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionFlagsDataV1.avsc 
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionFlagsDataV1.avsc
new file mode 100644
index 0000000000..3fa5f7b313
--- /dev/null
+++ 
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionFlagsDataV1.avsc
@@ -0,0 +1,15 @@
+{
+    "name": "LoanTransactionFlagsDataV1",
+    "namespace": "org.apache.fineract.avro.loan.v1",
+    "doc": "Optional flags associated with a loan transaction event",
+    "type": "record",
+    "fields": [{
+        "default": null,
+        "name": "changedTerms",
+        "doc": "True if the transaction changed the number of loan repayment 
terms (installments excluding downpayment and additional)",
+        "type": [
+            "null",
+            "boolean"
+        ]
+    }]
+}
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanDisbursalTransactionBusinessEvent.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanReAgeTransactionEvent.java
similarity index 63%
copy from 
fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanDisbursalTransactionBusinessEvent.java
copy to 
fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanReAgeTransactionEvent.java
index efdf3ac9ba..0dfa67a61f 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanDisbursalTransactionBusinessEvent.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanReAgeTransactionEvent.java
@@ -16,20 +16,13 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction;
+package org.apache.fineract.test.messaging.event.loan.transaction;
 
-import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
-
-public class LoanDisbursalTransactionBusinessEvent extends 
LoanTransactionBusinessEvent {
-
-    private static final String TYPE = "LoanDisbursalTransactionBusinessEvent";
-
-    public LoanDisbursalTransactionBusinessEvent(LoanTransaction value) {
-        super(value);
-    }
+public class LoanReAgeTransactionEvent extends AbstractLoanTransactionEvent {
 
     @Override
-    public String getType() {
-        return TYPE;
+    public String getEventName() {
+        return "LoanReAgeTransactionBusinessEvent";
     }
+
 }
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAgingStepDef.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAgingStepDef.java
index 145c8e12af..d70acbe9a3 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAgingStepDef.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReAgingStepDef.java
@@ -37,6 +37,7 @@ import java.util.Map;
 import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.avro.loan.v1.LoanTransactionFlagsDataV1;
 import org.apache.fineract.client.feign.FineractFeignClient;
 import org.apache.fineract.client.feign.util.CallFailedRuntimeException;
 import org.apache.fineract.client.models.LoanScheduleData;
@@ -49,6 +50,7 @@ import org.apache.fineract.test.helper.ErrorMessageHelper;
 import org.apache.fineract.test.helper.Utils;
 import org.apache.fineract.test.messaging.EventAssertion;
 import org.apache.fineract.test.messaging.event.loan.LoanReAgeEvent;
+import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanReAgeTransactionEvent;
 import org.apache.fineract.test.stepdef.AbstractStepDef;
 import org.apache.fineract.test.support.TestContextKey;
 import org.junit.jupiter.api.Assertions;
@@ -414,4 +416,20 @@ public class LoanReAgingStepDef extends AbstractStepDef {
         assertThat(exception.getDeveloperMessage()).contains(errorMessage);
     }
 
+    @Then("LoanReAgeTransactionBusinessEvent has changedTerms {string}")
+    public void checkReAgeTransactionEventChangedTerms(final String 
expectedChangedTerms) {
+        final PostLoansLoanIdTransactionsResponse reAgingResponse = 
testContext().get(TestContextKey.LOAN_REAGING_RESPONSE);
+        Assertions.assertNotNull(reAgingResponse);
+        final Long transactionId = reAgingResponse.getResourceId();
+        Assertions.assertNotNull(transactionId);
+
+        final Boolean expectedValue = 
"null".equalsIgnoreCase(expectedChangedTerms) ? null : 
Boolean.valueOf(expectedChangedTerms);
+
+        eventAssertion.assertEvent(LoanReAgeTransactionEvent.class, 
transactionId).extractingData(loanTransactionDataV1 -> {
+            final LoanTransactionFlagsDataV1 flags = 
loanTransactionDataV1.getFlags();
+            final Boolean actualChangedTerms = flags == null ? null : 
flags.getChangedTerms();
+            assertThat(actualChangedTerms).as("changedTerms in 
LoanReAgeTransactionBusinessEvent").isEqualTo(expectedValue);
+            return null;
+        });
+    }
 }
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
index 8b5a5470d6..4e22724e98 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
@@ -67,6 +67,7 @@ import 
org.apache.fineract.avro.loan.v1.LoanChargePaidByDataV1;
 import org.apache.fineract.avro.loan.v1.LoanStatusEnumDataV1;
 import org.apache.fineract.avro.loan.v1.LoanTransactionAdjustmentDataV1;
 import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
+import org.apache.fineract.avro.loan.v1.LoanTransactionFlagsDataV1;
 import org.apache.fineract.client.feign.FineractFeignClient;
 import org.apache.fineract.client.feign.util.CallFailedRuntimeException;
 import org.apache.fineract.client.models.AdvancedPaymentData;
@@ -162,6 +163,7 @@ import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanCapitalized
 import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanChargeAdjustmentPostBusinessEvent;
 import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanChargeOffEvent;
 import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanChargeOffUndoEvent;
+import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanDisbursalTransactionEvent;
 import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionAccrualActivityPostEvent;
 import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanTransactionContractTerminationPostBusinessEvent;
 import org.apache.fineract.test.messaging.store.EventStore;
@@ -5922,4 +5924,35 @@ public class LoanStepDef extends AbstractStepDef {
 
         return advancedPaymentData;
     }
+
+    @When("Admin disburses the loan on {string} with {string} EUR transaction 
amount")
+    public void disburseLoanForEventVerification(String 
actualDisbursementDate, String transactionAmount) {
+        PostLoansResponse loanResponse = 
testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+        assertNotNull(loanResponse);
+        long loanId = loanResponse.getLoanId();
+
+        PostLoansLoanIdRequest disburseRequest = 
LoanRequestFactory.defaultLoanDisburseRequest()
+                
.actualDisbursementDate(actualDisbursementDate).transactionAmount(new 
BigDecimal(transactionAmount));
+
+        PostLoansLoanIdResponse loanDisburseResponse = ok(
+                () -> fineractClient.loans().stateTransitions(loanId, 
disburseRequest, Map.of("command", "disburse")));
+        testContext().set(TestContextKey.LOAN_DISBURSE_RESPONSE, 
loanDisburseResponse);
+    }
+
+    @Then("LoanDisbursalTransactionBusinessEvent has changedTerms {string}")
+    public void checkDisbursalTransactionEventChangedTerms(final String 
expectedChangedTerms) {
+        final PostLoansLoanIdResponse disburseResponse = 
testContext().get(TestContextKey.LOAN_DISBURSE_RESPONSE);
+        assertNotNull(disburseResponse);
+        final Long transactionId = disburseResponse.getSubResourceId();
+        assertNotNull(transactionId);
+
+        final Boolean expectedValue = 
"null".equalsIgnoreCase(expectedChangedTerms) ? null : 
Boolean.valueOf(expectedChangedTerms);
+
+        eventAssertion.assertEvent(LoanDisbursalTransactionEvent.class, 
transactionId).extractingData(loanTransactionDataV1 -> {
+            final LoanTransactionFlagsDataV1 flags = 
loanTransactionDataV1.getFlags();
+            final Boolean actualChangedTerms = flags == null ? null : 
flags.getChangedTerms();
+            assertThat(actualChangedTerms).as("changedTerms in 
LoanDisbursalTransactionBusinessEvent").isEqualTo(expectedValue);
+            return null;
+        });
+    }
 }
diff --git a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature 
b/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature
index 5ba518723a..5223f7b12d 100644
--- a/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature
+++ b/fineract-e2e-tests-runner/src/test/resources/features/Loan.feature
@@ -9021,4 +9021,41 @@ Feature: Loan
       | 01 January 2025  | Disbursement      | 1000.0  | 0.0       | 0.0      
| 0.0  | 0.0       | 1000.0       | false    | false    |
       | 01 February 2025 | Disbursement      |  500.0  | 0.0       | 0.0      
| 0.0  | 0.0       | 1500.0       | false    | false    |
       | 01 February 2025 | Repayment         | 1525.89 | 1500.0    | 25.89    
| 0.0  | 0.0       |    0.0       | false    | false    |
-      | 01 February 2025 | Accrual           |  25.89  | 0.0       | 25.89    
| 0.0  | 0.0       |    0.0       | false    | false    |
\ No newline at end of file
+      | 01 February 2025 | Accrual           |  25.89  | 0.0       | 25.89    
| 0.0  | 0.0       |    0.0       | false    | false    |
+
+  Scenario: Verify that changedTerms is false in 
LoanDisbursalTransactionBusinessEvent for initial disbursement
+    When Admin sets the business date to "01 January 2024"
+    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 | 01 
January 2024   | 1000           | 7                      | DECLINING_BALANCE | 
DAILY                       | EQUAL_INSTALLMENTS | 45                | DAYS     
             | 15             | DAYS                   | 3                  | 0 
                      | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "01 January 2024" with "1000" 
amount and expected disbursement date on "01 January 2024"
+    When Admin disburses the loan on "01 January 2024" with "1000" EUR 
transaction amount
+    Then LoanDisbursalTransactionBusinessEvent has changedTerms "false"
+
+  Scenario: Verify that changedTerms is true in 
LoanDisbursalTransactionBusinessEvent when additional disbursement adds new 
terms
+    When Admin sets the business date to "01 January 2024"
+    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_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSE_FULL_TERM_TRANCHE
 | 01 January 2024   | 200            | 9.4822                 | 
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 "200" 
amount and expected disbursement date on "01 January 2024"
+    When Admin disburses the loan on "01 January 2024" with "100" EUR 
transaction amount
+    Then LoanDisbursalTransactionBusinessEvent has changedTerms "false"
+    When Admin sets the business date to "01 February 2024"
+    When Admin disburses the loan on "01 February 2024" with "100" EUR 
transaction amount
+    Then LoanDisbursalTransactionBusinessEvent has changedTerms "true"
+
+  Scenario: Verify that changedTerms is false in 
LoanDisbursalTransactionBusinessEvent when additional disbursement does not 
change terms
+    When Admin sets the business date to "01 January 2024"
+    When Admin creates a client with random data
+    When Admin set "LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE" 
loan product "DEFAULT" transaction type to "NEXT_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_PYMNT_INTEREST_DAILY_EMI_360_30_MULTIDISBURSE | 01 January 
2024   | 300            | 9.4822                 | 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 "300" 
amount and expected disbursement date on "01 January 2024"
+    When Admin disburses the loan on "01 January 2024" with "100" EUR 
transaction amount
+    Then LoanDisbursalTransactionBusinessEvent has changedTerms "false"
+    When Admin sets the business date to "08 January 2024"
+    When Admin disburses the loan on "08 January 2024" with "200" EUR 
transaction amount
+    Then LoanDisbursalTransactionBusinessEvent has changedTerms "false"
\ No newline at end of file
diff --git 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature
index 82e9f1ff17..67622a61bb 100644
--- a/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature
+++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanReAging.feature
@@ -46,7 +46,7 @@ Feature: LoanReAging
       | 01 January 2024  | Down Payment     | 250.0  | 250.0     | 0.0      | 
0.0  | 0.0       | 750.0        | false    |
       | 20 February 2024 | Re-age           | 750.0  | 750.0     | 0.0      | 
0.0  | 0.0       | 0.0          | false    |
     Then Admin checks that delinquency range is: "NO_DELINQUENCY" and has 
delinquentDate ""
-
+    Then LoanReAgeTransactionBusinessEvent has changedTerms "true"
     When Loan Pay-off is made on "20 February 2024"
     Then Loan is closed with zero outstanding balance and it's all 
installments have obligations met
 
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanDisbursalTransactionBusinessEvent.java
 
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanDisbursalTransactionBusinessEvent.java
index efdf3ac9ba..f3b65ffed7 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanDisbursalTransactionBusinessEvent.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanDisbursalTransactionBusinessEvent.java
@@ -28,6 +28,10 @@ public class LoanDisbursalTransactionBusinessEvent extends 
LoanTransactionBusine
         super(value);
     }
 
+    public LoanDisbursalTransactionBusinessEvent(final LoanTransaction value, 
final LoanTransactionFlagsData flags) {
+        super(value, flags);
+    }
+
     @Override
     public String getType() {
         return TYPE;
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanTransactionBusinessEvent.java
 
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanTransactionBusinessEvent.java
index 5a6b434b26..cd9846dd5b 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanTransactionBusinessEvent.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanTransactionBusinessEvent.java
@@ -18,15 +18,24 @@
  */
 package 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction;
 
+import lombok.Getter;
 import 
org.apache.fineract.infrastructure.event.business.domain.AbstractBusinessEvent;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 
+@Getter
 public abstract class LoanTransactionBusinessEvent extends 
AbstractBusinessEvent<LoanTransaction> {
 
     private static final String CATEGORY = "Loan";
 
+    private final LoanTransactionFlagsData flags;
+
     public LoanTransactionBusinessEvent(LoanTransaction value) {
+        this(value, null);
+    }
+
+    public LoanTransactionBusinessEvent(LoanTransaction value, 
LoanTransactionFlagsData flags) {
         super(value);
+        this.flags = flags;
     }
 
     @Override
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanDisbursalTransactionBusinessEvent.java
 
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanTransactionFlagsData.java
similarity index 67%
copy from 
fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanDisbursalTransactionBusinessEvent.java
copy to 
fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanTransactionFlagsData.java
index efdf3ac9ba..a8288b8e23 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanDisbursalTransactionBusinessEvent.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanTransactionFlagsData.java
@@ -18,18 +18,6 @@
  */
 package 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction;
 
-import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+public record LoanTransactionFlagsData(boolean changedTerms) {
 
-public class LoanDisbursalTransactionBusinessEvent extends 
LoanTransactionBusinessEvent {
-
-    private static final String TYPE = "LoanDisbursalTransactionBusinessEvent";
-
-    public LoanDisbursalTransactionBusinessEvent(LoanTransaction value) {
-        super(value);
-    }
-
-    @Override
-    public String getType() {
-        return TYPE;
-    }
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/reaging/LoanReAgeTransactionBusinessEvent.java
 
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/reaging/LoanReAgeTransactionBusinessEvent.java
index fea932aa75..9095d533ba 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/reaging/LoanReAgeTransactionBusinessEvent.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/reaging/LoanReAgeTransactionBusinessEvent.java
@@ -19,6 +19,7 @@
 package 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.reaging;
 
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionFlagsData;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 
 public class LoanReAgeTransactionBusinessEvent extends 
LoanTransactionBusinessEvent {
@@ -29,6 +30,10 @@ public class LoanReAgeTransactionBusinessEvent extends 
LoanTransactionBusinessEv
         super(value);
     }
 
+    public LoanReAgeTransactionBusinessEvent(LoanTransaction value, 
LoanTransactionFlagsData flags) {
+        super(value, flags);
+    }
+
     @Override
     public String getType() {
         return TYPE;
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
index ee155ca67e..56357e9ea0 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java
@@ -1836,19 +1836,12 @@ public class Loan extends 
AbstractAuditableWithUTCDateTimeCustom<Long> {
         return false;
     }
 
-    public boolean hasChargeOffTransaction() {
-        return 
getLoanTransactions().stream().anyMatch(LoanTransaction::isChargeOff);
-    }
-
-    public boolean hasAccelerateChargeOffStrategy() {
-        return 
LoanChargeOffBehaviour.ACCELERATE_MATURITY.equals(getLoanProductRelatedDetail().getChargeOffBehaviour());
-    }
-
     public boolean hasContractTerminationTransaction() {
         return getLoanTransactions().stream().anyMatch(t -> 
t.isContractTermination() && t.isNotReversed());
     }
 
-    public boolean hasReAgingTransaction() {
-        return getLoanTransactions().stream().anyMatch(t -> t.isReAge() && 
t.isNotReversed());
+    public long getTermsCount() {
+        return getRepaymentScheduleInstallments().stream().filter(i -> 
!i.isDownPayment() && !i.isAdditional()).count();
     }
+
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanTransactionDataMapper.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanTransactionDataMapper.java
index 3b0b906ea8..f3f55d4193 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanTransactionDataMapper.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanTransactionDataMapper.java
@@ -33,6 +33,7 @@ public interface LoanTransactionDataMapper {
     @Mapping(target = "customData", ignore = true)
     @Mapping(target = "reversed", expression = "java(isReversed(source))")
     @Mapping(target = "originators", ignore = true)
+    @Mapping(target = "flags", ignore = true)
     LoanTransactionDataV1 map(LoanTransactionData source);
 
     default boolean isReversed(LoanTransactionData source) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializer.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializer.java
index 6d2b303e2f..e6c4ddd8e2 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializer.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializer.java
@@ -23,8 +23,10 @@ import lombok.RequiredArgsConstructor;
 import org.apache.avro.generic.GenericContainer;
 import org.apache.fineract.avro.generator.ByteBufferSerializable;
 import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
+import org.apache.fineract.avro.loan.v1.LoanTransactionFlagsDataV1;
 import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionFlagsData;
 import 
org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanTransactionDataMapper;
 import 
org.apache.fineract.infrastructure.event.external.service.serialization.serializer.AbstractBusinessEventWithCustomDataSerializer;
 import 
org.apache.fineract.infrastructure.event.external.service.serialization.serializer.BusinessEventSerializer;
@@ -60,6 +62,11 @@ public class LoanTransactionBusinessEventSerializer extends 
AbstractBusinessEven
         final LoanTransactionDataV1 result = 
loanTransactionMapper.map(transactionData);
         result.setCustomData(collectCustomData(event));
 
+        LoanTransactionFlagsData flags = event.getFlags();
+        if (flags != null) {
+            
result.setFlags(LoanTransactionFlagsDataV1.newBuilder().setChangedTerms(flags.changedTerms()).build());
+        }
+
         return result;
     }
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
index 7e359523b8..066de64b96 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java
@@ -95,6 +95,7 @@ import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPreBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanDisbursalTransactionBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionFlagsData;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPostBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestPaymentWaiverPreBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionInterestRefundPostBusinessEvent;
@@ -368,6 +369,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
         final Locale locale = command.extractLocale();
         final DateTimeFormatter fmt = 
DateTimeFormatter.ofPattern(command.dateFormat()).withLocale(locale);
 
+        final long termsBefore = loan.getTermsCount();
+
         if (canDisburse(loan)) {
             // Get netDisbursalAmount from disbursal screen field.
             final BigDecimal netDisbursalAmount = command
@@ -511,7 +514,9 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
                     .filter(e -> 
LoanTransactionType.DISBURSEMENT.equals(e.getTypeOf())).findFirst().orElseThrow();
             disbursalTransactionId = disbursalTransaction.getId();
             disbursalTransactionExternalId = 
disbursalTransaction.getExternalId();
-            businessEventNotifierService.notifyPostBusinessEvent(new 
LoanDisbursalTransactionBusinessEvent(disbursalTransaction));
+            final long termsAfter = loan.getTermsCount();
+            businessEventNotifierService.notifyPostBusinessEvent(new 
LoanDisbursalTransactionBusinessEvent(disbursalTransaction,
+                    new LoanTransactionFlagsData(termsAfter != termsBefore)));
         }
 
         businessEventNotifierService.notifyPostBusinessEvent(new 
LoanBalanceChangedBusinessEvent(loan));
@@ -757,17 +762,17 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
                 Money disburseAmount = 
loanDisbursementService.adjustDisburseAmount(loan, command, 
actualDisbursementDate);
                 boolean recalculateSchedule = 
amountBeforeAdjust.isNotEqualTo(loan.getPrincipal());
                 final ExternalId txnExternalId = 
externalIdFactory.createFromCommand(command, 
LoanApiConstants.externalIdParameterName);
+                final long termsBefore = loan.getTermsCount();
+                LoanTransaction disbursementTransaction = null;
                 if (isAccountTransfer) {
                     disburseLoanToSavings(loan, command, disburseAmount, 
paymentDetail);
                 } else {
-                    LoanTransaction disbursementTransaction = 
LoanTransaction.disbursement(loan, disburseAmount, paymentDetail,
-                            actualDisbursementDate, txnExternalId, 
loan.getTotalOverpaidAsMoney());
+                    disbursementTransaction = 
LoanTransaction.disbursement(loan, disburseAmount, paymentDetail, 
actualDisbursementDate,
+                            txnExternalId, loan.getTotalOverpaidAsMoney());
                     disbursementTransaction.updateLoan(loan);
                     loan.addLoanTransaction(disbursementTransaction);
                     
loanTransactionRepository.saveAndFlush(disbursementTransaction);
                     
journalEntryPoster.postJournalEntriesForLoanTransaction(disbursementTransaction,
 false, false);
-                    businessEventNotifierService
-                            .notifyPostBusinessEvent(new 
LoanDisbursalTransactionBusinessEvent(disbursementTransaction));
                 }
                 LocalDate recalculateFrom = null;
                 final ScheduleGeneratorDTO scheduleGeneratorDTO = 
this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
@@ -779,6 +784,12 @@ public class LoanWritePlatformServiceJpaRepositoryImpl 
implements LoanWritePlatf
                 }
                 disburseLoan(command, 
configurationDomainService.isPaymentTypeApplicableForDisbursementCharge(), 
paymentDetail, loan,
                         currentUser, changes, scheduleGeneratorDTO);
+                if (disbursementTransaction != null) {
+                    final long termsAfter = loan.getTermsCount();
+                    final LoanTransactionFlagsData additionalDisbursalFlags = 
new LoanTransactionFlagsData(termsAfter != termsBefore);
+                    businessEventNotifierService.notifyPostBusinessEvent(
+                            new 
LoanDisbursalTransactionBusinessEvent(disbursementTransaction, 
additionalDisbursalFlags));
+                }
 
                 loanAccrualsProcessingService.reprocessExistingAccruals(loan, 
true);
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingService.java
index 9ad702ea0d..665d0a54f2 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingService.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/reaging/LoanReAgingService.java
@@ -44,6 +44,7 @@ import 
org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
 import org.apache.fineract.infrastructure.core.service.MathUtil;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.reaging.LoanReAgeBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.reaging.LoanUndoReAgeBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionFlagsData;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.reaging.LoanReAgeTransactionBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.reaging.LoanUndoReAgeTransactionBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
@@ -105,6 +106,8 @@ public class LoanReAgingService {
         reAgingValidator.validateReAge(loan, command);
         BigDecimal userProvidedTxnAmount = 
command.bigDecimalValueOfParameterNamed(LoanReAgingApiConstants.transactionAmountParamName);
 
+        final long termsBefore = loan.getTermsCount();
+
         final LoanTransaction reAgeTransaction = createReAgeTransaction(loan, 
command);
         processReAgeTransaction(loan, reAgeTransaction, true);
         validateUserProvidedTransactionAmount(userProvidedTxnAmount, 
reAgeTransaction);
@@ -116,9 +119,12 @@ public class LoanReAgingService {
         changes.put(LoanReAgingApiConstants.dateFormatParameterName, 
command.dateFormat());
         persistNote(loan, command, changes);
 
+        final long termsAfter = loan.getTermsCount();
+
         // delinquency recalculation will be triggered by the event in a 
decoupled way via a listener
         businessEventNotifierService.notifyPostBusinessEvent(new 
LoanReAgeBusinessEvent(loan));
-        businessEventNotifierService.notifyPostBusinessEvent(new 
LoanReAgeTransactionBusinessEvent(reAgeTransaction));
+        businessEventNotifierService.notifyPostBusinessEvent(
+                new LoanReAgeTransactionBusinessEvent(reAgeTransaction, new 
LoanTransactionFlagsData(termsAfter != termsBefore)));
         return new CommandProcessingResultBuilder() //
                 .withCommandId(command.commandId()) //
                 .withEntityId(reAgeTransaction.getId()) //
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializerTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializerTest.java
index 0ea71f9cdb..c593b3109c 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializerTest.java
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanTransactionBusinessEventSerializerTest.java
@@ -21,6 +21,8 @@ package 
org.apache.fineract.infrastructure.event.external.service.serialization.
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.mock;
@@ -32,7 +34,9 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanDisbursalTransactionBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionFlagsData;
 import 
org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanTransactionDataMapper;
 import 
org.apache.fineract.infrastructure.event.external.service.serialization.serializer.ExternalEventCustomDataSerializer;
 import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
@@ -133,4 +137,62 @@ class LoanTransactionBusinessEventSerializerTest {
         assertEquals("test_data_for_loan_transaction_2", new 
String(customData.get("test_key_2").array(), UTF_8));
     }
 
+    @Test
+    void testTransactionFlagsSerializationWithChangedTerms() {
+        final long loanId = 1;
+        final long transactionId = 2;
+
+        final Loan loan = mock(Loan.class);
+        final LoanTransaction loanTransaction = mock(LoanTransaction.class);
+        final LoanTransactionData transactionData = 
mock(LoanTransactionData.class);
+
+        when(loan.getId()).thenReturn(loanId);
+        when(loanTransaction.getId()).thenReturn(transactionId);
+        when(loanTransaction.getLoan()).thenReturn(loan);
+
+        final LoanTransactionFlagsData flags = new 
LoanTransactionFlagsData(true);
+        final LoanDisbursalTransactionBusinessEvent event = new 
LoanDisbursalTransactionBusinessEvent(loanTransaction, flags);
+
+        when(loanReadPlatformService.retrieveLoanTransaction(loanId, 
transactionId)).thenReturn(transactionData);
+        
when(loanChargePaidByReadService.fetchLoanChargesPaidByDataTransactionId(anyLong())).thenReturn(new
 ArrayList<>());
+
+        final LoanTransactionDataV1 expectedAvroData = 
LoanTransactionDataV1.newBuilder().setId(transactionId).setLoanId(loanId)
+                .setCustomData(new HashMap<>()).build();
+        
when(loanTransactionMapper.map(any(LoanTransactionData.class))).thenReturn(expectedAvroData);
+
+        final LoanTransactionDataV1 result = (LoanTransactionDataV1) 
serializer.toAvroDTO(event);
+
+        assertNotNull(result);
+        assertNotNull(result.getFlags());
+        assertTrue(result.getFlags().getChangedTerms());
+    }
+
+    @Test
+    void testTransactionWithoutFlagsHasNullFlags() {
+        final long loanId = 1;
+        final long transactionId = 2;
+
+        final Loan loan = mock(Loan.class);
+        final LoanTransaction loanTransaction = mock(LoanTransaction.class);
+        final LoanTransactionData transactionData = 
mock(LoanTransactionData.class);
+
+        when(loan.getId()).thenReturn(loanId);
+        when(loanTransaction.getId()).thenReturn(transactionId);
+        when(loanTransaction.getLoan()).thenReturn(loan);
+
+        final LoanDisbursalTransactionBusinessEvent event = new 
LoanDisbursalTransactionBusinessEvent(loanTransaction);
+
+        when(loanReadPlatformService.retrieveLoanTransaction(loanId, 
transactionId)).thenReturn(transactionData);
+        
when(loanChargePaidByReadService.fetchLoanChargesPaidByDataTransactionId(anyLong())).thenReturn(new
 ArrayList<>());
+
+        final LoanTransactionDataV1 expectedAvroData = 
LoanTransactionDataV1.newBuilder().setId(transactionId).setLoanId(loanId)
+                .setCustomData(new HashMap<>()).build();
+        
when(loanTransactionMapper.map(any(LoanTransactionData.class))).thenReturn(expectedAvroData);
+
+        final LoanTransactionDataV1 result = (LoanTransactionDataV1) 
serializer.toAvroDTO(event);
+
+        assertNotNull(result);
+        assertNull(result.getFlags());
+    }
+
 }


Reply via email to