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 6cd634cb2fde6b73d81ef32e82d8afbec2d61b4d Author: adam.magyari <[email protected]> AuthorDate: Tue Jun 3 12:23:20 2025 +0200 FINERACT-2232: Capitalized income - Business events --- ...lizedIncomeTransactionCreatedBusinessEvent.java | 35 +++ .../CapitalizedIncomeWritePlatformServiceImpl.java | 12 + .../ProgressiveLoanAccountConfiguration.java | 6 +- .../db/changelog/tenant/changelog-tenant.xml | 1 + ...alizedIncomeTransactionCreatedBusinessEvent.xml | 31 +++ ...nalEventConfigurationValidationServiceTest.java | 6 +- .../integrationtests/BaseLoanIntegrationTest.java | 43 +++- .../CustomSnapshotEventIntegrationTest.java | 7 - .../ExternalBusinessEventTest.java | 253 +-------------------- .../LoanCapitalizedIncomeTest.java | 113 +++++++++ .../common/ExternalEventConfigurationHelper.java | 5 + .../common/externalevents/BusinessEvent.java | 43 ++++ .../LoanAdjustTransactionBusinessEvent.java | 122 ++++++++++ .../common/externalevents/LoanBusinessEvent.java | 85 +++++++ .../LoanTransactionBusinessEvent.java | 63 +++++ 15 files changed, 563 insertions(+), 262 deletions(-) diff --git a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanCapitalizedIncomeTransactionCreatedBusinessEvent.java b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanCapitalizedIncomeTransactionCreatedBusinessEvent.java new file mode 100644 index 0000000000..49a7e22d13 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanCapitalizedIncomeTransactionCreatedBusinessEvent.java @@ -0,0 +1,35 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.event.business.domain.loan.transaction; + +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; + +public class LoanCapitalizedIncomeTransactionCreatedBusinessEvent extends LoanTransactionBusinessEvent { + + private static final String TYPE = "LoanCapitalizedIncomeTransactionCreatedBusinessEvent"; + + public LoanCapitalizedIncomeTransactionCreatedBusinessEvent(LoanTransaction value) { + super(value); + } + + @Override + public String getType() { + return TYPE; + } +} diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java index 2df2b7d2b3..43894c0e3d 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java @@ -34,6 +34,10 @@ import org.apache.fineract.infrastructure.core.domain.ExternalId; import org.apache.fineract.infrastructure.core.service.DateUtils; 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.LoanBalanceChangedBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanCapitalizedIncomeAdjustmentTransactionCreatedBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanCapitalizedIncomeTransactionCreatedBusinessEvent; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.organisation.monetary.domain.Money; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance; @@ -61,6 +65,7 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc private final LoanCapitalizedIncomeBalanceRepository capitalizedIncomeBalanceRepository; private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; private final LoanBalanceService loanBalanceService; + private final BusinessEventNotifierService businessEventNotifierService; @Transactional @Override @@ -101,6 +106,9 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc // Post journal entries journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + businessEventNotifierService + .notifyPostBusinessEvent(new LoanCapitalizedIncomeTransactionCreatedBusinessEvent(capitalizedIncomeTransaction)); + businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); return new CommandProcessingResultBuilder() // .withEntityId(capitalizedIncomeTransaction.getId()) // .withEntityExternalId(capitalizedIncomeTransaction.getExternalId()) // @@ -153,6 +161,10 @@ public class CapitalizedIncomeWritePlatformServiceImpl implements CapitalizedInc MathUtil.negativeToZero(capitalizedIncomeBalance.getUnrecognizedAmount().subtract(transactionAmount))); capitalizedIncomeBalanceRepository.save(capitalizedIncomeBalance); + businessEventNotifierService.notifyPostBusinessEvent( + new LoanCapitalizedIncomeAdjustmentTransactionCreatedBusinessEvent(savedCapitalizedIncomeAdjustment)); + businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan)); + return new CommandProcessingResultBuilder() // .withEntityId(savedCapitalizedIncomeAdjustment.getId()) // .withEntityExternalId(savedCapitalizedIncomeAdjustment.getExternalId()) // diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java index 6694b9eab0..2cd92df3d2 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java @@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.starter; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.infrastructure.core.service.ExternalIdFactory; +import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository; @@ -50,10 +51,11 @@ public class ProgressiveLoanAccountConfiguration { PaymentDetailWritePlatformService paymentDetailWritePlatformService, LoanJournalEntryPoster journalEntryPoster, NoteWritePlatformService noteWritePlatformService, ExternalIdFactory externalIdFactory, LoanCapitalizedIncomeBalanceRepository capitalizedIncomeBalanceRepository, - ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanBalanceService loanBalanceService) { + ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanBalanceService loanBalanceService, + BusinessEventNotifierService businessEventNotifierService) { return new CapitalizedIncomeWritePlatformServiceImpl(loanTransactionValidator, loanAssembler, loanTransactionRepository, paymentDetailWritePlatformService, journalEntryPoster, noteWritePlatformService, externalIdFactory, - capitalizedIncomeBalanceRepository, reprocessLoanTransactionsService, loanBalanceService); + capitalizedIncomeBalanceRepository, reprocessLoanTransactionsService, loanBalanceService, businessEventNotifierService); } @Bean diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml index 23d39d7f9e..50b68a01f9 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml @@ -201,4 +201,5 @@ <include file="parts/0180_add_version_field_to_loan_transaction.xml" relativeToChangelogFile="true" /> <include file="parts/0181_add_capitalized_income_amortization_adjustment_transaction.xml" relativeToChangelogFile="true" /> <include file="parts/0182_transaction_summary_with_asset_owner_report_fix_charge_reason_and_add_buyback_intermediate.xml" relativeToChangelogFile="true" /> + <include file="parts/0183_add_LoanCapitalizedIncomeTransactionCreatedBusinessEvent.xml" relativeToChangelogFile="true" /> </databaseChangeLog> diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0183_add_LoanCapitalizedIncomeTransactionCreatedBusinessEvent.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0183_add_LoanCapitalizedIncomeTransactionCreatedBusinessEvent.xml new file mode 100644 index 0000000000..66a28bab0c --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0183_add_LoanCapitalizedIncomeTransactionCreatedBusinessEvent.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + +--> +<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd"> + <changeSet id="1" author="fineract"> + <insert tableName="m_external_event_configuration"> + <column name="type" value="LoanCapitalizedIncomeTransactionCreatedBusinessEvent"/> + <column name="enabled" valueBoolean="false"/> + </insert> + </changeSet> +</databaseChangeLog> diff --git a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java index f802e918fe..5d5e47edde 100644 --- a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java +++ b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java @@ -108,7 +108,8 @@ public class ExternalEventConfigurationValidationServiceTest { "LoanTransactionInterestRefundPreBusinessEvent", "LoanAccrualAdjustmentTransactionBusinessEvent", "LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent", "LoanCapitalizedIncomeAdjustmentTransactionCreatedBusinessEvent", "LoanTransactionContractTerminationPostBusinessEvent", - "LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent"); + "LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent", + "LoanCapitalizedIncomeTransactionCreatedBusinessEvent"); List<FineractPlatformTenant> tenants = Arrays .asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null)); @@ -197,7 +198,8 @@ public class ExternalEventConfigurationValidationServiceTest { "LoanTransactionInterestRefundPreBusinessEvent", "LoanAccrualAdjustmentTransactionBusinessEvent", "LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent", "LoanCapitalizedIncomeAdjustmentTransactionCreatedBusinessEvent", "LoanTransactionContractTerminationPostBusinessEvent", - "LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent"); + "LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent", + "LoanCapitalizedIncomeTransactionCreatedBusinessEvent"); List<FineractPlatformTenant> tenants = Arrays .asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null)); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java index 8659659b80..201e41caf7 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/BaseLoanIntegrationTest.java @@ -87,6 +87,7 @@ import org.apache.fineract.client.models.RetrieveLoansPointInTimeRequest; import org.apache.fineract.client.util.CallFailedRuntimeException; import org.apache.fineract.client.util.Calls; import org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants; +import org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO; import org.apache.fineract.integrationtests.client.IntegrationTest; import org.apache.fineract.integrationtests.common.BatchHelper; import org.apache.fineract.integrationtests.common.BusinessDateHelper; @@ -99,6 +100,9 @@ import org.apache.fineract.integrationtests.common.accounting.AccountHelper; import org.apache.fineract.integrationtests.common.accounting.JournalEntryHelper; import org.apache.fineract.integrationtests.common.charges.ChargesHelper; import org.apache.fineract.integrationtests.common.error.ErrorResponse; +import org.apache.fineract.integrationtests.common.externalevents.BusinessEvent; +import org.apache.fineract.integrationtests.common.externalevents.ExternalEventHelper; +import org.apache.fineract.integrationtests.common.externalevents.ExternalEventsExtension; import org.apache.fineract.integrationtests.common.loans.LoanAccountLockHelper; import org.apache.fineract.integrationtests.common.loans.LoanProductHelper; import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; @@ -119,7 +123,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.extension.ExtendWith; @Slf4j -@ExtendWith(LoanTestLifecycleExtension.class) +@ExtendWith({ LoanTestLifecycleExtension.class, ExternalEventsExtension.class }) public abstract class BaseLoanIntegrationTest extends IntegrationTest { protected static final String DATETIME_PATTERN = "dd MMMM yyyy"; @@ -171,6 +175,7 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest { protected GlobalConfigurationHelper globalConfigurationHelper = new GlobalConfigurationHelper(); protected final CodeHelper codeHelper = new CodeHelper(); protected final ChargesHelper chargesHelper = new ChargesHelper(); + protected final ExternalEventHelper externalEventHelper = new ExternalEventHelper(); protected static void validateRepaymentPeriod(GetLoansLoanIdResponse loanDetails, Integer index, LocalDate dueDate, double principalDue, double principalPaid, double principalOutstanding, double paidInAdvance, double paidLate) { @@ -1269,6 +1274,42 @@ public abstract class BaseLoanIntegrationTest extends IntegrationTest { new PostLoansLoanIdRequest().rejectedOnDate(rejectedOnDate).locale("en").dateFormat(DATETIME_PATTERN)); } + protected void verifyBusinessEvents(BusinessEvent... businessEvents) { + List<ExternalEventDTO> allExternalEvents = ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec); + logBusinessEvents(allExternalEvents); + Assertions.assertNotNull(businessEvents); + Assertions.assertNotNull(allExternalEvents); + Assertions.assertTrue(businessEvents.length <= allExternalEvents.size(), + "Expected business event count is less than actual. Expected: " + businessEvents.length + " Actual: " + + allExternalEvents.size()); + final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN, Locale.ENGLISH); + for (BusinessEvent businessEvent : businessEvents) { + long count = allExternalEvents.stream().filter(externalEvent -> businessEvent.verify(externalEvent, formatter)).count(); + Assertions.assertEquals(1, count, "Expected business event not found " + businessEvent); + } + } + + protected void logBusinessEvents(List<ExternalEventDTO> allExternalEvents) { + allExternalEvents.forEach(externalEventDTO -> { + Object amount = externalEventDTO.getPayLoad().get("amount"); + Object outstandingLoanBalance = externalEventDTO.getPayLoad().get("outstandingLoanBalance"); + Object principalPortion = externalEventDTO.getPayLoad().get("principalPortion"); + Object interestPortion = externalEventDTO.getPayLoad().get("interestPortion"); + Object feePortion = externalEventDTO.getPayLoad().get("feeChargesPortion"); + Object penaltyPortion = externalEventDTO.getPayLoad().get("penaltyChargesPortion"); + log.info("Event Received\n type:'{}'\n businessDate:'{}'", externalEventDTO.getType(), externalEventDTO.getBusinessDate()); + log.info( + "Values\n amount: {}\n outstandingLoanBalance: {}\n principalPortion: {}\n interestPortion: {}\n feePortion: {}\n penaltyPortion: {}", + amount, outstandingLoanBalance, principalPortion, interestPortion, feePortion, penaltyPortion); + }); + } + + protected void deleteAllExternalEvents() { + ExternalEventHelper.deleteAllExternalEvents(requestSpec, createResponseSpecification(Matchers.is(204))); + List<ExternalEventDTO> allExternalEvents = ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec); + Assertions.assertEquals(0, allExternalEvents.size()); + } + @RequiredArgsConstructor public static class BatchRequestBuilder { diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/CustomSnapshotEventIntegrationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/CustomSnapshotEventIntegrationTest.java index abb535e37e..59786806af 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/CustomSnapshotEventIntegrationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/CustomSnapshotEventIntegrationTest.java @@ -37,7 +37,6 @@ import org.apache.fineract.integrationtests.common.SchedulerJobHelper; import org.apache.fineract.integrationtests.common.externalevents.ExternalEventHelper; import org.apache.fineract.integrationtests.common.externalevents.ExternalEventsExtension; import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension; -import org.hamcrest.Matchers; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -305,12 +304,6 @@ public class CustomSnapshotEventIntegrationTest extends BaseLoanIntegrationTest }); } - private void deleteAllExternalEvents() { - ExternalEventHelper.deleteAllExternalEvents(requestSpec, createResponseSpecification(Matchers.is(204))); - List<ExternalEventDTO> allExternalEvents = ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec); - Assertions.assertEquals(0, allExternalEvents.size()); - } - private void enableCOBBusinessStep(String... steps) { new BusinessStepHelper().updateSteps("LOAN_CLOSE_OF_BUSINESS", steps); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalBusinessEventTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalBusinessEventTest.java index 715ca3e936..10988e91c7 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalBusinessEventTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/ExternalBusinessEventTest.java @@ -25,24 +25,12 @@ import io.restassured.builder.ResponseSpecBuilder; import io.restassured.http.ContentType; import io.restassured.specification.RequestSpecification; import io.restassured.specification.ResponseSpecification; -import jakarta.validation.constraints.NotNull; import java.math.BigDecimal; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.fineract.client.models.GetLoansLoanIdResponse; import org.apache.fineract.client.models.GetLoansLoanIdStatus; import org.apache.fineract.client.models.GlobalConfigurationPropertyData; @@ -66,8 +54,10 @@ import org.apache.fineract.integrationtests.common.LoanRescheduleRequestHelper; import org.apache.fineract.integrationtests.common.Utils; import org.apache.fineract.integrationtests.common.externalevents.ExternalEventHelper; import org.apache.fineract.integrationtests.common.externalevents.ExternalEventsExtension; +import org.apache.fineract.integrationtests.common.externalevents.LoanAdjustTransactionBusinessEvent; +import org.apache.fineract.integrationtests.common.externalevents.LoanBusinessEvent; +import org.apache.fineract.integrationtests.common.externalevents.LoanTransactionBusinessEvent; import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; -import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -1012,12 +1002,6 @@ public class ExternalBusinessEventTest extends BaseLoanIntegrationTest { externalEventHelper.disableBusinessEvent("LoanBalanceChangedBusinessEvent"); } - private void deleteAllExternalEvents() { - ExternalEventHelper.deleteAllExternalEvents(requestSpec, createResponseSpecification(Matchers.is(204))); - List<ExternalEventDTO> allExternalEvents = ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec); - Assertions.assertEquals(0, allExternalEvents.size()); - } - private static Long createLoanProductPeriodicWithInterest() { String name = Utils.uniqueRandomStringGenerator("LOAN_PRODUCT_", 6); String shortName = Utils.uniqueRandomStringGenerator("", 4); @@ -1070,21 +1054,6 @@ public class ExternalBusinessEventTest extends BaseLoanIntegrationTest { return loanId; } - private void logBusinessEvents(List<ExternalEventDTO> allExternalEvents) { - allExternalEvents.forEach(externalEventDTO -> { - Object amount = externalEventDTO.getPayLoad().get("amount"); - Object outstandingLoanBalance = externalEventDTO.getPayLoad().get("outstandingLoanBalance"); - Object principalPortion = externalEventDTO.getPayLoad().get("principalPortion"); - Object interestPortion = externalEventDTO.getPayLoad().get("interestPortion"); - Object feePortion = externalEventDTO.getPayLoad().get("feeChargesPortion"); - Object penaltyPortion = externalEventDTO.getPayLoad().get("penaltyChargesPortion"); - log.info("Event Received\n type:'{}'\n businessDate:'{}'", externalEventDTO.getType(), externalEventDTO.getBusinessDate()); - log.info( - "Values\n amount: {}\n outstandingLoanBalance: {}\n principalPortion: {}\n interestPortion: {}\n feePortion: {}\n penaltyPortion: {}", - amount, outstandingLoanBalance, principalPortion, interestPortion, feePortion, penaltyPortion); - }); - } - private void enableLoanInterestRefundPstBusinessEvent(boolean enabled) { externalEventHelper.configureBusinessEvent("LoanTransactionInterestRefundPostBusinessEvent", enabled); } @@ -1096,220 +1065,4 @@ public class ExternalBusinessEventTest extends BaseLoanIntegrationTest { private void configureLoanAccrualTransactionCreatedBusinessEvent(boolean enabled) { externalEventHelper.configureBusinessEvent("LoanAccrualTransactionCreatedBusinessEvent", enabled); } - - public void verifyBusinessEvents(BusinessEvent... businessEvents) { - List<ExternalEventDTO> allExternalEvents = ExternalEventHelper.getAllExternalEvents(requestSpec, responseSpec); - logBusinessEvents(allExternalEvents); - Assertions.assertNotNull(businessEvents); - Assertions.assertNotNull(allExternalEvents); - Assertions.assertTrue(businessEvents.length <= allExternalEvents.size(), - "Expected business event count is less than actual. Expected: " + businessEvents.length + " Actual: " - + allExternalEvents.size()); - final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN, Locale.ENGLISH); - for (BusinessEvent businessEvent : businessEvents) { - long count = allExternalEvents.stream().filter(externalEvent -> businessEvent.verify(externalEvent, formatter)).count(); - Assertions.assertEquals(1, count, "Expected business event not found " + businessEvent); - } - } - - @Data - @AllArgsConstructor - @NoArgsConstructor - public static class BusinessEvent { - - String type; - String businessDate; - - boolean verify(@NotNull ExternalEventDTO externalEvent, DateTimeFormatter formatter) { - var businessDate = LocalDate.parse(getBusinessDate(), formatter); - - return Objects.equals(externalEvent.getType(), getType()) && Objects.equals(externalEvent.getBusinessDate(), businessDate); - } - } - - @EqualsAndHashCode(callSuper = true) - @Data - @AllArgsConstructor - public static class LoanTransactionBusinessEvent extends BusinessEvent { - - private Double amount; - private Double outstandingLoanBalance; - private Double principalPortion; - private Double interestPortion; - private Double feeChargesPortion; - private Double penaltyChargesPortion; - - public LoanTransactionBusinessEvent(String type, String businessDate, Double amount, Double outstandingLoanBalance, - Double principalPortion, Double interestPortion, Double feeChargesPortion, Double penaltyChargesPortion) { - super(type, businessDate); - this.amount = amount; - this.outstandingLoanBalance = outstandingLoanBalance; - this.principalPortion = principalPortion; - this.interestPortion = interestPortion; - this.feeChargesPortion = feeChargesPortion; - this.penaltyChargesPortion = penaltyChargesPortion; - } - - @Override - boolean verify(ExternalEventDTO externalEvent, DateTimeFormatter formatter) { - Object amount = externalEvent.getPayLoad().get("amount"); - Object outstandingLoanBalance = externalEvent.getPayLoad().get("outstandingLoanBalance"); - Object principalPortion = externalEvent.getPayLoad().get("principalPortion"); - Object interestPortion = externalEvent.getPayLoad().get("interestPortion"); - Object feePortion = externalEvent.getPayLoad().get("feeChargesPortion"); - Object penaltyPortion = externalEvent.getPayLoad().get("penaltyChargesPortion"); - - return super.verify(externalEvent, formatter) && Objects.equals(amount, getAmount()) - && Objects.equals(outstandingLoanBalance, getOutstandingLoanBalance()) - && Objects.equals(principalPortion, getPrincipalPortion()) && Objects.equals(interestPortion, getInterestPortion()) - && Objects.equals(feePortion, getFeeChargesPortion()) && Objects.equals(penaltyPortion, getPenaltyChargesPortion()); - } - } - - @EqualsAndHashCode(callSuper = true) - @Data - @AllArgsConstructor - public static class LoanBusinessEvent extends BusinessEvent { - - private Integer statusId; - private Double principalDisbursed; - private Double principalOutstanding; - private List<String> loanTermVariationType; - - public LoanBusinessEvent(String type, String businessDate, Integer statusId, Double principalDisbursed, - Double principalOutstanding) { - super(type, businessDate); - this.statusId = statusId; - this.principalDisbursed = principalDisbursed; - this.principalOutstanding = principalOutstanding; - } - - public LoanBusinessEvent(String type, String businessDate, Integer statusId, Double principalDisbursed, Double principalOutstanding, - List<String> loanTermVariationType) { - super(type, businessDate); - this.statusId = statusId; - this.principalDisbursed = principalDisbursed; - this.principalOutstanding = principalOutstanding; - this.loanTermVariationType = loanTermVariationType; - } - - @Override - public boolean verify(ExternalEventDTO externalEvent, DateTimeFormatter formatter) { - Object summaryRes = externalEvent.getPayLoad().get("summary"); - Object statusRes = externalEvent.getPayLoad().get("status"); - Map<String, Object> summary = summaryRes instanceof Map ? (Map<String, Object>) summaryRes : Map.of(); - Map<String, Object> status = statusRes instanceof Map ? (Map<String, Object>) statusRes : Map.of(); - var principalDisbursed = summary.get("principalDisbursed"); - - var principalOutstanding = summary.get("principalOutstanding"); - Double statusId = (Double) status.get("id"); - return super.verify(externalEvent, formatter) && Objects.equals(statusId, getStatusId().doubleValue()) - && Objects.equals(principalDisbursed, getPrincipalDisbursed()) - && Objects.equals(principalOutstanding, getPrincipalOutstanding()) && loanTermVariationsMatch( - (List<Map<String, Object>>) externalEvent.getPayLoad().get("loanTermVariations"), loanTermVariationType); - } - - private boolean loanTermVariationsMatch(final List<Map<String, Object>> loanTermVariations, final List<String> expectedTypes) { - if (CollectionUtils.isEmpty(expectedTypes)) { - return true; - } - final long numberOfMatches = expectedTypes.stream().filter(expectedType -> loanTermVariations.stream().anyMatch( - variation -> StringUtils.equals((String) ((Map<String, Object>) variation.get("termType")).get("value"), expectedType))) - .count(); - - return numberOfMatches == expectedTypes.size(); - } - } - - public static class LoanAdjustTransactionBusinessEvent extends BusinessEvent { - - private String transactionTypeCode; - private String transactionDate; - private Double oldAmount; - private Double newAmount; - private Double oldPrincipalPortion; - private Double newPrincipalPortion; - private Double oldInterestPortion; - private Double newInterestPortion; - private Double oldFeePortion; - private Double newFeePortion; - private Double oldPenaltyPortion; - private Double newPenaltyPortion; - - public LoanAdjustTransactionBusinessEvent(String type, String businessDate, String transactionTypeCode, String transactionDate) { - super(type, businessDate); - this.transactionTypeCode = transactionTypeCode; - this.transactionDate = transactionDate; - } - - public LoanAdjustTransactionBusinessEvent(String type, String businessDate, String transactionTypeCode, String transactionDate, - Double oldAmount, Double newAmount) { - super(type, businessDate); - this.transactionTypeCode = transactionTypeCode; - this.transactionDate = transactionDate; - this.oldAmount = oldAmount; - this.newAmount = newAmount; - } - - public LoanAdjustTransactionBusinessEvent(String type, String businessDate, String transactionTypeCode, String transactionDate, - Double oldAmount, Double newAmount, Double oldPrincipalPortion, Double newPrincipalPortion, Double oldInterestPortion, - Double newInterestPortion, Double oldFeePortion, Double newFeePortion, Double oldPenaltyPortion, Double newPenaltyPortion) { - super(type, businessDate); - this.transactionTypeCode = transactionTypeCode; - this.transactionDate = transactionDate; - this.oldAmount = oldAmount; - this.newAmount = newAmount; - this.oldPrincipalPortion = oldPrincipalPortion; - this.newPrincipalPortion = newPrincipalPortion; - this.oldInterestPortion = oldInterestPortion; - this.newInterestPortion = newInterestPortion; - this.oldFeePortion = oldFeePortion; - this.newFeePortion = newFeePortion; - this.oldPenaltyPortion = oldPenaltyPortion; - this.newPenaltyPortion = newPenaltyPortion; - } - - @Override - boolean verify(ExternalEventDTO externalEvent, DateTimeFormatter formatter) { - final Object transactionToAdjust = externalEvent.getPayLoad().get("transactionToAdjust"); - final Map<?, Object> transActionToAdjustMap = transactionToAdjust instanceof Map ? (Map<String, Object>) transactionToAdjust - : Collections.emptyMap(); - - Object actualOldAmount = transActionToAdjustMap.get("amount"); - Object actualOldPrincipalPortion = transActionToAdjustMap.get("principalPortion"); - Object actualOldInterestPortion = transActionToAdjustMap.get("interestPortion"); - Object actualOldFeePortion = transActionToAdjustMap.get("feeChargesPortion"); - Object actualOldPenaltyPortion = transActionToAdjustMap.get("penaltyChargesPortion"); - - final Object newTransactionDetail = externalEvent.getPayLoad().get("newTransactionDetail"); - final Map<?, Object> newTransactionDetailMap = newTransactionDetail instanceof Map ? (Map<String, Object>) newTransactionDetail - : Collections.emptyMap(); - - Object actualNewAmount = newTransactionDetailMap.get("amount"); - Object actualNewPrincipalPortion = newTransactionDetailMap.get("principalPortion"); - Object actualNewInterestPortion = newTransactionDetailMap.get("interestPortion"); - Object actualNewFeePortion = newTransactionDetailMap.get("feeChargesPortion"); - Object actualNewPenaltyPortion = newTransactionDetailMap.get("penaltyChargesPortion"); - - final Object actualTransactionDate = transActionToAdjustMap.get("date"); - final Object transactionType = transActionToAdjustMap.get("type"); - final Map<?, Object> transactionTypeMap = transactionType instanceof Map ? (Map<String, Object>) transactionType - : Collections.emptyMap(); - final Object actualTransactionTypeCode = transactionTypeMap.get("code"); - - return super.verify(externalEvent, formatter)// - && Objects.equals(actualTransactionTypeCode, transactionTypeCode) - && Objects.equals(actualTransactionDate, transactionDate)// - && (oldAmount == null || Objects.equals(actualOldAmount, oldAmount))// - && (newAmount == null || Objects.equals(actualNewAmount, newAmount))// - && (oldPrincipalPortion == null || Objects.equals(actualOldPrincipalPortion, oldPrincipalPortion))// - && (newPrincipalPortion == null || Objects.equals(actualNewPrincipalPortion, newPrincipalPortion))// - && (oldInterestPortion == null || Objects.equals(actualOldInterestPortion, oldInterestPortion))// - && (newInterestPortion == null || Objects.equals(actualNewInterestPortion, newInterestPortion))// - && (oldFeePortion == null || Objects.equals(actualOldFeePortion, oldFeePortion))// - && (newFeePortion == null || Objects.equals(actualNewFeePortion, newFeePortion))// - && (oldPenaltyPortion == null || Objects.equals(actualOldPenaltyPortion, oldPenaltyPortion))// - && (newPenaltyPortion == null || Objects.equals(actualNewPenaltyPortion, newPenaltyPortion)); - } - } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java index 28b18e112d..b2d1ae2c4a 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java @@ -36,6 +36,9 @@ import org.apache.fineract.client.models.PostLoansResponse; import org.apache.fineract.client.util.CallFailedRuntimeException; import org.apache.fineract.integrationtests.common.BusinessStepHelper; import org.apache.fineract.integrationtests.common.ClientHelper; +import org.apache.fineract.integrationtests.common.externalevents.LoanAdjustTransactionBusinessEvent; +import org.apache.fineract.integrationtests.common.externalevents.LoanBusinessEvent; +import org.apache.fineract.integrationtests.common.externalevents.LoanTransactionBusinessEvent; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -780,4 +783,114 @@ public class LoanCapitalizedIncomeTest extends BaseLoanIntegrationTest { Assertions.assertEquals(thousandFiveHundred, loanDetails.getSummary().getTotalPrincipal().setScale(1, RoundingMode.HALF_UP)); Assertions.assertEquals(zero, loanDetails.getSummary().getPrincipalOutstanding().setScale(0, RoundingMode.HALF_UP)); } + + @Test + public void testCapitalizedIncomeEvents() { + externalEventHelper.enableBusinessEvent("LoanCapitalizedIncomeTransactionCreatedBusinessEvent"); + externalEventHelper.enableBusinessEvent("LoanCapitalizedIncomeAdjustmentTransactionCreatedBusinessEvent"); + externalEventHelper.enableBusinessEvent("LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent"); + externalEventHelper.enableBusinessEvent("LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent"); + externalEventHelper.enableBusinessEvent("LoanAdjustTransactionBusinessEvent"); + externalEventHelper.enableBusinessEvent("LoanBalanceChangedBusinessEvent"); + + final AtomicReference<Long> loanIdRef = new AtomicReference<>(); + final AtomicReference<Long> capitalizedIncomeTransactionIdRef = new AtomicReference<>(); + + final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()); + + final PostLoanProductsResponse loanProductsResponse = loanProductHelper + .createLoanProduct(create4IProgressive().enableIncomeCapitalization(true) + .capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT) + .capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION) + .deferredIncomeLiabilityAccountId(deferredIncomeLiabilityAccount.getAccountID().longValue()) + .incomeFromCapitalizationAccountId(feeIncomeAccount.getAccountID().longValue()) + .capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.FEE)); + + runAt("1 January 2024", () -> { + Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "1 January 2024", + 500.0, 7.0, 3, null); + loanIdRef.set(loanId); + + disburseLoan(loanId, BigDecimal.valueOf(100), "1 January 2024"); + + deleteAllExternalEvents(); + + Long capitalizedIncomeTransactionId = loanTransactionHelper.addCapitalizedIncome(loanId, "1 January 2024", 100.0) + .getResourceId(); + capitalizedIncomeTransactionIdRef.set(capitalizedIncomeTransactionId); + + verifyBusinessEvents( + new LoanTransactionBusinessEvent("LoanCapitalizedIncomeTransactionCreatedBusinessEvent", "01 January 2024", 100.0, + 200.0, 100.0, 0.0, 0.0, 0.0), + new LoanBusinessEvent("LoanBalanceChangedBusinessEvent", "01 January 2024", 300, 100.0, 200.0)); + }); + runAt("2 January 2024", () -> { + Long loanId = loanIdRef.get(); + + deleteAllExternalEvents(); + + executeInlineCOB(loanId); + + verifyTransactions(loanId, // + transaction(100.0, "Disbursement", "01 January 2024"), // + transaction(100.0, "Capitalized Income", "01 January 2024"), // + transaction(1.10, "Capitalized Income Amortization", "01 January 2024") // + ); + verifyBusinessEvents(new LoanTransactionBusinessEvent("LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent", + "01 January 2024", 1.10, 0.0, 0.0, 0.0, 1.10, 0.0)); + }); + runAt("3 January 2024", () -> { + Long loanId = loanIdRef.get(); + executeInlineCOB(loanId); + + deleteAllExternalEvents(); + + Long capitalizedIncomeAdjustmentTransactionId = loanTransactionHelper + .capitalizedIncomeAdjustment(loanId, capitalizedIncomeTransactionIdRef.get(), "3 January 2024", 50.0).getResourceId(); + + verifyTransactions(loanId, // + transaction(100.0, "Disbursement", "01 January 2024"), // + transaction(100.0, "Capitalized Income", "01 January 2024"), // + transaction(1.10, "Capitalized Income Amortization", "01 January 2024"), // + transaction(0.04, "Accrual", "02 January 2024"), // + transaction(1.10, "Capitalized Income Amortization", "02 January 2024"), // + transaction(50.0, "Capitalized Income Adjustment", "03 January 2024") // + ); + + verifyBusinessEvents( + new LoanTransactionBusinessEvent("LoanCapitalizedIncomeAdjustmentTransactionCreatedBusinessEvent", "03 January 2024", + 50.0, 150.0, 50.0, 0.0, 0.0, 0.0), + new LoanBusinessEvent("LoanBalanceChangedBusinessEvent", "03 January 2024", 300, 100.0, 150.0)); + + deleteAllExternalEvents(); + + loanTransactionHelper.reverseLoanTransaction(loanId, capitalizedIncomeAdjustmentTransactionId, "3 January 2024"); + + verifyBusinessEvents(new LoanAdjustTransactionBusinessEvent("LoanAdjustTransactionBusinessEvent", "03 January 2024", + "loanTransactionType.capitalizedIncomeAdjustment", "2024-01-03")); + }); + runAt("4 January 2024", () -> { + Long loanId = loanIdRef.get(); + executeInlineCOB(loanId); + + deleteAllExternalEvents(); + + loanTransactionHelper.reverseLoanTransaction(loanId, capitalizedIncomeTransactionIdRef.get(), "3 January 2024"); + + verifyTransactions(loanId, // + transaction(100.0, "Disbursement", "01 January 2024"), // + transaction(100.0, "Capitalized Income", "01 January 2024"), // + transaction(1.10, "Capitalized Income Amortization", "01 January 2024"), // + transaction(0.04, "Accrual", "02 January 2024"), // + transaction(1.10, "Capitalized Income Amortization", "02 January 2024"), // + transaction(0.04, "Accrual", "03 January 2024"), // + transaction(1.10, "Capitalized Income Amortization", "02 January 2024"), // + transaction(50.0, "Capitalized Income Adjustment", "03 January 2024") // + ); + + verifyBusinessEvents(new LoanAdjustTransactionBusinessEvent("LoanAdjustTransactionBusinessEvent", "04 January 2024", + "loanTransactionType.capitalizedIncome", "2024-01-01") // + ); + }); + } } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java index 1e2461b9b0..acbadd414d 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java @@ -606,6 +606,11 @@ public class ExternalEventConfigurationHelper { loanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent.put("enabled", false); defaults.add(loanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent); + Map<String, Object> loanCapitalizedIncomeTransactionCreatedBusinessEvent = new HashMap<>(); + loanCapitalizedIncomeTransactionCreatedBusinessEvent.put("type", "LoanCapitalizedIncomeTransactionCreatedBusinessEvent"); + loanCapitalizedIncomeTransactionCreatedBusinessEvent.put("enabled", false); + defaults.add(loanCapitalizedIncomeTransactionCreatedBusinessEvent); + return defaults; } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/BusinessEvent.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/BusinessEvent.java new file mode 100644 index 0000000000..340648b13b --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/BusinessEvent.java @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests.common.externalevents; + +import jakarta.validation.constraints.NotNull; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Objects; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class BusinessEvent { + + protected String type; + protected String businessDate; + + public boolean verify(@NotNull ExternalEventDTO externalEvent, DateTimeFormatter formatter) { + var businessDate = LocalDate.parse(getBusinessDate(), formatter); + + return Objects.equals(externalEvent.getType(), getType()) && Objects.equals(externalEvent.getBusinessDate(), businessDate); + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/LoanAdjustTransactionBusinessEvent.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/LoanAdjustTransactionBusinessEvent.java new file mode 100644 index 0000000000..2d725b1a87 --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/LoanAdjustTransactionBusinessEvent.java @@ -0,0 +1,122 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests.common.externalevents; + +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.Map; +import java.util.Objects; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO; + +@EqualsAndHashCode(callSuper = true) +@Data +public class LoanAdjustTransactionBusinessEvent extends BusinessEvent { + + private String transactionTypeCode; + private String transactionDate; + private Double oldAmount; + private Double newAmount; + private Double oldPrincipalPortion; + private Double newPrincipalPortion; + private Double oldInterestPortion; + private Double newInterestPortion; + private Double oldFeePortion; + private Double newFeePortion; + private Double oldPenaltyPortion; + private Double newPenaltyPortion; + + // minimum data for checking if transaction was reversed + public LoanAdjustTransactionBusinessEvent(String type, String businessDate, String transactionTypeCode, String transactionDate) { + super(type, businessDate); + this.transactionTypeCode = transactionTypeCode; + this.transactionDate = transactionDate; + } + + // minimum data for checking if transaction was adjusted + public LoanAdjustTransactionBusinessEvent(String type, String businessDate, String transactionTypeCode, String transactionDate, + Double oldAmount, Double newAmount) { + super(type, businessDate); + this.transactionTypeCode = transactionTypeCode; + this.transactionDate = transactionDate; + this.oldAmount = oldAmount; + this.newAmount = newAmount; + } + + public LoanAdjustTransactionBusinessEvent(String type, String businessDate, String transactionTypeCode, String transactionDate, + Double oldAmount, Double newAmount, Double oldPrincipalPortion, Double newPrincipalPortion, Double oldInterestPortion, + Double newInterestPortion, Double oldFeePortion, Double newFeePortion, Double oldPenaltyPortion, Double newPenaltyPortion) { + super(type, businessDate); + this.transactionTypeCode = transactionTypeCode; + this.transactionDate = transactionDate; + this.oldAmount = oldAmount; + this.newAmount = newAmount; + this.oldPrincipalPortion = oldPrincipalPortion; + this.newPrincipalPortion = newPrincipalPortion; + this.oldInterestPortion = oldInterestPortion; + this.newInterestPortion = newInterestPortion; + this.oldFeePortion = oldFeePortion; + this.newFeePortion = newFeePortion; + this.oldPenaltyPortion = oldPenaltyPortion; + this.newPenaltyPortion = newPenaltyPortion; + } + + @Override + public boolean verify(ExternalEventDTO externalEvent, DateTimeFormatter formatter) { + final Object transactionToAdjust = externalEvent.getPayLoad().get("transactionToAdjust"); + final Map<?, Object> transActionToAdjustMap = transactionToAdjust instanceof Map ? (Map<String, Object>) transactionToAdjust + : Collections.emptyMap(); + + Object actualOldAmount = transActionToAdjustMap.get("amount"); + Object actualOldPrincipalPortion = transActionToAdjustMap.get("principalPortion"); + Object actualOldInterestPortion = transActionToAdjustMap.get("interestPortion"); + Object actualOldFeePortion = transActionToAdjustMap.get("feeChargesPortion"); + Object actualOldPenaltyPortion = transActionToAdjustMap.get("penaltyChargesPortion"); + + final Object newTransactionDetail = externalEvent.getPayLoad().get("newTransactionDetail"); + final Map<?, Object> newTransactionDetailMap = newTransactionDetail instanceof Map ? (Map<String, Object>) newTransactionDetail + : Collections.emptyMap(); + + Object actualNewAmount = newTransactionDetailMap.get("amount"); + Object actualNewPrincipalPortion = newTransactionDetailMap.get("principalPortion"); + Object actualNewInterestPortion = newTransactionDetailMap.get("interestPortion"); + Object actualNewFeePortion = newTransactionDetailMap.get("feeChargesPortion"); + Object actualNewPenaltyPortion = newTransactionDetailMap.get("penaltyChargesPortion"); + + final Object actualTransactionDate = transActionToAdjustMap.get("date"); + final Object transactionType = transActionToAdjustMap.get("type"); + final Map<?, Object> transactionTypeMap = transactionType instanceof Map ? (Map<String, Object>) transactionType + : Collections.emptyMap(); + final Object actualTransactionTypeCode = transactionTypeMap.get("code"); + + return super.verify(externalEvent, formatter)// + && Objects.equals(actualTransactionTypeCode, transactionTypeCode) && Objects.equals(actualTransactionDate, transactionDate)// + && (oldAmount == null || Objects.equals(actualOldAmount, oldAmount))// + && (newAmount == null || Objects.equals(actualNewAmount, newAmount))// + && (oldPrincipalPortion == null || Objects.equals(actualOldPrincipalPortion, oldPrincipalPortion))// + && (newPrincipalPortion == null || Objects.equals(actualNewPrincipalPortion, newPrincipalPortion))// + && (oldInterestPortion == null || Objects.equals(actualOldInterestPortion, oldInterestPortion))// + && (newInterestPortion == null || Objects.equals(actualNewInterestPortion, newInterestPortion))// + && (oldFeePortion == null || Objects.equals(actualOldFeePortion, oldFeePortion))// + && (newFeePortion == null || Objects.equals(actualNewFeePortion, newFeePortion))// + && (oldPenaltyPortion == null || Objects.equals(actualOldPenaltyPortion, oldPenaltyPortion))// + && (newPenaltyPortion == null || Objects.equals(actualNewPenaltyPortion, newPenaltyPortion)); + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/LoanBusinessEvent.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/LoanBusinessEvent.java new file mode 100644 index 0000000000..da683f2c1b --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/LoanBusinessEvent.java @@ -0,0 +1,85 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests.common.externalevents; + +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO; + +@EqualsAndHashCode(callSuper = true) +@Data +public class LoanBusinessEvent extends BusinessEvent { + + private Integer statusId; + private Double principalDisbursed; + private Double principalOutstanding; + private List<String> loanTermVariationType; + + public LoanBusinessEvent(String type, String businessDate, Integer statusId, Double principalDisbursed, Double principalOutstanding) { + super(type, businessDate); + this.statusId = statusId; + this.principalDisbursed = principalDisbursed; + this.principalOutstanding = principalOutstanding; + } + + public LoanBusinessEvent(String type, String businessDate, Integer statusId, Double principalDisbursed, Double principalOutstanding, + List<String> loanTermVariationType) { + super(type, businessDate); + this.statusId = statusId; + this.principalDisbursed = principalDisbursed; + this.principalOutstanding = principalOutstanding; + this.loanTermVariationType = loanTermVariationType; + } + + @Override + public boolean verify(ExternalEventDTO externalEvent, DateTimeFormatter formatter) { + Object summaryRes = externalEvent.getPayLoad().get("summary"); + Object statusRes = externalEvent.getPayLoad().get("status"); + Map<String, Object> summary = summaryRes instanceof Map ? (Map<String, Object>) summaryRes : Map.of(); + Map<String, Object> status = statusRes instanceof Map ? (Map<String, Object>) statusRes : Map.of(); + var principalDisbursed = summary.get("principalDisbursed"); + + var principalOutstanding = summary.get("principalOutstanding"); + Double statusId = (Double) status.get("id"); + return super.verify(externalEvent, formatter) && Objects.equals(statusId, getStatusId().doubleValue()) + && Objects.equals(principalDisbursed, getPrincipalDisbursed()) + && Objects.equals(principalOutstanding, getPrincipalOutstanding()) && loanTermVariationsMatch( + (List<Map<String, Object>>) externalEvent.getPayLoad().get("loanTermVariations"), loanTermVariationType); + } + + private boolean loanTermVariationsMatch(final List<Map<String, Object>> loanTermVariations, final List<String> expectedTypes) { + if (CollectionUtils.isEmpty(expectedTypes)) { + return true; + } + final long numberOfMatches = expectedTypes + .stream().filter( + expectedType -> loanTermVariations.stream() + .anyMatch(variation -> StringUtils + .equals((String) ((Map<String, Object>) variation.get("termType")).get("value"), expectedType))) + .count(); + + return numberOfMatches == expectedTypes.size(); + } +} diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/LoanTransactionBusinessEvent.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/LoanTransactionBusinessEvent.java new file mode 100644 index 0000000000..c2c061d83f --- /dev/null +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/externalevents/LoanTransactionBusinessEvent.java @@ -0,0 +1,63 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.integrationtests.common.externalevents; + +import java.time.format.DateTimeFormatter; +import java.util.Objects; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.fineract.infrastructure.event.external.service.validation.ExternalEventDTO; + +@EqualsAndHashCode(callSuper = true) +@Data +public class LoanTransactionBusinessEvent extends BusinessEvent { + + private Double amount; + private Double outstandingLoanBalance; + private Double principalPortion; + private Double interestPortion; + private Double feeChargesPortion; + private Double penaltyChargesPortion; + + public LoanTransactionBusinessEvent(String type, String businessDate, Double amount, Double outstandingLoanBalance, + Double principalPortion, Double interestPortion, Double feeChargesPortion, Double penaltyChargesPortion) { + super(type, businessDate); + this.amount = amount; + this.outstandingLoanBalance = outstandingLoanBalance; + this.principalPortion = principalPortion; + this.interestPortion = interestPortion; + this.feeChargesPortion = feeChargesPortion; + this.penaltyChargesPortion = penaltyChargesPortion; + } + + @Override + public boolean verify(ExternalEventDTO externalEvent, DateTimeFormatter formatter) { + Object amount = externalEvent.getPayLoad().get("amount"); + Object outstandingLoanBalance = externalEvent.getPayLoad().get("outstandingLoanBalance"); + Object principalPortion = externalEvent.getPayLoad().get("principalPortion"); + Object interestPortion = externalEvent.getPayLoad().get("interestPortion"); + Object feePortion = externalEvent.getPayLoad().get("feeChargesPortion"); + Object penaltyPortion = externalEvent.getPayLoad().get("penaltyChargesPortion"); + + return super.verify(externalEvent, formatter) && Objects.equals(amount, getAmount()) + && Objects.equals(outstandingLoanBalance, getOutstandingLoanBalance()) + && Objects.equals(principalPortion, getPrincipalPortion()) && Objects.equals(interestPortion, getInterestPortion()) + && Objects.equals(feePortion, getFeeChargesPortion()) && Objects.equals(penaltyPortion, getPenaltyChargesPortion()); + } +}
