You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fineract.apache.org by ad...@apache.org on 2025/07/07 13:29:33 UTC

(fineract) branch develop updated: FINERACT-2311: Add buydown fees adjustments

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


The following commit(s) were added to refs/heads/develop by this push:
     new b2bce4a45d FINERACT-2311: Add buydown fees adjustments
b2bce4a45d is described below

commit b2bce4a45df3b0c56a43c241e49b93e37b43f3f6
Author: Attila Budai <bu...@gmail.com>
AuthorDate: Sun Jul 6 10:31:06 2025 +0200

    FINERACT-2311: Add buydown fees adjustments
---
 .../commands/service/CommandWrapperBuilder.java    |   9 ++
 ...eAdjustmentTransactionCreatedBusinessEvent.java |  20 ++-
 ...nBuyDownFeeTransactionCreatedBusinessEvent.java |  20 ++-
 .../loanaccount/domain/LoanTransaction.java        |   6 +
 .../BuyDownFeeAdjustmentCommandHandler.java        |  53 +++++++
 .../LoanBuyDownFeesBalanceRepository.java          |  25 +++-
 .../service/BuyDownFeePlatformService.java         |   3 +
 .../BuyDownFeeWritePlatformServiceImpl.java        | 104 ++++++++++++-
 .../ProgressiveLoanTransactionValidator.java       |   2 +
 .../ProgressiveLoanTransactionValidatorImpl.java   |  96 ++++++++++++
 .../ProgressiveLoanAccountConfiguration.java       |   5 +-
 .../AccrualBasedAccountingProcessorForLoan.java    |  26 ++++
 .../api/LoanTransactionsApiResource.java           |   2 +
 .../starter/LoanAccountConfiguration.java          |   7 +-
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 .../parts/0189_add_loan_buydown_fee_event.xml      |  35 +++++
 ...nalEventConfigurationValidationServiceTest.java |   6 +-
 .../integrationtests/LoanBuyDownFeeTest.java       | 164 +++++++++++++++++++++
 .../common/ExternalEventConfigurationHelper.java   |  10 ++
 .../common/loans/LoanTransactionHelper.java        |  30 ++++
 20 files changed, 602 insertions(+), 22 deletions(-)

diff --git a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
index bbae94c105..a3f2c14c4b 100644
--- a/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
+++ b/fineract-core/src/main/java/org/apache/fineract/commands/service/CommandWrapperBuilder.java
@@ -3841,6 +3841,15 @@ public class CommandWrapperBuilder {
         return this;
     }
 
+    public CommandWrapperBuilder buyDownFeeAdjustment(final Long loanId, final Long transactionId) {
+        this.actionName = "BUYDOWNFEEADJUSTMENT";
+        this.entityName = "LOAN";
+        this.entityId = transactionId;
+        this.loanId = loanId;
+        this.href = "/loans/" + loanId + "/transactions/" + transactionId;
+        return this;
+    }
+
     public CommandWrapperBuilder applyContractTermination(final Long loanId) {
         this.actionName = "CONTRACT_TERMINATION";
         this.entityName = "LOAN";
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeePlatformService.java b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent.java
similarity index 59%
copy from fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeePlatformService.java
copy to fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent.java
index 8ef5620ab6..ee91f4b17b 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeePlatformService.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent.java
@@ -16,14 +16,20 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.portfolio.loanaccount.service;
+package org.apache.fineract.infrastructure.event.business.domain.loan.transaction;
 
-import org.apache.fineract.infrastructure.core.api.JsonCommand;
-import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
-import org.springframework.transaction.annotation.Transactional;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 
-public interface BuyDownFeePlatformService {
+public class LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent extends LoanTransactionBusinessEvent {
 
-    @Transactional
-    CommandProcessingResult makeLoanBuyDownFee(Long loanId, JsonCommand command);
+    private static final String TYPE = "LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent";
+
+    public LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent(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/BuyDownFeePlatformService.java b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeTransactionCreatedBusinessEvent.java
similarity index 60%
copy from fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeePlatformService.java
copy to fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeTransactionCreatedBusinessEvent.java
index 8ef5620ab6..ae9c7497df 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeePlatformService.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeTransactionCreatedBusinessEvent.java
@@ -16,14 +16,20 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.portfolio.loanaccount.service;
+package org.apache.fineract.infrastructure.event.business.domain.loan.transaction;
 
-import org.apache.fineract.infrastructure.core.api.JsonCommand;
-import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
-import org.springframework.transaction.annotation.Transactional;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 
-public interface BuyDownFeePlatformService {
+public class LoanBuyDownFeeTransactionCreatedBusinessEvent extends LoanTransactionBusinessEvent {
 
-    @Transactional
-    CommandProcessingResult makeLoanBuyDownFee(Long loanId, JsonCommand command);
+    private static final String TYPE = "LoanBuyDownFeeTransactionCreatedBusinessEvent";
+
+    public LoanBuyDownFeeTransactionCreatedBusinessEvent(LoanTransaction value) {
+        super(value);
+    }
+
+    @Override
+    public String getType() {
+        return TYPE;
+    }
 }
diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index 6c59aa39bb..9a1c48dd14 100644
--- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -332,6 +332,12 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
                 amount.getAmount(), amount.getAmount(), null, null, null, null, false, paymentDetail, externalId);
     }
 
+    public static LoanTransaction buyDownFeeAdjustment(final Loan loan, final Money amount, final PaymentDetail paymentDetail,
+            final LocalDate transactionDate, final ExternalId externalId) {
+        return new LoanTransaction(loan, loan.getOffice(), LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT, transactionDate, amount.getAmount(),
+                amount.getAmount(), null, null, null, null, false, paymentDetail, externalId);
+    }
+
     public static LoanTransaction capitalizedIncomeAmortizationAdjustment(final Loan loan, final Money amount,
             final LocalDate transactionDate, final ExternalId externalId) {
         return switch (loan.getLoanProductRelatedDetail().getCapitalizedIncomeType()) {
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/BuyDownFeeAdjustmentCommandHandler.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/BuyDownFeeAdjustmentCommandHandler.java
new file mode 100644
index 0000000000..27f5c937a9
--- /dev/null
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/handler/BuyDownFeeAdjustmentCommandHandler.java
@@ -0,0 +1,53 @@
+/**
+ * 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.handler;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.commands.annotation.CommandType;
+import org.apache.fineract.commands.handler.NewCommandSourceHandler;
+import org.apache.fineract.infrastructure.DataIntegrityErrorHandler;
+import org.apache.fineract.infrastructure.core.api.JsonCommand;
+import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
+import org.apache.fineract.portfolio.loanaccount.service.BuyDownFeePlatformService;
+import org.springframework.dao.DataIntegrityViolationException;
+import org.springframework.orm.jpa.JpaSystemException;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+@Service
+@RequiredArgsConstructor
+@CommandType(entity = "LOAN", action = "BUYDOWNFEEADJUSTMENT")
+public class BuyDownFeeAdjustmentCommandHandler implements NewCommandSourceHandler {
+
+    private final BuyDownFeePlatformService buyDownFeePlatformService;
+    private final DataIntegrityErrorHandler dataIntegrityErrorHandler;
+
+    @Transactional
+    @Override
+    public CommandProcessingResult processCommand(final JsonCommand command) {
+
+        try {
+            return this.buyDownFeePlatformService.buyDownFeeAdjustment(command.getLoanId(), command.entityId(), command);
+        } catch (final JpaSystemException | DataIntegrityViolationException dve) {
+            dataIntegrityErrorHandler.handleDataIntegrityIssues(command, dve.getMostSpecificCause(), dve, "loan.buy.down.fee",
+                    "Buy Down Fee");
+            return CommandProcessingResult.empty();
+        }
+    }
+}
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/repository/LoanBuyDownFeesBalanceRepository.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/repository/LoanBuyDownFeesBalanceRepository.java
index 5c160ece45..88dceec5d8 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/repository/LoanBuyDownFeesBalanceRepository.java
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/repository/LoanBuyDownFeesBalanceRepository.java
@@ -18,9 +18,32 @@
  */
 package org.apache.fineract.portfolio.loanaccount.repository;
 
+import java.math.BigDecimal;
+import java.util.List;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepaymentPeriodData;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.data.jpa.repository.Query;
 
 public interface LoanBuyDownFeesBalanceRepository
-        extends JpaRepository<LoanBuyDownFeeBalance, Long>, JpaSpecificationExecutor<LoanBuyDownFeeBalance> {}
+        extends JpaRepository<LoanBuyDownFeeBalance, Long>, JpaSpecificationExecutor<LoanBuyDownFeeBalance> {
+
+    String FIND_BALANCE_REPAYMENT_SCHEDULE_DATA = "SELECT new org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepaymentPeriodData(lbfb.loanTransaction.id, lbfb.loan.id, lbfb.loanTransaction.dateOf, lbfb.loanTransaction.reversed, lbfb.amount, lbfb.unrecognizedAmount, lbfb.loanTransaction.feeChargesPortion) FROM LoanBuyDownFeeBalance lbfb ";
+
+    List<LoanBuyDownFeeBalance> findAllByLoanId(Long loanId);
+
+    LoanBuyDownFeeBalance findByLoanIdAndLoanTransactionId(Long loanId, Long transactionId);
+
+    @Query(FIND_BALANCE_REPAYMENT_SCHEDULE_DATA + " WHERE lbfb.loan.id = :loanId")
+    List<LoanTransactionRepaymentPeriodData> findRepaymentPeriodDataByLoanId(Long loanId);
+
+    @Query("SELECT SUM(lbfb.amount) FROM LoanBuyDownFeeBalance lbfb WHERE lbfb.loan.id = :loanId")
+    BigDecimal calculateBuydownFeeAmount(Long loanId);
+
+    @Query("SELECT SUM(lbfb.amountAdjustment) FROM LoanBuyDownFeeBalance lbfb WHERE lbfb.loan.id = :loanId")
+    BigDecimal calculateBuydownFeeAdjustment(Long loanId);
+
+    @Query("SELECT lbfb FROM LoanBuyDownFeeBalance lbfb, LoanTransaction lt, LoanTransactionRelation ltr WHERE lt.loan.id = lbfb.loan.id AND ltr.fromTransaction.id =:transactionId AND ltr.toTransaction.id=lt.id AND lbfb.loanTransaction.id = lt.id")
+    LoanBuyDownFeeBalance findBalanceForAdjustment(Long transactionId);
+}
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeePlatformService.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeePlatformService.java
index 8ef5620ab6..69180af13f 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeePlatformService.java
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeePlatformService.java
@@ -26,4 +26,7 @@ public interface BuyDownFeePlatformService {
 
     @Transactional
     CommandProcessingResult makeLoanBuyDownFee(Long loanId, JsonCommand command);
+
+    @Transactional
+    CommandProcessingResult buyDownFeeAdjustment(Long loanId, Long buyDownFeeTransactionId, JsonCommand command);
 }
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java
index b54c18de23..902b0a46d1 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java
@@ -24,6 +24,7 @@ import java.util.ArrayList;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
@@ -31,7 +32,13 @@ import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
 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.LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent;
+import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanBuyDownFeeTransactionCreatedBusinessEvent;
+import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
 import org.apache.fineract.organisation.monetary.domain.Money;
 import org.apache.fineract.portfolio.client.domain.Client;
 import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
@@ -39,7 +46,10 @@ import org.apache.fineract.portfolio.group.domain.Group;
 import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeesBalanceRepository;
 import org.apache.fineract.portfolio.note.service.NoteWritePlatformService;
@@ -59,6 +69,10 @@ public class BuyDownFeeWritePlatformServiceImpl implements BuyDownFeePlatformSer
     private final NoteWritePlatformService noteWritePlatformService;
     private final ExternalIdFactory externalIdFactory;
     private final LoanBuyDownFeesBalanceRepository loanBuyDownFeesBalanceRepository;
+    private final ReprocessLoanTransactionsService reprocessLoanTransactionsService;
+    private final LoanBalanceService loanBalanceService;
+    private final LoanLifecycleStateMachine loanLifecycleStateMachine;
+    private final BusinessEventNotifierService businessEventNotifierService;
 
     @Transactional
     @Override
@@ -92,7 +106,7 @@ public class BuyDownFeeWritePlatformServiceImpl implements BuyDownFeePlatformSer
         // Save transaction
         loanTransactionRepository.saveAndFlush(buyDownFeeTransaction);
 
-        // Create Buy Down Fee balances
+        // Create Buy Down Fee balance
         createBuyDownFeeBalance(buyDownFeeTransaction);
 
         // Update loan derived fields
@@ -107,6 +121,10 @@ public class BuyDownFeeWritePlatformServiceImpl implements BuyDownFeePlatformSer
         // Post journal entries
         loanJournalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
 
+        // Notify business events
+        businessEventNotifierService.notifyPostBusinessEvent(new LoanBuyDownFeeTransactionCreatedBusinessEvent(buyDownFeeTransaction));
+        businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
+
         return new CommandProcessingResultBuilder() //
                 .withClientId(loan.getClientId()) //
                 .withOfficeId(loan.getOfficeId()) //
@@ -116,6 +134,90 @@ public class BuyDownFeeWritePlatformServiceImpl implements BuyDownFeePlatformSer
                 .build();
     }
 
+    @Override
+    @Transactional
+    public CommandProcessingResult buyDownFeeAdjustment(final Long loanId, final Long buyDownFeeTransactionId, final JsonCommand command) {
+        this.loanTransactionValidator.validateBuyDownFeeAdjustment(command, loanId, buyDownFeeTransactionId);
+        final Loan loan = this.loanAssembler.assembleFrom(loanId);
+        checkClientOrGroupActive(loan);
+
+        final List<Long> existingTransactionIds = new ArrayList<>(loanTransactionRepository.findTransactionIdsByLoan(loan));
+        final List<Long> existingReversedTransactionIds = new ArrayList<>(loanTransactionRepository.findReversedTransactionIdsByLoan(loan));
+        final Map<String, Object> changes = new LinkedHashMap<>();
+
+        // Create payment details
+        final PaymentDetail paymentDetail = this.paymentDetailWritePlatformService.createAndPersistPaymentDetail(command, changes);
+
+        // Extract transaction details
+        final LocalDate transactionDate = command.localDateValueOfParameterNamed("transactionDate");
+        final BigDecimal transactionAmount = command.bigDecimalValueOfParameterNamed("transactionAmount");
+        final ExternalId txnExternalId = externalIdFactory.createFromCommand(command, "externalId");
+
+        // Find and validate original buy down fee transaction
+        Optional<LoanTransaction> originalBuyDownFee = loanTransactionRepository.findById(buyDownFeeTransactionId);
+        if (originalBuyDownFee.isEmpty() || !originalBuyDownFee.get().isBuyDownFee()) {
+            throw new IllegalArgumentException("Original transaction must be a valid Buy Down Fee transaction");
+        }
+
+        // Create buy down fee adjustment transaction
+        LoanTransaction buyDownFeeAdjustment = LoanTransaction.buyDownFeeAdjustment(loan, Money.of(loan.getCurrency(), transactionAmount),
+                paymentDetail, transactionDate, txnExternalId);
+
+        // Link to original transaction
+        buyDownFeeAdjustment.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(buyDownFeeAdjustment,
+                originalBuyDownFee.get(), LoanTransactionRelationTypeEnum.ADJUSTMENT));
+
+        // Add transaction to loan
+        loan.addLoanTransaction(buyDownFeeAdjustment);
+
+        // Recalculate loan transactions
+        recalculateLoanTransactions(loan, transactionDate, buyDownFeeAdjustment);
+
+        // Save transaction
+        LoanTransaction savedBuyDownFeeAdjustment = loanTransactionRepository.saveAndFlush(buyDownFeeAdjustment);
+
+        // Update buy down fee balance
+        LoanBuyDownFeeBalance buydownFeeBalance = loanBuyDownFeesBalanceRepository.findByLoanIdAndLoanTransactionId(loanId,
+                buyDownFeeTransactionId);
+        if (buydownFeeBalance != null) {
+            buydownFeeBalance.setAmountAdjustment(MathUtil.nullToZero(buydownFeeBalance.getAmountAdjustment()).add(transactionAmount));
+            buydownFeeBalance
+                    .setUnrecognizedAmount(MathUtil.negativeToZero(buydownFeeBalance.getUnrecognizedAmount().subtract(transactionAmount)));
+            loanBuyDownFeesBalanceRepository.save(buydownFeeBalance);
+        }
+
+        // Update outstanding loan balances
+        loanBalanceService.updateLoanOutstandingBalances(loan);
+
+        // Create a note if provided
+        final String noteText = command.stringValueOfParameterNamed("note");
+        if (StringUtils.isNotBlank(noteText)) {
+            noteWritePlatformService.createLoanTransactionNote(savedBuyDownFeeAdjustment.getId(), noteText);
+        }
+        // Post journal entries
+        loanJournalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
+
+        // Determine loan lifecycle transition
+        loanLifecycleStateMachine.determineAndTransition(loan, transactionDate);
+
+        // Notify business events
+        businessEventNotifierService
+                .notifyPostBusinessEvent(new LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent(savedBuyDownFeeAdjustment));
+        businessEventNotifierService.notifyPostBusinessEvent(new LoanBalanceChangedBusinessEvent(loan));
+
+        return new CommandProcessingResultBuilder().withEntityId(savedBuyDownFeeAdjustment.getId())
+                .withEntityExternalId(savedBuyDownFeeAdjustment.getExternalId()).withOfficeId(loan.getOfficeId())
+                .withClientId(loan.getClientId()).withLoanId(loan.getId()).build();
+    }
+
+    private void recalculateLoanTransactions(Loan loan, LocalDate transactionDate, LoanTransaction transaction) {
+        if (loan.isInterestBearingAndInterestRecalculationEnabled() || DateUtils.isBeforeBusinessDate(transactionDate)) {
+            reprocessLoanTransactionsService.reprocessTransactions(loan);
+        } else {
+            reprocessLoanTransactionsService.processLatestTransaction(transaction, loan);
+        }
+    }
+
     private void checkClientOrGroupActive(final Loan loan) {
         final Client client = loan.client();
         if (client != null && client.isNotActive()) {
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidator.java
index 825a6e85fd..553c6adff6 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidator.java
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidator.java
@@ -30,4 +30,6 @@ public interface ProgressiveLoanTransactionValidator extends LoanTransactionVali
     void validateContractTerminationUndo(JsonCommand command, Long loanId);
 
     void validateBuyDownFee(JsonCommand command, Long loanId);
+
+    void validateBuyDownFeeAdjustment(JsonCommand command, Long loanId, Long buyDownFeeTransactionId);
 }
diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java
index 73480a7a44..46bf83fdd1 100644
--- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java
+++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java
@@ -48,6 +48,7 @@ import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
 import org.apache.fineract.portfolio.common.service.Validator;
 import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanDisbursementDetails;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanEvent;
@@ -57,6 +58,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType;
 import org.apache.fineract.portfolio.loanaccount.exception.LoanTransactionProcessingException;
+import org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeesBalanceRepository;
 import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
 import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator;
 import org.apache.fineract.portfolio.loanproduct.domain.LoanProduct;
@@ -69,6 +71,7 @@ public class ProgressiveLoanTransactionValidatorImpl implements ProgressiveLoanT
     private final LoanTransactionValidator loanTransactionValidator;
     private final LoanRepositoryWrapper loanRepositoryWrapper;
     private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository;
+    private final LoanBuyDownFeesBalanceRepository loanBuydownFeeBalanceRepository;
     private final LoanTransactionRepository loanTransactionRepository;
 
     @Override
@@ -320,6 +323,94 @@ public class ProgressiveLoanTransactionValidatorImpl implements ProgressiveLoanT
         }
     }
 
+    @Override
+    public void validateBuyDownFeeAdjustment(JsonCommand command, Long loanId, Long buyDownFeeTransactionId) {
+        final String json = command.json();
+        if (StringUtils.isBlank(json)) {
+            throw new InvalidJsonException();
+        }
+
+        final JsonElement element = this.fromApiJsonHelper.parse(json);
+        final Type typeOfMap = new TypeToken<Map<String, Object>>() {}.getType();
+        this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, getBuyDownFeeAdjustmentParameters());
+
+        Validator.validateOrThrow("loan.buyDownFeeAdjustment", baseDataValidator -> {
+            final Loan loan = this.loanRepositoryWrapper.findOneWithNotFoundDetection(loanId, true);
+            validateLoanClientIsActive(loan);
+            validateLoanGroupIsActive(loan);
+
+            // Validate loan is progressive
+            if (!loan.isProgressiveSchedule()) {
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.progressive.loan");
+            }
+
+            // Validate buy down fee is enabled
+            if (!loan.getLoanProductRelatedDetail().isEnableBuyDownFee()) {
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("buy.down.fee.not.enabled");
+            }
+
+            // Validate loan is active, or closed or overpaid
+            final LoanStatus loanStatus = loan.getStatus();
+            if (!loanStatus.isActive() && !loanStatus.isClosed() && !loanStatus.isOverpaid()) {
+                baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("not.valid.loan.status");
+            }
+
+            final LocalDate transactionDate = this.fromApiJsonHelper.extractLocalDateNamed("transactionDate", element);
+            baseDataValidator.reset().parameter("transactionDate").value(transactionDate).notNull();
+
+            // Validate transaction date is not before disbursement date
+            if (transactionDate != null && loan.getDisbursementDate() != null && transactionDate.isBefore(loan.getDisbursementDate())) {
+                baseDataValidator.reset().parameter("transactionDate").failWithCode("before.disbursement.date",
+                        "Transaction date cannot be before disbursement date");
+            }
+
+            // Validate transaction date is not in the future
+            if (transactionDate != null && transactionDate.isAfter(DateUtils.getBusinessLocalDate())) {
+                baseDataValidator.reset().parameter("transactionDate").failWithCode("cannot.be.in.the.future",
+                        "Transaction date cannot be in the future");
+            }
+
+            final BigDecimal transactionAmount = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", element);
+            baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount();
+
+            Optional<LoanTransaction> buyDownFeeTransactionOpt = loanTransactionRepository.findById(buyDownFeeTransactionId);
+            if (buyDownFeeTransactionOpt.isEmpty()) {
+                baseDataValidator.reset().parameter("buyDownFeeTransactionId").failWithCode("loan.transaction.not.found",
+                        "Buy Down Fee transaction not found.");
+            } else {
+                // Validate that the transaction is actually a buy down fee transaction
+                if (!buyDownFeeTransactionOpt.get().isBuyDownFee()) {
+                    baseDataValidator.reset().parameter("buyDownFeeTransactionId").failWithCode("not.buyDownFee.transaction",
+                            "The specified transaction is not a Buy Down Fee transaction.");
+                }
+                // Validate not before buy down fee transaction
+                if (transactionDate != null && transactionDate.isBefore(buyDownFeeTransactionOpt.get().getTransactionDate())) {
+                    baseDataValidator.reset().parameter("transactionDate").failWithCode("before.buyDownFee.transaction.date",
+                            "Transaction date cannot be before buy down fee transaction date");
+
+                }
+                if (transactionAmount != null) {
+                    LoanBuyDownFeeBalance buydownFeeBalance = loanBuydownFeeBalanceRepository.findByLoanIdAndLoanTransactionId(loanId,
+                            buyDownFeeTransactionId);
+                    if (buydownFeeBalance == null) {
+                        baseDataValidator.reset().parameter("buyDownFeeTransactionId").failWithCode("buydown.fee.balance.not.found",
+                                "Buy down fee balance not found for the specified transaction.");
+                    } else if (MathUtil.isLessThan(
+                            buydownFeeBalance.getAmount().subtract(MathUtil.nullToZero(buydownFeeBalance.getAmountAdjustment())),
+                            transactionAmount)) {
+                        baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).failWithCode(
+                                "cannot.be.more.than.remaining.amount",
+                                " Buy down fee adjustment amount cannot be more than remaining amount");
+                    }
+                }
+            }
+
+            validatePaymentDetails(baseDataValidator, element);
+            validateNote(baseDataValidator, element);
+            validateExternalId(baseDataValidator, element);
+        });
+    }
+
     private void throwExceptionIfValidationWarningsExist(final List<ApiParameterError> dataValidationErrors) {
         if (!dataValidationErrors.isEmpty()) {
             throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
@@ -495,6 +586,11 @@ public class ProgressiveLoanTransactionValidatorImpl implements ProgressiveLoanT
         return new HashSet<>(Arrays.asList("note", "reversalExternalId"));
     }
 
+    private Set<String> getBuyDownFeeAdjustmentParameters() {
+        return new HashSet<>(
+                Arrays.asList("transactionDate", "dateFormat", "locale", "transactionAmount", "paymentTypeId", "note", "externalId"));
+    }
+
     private BigDecimal getOverAppliedMax(final Loan loan) {
         final LoanProduct loanProduct = loan.getLoanProduct();
         if ("percentage".equals(loanProduct.getOverAppliedCalculationType())) {
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 8eb7fbad22..6e6665ed6c 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
@@ -24,6 +24,7 @@ import org.apache.fineract.infrastructure.event.business.service.BusinessEventNo
 import org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
+import org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeesBalanceRepository;
 import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
 import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator;
 import org.apache.fineract.portfolio.loanaccount.service.CapitalizedIncomeBalanceService;
@@ -65,9 +66,9 @@ public class ProgressiveLoanAccountConfiguration {
     public ProgressiveLoanTransactionValidator progressiveLoanTransactionValidator(FromJsonHelper fromApiJsonHelper,
             LoanTransactionValidator loanTransactionValidator, LoanRepositoryWrapper loanRepositoryWrapper,
             LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository,
-            LoanTransactionRepository loanTransactionRepository) {
+            LoanBuyDownFeesBalanceRepository loanBuydownFeesBalanceRepository, LoanTransactionRepository loanTransactionRepository) {
         return new ProgressiveLoanTransactionValidatorImpl(fromApiJsonHelper, loanTransactionValidator, loanRepositoryWrapper,
-                loanCapitalizedIncomeBalanceRepository, loanTransactionRepository);
+                loanCapitalizedIncomeBalanceRepository, loanBuydownFeesBalanceRepository, loanTransactionRepository);
     }
 
     @Bean
diff --git a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
index 5397617ab9..9d5fbef252 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccrualBasedAccountingProcessorForLoan.java
@@ -149,6 +149,10 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
             if (transactionType.isBuyDownFee()) {
                 createJournalEntriesForBuyDownFee(loanDTO, loanTransactionDTO, office);
             }
+            // Handle Buy Down Fee Adjustment
+            if (transactionType.isBuyDownFeeAdjustment()) {
+                createJournalEntriesForBuyDownFeeAdjustment(loanDTO, loanTransactionDTO, office);
+            }
         }
     }
 
@@ -436,6 +440,28 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
         }
     }
 
+    private void createJournalEntriesForBuyDownFeeAdjustment(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
+            final Office office) {
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final LocalDate transactionDate = loanTransactionDTO.getTransactionDate();
+        final BigDecimal amount = loanTransactionDTO.getAmount();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+
+        if (MathUtil.isGreaterThanZero(amount)) {
+            // Mirror of Buy Down Fee entries (as per PS-2574 requirements)
+            // Debit: Deferred Income Liability, Credit: Buy Down Expense
+            this.helper.createJournalEntriesForLoan(office, currencyCode, AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(),
+                    AccrualAccountsForLoan.BUY_DOWN_EXPENSE.getValue(), loanProductId, paymentTypeId, loanId, transactionId,
+                    transactionDate, amount);
+        }
+    }
+
     private void createJournalEntriesForInterestPaymentWaiverOrInterestRefund(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO,
             Office office) {
         final Long loanProductId = loanDTO.getLoanProductId();
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
index 60441e2a78..e5f503de9c 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
@@ -710,6 +710,8 @@ public class LoanTransactionsApiResource {
             commandRequest = builder.chargebackTransaction(resolvedLoanId, resolvedTransactionId).build();
         } else if (CommandParameterUtil.is(commandParam, LoanApiConstants.CAPITALIZED_INCOME_ADJUSTMENT_TRANSACTION_COMMAND)) {
             commandRequest = builder.capitalizedIncomeAdjustment(resolvedLoanId, resolvedTransactionId).build();
+        } else if (CommandParameterUtil.is(commandParam, LoanApiConstants.BUY_DOWN_FEE_ADJUSTMENT_COMMAND)) {
+            commandRequest = builder.buyDownFeeAdjustment(resolvedLoanId, resolvedTransactionId).build();
         } else { // Default to adjust the Loan Transaction
             commandRequest = builder.adjustTransaction(resolvedLoanId, resolvedTransactionId).build();
         }
diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
index 7e1a619d5a..1f94fa781c 100644
--- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
+++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
@@ -383,10 +383,13 @@ public class LoanAccountConfiguration {
             LoanAssembler loanAssembler, LoanTransactionRepository loanTransactionRepository,
             PaymentDetailWritePlatformService paymentDetailWritePlatformService, LoanJournalEntryPoster loanJournalEntryPoster,
             NoteWritePlatformService noteWritePlatformService, ExternalIdFactory externalIdFactory,
-            LoanBuyDownFeesBalanceRepository loanBuyDownFeesBalanceRepository) {
+            LoanBuyDownFeesBalanceRepository loanBuyDownFeesBalanceRepository,
+            ReprocessLoanTransactionsService reprocessLoanTransactionsService, LoanBalanceService loanBalanceService,
+            LoanLifecycleStateMachine loanLifecycleStateMachine, BusinessEventNotifierService businessEventNotifierService) {
         return new BuyDownFeeWritePlatformServiceImpl(loanTransactionValidator, loanAssembler, loanTransactionRepository,
                 paymentDetailWritePlatformService, loanJournalEntryPoster, noteWritePlatformService, externalIdFactory,
-                loanBuyDownFeesBalanceRepository);
+                loanBuyDownFeesBalanceRepository, reprocessLoanTransactionsService, loanBalanceService, loanLifecycleStateMachine,
+                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 28ddd5e822..4307ea39ee 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
@@ -207,4 +207,5 @@
     <include file="parts/0186_add_buy_down_fee_to_loan_product.xml" relativeToChangelogFile="true" />
     <include file="parts/0187_add_Loan_modified_and_withdrawn_event_configuration.xml" relativeToChangelogFile="true" />
     <include file="parts/0188_create_loan_buy_down_fee_balance.xml" relativeToChangelogFile="true" />
+    <include file="parts/0189_add_loan_buydown_fee_event.xml" relativeToChangelogFile="true" />
 </databaseChangeLog>
diff --git a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0189_add_loan_buydown_fee_event.xml b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0189_add_loan_buydown_fee_event.xml
new file mode 100644
index 0000000000..b21f786905
--- /dev/null
+++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0189_add_loan_buydown_fee_event.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.3.xsd">
+    <changeSet id="1" author="fineract">
+        <insert tableName="m_external_event_configuration">
+            <column name="type" value="LoanBuyDownFeeTransactionCreatedBusinessEvent"/>
+            <column name="enabled" valueBoolean="false"/>
+        </insert>
+        <insert tableName="m_external_event_configuration">
+            <column name="type" value="LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent"/>
+            <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 99f0284a66..9ac262fcab 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
@@ -109,7 +109,8 @@ public class ExternalEventConfigurationValidationServiceTest {
                 "LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent",
                 "LoanCapitalizedIncomeAdjustmentTransactionCreatedBusinessEvent", "LoanTransactionContractTerminationPostBusinessEvent",
                 "LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent",
-                "LoanCapitalizedIncomeTransactionCreatedBusinessEvent", "LoanUndoContractTerminationBusinessEvent");
+                "LoanCapitalizedIncomeTransactionCreatedBusinessEvent", "LoanUndoContractTerminationBusinessEvent",
+                "LoanBuyDownFeeTransactionCreatedBusinessEvent", "LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent");
 
         List<FineractPlatformTenant> tenants = Arrays
                 .asList(new FineractPlatformTenant(1L, "default", "Default Tenant", "Europe/Budapest", null));
@@ -200,7 +201,8 @@ public class ExternalEventConfigurationValidationServiceTest {
                 "LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent",
                 "LoanCapitalizedIncomeAdjustmentTransactionCreatedBusinessEvent", "LoanTransactionContractTerminationPostBusinessEvent",
                 "LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent",
-                "LoanCapitalizedIncomeTransactionCreatedBusinessEvent", "LoanUndoContractTerminationBusinessEvent");
+                "LoanCapitalizedIncomeTransactionCreatedBusinessEvent", "LoanUndoContractTerminationBusinessEvent",
+                "LoanBuyDownFeeTransactionCreatedBusinessEvent", "LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent");
 
         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/LoanBuyDownFeeTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanBuyDownFeeTest.java
index 4ce6ed3085..786e207831 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanBuyDownFeeTest.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanBuyDownFeeTest.java
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.math.BigDecimal;
+import java.time.format.DateTimeFormatter;
 import java.util.List;
 import java.util.UUID;
 import lombok.extern.slf4j.Slf4j;
@@ -32,6 +33,7 @@ import org.apache.fineract.client.models.PostLoanProductsRequest;
 import org.apache.fineract.client.models.PostLoanProductsResponse;
 import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest;
 import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
+import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest;
 import org.apache.fineract.client.models.PostLoansResponse;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.Utils;
@@ -243,6 +245,168 @@ public class LoanBuyDownFeeTest extends BaseLoanIntegrationTest {
                 .buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE).locale("en").dateFormat("dd MMMM yyyy");
     }
 
+    @Test
+    public void testBuyDownFeeAdjustment() {
+        runAt("06 September 2024", () -> {
+            // Add initial buy down fee
+            Long buyDownFeeTransactionId = addBuyDownFeeForLoan(loanId, 500.0, "06 September 2024");
+            assertNotNull(buyDownFeeTransactionId);
+
+            // Create buy down fee adjustment (use same date as business date)
+            PostLoansLoanIdTransactionsResponse adjustmentResponse = loanTransactionHelper.buyDownFeeAdjustment(loanId,
+                    buyDownFeeTransactionId, "06 September 2024", 100.0);
+
+            assertNotNull(adjustmentResponse);
+            assertNotNull(adjustmentResponse.getLoanId());
+            assertNotNull(adjustmentResponse.getClientId());
+            assertNotNull(adjustmentResponse.getOfficeId());
+            assertEquals(loanId, adjustmentResponse.getLoanId());
+
+            // Verify loan details show both transactions
+            final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
+            assertNotNull(loanDetails);
+
+            List<GetLoansLoanIdTransactions> transactions = loanDetails.getTransactions();
+            assertTrue(transactions.size() >= 3); // Disbursement, Buy Down Fee, Buy Down Fee Adjustment
+
+            // Find the buy down fee adjustment transaction
+            GetLoansLoanIdTransactions adjustmentTransaction = transactions.stream()
+                    .filter(txn -> "Buy Down Fee Adjustment".equals(txn.getType().getValue())).findFirst().orElse(null);
+
+            assertNotNull(adjustmentTransaction);
+            assertEquals(0, BigDecimal.valueOf(100.0).compareTo(adjustmentTransaction.getAmount()));
+            assertEquals("06 September 2024", adjustmentTransaction.getDate().format(DateTimeFormatter.ofPattern("dd MMMM yyyy")));
+        });
+    }
+
+    @Test
+    public void testBuyDownFeeAdjustmentValidations() {
+        runAt("08 September 2024", () -> {
+            // Add initial buy down fee
+            Long buyDownFeeTransactionId = addBuyDownFeeForLoan(loanId, 300.0, "08 September 2024");
+            assertNotNull(buyDownFeeTransactionId);
+
+            // Test 1: Adjustment amount more than original amount (should fail)
+            try {
+                loanTransactionHelper.buyDownFeeAdjustment(loanId, buyDownFeeTransactionId, "08 September 2024", 400.0);
+                assertTrue(false, "Expected validation error for adjustment amount exceeding original amount");
+            } catch (Exception e) {
+                log.info("Expected validation error for excessive adjustment amount: {}", e.getMessage());
+                assertTrue(e.getMessage().contains("amount") || e.getMessage().contains("exceed"));
+            }
+
+            // Test 2: Adjustment date before original transaction date (should fail)
+            try {
+                loanTransactionHelper.buyDownFeeAdjustment(loanId, buyDownFeeTransactionId, "07 September 2024", 100.0);
+                assertTrue(false, "Expected validation error for adjustment date before original transaction date");
+            } catch (Exception e) {
+                log.info("Expected validation error for early adjustment date: {}", e.getMessage());
+                assertTrue(e.getMessage().contains("date") || e.getMessage().contains("before"));
+            }
+
+            // Test 3: Valid adjustment should succeed
+            PostLoansLoanIdTransactionsResponse validAdjustment = loanTransactionHelper.buyDownFeeAdjustment(loanId,
+                    buyDownFeeTransactionId, "08 September 2024", 150.0);
+            assertNotNull(validAdjustment);
+
+            // Test 4: Second adjustment that would exceed total should fail
+            try {
+                loanTransactionHelper.buyDownFeeAdjustment(loanId, buyDownFeeTransactionId, "08 September 2024", 200.0);
+                assertTrue(false, "Expected validation error for total adjustments exceeding original amount");
+            } catch (Exception e) {
+                log.info("Expected validation error for cumulative adjustment excess: {}", e.getMessage());
+                assertTrue(e.getMessage().contains("amount") || e.getMessage().contains("exceed"));
+            }
+        });
+    }
+
+    @Test
+    public void testBuyDownFeeAdjustmentAccountingEntries() {
+        runAt("10 September 2024", () -> {
+            // Add initial buy down fee
+            Long buyDownFeeTransactionId = addBuyDownFeeForLoan(loanId, 400.0, "10 September 2024");
+            assertNotNull(buyDownFeeTransactionId);
+
+            // Verify initial buy down fee accounting entries
+            verifyTRJournalEntries(buyDownFeeTransactionId, debit(buyDownExpenseAccount, 400.0),
+                    credit(deferredIncomeLiabilityAccount, 400.0));
+
+            // Create buy down fee adjustment
+            PostLoansLoanIdTransactionsResponse adjustmentResponse = loanTransactionHelper.buyDownFeeAdjustment(loanId,
+                    buyDownFeeTransactionId, "10 September 2024", 120.0);
+            assertNotNull(adjustmentResponse);
+        });
+    }
+
+    @Test
+    public void testMultipleBuyDownFeeAdjustments() {
+        runAt("12 September 2024", () -> {
+            // Add initial buy down fee
+            Long buyDownFeeTransactionId = addBuyDownFeeForLoan(loanId, 600.0, "12 September 2024");
+            assertNotNull(buyDownFeeTransactionId);
+
+            // First adjustment
+            PostLoansLoanIdTransactionsResponse adjustment1 = loanTransactionHelper.buyDownFeeAdjustment(loanId, buyDownFeeTransactionId,
+                    "12 September 2024", 100.0);
+            assertNotNull(adjustment1);
+
+            // Second adjustment
+            PostLoansLoanIdTransactionsResponse adjustment2 = loanTransactionHelper.buyDownFeeAdjustment(loanId, buyDownFeeTransactionId,
+                    "12 September 2024", 150.0);
+            assertNotNull(adjustment2);
+
+            // Verify both adjustments are recorded
+            final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
+            assertNotNull(loanDetails);
+
+            List<GetLoansLoanIdTransactions> adjustmentTransactions = loanDetails.getTransactions().stream()
+                    .filter(txn -> "Buy Down Fee Adjustment".equals(txn.getType().getValue())).toList();
+
+            assertEquals(2, adjustmentTransactions.size());
+
+            // Verify total adjustment amounts
+            BigDecimal totalAdjustments = adjustmentTransactions.stream().map(GetLoansLoanIdTransactions::getAmount).reduce(BigDecimal.ZERO,
+                    BigDecimal::add);
+            assertEquals(0, BigDecimal.valueOf(250.0).compareTo(totalAdjustments));
+
+            // Third adjustment that would exceed limit should fail
+            try {
+                loanTransactionHelper.buyDownFeeAdjustment(loanId, buyDownFeeTransactionId, "12 September 2024", 400.0);
+                assertTrue(false, "Expected validation error for total adjustments exceeding original amount");
+            } catch (Exception e) {
+                log.info("Expected validation error for cumulative adjustments exceeding limit: {}", e.getMessage());
+            }
+        });
+    }
+
+    @Test
+    public void testBuyDownFeeAdjustmentWithExternalId() {
+        runAt("16 September 2024", () -> {
+            // Add initial buy down fee
+            Long buyDownFeeTransactionId = addBuyDownFeeForLoan(loanId, 350.0, "16 September 2024");
+            assertNotNull(buyDownFeeTransactionId);
+
+            // Create adjustment with external ID
+            String adjustmentExternalId = UUID.randomUUID().toString();
+            PostLoansLoanIdTransactionsResponse adjustmentResponse = loanTransactionHelper.buyDownFeeAdjustment(loanId,
+                    buyDownFeeTransactionId,
+                    new PostLoansLoanIdTransactionsTransactionIdRequest().transactionDate("16 September 2024").transactionAmount(80.0)
+                            .externalId(adjustmentExternalId).note("Buy Down Fee Adjustment with external ID").dateFormat("dd MMMM yyyy")
+                            .locale("en"));
+
+            assertNotNull(adjustmentResponse);
+            assertEquals(adjustmentExternalId, adjustmentResponse.getResourceExternalId());
+
+            // Verify adjustment transaction details
+            final GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
+            GetLoansLoanIdTransactions adjustmentTransaction = loanDetails.getTransactions().stream()
+                    .filter(txn -> adjustmentExternalId.equals(txn.getExternalId())).findFirst().orElse(null);
+
+            assertNotNull(adjustmentTransaction);
+            assertEquals(0, BigDecimal.valueOf(80.0).compareTo(adjustmentTransaction.getAmount()));
+        });
+    }
+
     /**
      * Helper method to add buy down fee for a loan
      *
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 4e3819e476..a8c4300cb5 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
@@ -636,6 +636,16 @@ public class ExternalEventConfigurationHelper {
         loanTransactionUndoContractTerminationBusinessEvent.put("enabled", false);
         defaults.add(loanTransactionUndoContractTerminationBusinessEvent);
 
+        Map<String, Object> loanBuyDownFeeTransactionCreatedBusinessEvent = new HashMap<>();
+        loanBuyDownFeeTransactionCreatedBusinessEvent.put("type", "LoanBuyDownFeeTransactionCreatedBusinessEvent");
+        loanBuyDownFeeTransactionCreatedBusinessEvent.put("enabled", false);
+        defaults.add(loanBuyDownFeeTransactionCreatedBusinessEvent);
+
+        Map<String, Object> loanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent = new HashMap<>();
+        loanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent.put("type", "LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent");
+        loanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent.put("enabled", false);
+        defaults.add(loanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent);
+
         return defaults;
     }
 
diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index 9b060bdd6c..bec059a9f3 100644
--- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -963,6 +963,36 @@ public class LoanTransactionHelper {
                 .transactionAmount(amount).transactionDate(transactionDate).dateFormat("dd MMMM yyyy").locale("en"));
     }
 
+    public PostLoansLoanIdTransactionsResponse buyDownFeeAdjustment(final Long loanId, final Long buyDownFeeTransactionId,
+            final PostLoansLoanIdTransactionsTransactionIdRequest request) {
+        return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction(loanId, buyDownFeeTransactionId,
+                request, "buyDownFeeAdjustment"));
+    }
+
+    public PostLoansLoanIdTransactionsResponse buyDownFeeAdjustment(final String loanExternalId, final Long transactionId,
+            final PostLoansLoanIdTransactionsTransactionIdRequest request) {
+        return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction2(loanExternalId, transactionId,
+                request, "buyDownFeeAdjustment"));
+    }
+
+    public PostLoansLoanIdTransactionsResponse buyDownFeeAdjustment(final String loanExternalId, final String transactionExternalId,
+            final PostLoansLoanIdTransactionsTransactionIdRequest request) {
+        return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction3(loanExternalId,
+                transactionExternalId, request, "buyDownFeeAdjustment"));
+    }
+
+    public PostLoansLoanIdTransactionsResponse buyDownFeeAdjustment(final Long loanId, final String transactionExternalId,
+            final PostLoansLoanIdTransactionsTransactionIdRequest request) {
+        return Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.adjustLoanTransaction1(loanId, transactionExternalId,
+                request, "buyDownFeeAdjustment"));
+    }
+
+    public PostLoansLoanIdTransactionsResponse buyDownFeeAdjustment(final Long loanId, final Long buyDownFeeTransactionId,
+            final String transactionDate, final double amount) {
+        return buyDownFeeAdjustment(loanId, buyDownFeeTransactionId, new PostLoansLoanIdTransactionsTransactionIdRequest()
+                .transactionAmount(amount).transactionDate(transactionDate).dateFormat("dd MMMM yyyy").locale("en"));
+    }
+
     // TODO: Rewrite to use fineract-client instead!
     // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long,
     // org.apache.fineract.client.models.PostLoansLoanIdRequest)