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 2026/02/18 09:28:06 UTC
(fineract) 01/02: FINERACT-2418: Extend disbursal and reage events with changed terms flag
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 <al...@onix-systems.com>
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());
+ }
+
}