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 2023/01/17 14:27:28 UTC

[fineract] branch develop updated: FINERACT-1806: Trigger event when charge-off happens

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 b24cdb4a6 FINERACT-1806: Trigger event when charge-off happens
b24cdb4a6 is described below

commit b24cdb4a659524816df2c950ee069c14972d6493
Author: Adam Saghy <ad...@gmail.com>
AuthorDate: Tue Jan 17 09:49:35 2023 +0100

    FINERACT-1806: Trigger event when charge-off happens
---
 .../main/avro/loan/v1/LoanTransactionDataV1.avsc   | 12 ++++
 .../src/main/avro/loan/v1/UnpaidChargeDataV1.avsc  | 19 ++++++
 .../LoanChargeOffPostBusinessEvent.java}           | 21 ++++---
 .../LoanChargeOffPreBusinessEvent.java}            | 22 ++++---
 .../mapper/loan/LoanAccountDataMapper.java         |  6 +-
 .../mapper/loan/LoanTransactionDataMapper.java     |  3 +
 ...DataMapper.java => UnpaidChargeDataMapper.java} | 11 ++--
 .../loan/LoanChargeOffBusinessEventSerializer.java | 71 ++++++++++++++++++++++
 .../loanaccount/data/UnpaidChargeData.java         | 41 +++++++++++++
 .../domain/LoanTransactionRepository.java          | 15 +++++
 .../LoanWritePlatformServiceJpaRepositoryImpl.java |  6 +-
 .../db/changelog/tenant/changelog-tenant.xml       |  1 +
 ...82_add_external_event_default_configuration.xml | 35 +++++++++++
 ...nalEventConfigurationValidationServiceTest.java |  5 +-
 .../common/ExternalEventConfigurationHelper.java   | 10 +++
 15 files changed, 250 insertions(+), 28 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 605876fcb..fd8262f9b 100644
--- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc
+++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc
@@ -216,6 +216,18 @@
                     "items": "org.apache.fineract.avro.loan.v1.LoanTransactionRelationDataV1"
                 }
             ]
+        },
+        {
+            "default": null,
+            "name": "unpaidCharges",
+            "doc": "Only used for Charge-off transaction",
+            "type": [
+                "null",
+                {
+                    "type": "array",
+                    "items": "org.apache.fineract.avro.loan.v1.UnpaidChargeDataV1"
+                }
+            ]
         }
     ]
 }
diff --git a/fineract-avro-schemas/src/main/avro/loan/v1/UnpaidChargeDataV1.avsc b/fineract-avro-schemas/src/main/avro/loan/v1/UnpaidChargeDataV1.avsc
new file mode 100644
index 000000000..f3a18e365
--- /dev/null
+++ b/fineract-avro-schemas/src/main/avro/loan/v1/UnpaidChargeDataV1.avsc
@@ -0,0 +1,19 @@
+{
+    "name": "UnpaidChargeDataV1",
+    "namespace": "org.apache.fineract.avro.loan.v1",
+    "type": "record",
+    "fields": [
+        {
+            "name": "chargeId",
+            "type": "long"
+        },
+        {
+            "name": "chargeName",
+            "type": "string"
+        },
+        {
+            "name": "outstandingAmount",
+            "type": "bigdecimal"
+        }
+    ]
+}
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/business/domain/loan/transaction/LoanChargeOffPostBusinessEvent.java
similarity index 60%
copy from fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanTransactionDataMapper.java
copy to fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanChargeOffPostBusinessEvent.java
index dbe24a0c9..93c343d56 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/business/domain/loan/transaction/LoanChargeOffPostBusinessEvent.java
@@ -16,15 +16,20 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan;
+package org.apache.fineract.infrastructure.event.business.domain.loan.transaction;
 
-import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
-import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.support.AvroMapperConfig;
-import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
-import org.mapstruct.Mapper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 
-@Mapper(config = AvroMapperConfig.class)
-public interface LoanTransactionDataMapper {
+public class LoanChargeOffPostBusinessEvent extends LoanTransactionBusinessEvent {
 
-    LoanTransactionDataV1 map(LoanTransactionData source);
+    private static final String TYPE = "LoanChargeOffPostBusinessEvent";
+
+    public LoanChargeOffPostBusinessEvent(LoanTransaction 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/LoanTransactionDataMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanChargeOffPreBusinessEvent.java
similarity index 60%
copy from fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanTransactionDataMapper.java
copy to fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanChargeOffPreBusinessEvent.java
index dbe24a0c9..cc246b3f8 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/business/domain/loan/transaction/LoanChargeOffPreBusinessEvent.java
@@ -16,15 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan;
+package org.apache.fineract.infrastructure.event.business.domain.loan.transaction;
 
-import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
-import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.support.AvroMapperConfig;
-import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
-import org.mapstruct.Mapper;
+import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBusinessEvent;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 
-@Mapper(config = AvroMapperConfig.class)
-public interface LoanTransactionDataMapper {
+public class LoanChargeOffPreBusinessEvent extends LoanBusinessEvent {
 
-    LoanTransactionDataV1 map(LoanTransactionData source);
+    private static final String TYPE = "LoanChargeOffPreBusinessEvent";
+
+    public LoanChargeOffPreBusinessEvent(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/LoanAccountDataMapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanAccountDataMapper.java
index 50edc033a..1ada4f441 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanAccountDataMapper.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanAccountDataMapper.java
@@ -19,14 +19,12 @@
 package org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan;
 
 import org.apache.fineract.avro.loan.v1.LoanAccountDataV1;
-import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
 import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.support.AvroMapperConfig;
 import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
-import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 
-@Mapper(config = AvroMapperConfig.class)
+@Mapper(config = AvroMapperConfig.class, uses = { LoanTransactionDataMapper.class })
 public interface LoanAccountDataMapper {
 
     // TODO: avoid prefix "is" in class attributes; I would recommend to fix this also in the Avro structures
@@ -35,6 +33,4 @@ public interface LoanAccountDataMapper {
     @Mapping(source = "topup", target = "isTopup")
     @Mapping(source = "interestRecalculationEnabled", target = "isInterestRecalculationEnabled")
     LoanAccountDataV1 map(LoanAccountData source);
-
-    LoanTransactionDataV1 map(LoanTransactionData source);
 }
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 dbe24a0c9..671d091a6 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
@@ -22,9 +22,12 @@ import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
 import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.support.AvroMapperConfig;
 import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
 import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
 
 @Mapper(config = AvroMapperConfig.class)
 public interface LoanTransactionDataMapper {
 
+    // unpaidCharges are calculated and set explicitly based on if needed (only for charge-off transaction yet)
+    @Mapping(target = "unpaidCharges", ignore = true)
     LoanTransactionDataV1 map(LoanTransactionData source);
 }
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/UnpaidChargeDataMapper.java
similarity index 77%
copy from fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/LoanTransactionDataMapper.java
copy to fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/mapper/loan/UnpaidChargeDataMapper.java
index dbe24a0c9..69905d34b 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/UnpaidChargeDataMapper.java
@@ -18,13 +18,16 @@
  */
 package org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan;
 
-import org.apache.fineract.avro.loan.v1.LoanTransactionDataV1;
+import java.util.List;
+import org.apache.fineract.avro.loan.v1.UnpaidChargeDataV1;
 import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.support.AvroMapperConfig;
-import org.apache.fineract.portfolio.loanaccount.data.LoanTransactionData;
+import org.apache.fineract.portfolio.loanaccount.data.UnpaidChargeData;
 import org.mapstruct.Mapper;
 
 @Mapper(config = AvroMapperConfig.class)
-public interface LoanTransactionDataMapper {
+public interface UnpaidChargeDataMapper {
 
-    LoanTransactionDataV1 map(LoanTransactionData source);
+    UnpaidChargeDataV1 map(UnpaidChargeData source);
+
+    List<UnpaidChargeDataV1> map(List<UnpaidChargeData> source);
 }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanChargeOffBusinessEventSerializer.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanChargeOffBusinessEventSerializer.java
new file mode 100644
index 000000000..3c94d1bf1
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/event/external/service/serialization/serializer/loan/LoanChargeOffBusinessEventSerializer.java
@@ -0,0 +1,71 @@
+/**
+ * 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 java.util.List;
+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.infrastructure.event.business.domain.BusinessEvent;
+import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPostBusinessEvent;
+import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanTransactionBusinessEvent;
+import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.LoanTransactionDataMapper;
+import org.apache.fineract.infrastructure.event.external.service.serialization.mapper.loan.UnpaidChargeDataMapper;
+import org.apache.fineract.portfolio.loanaccount.data.UnpaidChargeData;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
+import org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadPlatformService;
+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
+@Order(Ordered.LOWEST_PRECEDENCE - 1)
+public class LoanChargeOffBusinessEventSerializer extends LoanTransactionBusinessEventSerializer {
+
+    private final UnpaidChargeDataMapper unpaidChargeDataMapper;
+    private final LoanTransactionRepository loanTransactionRepository;
+
+    public LoanChargeOffBusinessEventSerializer(LoanReadPlatformService loanReadPlatformService,
+            LoanTransactionDataMapper loanTransactionMapper, LoanChargePaidByReadPlatformService loanChargePaidByReadPlatformService,
+            UnpaidChargeDataMapper unpaidChargeDataMapper, LoanTransactionRepository loanTransactionRepository) {
+        super(loanReadPlatformService, loanTransactionMapper, loanChargePaidByReadPlatformService);
+        this.unpaidChargeDataMapper = unpaidChargeDataMapper;
+        this.loanTransactionRepository = loanTransactionRepository;
+    }
+
+    @Override
+    public <T> boolean canSerialize(BusinessEvent<T> event) {
+        return event instanceof LoanChargeOffPostBusinessEvent;
+    }
+
+    @Override
+    protected <T> ByteBufferSerializable toAvroDTO(BusinessEvent<T> rawEvent) {
+        LoanTransactionDataV1 transactionDataV1 = (LoanTransactionDataV1) super.toAvroDTO(rawEvent);
+        LoanTransactionBusinessEvent event = (LoanTransactionBusinessEvent) rawEvent;
+        List<UnpaidChargeData> unpaidChargeDataList = loanTransactionRepository.fetchTotalUnpaidChargesForLoan(event.get().getLoan());
+        transactionDataV1.setUnpaidCharges(unpaidChargeDataMapper.map(unpaidChargeDataList));
+        return transactionDataV1;
+    }
+
+    @Override
+    public Class<? extends GenericContainer> getSupportedSchema() {
+        return LoanTransactionDataV1.class;
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/UnpaidChargeData.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/UnpaidChargeData.java
new file mode 100644
index 000000000..6e9ede09b
--- /dev/null
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/data/UnpaidChargeData.java
@@ -0,0 +1,41 @@
+/**
+ * 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.loanaccount.data;
+
+import java.math.BigDecimal;
+import java.util.Objects;
+import lombok.Getter;
+
+/**
+ * Immutable data object representing the total unpaid charge details
+ */
+@Getter
+public class UnpaidChargeData {
+
+    private static final String NULL_VALUE_IS_NOT_ALLOWED = "Null value is not allowed";
+    private final Long chargeId;
+    private final String chargeName;
+    private final BigDecimal outstandingAmount;
+
+    public UnpaidChargeData(Long chargeId, String chargeName, BigDecimal outstandingAmount) {
+        this.chargeId = Objects.requireNonNull(chargeId, NULL_VALUE_IS_NOT_ALLOWED);
+        this.chargeName = Objects.requireNonNull(chargeName, NULL_VALUE_IS_NOT_ALLOWED);
+        this.outstandingAmount = Objects.requireNonNull(outstandingAmount, NULL_VALUE_IS_NOT_ALLOWED);
+    }
+}
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
index 65229fe99..5b77f107d 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
@@ -20,9 +20,11 @@ package org.apache.fineract.portfolio.loanaccount.domain;
 
 import java.time.LocalDate;
 import java.util.Collection;
+import java.util.List;
 import java.util.Optional;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
+import org.apache.fineract.portfolio.loanaccount.data.UnpaidChargeData;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.data.jpa.repository.Query;
@@ -50,4 +52,17 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction
 
     @Query(FIND_ID_BY_EXTERNAL_ID)
     Long findIdByExternalId(@Param("externalId") ExternalId externalId);
+
+    @Query("""
+                    SELECT new org.apache.fineract.portfolio.loanaccount.data.UnpaidChargeData(
+                        lc.charge.id,
+                        lc.charge.name,
+                        SUM(lc.amountOutstanding)
+                    ) FROM LoanCharge lc
+                    WHERE lc.loan = :loan
+                    AND lc.active = true
+                    AND lc.amountOutstanding > 0
+                    GROUP BY lc.charge.id, lc.charge.name
+            """)
+    List<UnpaidChargeData> fetchTotalUnpaidChargesForLoan(Loan loan);
 }
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 760f76e2a..408b20a01 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
@@ -77,6 +77,8 @@ import org.apache.fineract.infrastructure.event.business.domain.loan.LoanUndoDis
 import org.apache.fineract.infrastructure.event.business.domain.loan.LoanUndoLastDisbursalBusinessEvent;
 import org.apache.fineract.infrastructure.event.business.domain.loan.LoanUpdateDisbursementDataBusinessEvent;
 import org.apache.fineract.infrastructure.event.business.domain.loan.LoanWithdrawTransferBusinessEvent;
+import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPostBusinessEvent;
+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.LoanUndoWrittenOffBusinessEvent;
 import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanWaiveInterestBusinessEvent;
@@ -2661,6 +2663,8 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
         // TODO: add business logic validation (transaction date cannot be future, cannot be earlier than latest
         // transactions, etc)
         Loan loan = loanAssembler.assembleFrom(command.getLoanId());
+        businessEventNotifierService.notifyPreBusinessEvent(new LoanChargeOffPreBusinessEvent(loan));
+
         final LocalDate transactionDate = command.localDateValueOfParameterNamed(LoanApiConstants.transactionDateParamName);
         final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, LoanApiConstants.externalIdParameterName);
         final AppUser currentUser = getAppUserIfPresent();
@@ -2686,7 +2690,7 @@ public class LoanWritePlatformServiceJpaRepositoryImpl implements LoanWritePlatf
         }
 
         // TODO: add accounting
-        // TODO: add external events
+        businessEventNotifierService.notifyPostBusinessEvent(new LoanChargeOffPostBusinessEvent(chargeOffTransaction));
         return new CommandProcessingResultBuilder() //
                 .withCommandId(command.commandId()) //
                 .withEntityId(chargeOffTransaction.getId()) //
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 fecc2da04..00d798e17 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
@@ -101,4 +101,5 @@
     <include file="parts/0079_add_charge_off_details_to_loan.xml" relativeToChangelogFile="true" />
     <include file="parts/0080_add_external_event_configuration_for_stayed_locked_loans_business_event.xml" relativeToChangelogFile="true" />
     <include file="parts/0081_add_configuration_event_producer_batch_size.xml" relativeToChangelogFile="true" />
+    <include file="parts/0082_add_external_event_default_configuration.xml" relativeToChangelogFile="true" />
 </databaseChangeLog>
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0082_add_external_event_default_configuration.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0082_add_external_event_default_configuration.xml
new file mode 100644
index 000000000..e43dd849c
--- /dev/null
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0082_add_external_event_default_configuration.xml
@@ -0,0 +1,35 @@
+<?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 author="fineract" id="1">
+        <insert tableName="m_external_event_configuration">
+            <column name="type" value="LoanChargeOffPreBusinessEvent"/>
+            <column name="enabled" valueBoolean="false"/>
+        </insert>
+        <insert tableName="m_external_event_configuration">
+            <column name="type" value="LoanChargeOffPostBusinessEvent"/>
+            <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 495d46faa..ed0d22ae4 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
@@ -94,7 +94,8 @@ public class ExternalEventConfigurationValidationServiceTest {
                 "SavingsPostInterestBusinessEvent", "SavingsRejectBusinessEvent", "SavingsWithdrawalBusinessEvent",
                 "ShareAccountApproveBusinessEvent", "ShareAccountCreateBusinessEvent", "ShareProductDividentsCreateBusinessEvent",
                 "LoanChargeAdjustmentPostBusinessEvent", "LoanChargeAdjustmentPreBusinessEvent", "LoanDelinquencyRangeChangeBusinessEvent",
-                "LoanAccountsStayedLockedBusinessEvent", "MockBusinessEvent");
+                "LoanAccountsStayedLockedBusinessEvent", "MockBusinessEvent", "LoanChargeOffPreBusinessEvent",
+                "LoanChargeOffPostBusinessEvent");
 
         List<FineractPlatformTenant> tenants = Arrays
                 .asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
@@ -170,7 +171,7 @@ public class ExternalEventConfigurationValidationServiceTest {
                 "SavingsPostInterestBusinessEvent", "SavingsRejectBusinessEvent", "SavingsWithdrawalBusinessEvent",
                 "ShareAccountApproveBusinessEvent", "ShareAccountCreateBusinessEvent", "ShareProductDividentsCreateBusinessEvent",
                 "LoanChargeAdjustmentPostBusinessEvent", "LoanChargeAdjustmentPreBusinessEvent", "LoanDelinquencyRangeChangeBusinessEvent",
-                "LoanAccountsStayedLockedBusinessEvent");
+                "LoanAccountsStayedLockedBusinessEvent", "LoanChargeOffPreBusinessEvent", "LoanChargeOffPostBusinessEvent");
 
         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/common/ExternalEventConfigurationHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
index 9ee19cc48..036adfec2 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
@@ -445,6 +445,16 @@ public class ExternalEventConfigurationHelper {
         loanAccountsStayedLockedBusinessEvent.put("enabled", false);
         defaults.add(loanAccountsStayedLockedBusinessEvent);
 
+        Map<String, Object> loanChargeOffPreBusinessEvent = new HashMap<>();
+        loanChargeOffPreBusinessEvent.put("type", "LoanChargeOffPreBusinessEvent");
+        loanChargeOffPreBusinessEvent.put("enabled", false);
+        defaults.add(loanChargeOffPreBusinessEvent);
+
+        Map<String, Object> loanChargeOffPostBusinessEvent = new HashMap<>();
+        loanChargeOffPostBusinessEvent.put("type", "LoanChargeOffPostBusinessEvent");
+        loanChargeOffPostBusinessEvent.put("enabled", false);
+        defaults.add(loanChargeOffPostBusinessEvent);
+
         return defaults;
 
     }