You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ar...@apache.org on 2022/11/10 15:38:58 UTC

[fineract] branch develop updated: FINERACT-1734-Deliquency-range-event

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

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


The following commit(s) were added to refs/heads/develop by this push:
     new 1cf37341d FINERACT-1734-Deliquency-range-event
1cf37341d is described below

commit 1cf37341d5a32d9179b95f50f17ae0f7be51d32e
Author: Ruchi Dhamankar <ru...@gmail.com>
AuthorDate: Thu Nov 10 15:52:05 2022 +0530

    FINERACT-1734-Deliquency-range-event
---
 ...avsc => LoanAccountDelinquencyRangeDataV1.avsc} |  26 +--
 .../loan/v1/LoanTransactionRelationDataV1.avsc     |   6 +-
 .../LoanDelinquencyRangeChangeBusinessEvent.java   |  35 ++++
 .../loan/LoanDelinquencyRangeDataMapper.java       |  30 ++++
 ...inquencyRangeChangeBusinessEventSerializer.java |  64 +++++++
 .../DelinquencyWritePlatformServiceImpl.java       |   4 +
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 ...guration_for_delinquency_range_change_event.xml |  31 ++++
 ...nalEventConfigurationValidationServiceTest.java |   5 +-
 ...cyWritePlatformServiceRangeChangeEventTest.java | 191 +++++++++++++++++++++
 .../common/ExternalEventConfigurationHelper.java   |   5 +
 11 files changed, 376 insertions(+), 22 deletions(-)

diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionRelationDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
similarity index 56%
copy from fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionRelationDataV1.avsc
copy to fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
index 56b296edd..048526c34 100644
--- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionRelationDataV1.avsc
+++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanAccountDelinquencyRangeDataV1.avsc
@@ -1,46 +1,38 @@
 {
-    "name": "LoanTransactionRelationDataV1",
+    "name": "LoanAccountDelinquencyRangeDataV1",
     "namespace": "org.apache.fineract.avro.loan.v1",
     "type": "record",
     "fields": [
-        {
-            "name": "fromLoanTransaction",
-            "type": "int"
-        },
         {
             "default": null,
-            "name": "toLoanTransaction",
+            "name": "id",
             "type": [
                 "null",
-                "int"
+                "long"
             ]
         },
         {
             "default": null,
-            "name": "toLoanCharge",
+            "name": "accountNo",
             "type": [
                 "null",
-                "int"
+                "string"
             ]
         },
         {
             "default": null,
-            "name": "amount",
+            "name": "externalId",
             "type": [
                 "null",
-                "bigdecimal"
+                "string"
             ]
         },
-        {
-            "name": "relationType",
-            "type": "string"
-        },
         {
             "default": null,
-            "name": "paymentType",
+            "name": "delinquencyRange",
             "type": [
                 "null",
-                "string"
+                "org.apache.fineract.avro.loan.v1.DelinquencyRangeDataV1"
             ]
         }
     ]
diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionRelationDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionRelationDataV1.avsc
index 56b296edd..0d7d8dac9 100644
--- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionRelationDataV1.avsc
+++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionRelationDataV1.avsc
@@ -5,14 +5,14 @@
     "fields": [
         {
             "name": "fromLoanTransaction",
-            "type": "int"
+            "type": "long"
         },
         {
             "default": null,
             "name": "toLoanTransaction",
             "type": [
                 "null",
-                "int"
+                "long"
             ]
         },
         {
@@ -20,7 +20,7 @@
             "name": "toLoanCharge",
             "type": [
                 "null",
-                "int"
+                "long"
             ]
         },
         {
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanDelinquencyRangeChangeBusinessEvent.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanDelinquencyRangeChangeBusinessEvent.java
new file mode 100644
index 000000000..474956838
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/LoanDelinquencyRangeChangeBusinessEvent.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;
+
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+
+public class LoanDelinquencyRangeChangeBusinessEvent extends LoanBusinessEvent {
+
+    private static final String TYPE = "LoanDelinquencyRangeChangeBusinessEvent";
+
+    public LoanDelinquencyRangeChangeBusinessEvent(Loan value) {
+        super(value);
+    }
+
+    @Override
+    public String getType() {
+        return TYPE;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanDelinquencyRangeDataMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanDelinquencyRangeDataMapper.java
new file mode 100644
index 000000000..e87b3db7c
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanDelinquencyRangeDataMapper.java
@@ -0,0 +1,30 @@
+/**
+ * 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.external.service.serialization.mapper.loan;
+
+import org.apache.fineract.avro.loan.v1.DelinquencyRangeDataV1;
+import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.support.AvroMapperConfig;
+import org.apache.fineract.portfolio.delinquency.data.DelinquencyRangeData;
+import org.mapstruct.Mapper;
+
+@Mapper(config = AvroMapperConfig.class)
+public interface LoanDelinquencyRangeDataMapper {
+
+    DelinquencyRangeDataV1 map(DelinquencyRangeData source);
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanDelinquencyRangeChangeBusinessEventSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanDelinquencyRangeChangeBusinessEventSerializer.java
new file mode 100644
index 000000000..8e8bc4335
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanDelinquencyRangeChangeBusinessEventSerializer.java
@@ -0,0 +1,64 @@
+/**
+ * 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.external.service.serialization.serializer.loan;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.avro.generic.GenericContainer;
+import org.apache.fineract.avro.generator.ByteBufferSerializable;
+import org.apache.fineract.avro.loan.v1.DelinquencyRangeDataV1;
+import org.apache.fineract.avro.loan.v1.LoanAccountDelinquencyRangeDataV1;
+import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
+import org.apache.fineract.infrastructure.event.business.domain.loan.LoanDelinquencyRangeChangeBusinessEvent;
+import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanDelinquencyRangeDataMapper;
+import org.apache.fineract.infrastructure.event.external.service.serialization.serializer.AbstractBusinessEventSerializer;
+import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
+import org.apache.fineract.portfolio.loanaccount.service.LoanReadPlatformService;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+@Order(Ordered.LOWEST_PRECEDENCE - 1)
+public class LoanDelinquencyRangeChangeBusinessEventSerializer extends AbstractBusinessEventSerializer {
+
+    private final LoanReadPlatformService service;
+    private final LoanDelinquencyRangeDataMapper mapper;
+
+    @Override
+    protected <T> ByteBufferSerializable toAvroDTO(BusinessEvent<T> rawEvent) {
+        LoanDelinquencyRangeChangeBusinessEvent event = (LoanDelinquencyRangeChangeBusinessEvent) rawEvent;
+        LoanAccountData data = service.retrieveOne(event.get().getId());
+        Long id = data.getId();
+        String accountNumber = data.getAccountNo();
+        String externalId = data.getExternalId();
+        DelinquencyRangeDataV1 delinquencyRange = mapper.map(data.getDelinquencyRange());
+        return new LoanAccountDelinquencyRangeDataV1(id, accountNumber, externalId, delinquencyRange);
+    }
+
+    @Override
+    public <T> boolean canSerialize(BusinessEvent<T> event) {
+        return event instanceof LoanDelinquencyRangeChangeBusinessEvent;
+    }
+
+    @Override
+    public Class<? extends GenericContainer> getSupportedSchema() {
+        return LoanAccountDelinquencyRangeDataV1.class;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java
index b1d543a7d..a0c678fed 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/delinquency/service/DelinquencyWritePlatformServiceImpl.java
@@ -33,6 +33,8 @@ import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
 import org.apache.fineract.infrastructure.core.exception.PlatformDataIntegrityException;
 import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.event.business.domain.loan.LoanDelinquencyRangeChangeBusinessEvent;
+import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
 import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
 import org.apache.fineract.portfolio.delinquency.api.DelinquencyApiConstants;
 import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
@@ -73,6 +75,7 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
     private final LoanDelinquencyTagHistoryRepository loanDelinquencyTagRepository;
     private final LoanRepositoryWrapper loanRepository;
     private final LoanProductRepository loanProductRepository;
+    private final BusinessEventNotifierService businessEventNotifierService;
 
     @Override
     public CommandProcessingResult createDelinquencyRange(JsonCommand command) {
@@ -431,6 +434,7 @@ public class DelinquencyWritePlatformServiceImpl implements DelinquencyWritePlat
         }
         if (loanDelinquencyTagHistory.size() > 0) {
             this.loanDelinquencyTagRepository.saveAllAndFlush(loanDelinquencyTagHistory);
+            businessEventNotifierService.notifyPostBusinessEvent(new LoanDelinquencyRangeChangeBusinessEvent(loan));
         }
         return changes;
     }
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 c945903f4..ac677cd75 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
@@ -89,4 +89,5 @@
     <include file="parts/0067_add_configurations_for_repayment_due_business_steps.xml" relativeToChangelogFile="true"/>
     <include file="parts/0068_loan_charge_adjustment.xml" relativeToChangelogFile="true"/>
     <include file="parts/0069_add_unique_constraint_for_reversal_external_id_of_loan_transactions.xml" relativeToChangelogFile="true"/>
+    <include file="parts/0070_add_event_configuration_for_delinquency_range_change_event.xml" relativeToChangelogFile="true"/>
 </databaseChangeLog>
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0070_add_event_configuration_for_delinquency_range_change_event.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0070_add_event_configuration_for_delinquency_range_change_event.xml
new file mode 100644
index 000000000..7a653df42
--- /dev/null
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0070_add_event_configuration_for_delinquency_range_change_event.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.3.xsd">
+    <changeSet author="fineract" id="1">
+        <insert tableName="m_external_event_configuration">
+            <column name="type" value="LoanDelinquencyRangeChangeBusinessEvent"/>
+            <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 3c3599429..d17e90d42 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
@@ -89,7 +89,7 @@ public class ExternalEventConfigurationValidationServiceTest {
                 "SavingsCloseBusinessEvent", "SavingsCreateBusinessEvent", "SavingsDepositBusinessEvent",
                 "SavingsPostInterestBusinessEvent", "SavingsRejectBusinessEvent", "SavingsWithdrawalBusinessEvent",
                 "ShareAccountApproveBusinessEvent", "ShareAccountCreateBusinessEvent", "ShareProductDividentsCreateBusinessEvent",
-                "LoanChargeAdjustmentPostBusinessEvent", "LoanChargeAdjustmentPreBusinessEvent");
+                "LoanChargeAdjustmentPostBusinessEvent", "LoanChargeAdjustmentPreBusinessEvent", "LoanDelinquencyRangeChangeBusinessEvent");
 
         List<FineractPlatformTenant> tenants = Arrays
                 .asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
@@ -162,7 +162,8 @@ public class ExternalEventConfigurationValidationServiceTest {
                 "SavingsCloseBusinessEvent", "SavingsCreateBusinessEvent", "SavingsDepositBusinessEvent",
                 "SavingsPostInterestBusinessEvent", "SavingsRejectBusinessEvent", "SavingsWithdrawalBusinessEvent",
                 "ShareAccountApproveBusinessEvent", "ShareAccountCreateBusinessEvent", "ShareProductDividentsCreateBusinessEvent",
-                "LoanChargeAdjustmentPostBusinessEvent", "LoanChargeAdjustmentPreBusinessEvent");
+                "LoanChargeAdjustmentPostBusinessEvent", "LoanChargeAdjustmentPreBusinessEvent", "LoanDelinquencyRangeChangeBusinessEvent");
+
         List<FineractPlatformTenant> tenants = Arrays
                 .asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
 
diff --git a/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java
new file mode 100644
index 000000000..5343b1ed4
--- /dev/null
+++ b/fineract-provider/src/test/java/org/apache/fineract/portfolio/deliquency/DelinquencyWritePlatformServiceRangeChangeEventTest.java
@@ -0,0 +1,191 @@
+/**
+ * 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.portfolio.deliquency;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyIterable;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import org.apache.fineract.infrastructure.businessdate.domain.BusinessDateType;
+import org.apache.fineract.infrastructure.core.domain.FineractPlatformTenant;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil;
+import org.apache.fineract.infrastructure.event.business.domain.loan.LoanDelinquencyRangeChangeBusinessEvent;
+import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucket;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketMappingsRepository;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyBucketRepository;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRange;
+import org.apache.fineract.portfolio.delinquency.domain.DelinquencyRangeRepository;
+import org.apache.fineract.portfolio.delinquency.domain.LoanDelinquencyTagHistoryRepository;
+import org.apache.fineract.portfolio.delinquency.service.DelinquencyWritePlatformServiceImpl;
+import org.apache.fineract.portfolio.delinquency.validator.DelinquencyBucketParseAndValidator;
+import org.apache.fineract.portfolio.delinquency.validator.DelinquencyRangeParseAndValidator;
+import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRelatedDetail;
+import org.apache.fineract.portfolio.loanproduct.domain.LoanProductRepository;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+@ExtendWith(MockitoExtension.class)
+public class DelinquencyWritePlatformServiceRangeChangeEventTest {
+
+    @Mock
+    private DelinquencyBucketParseAndValidator dataValidatorBucket;
+    @Mock
+    private DelinquencyRangeParseAndValidator dataValidatorRange;
+    @Mock
+    private DelinquencyRangeRepository repositoryRange;
+    @Mock
+    private DelinquencyBucketRepository repositoryBucket;
+    @Mock
+    private DelinquencyBucketMappingsRepository repositoryBucketMappings;
+    @Mock
+    private LoanDelinquencyTagHistoryRepository loanDelinquencyTagRepository;
+    @Mock
+    private LoanRepositoryWrapper loanRepository;
+    @Mock
+    private LoanProductRepository loanProductRepository;
+    @Mock
+    private BusinessEventNotifierService businessEventNotifierService;
+    @InjectMocks
+    private DelinquencyWritePlatformServiceImpl underTest;
+
+    @BeforeEach
+    public void setUp() {
+        ThreadLocalContextUtil.setTenant(new FineractPlatformTenant(1L, "default", "Default", "Asia/Kolkata", null));
+        ThreadLocalContextUtil
+                .setBusinessDates(new HashMap<>(Map.of(BusinessDateType.BUSINESS_DATE, LocalDate.now(ZoneId.systemDefault()))));
+    }
+
+    @Test
+    public void givenLoanAccountWithDelinquencyBucketWhenRangeChangeThenEventIsRaised() {
+        ArgumentCaptor<LoanDelinquencyRangeChangeBusinessEvent> loanDeliquencyRangeChangeEvent = ArgumentCaptor
+                .forClass(LoanDelinquencyRangeChangeBusinessEvent.class);
+        // given
+        Loan loanForProcessing = Mockito.mock(Loan.class);
+        LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
+        LoanProductRelatedDetail loanProductRelatedDetail = Mockito.mock(LoanProductRelatedDetail.class);
+        DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2);
+        range1.setId(1L);
+        DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30);
+        range2.setId(2L);
+        List<DelinquencyRange> listDelinquencyRanges = Arrays.asList(range1, range2);
+        DelinquencyBucket delinquencyBucket = new DelinquencyBucket("test Bucket");
+        delinquencyBucket.setRanges(listDelinquencyRanges);
+
+        LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate().minusDays(2);
+        LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 1L,
+                loanForProcessing);
+
+        when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
+        when(loanProduct.getDelinquencyBucket()).thenReturn(delinquencyBucket);
+        when(loanProduct.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
+        when(loanProductRelatedDetail.getGraceOnArrearsAgeing()).thenReturn(1);
+        when(loanForProcessing.hasDelinquencyBucket()).thenReturn(true);
+        when(loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(any(), any())).thenReturn(Optional.empty());
+        when(repositoryRange.getReferenceById(anyLong())).thenReturn(range1);
+
+        // when
+        underTest.applyDelinquencyTagToLoan(loanScheduleDelinquencyData);
+
+        // then
+        verify(loanDelinquencyTagRepository, times(1)).saveAllAndFlush(anyIterable());
+        verify(businessEventNotifierService, times(1)).notifyPostBusinessEvent(loanDeliquencyRangeChangeEvent.capture());
+        Loan loanPayloadForEvent = loanDeliquencyRangeChangeEvent.getValue().get();
+        assertEquals(loanForProcessing, loanPayloadForEvent);
+    }
+
+    @Test
+    public void givenLoanAccountWithDelinquencyBucketWhenNoRangeChangeThenNoEventIsRaised() {
+        // given
+        Loan loanForProcessing = Mockito.mock(Loan.class);
+        LoanProduct loanProduct = Mockito.mock(LoanProduct.class);
+        LoanProductRelatedDetail loanProductRelatedDetail = Mockito.mock(LoanProductRelatedDetail.class);
+
+        DelinquencyRange range1 = DelinquencyRange.instance("Range1", 1, 2);
+        range1.setId(1L);
+        DelinquencyRange range2 = DelinquencyRange.instance("Range30", 3, 30);
+        range2.setId(2L);
+        List<DelinquencyRange> listDelinquencyRanges = Arrays.asList(range1, range2);
+
+        DelinquencyBucket delinquencyBucket = new DelinquencyBucket("test Bucket");
+        delinquencyBucket.setRanges(listDelinquencyRanges);
+
+        LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate();
+        LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 2L,
+                loanForProcessing);
+
+        when(loanForProcessing.getLoanProduct()).thenReturn(loanProduct);
+        when(loanProduct.getDelinquencyBucket()).thenReturn(delinquencyBucket);
+        when(loanProduct.getLoanProductRelatedDetail()).thenReturn(loanProductRelatedDetail);
+        when(loanProductRelatedDetail.getGraceOnArrearsAgeing()).thenReturn(1);
+        when(loanForProcessing.hasDelinquencyBucket()).thenReturn(true);
+        when(loanDelinquencyTagRepository.findByLoanAndLiftedOnDate(any(), any())).thenReturn(Optional.empty());
+
+        // when
+        underTest.applyDelinquencyTagToLoan(loanScheduleDelinquencyData);
+
+        // then
+        verify(loanDelinquencyTagRepository, times(0)).saveAllAndFlush(anyIterable());
+        verify(businessEventNotifierService, times(0)).notifyPostBusinessEvent(any());
+
+    }
+
+    @Test
+    public void givenLoanAccountWithNoDelinquencyBucketThenNoEventIsRaised() {
+        // given
+        Loan loanForProcessing = Mockito.mock(Loan.class);
+
+        LocalDate overDueSinceDate = DateUtils.getBusinessLocalDate();
+        LoanScheduleDelinquencyData loanScheduleDelinquencyData = new LoanScheduleDelinquencyData(1L, overDueSinceDate, 2L,
+                loanForProcessing);
+
+        when(loanForProcessing.hasDelinquencyBucket()).thenReturn(false);
+
+        // when
+        underTest.applyDelinquencyTagToLoan(loanScheduleDelinquencyData);
+
+        // then
+        verify(loanDelinquencyTagRepository, times(0)).saveAllAndFlush(anyIterable());
+        verify(businessEventNotifierService, times(0)).notifyPostBusinessEvent(any());
+
+    }
+
+}
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 af6cf7a6d..572b60867 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
@@ -435,6 +435,11 @@ public class ExternalEventConfigurationHelper {
         loanChargeAdjustmentPreBusinessEvent.put("enabled", false);
         defaults.add(loanChargeAdjustmentPreBusinessEvent);
 
+        Map<String, Object> loanDelinquencyRangeChangeBusinessEvent = new HashMap<>();
+        loanDelinquencyRangeChangeBusinessEvent.put("type", "LoanDelinquencyRangeChangeBusinessEvent");
+        loanDelinquencyRangeChangeBusinessEvent.put("enabled", false);
+        defaults.add(loanDelinquencyRangeChangeBusinessEvent);
+
         return defaults;
 
     }