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 b8d6dfa794 FINERACT-2311: Buy down fee - daily amortization
b8d6dfa794 is described below

commit b8d6dfa794144b3a0971b8700eca6d6b36700610
Author: adam.magyari <[email protected]>
AuthorDate: Mon Jul 7 14:58:31 2025 +0200

    FINERACT-2311: Buy down fee - daily amortization
---
 .../fineract/test/helper/WorkFlowJobHelper.java    |   5 +-
 ...nAdjustmentTransactionCreatedBusinessEvent.java |  35 +++++
 ...mortizationTransactionCreatedBusinessEvent.java |  35 +++++
 .../api/LoanTransactionApiConstants.java           |   2 +
 .../loanaccount/data/LoanTransactionEnumData.java  |   5 +
 .../loanaccount/domain/LoanTransaction.java        |  24 +++-
 .../domain/LoanTransactionRepository.java          |  17 ++-
 .../loanaccount/domain/LoanTransactionType.java    |   4 +
 ...oanBuyDownFeeAmortizationProcessingService.java |  28 ++++
 .../loanproduct/service/LoanEnumerations.java      |   6 +
 .../AccrualBasedAccountingProcessorForLoan.java    | 159 +++++++++++++++++++++
 .../loan/BuyDownFeeAmortizationBusinessStep.java   |  60 ++++++++
 .../api/LoanTransactionsApiResource.java           |   2 +
 .../loanaccount/api/LoansApiResourceSwagger.java   |   8 ++
 ...uyDownFeeAmortizationProcessingServiceImpl.java | 113 +++++++++++++++
 ...zedIncomeAmortizationProcessingServiceImpl.java |   8 +-
 .../starter/LoanAccountConfiguration.java          |  24 ++++
 .../util/BuyDownFeeAmortizationUtil.java           |  84 +++++++++++
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 .../parts/0190_buy_down_fee_amortization.xml       |  49 +++++++
 ...nalEventConfigurationValidationServiceTest.java |   8 +-
 .../integrationtests/LoanBuyDownFeeTest.java       | 148 +++++++++++++++++++
 .../common/ExternalEventConfigurationHelper.java   |  11 ++
 .../common/loans/LoanTransactionHelper.java        |   5 +
 24 files changed, 829 insertions(+), 12 deletions(-)

diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/WorkFlowJobHelper.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/WorkFlowJobHelper.java
index fcaf44d892..1a4f2ece3c 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/WorkFlowJobHelper.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/WorkFlowJobHelper.java
@@ -50,8 +50,9 @@ public class WorkFlowJobHelper {
                 new 
BusinessStep().stepName("ADD_PERIODIC_ACCRUAL_ENTRIES").order(7L), //
                 new 
BusinessStep().stepName("ACCRUAL_ACTIVITY_POSTING").order(8L), //
                 new 
BusinessStep().stepName("CAPITALIZED_INCOME_AMORTIZATION").order(9L), //
-                new 
BusinessStep().stepName("LOAN_INTEREST_RECALCULATION").order(10L), //
-                new 
BusinessStep().stepName("EXTERNAL_ASSET_OWNER_TRANSFER").order(11L)//
+                new 
BusinessStep().stepName("BUY_DOWN_FEE_AMORTIZATION").order(10L), //
+                new 
BusinessStep().stepName("LOAN_INTEREST_RECALCULATION").order(11L), //
+                new 
BusinessStep().stepName("EXTERNAL_ASSET_OWNER_TRANSFER").order(12L)//
         );
         BusinessStepRequest request = new 
BusinessStepRequest().businessSteps(businessSteps);
         Response<Void> response = 
businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS,
 request)
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent.java
 
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent.java
new file mode 100644
index 0000000000..c8ef1b4121
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent.java
@@ -0,0 +1,35 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction;
+
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+
+public class 
LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent extends 
LoanTransactionBusinessEvent {
+
+    private static final String TYPE = 
"LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent";
+
+    public 
LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent(LoanTransaction
 transaction) {
+        super(transaction);
+    }
+
+    @Override
+    public String getType() {
+        return TYPE;
+    }
+}
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent.java
 
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent.java
new file mode 100644
index 0000000000..6abe1a53d8
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent.java
@@ -0,0 +1,35 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction;
+
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+
+public class LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent extends 
LoanTransactionBusinessEvent {
+
+    private static final String TYPE = 
"LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent";
+
+    public 
LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent(LoanTransaction 
transaction) {
+        super(transaction);
+    }
+
+    @Override
+    public String getType() {
+        return TYPE;
+    }
+}
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionApiConstants.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionApiConstants.java
index 51d7df8d18..6617def508 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionApiConstants.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionApiConstants.java
@@ -57,5 +57,7 @@ public interface LoanTransactionApiConstants {
         capitalizedIncomeAdjustment, //
         contractTermination, //
         capitalizedIncomeAmortizationAdjustment, //
+        buyDownFeeAmortization, //
+        buyDownFeeAmortizationAdjustment, //
     }
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
index 9de2b093dd..743143453f 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
@@ -74,6 +74,8 @@ public class LoanTransactionEnumData implements Serializable {
     private final boolean contractTermination;
     private final boolean buyDownFee;
     private final boolean buyDownFeeAdjustment;
+    private final boolean buyDownFeeAmortization;
+    private final boolean buyDownFeeAmortizationAdjustment;
 
     public LoanTransactionEnumData(final Long id, final String code, final 
String value) {
         this.id = id;
@@ -118,6 +120,9 @@ public class LoanTransactionEnumData implements 
Serializable {
         this.contractTermination = 
Long.valueOf(LoanTransactionType.CONTRACT_TERMINATION.getValue()).equals(this.id);
         this.buyDownFee = 
Long.valueOf(LoanTransactionType.BUY_DOWN_FEE.getValue()).equals(this.id);
         this.buyDownFeeAdjustment = 
Long.valueOf(LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT.getValue()).equals(this.id);
+        this.buyDownFeeAmortization = 
Long.valueOf(LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION.getValue()).equals(this.id);
+        this.buyDownFeeAmortizationAdjustment = 
Long.valueOf(LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT.getValue())
+                .equals(this.id);
     }
 
     public boolean isRepaymentType() {
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 9a1c48dd14..62c49d807b 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
@@ -348,6 +348,26 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom<Long
         };
     }
 
+    public static LoanTransaction buyDownFeeAmortization(final Loan loan, 
final Office office, final LocalDate dateOf,
+            final BigDecimal amount, final ExternalId externalId) {
+        return switch 
(loan.getLoanProductRelatedDetail().getBuyDownFeeIncomeType()) {
+            case FEE -> new LoanTransaction(loan, office, 
LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION, dateOf, amount, null, null, 
amount,
+                    null, null, false, null, externalId);
+            case INTEREST -> new LoanTransaction(loan, office, 
LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION, dateOf, amount, null, amount,
+                    null, null, null, false, null, externalId);
+        };
+    }
+
+    public static LoanTransaction buyDownFeeAmortizationAdjustment(final Loan 
loan, final Money amount, final LocalDate transactionDate,
+            final ExternalId externalId) {
+        return switch 
(loan.getLoanProductRelatedDetail().getBuyDownFeeIncomeType()) {
+            case FEE -> new LoanTransaction(loan, loan.getOffice(), 
LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT,
+                    transactionDate, amount.getAmount(), null, null, 
amount.getAmount(), null, null, false, null, externalId);
+            case INTEREST -> new LoanTransaction(loan, loan.getOffice(), 
LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT,
+                    transactionDate, amount.getAmount(), null, 
amount.getAmount(), null, null, null, false, null, externalId);
+        };
+    }
+
     public LoanTransaction copyTransactionPropertiesAndMappings() {
         LoanTransaction newTransaction = copyTransactionProperties(this);
         
newTransaction.updateLoanTransactionToRepaymentScheduleMappings(loanTransactionToRepaymentScheduleMappings);
@@ -806,7 +826,9 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom<Long
                 || type == LoanTransactionType.WITHDRAW_TRANSFER || type == 
LoanTransactionType.CHARGE_OFF
                 || type == LoanTransactionType.REAMORTIZE || type == 
LoanTransactionType.REAGE
                 || type == LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION 
|| type == LoanTransactionType.CONTRACT_TERMINATION
-                || type == 
LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT);
+                || type == 
LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT
+                || type == LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION
+                || type == 
LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT);
     }
 
     public void updateOutstandingLoanBalance(BigDecimal 
outstandingLoanBalance) {
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
index 3934f0253f..a41b352ea1 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java
@@ -361,16 +361,27 @@ public interface LoanTransactionRepository extends 
JpaRepository<LoanTransaction
             AND (lt.typeOf = 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION
               OR lt.typeOf = 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT)
             """)
-    BigDecimal getAmortizedAmount(@Param("loan") Loan loan);
+    BigDecimal getAmortizedAmountCapitalizedIncome(@Param("loan") Loan loan);
+
+    @Query("""
+            SELECT COALESCE(SUM(CASE WHEN lt.typeOf = 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION
 THEN lt.amount
+              WHEN lt.typeOf = 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT
 THEN -lt.amount
+              ELSE 0 END), 0) FROM LoanTransaction lt
+            WHERE lt.loan = :loan
+            AND lt.reversed = false
+            AND (lt.typeOf = 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION
+              OR lt.typeOf = 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT)
+            """)
+    BigDecimal getAmortizedAmountBuyDownFee(@Param("loan") Loan loan);
 
     @Query("""
             SELECT lt FROM LoanTransaction lt, LoanTransactionRelation ltr
             WHERE lt.reversed = false
             AND lt = ltr.fromTransaction
-            AND ltr.toTransaction = :capitalizedIncome
+            AND ltr.toTransaction = :transaction
             AND ltr.relationType = 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum.ADJUSTMENT
             """)
-    List<LoanTransaction> 
findAdjustmentsForCapitalizedIncome(@Param("capitalizedIncome") LoanTransaction 
capitalizedIncome);
+    List<LoanTransaction> findAdjustments(@Param("transaction") 
LoanTransaction transaction);
 
     @Query("""
             SELECT lt FROM LoanTransaction lt
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
index 0e014d0275..41489897c6 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
@@ -76,6 +76,8 @@ public enum LoanTransactionType {
 
     BUY_DOWN_FEE(40, "loanTransactionType.buyDownFee"), //
     BUY_DOWN_FEE_ADJUSTMENT(41, "loanTransactionType.buyDownFeeAdjustment"), //
+    BUY_DOWN_FEE_AMORTIZATION(42, 
"loanTransactionType.buyDownFeeAmortization"), //
+    BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT(43, 
"loanTransactionType.buyDownFeeAmortizationAdjustment"), //
     ;
 
     private final Integer value;
@@ -133,6 +135,8 @@ public enum LoanTransactionType {
             case 39 -> 
LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT;
             case 40 -> LoanTransactionType.BUY_DOWN_FEE;
             case 41 -> LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT;
+            case 42 -> LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION;
+            case 43 -> 
LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT;
             default -> LoanTransactionType.INVALID;
         };
     }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBuyDownFeeAmortizationProcessingService.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBuyDownFeeAmortizationProcessingService.java
new file mode 100644
index 0000000000..8c1cc6a043
--- /dev/null
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBuyDownFeeAmortizationProcessingService.java
@@ -0,0 +1,28 @@
+/**
+ * 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.service;
+
+import java.time.LocalDate;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.springframework.lang.NonNull;
+
+public interface LoanBuyDownFeeAmortizationProcessingService {
+
+    void processBuyDownFeeAmortizationTillDate(@NonNull Loan loan, @NonNull 
LocalDate tillDate, boolean addJournal);
+}
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
index 9d9f0b0b48..6ff0c86c38 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
@@ -342,6 +342,12 @@ public final class LoanEnumerations {
                     LoanTransactionType.BUY_DOWN_FEE.getCode(), "Buy Down 
Fee");
             case BUY_DOWN_FEE_ADJUSTMENT -> new 
LoanTransactionEnumData(LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT.getValue().longValue(),
                     LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT.getCode(), 
"Buy Down Fee Adjustment");
+            case BUY_DOWN_FEE_AMORTIZATION ->
+                new 
LoanTransactionEnumData(LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION.getValue().longValue(),
+                        
LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION.getCode(), "Buy Down Fee 
Amortization");
+            case BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT ->
+                new 
LoanTransactionEnumData(LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT.getValue().longValue(),
+                        
LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT.getCode(), "Buy Down 
Fee Amortization Adjustment");
         };
     }
 
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 9d5fbef252..481aefdb0b 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
@@ -153,6 +153,14 @@ public class AccrualBasedAccountingProcessorForLoan 
implements AccountingProcess
             if (transactionType.isBuyDownFeeAdjustment()) {
                 createJournalEntriesForBuyDownFeeAdjustment(loanDTO, 
loanTransactionDTO, office);
             }
+            // Handle Buy Down Fee Amortization
+            if (transactionType.isBuyDownFeeAmortization()) {
+                createJournalEntriesForBuyDownFeeAmortization(loanDTO, 
loanTransactionDTO, office);
+            }
+            // Handle Buy Down Fee Amortization Adjustment
+            if (transactionType.isBuyDownFeeAmortizationAdjustment()) {
+                
createJournalEntriesForBuyDownFeeAmortizationAdjustment(loanDTO, 
loanTransactionDTO, office);
+            }
         }
     }
 
@@ -462,6 +470,157 @@ public class AccrualBasedAccountingProcessorForLoan 
implements AccountingProcess
         }
     }
 
+    private void createJournalEntriesForBuyDownFeeAmortization(final LoanDTO 
loanDTO, final LoanTransactionDTO loanTransactionDTO,
+            final Office office) {
+        final boolean isMarkedAsChargeOff = loanDTO.isMarkedAsChargeOff();
+        if (isMarkedAsChargeOff) {
+            
createJournalEntriesForChargeOffLoanBuyDownFeeAmortization(loanDTO, 
loanTransactionDTO, office);
+        } else {
+            createJournalEntriesForLoanBuyDownFeeAmortization(loanDTO, 
loanTransactionDTO, office);
+        }
+    }
+
+    private void createJournalEntriesForLoanBuyDownFeeAmortization(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();
+        final boolean isLoanWrittenOff = loanDTO.isMarkedAsWrittenOff();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final LocalDate transactionDate = 
loanTransactionDTO.getTransactionDate();
+        final BigDecimal interestAmount = loanTransactionDTO.getInterest();
+        final BigDecimal feesAmount = loanTransactionDTO.getFees();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+        final GLAccountBalanceHolder glAccountBalanceHolder = new 
GLAccountBalanceHolder();
+
+        // interest payment
+        final AccrualAccountsForLoan creditAccountType = isLoanWrittenOff ? 
AccrualAccountsForLoan.LOSSES_WRITTEN_OFF
+                : AccrualAccountsForLoan.INCOME_FROM_BUY_DOWN;
+        if (MathUtil.isGreaterThanZero(interestAmount)) {
+            populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId, creditAccountType.getValue(),
+                    
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), 
glAccountBalanceHolder);
+        }
+        // handle fees payment
+        if (MathUtil.isGreaterThanZero(feesAmount)) {
+            populateCreditDebitMaps(loanProductId, feesAmount, paymentTypeId, 
creditAccountType.getValue(),
+                    
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), 
glAccountBalanceHolder);
+        }
+
+        // create credit entries
+        for (Map.Entry<Long, BigDecimal> creditEntry : 
glAccountBalanceHolder.getCreditBalances().entrySet()) {
+            if (MathUtil.isGreaterThanZero(creditEntry.getValue())) {
+                GLAccount glAccount = 
glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey());
+                this.helper.createCreditJournalEntryForLoan(office, 
currencyCode, loanId, transactionId, transactionDate,
+                        creditEntry.getValue(), glAccount);
+            }
+        }
+        // create debit entries
+        for (Map.Entry<Long, BigDecimal> debitEntry : 
glAccountBalanceHolder.getDebitBalances().entrySet()) {
+            if (MathUtil.isGreaterThanZero(debitEntry.getValue())) {
+                GLAccount glAccount = 
glAccountBalanceHolder.getGlAccountMap().get(debitEntry.getKey());
+                this.helper.createDebitJournalEntryForLoan(office, 
currencyCode, loanId, transactionId, transactionDate,
+                        debitEntry.getValue(), glAccount);
+            }
+        }
+    }
+
+    private void 
createJournalEntriesForChargeOffLoanBuyDownFeeAmortization(final LoanDTO 
loanDTO,
+            final LoanTransactionDTO loanTransactionDTO, final Office office) {
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final boolean isMarkedFraud = loanDTO.isMarkedAsFraud();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final LocalDate transactionDate = 
loanTransactionDTO.getTransactionDate();
+        final BigDecimal interestAmount = loanTransactionDTO.getInterest();
+        final BigDecimal feesAmount = loanTransactionDTO.getFees();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+        final GLAccountBalanceHolder glAccountBalanceHolder = new 
GLAccountBalanceHolder();
+        final Long chargeOffReasonCodeValue = 
loanDTO.getChargeOffReasonCodeValue();
+
+        final ProductToGLAccountMapping mapping = chargeOffReasonCodeValue != 
null
+                ? helper.getChargeOffMappingByCodeValue(loanProductId, 
PortfolioProductType.LOAN, chargeOffReasonCodeValue)
+                : null;
+
+        if (mapping != null) {
+            final GLAccount accountDebit = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
+                    
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), paymentTypeId);
+            // handle interest payment
+            if (MathUtil.isGreaterThanZero(interestAmount)) {
+                glAccountBalanceHolder.addToCredit(mapping.getGlAccount(), 
interestAmount);
+                glAccountBalanceHolder.addToDebit(accountDebit, 
interestAmount);
+            }
+            // handle fees payment
+            if (MathUtil.isGreaterThanZero(feesAmount)) {
+                glAccountBalanceHolder.addToCredit(mapping.getGlAccount(), 
feesAmount);
+                glAccountBalanceHolder.addToDebit(accountDebit, feesAmount);
+            }
+        } else {
+            final AccrualAccountsForLoan creditAccountType = isMarkedFraud ? 
AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE
+                    : AccrualAccountsForLoan.CHARGE_OFF_EXPENSE;
+            // handle interest payment
+            if (MathUtil.isGreaterThanZero(interestAmount)) {
+                populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId, creditAccountType.getValue(),
+                        
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), 
glAccountBalanceHolder);
+            }
+            // handle fees payment
+            if (MathUtil.isGreaterThanZero(feesAmount)) {
+                populateCreditDebitMaps(loanProductId, feesAmount, 
paymentTypeId, creditAccountType.getValue(),
+                        
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), 
glAccountBalanceHolder);
+            }
+        }
+
+        // create credit entries
+        for (Map.Entry<Long, BigDecimal> creditEntry : 
glAccountBalanceHolder.getCreditBalances().entrySet()) {
+            if (MathUtil.isGreaterThanZero(creditEntry.getValue())) {
+                GLAccount glAccount = 
glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey());
+                this.helper.createCreditJournalEntryForLoan(office, 
currencyCode, loanId, transactionId, transactionDate,
+                        creditEntry.getValue(), glAccount);
+            }
+        }
+        // create debit entries
+        for (Map.Entry<Long, BigDecimal> debitEntry : 
glAccountBalanceHolder.getDebitBalances().entrySet()) {
+            if (MathUtil.isGreaterThanZero(debitEntry.getValue())) {
+                GLAccount glAccount = 
glAccountBalanceHolder.getGlAccountMap().get(debitEntry.getKey());
+                this.helper.createDebitJournalEntryForLoan(office, 
currencyCode, loanId, transactionId, transactionDate,
+                        debitEntry.getValue(), glAccount);
+            }
+        }
+    }
+
+    private void createJournalEntriesForBuyDownFeeAmortizationAdjustment(final 
LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
+            final Office office) {
+        GLAccountBalanceHolder glAccountBalanceHolder = new 
GLAccountBalanceHolder();
+        if (MathUtil.isGreaterThanZero(loanTransactionDTO.getAmount())) {
+            populateCreditDebitMaps(loanDTO.getLoanProductId(), 
loanTransactionDTO.getAmount(), loanTransactionDTO.getPaymentTypeId(),
+                    
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), 
AccrualAccountsForLoan.INCOME_FROM_BUY_DOWN.getValue(),
+                    glAccountBalanceHolder);
+        }
+
+        // create credit entries
+        for (Map.Entry<Long, BigDecimal> creditEntry : 
glAccountBalanceHolder.getCreditBalances().entrySet()) {
+            if (MathUtil.isGreaterThanZero(creditEntry.getValue())) {
+                GLAccount glAccount = 
glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey());
+                this.helper.createCreditJournalEntryForLoan(office, 
loanDTO.getCurrencyCode(), loanDTO.getLoanId(),
+                        loanTransactionDTO.getTransactionId(), 
loanTransactionDTO.getTransactionDate(), creditEntry.getValue(), glAccount);
+            }
+        }
+        // create debit entries
+        for (Map.Entry<Long, BigDecimal> debitEntry : 
glAccountBalanceHolder.getDebitBalances().entrySet()) {
+            if (MathUtil.isGreaterThanZero(debitEntry.getValue())) {
+                GLAccount glAccount = 
glAccountBalanceHolder.getGlAccountMap().get(debitEntry.getKey());
+                this.helper.createDebitJournalEntryForLoan(office, 
loanDTO.getCurrencyCode(), loanDTO.getLoanId(),
+                        loanTransactionDTO.getTransactionId(), 
loanTransactionDTO.getTransactionDate(), debitEntry.getValue(), glAccount);
+            }
+        }
+    }
+
     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/cob/loan/BuyDownFeeAmortizationBusinessStep.java
 
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/BuyDownFeeAmortizationBusinessStep.java
new file mode 100644
index 0000000000..1f511c94a1
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/cob/loan/BuyDownFeeAmortizationBusinessStep.java
@@ -0,0 +1,60 @@
+/**
+ * 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.cob.loan;
+
+import jakarta.transaction.Transactional;
+import java.time.LocalDate;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import 
org.apache.fineract.portfolio.loanaccount.service.LoanBuyDownFeeAmortizationProcessingService;
+import org.springframework.stereotype.Component;
+
+@Component
+@RequiredArgsConstructor
+@Slf4j
+public class BuyDownFeeAmortizationBusinessStep implements LoanCOBBusinessStep 
{
+
+    private final LoanBuyDownFeeAmortizationProcessingService 
loanBuyDownFeeAmortizationProcessingService;
+
+    @Transactional
+    @Override
+    public Loan execute(Loan loan) {
+        if (!loan.getLoanProductRelatedDetail().isEnableBuyDownFee()) {
+            return loan;
+        }
+
+        LocalDate businessDate = DateUtils.getBusinessLocalDate();
+
+        
loanBuyDownFeeAmortizationProcessingService.processBuyDownFeeAmortizationTillDate(loan,
 businessDate, true);
+
+        return loan;
+    }
+
+    @Override
+    public String getEnumStyledName() {
+        return "BUY_DOWN_FEE_AMORTIZATION";
+    }
+
+    @Override
+    public String getHumanReadableName() {
+        return "Buy Down Fee amortization";
+    }
+}
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 039f7a70e4..366d1d022d 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
@@ -537,6 +537,8 @@ public class LoanTransactionsApiResource {
             case capitalizedIncomeAdjustment -> 
LoanTransactionType.CAPITALIZED_INCOME_ADJUSTMENT;
             case contractTermination -> 
LoanTransactionType.CONTRACT_TERMINATION;
             case capitalizedIncomeAmortizationAdjustment -> 
LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT;
+            case buyDownFeeAmortization -> 
LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION;
+            case buyDownFeeAmortizationAdjustment -> 
LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT;
             default ->
                 throw new InvalidLoanTransactionTypeException("transaction", 
transactionTypeParam.name(), "Unknown transaction type");
         };
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
index 44a8738b63..3d4b1cdbc2 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
@@ -735,6 +735,14 @@ final class LoansApiResourceSwagger {
                 public boolean capitalizedIncomeAdjustment;
                 @Schema(example = "false")
                 public boolean contractTermination;
+                @Schema(example = "false")
+                public boolean buyDownFee;
+                @Schema(example = "false")
+                public boolean buyDownFeeAdjustment;
+                @Schema(example = "false")
+                public boolean buyDownFeeAmortization;
+                @Schema(example = "false")
+                public boolean buyDownFeeAmortizationAdjustment;
             }
 
             static final class GetLoansLoanIdPaymentDetailData {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBuyDownFeeAmortizationProcessingServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBuyDownFeeAmortizationProcessingServiceImpl.java
new file mode 100644
index 0000000000..979bb4c065
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBuyDownFeeAmortizationProcessingServiceImpl.java
@@ -0,0 +1,113 @@
+/**
+ * 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.service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.List;
+import lombok.RequiredArgsConstructor;
+import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
+import org.apache.fineract.infrastructure.core.service.MathUtil;
+import org.apache.fineract.infrastructure.event.business.domain.BusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
+import 
org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeesBalanceRepository;
+import 
org.apache.fineract.portfolio.loanaccount.util.BuyDownFeeAmortizationUtil;
+import org.springframework.lang.NonNull;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@RequiredArgsConstructor
+public class LoanBuyDownFeeAmortizationProcessingServiceImpl implements 
LoanBuyDownFeeAmortizationProcessingService {
+
+    private final LoanTransactionRepository loanTransactionRepository;
+    private final LoanBuyDownFeesBalanceRepository 
loanBuyDownFeesBalanceRepository;
+    private final BusinessEventNotifierService businessEventNotifierService;
+    private final LoanJournalEntryPoster journalEntryPoster;
+    private final ExternalIdFactory externalIdFactory;
+
+    @Override
+    @Transactional
+    public void processBuyDownFeeAmortizationTillDate(@NonNull Loan loan, 
@NonNull LocalDate tillDate, boolean addJournal) {
+        final List<Long> existingTransactionIds = 
loanTransactionRepository.findTransactionIdsByLoan(loan);
+        final List<Long> existingReversedTransactionIds = 
loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
+
+        List<LoanBuyDownFeeBalance> balances = 
loanBuyDownFeesBalanceRepository.findAllByLoanId(loan.getId());
+
+        LocalDate maturityDate = loan.getMaturityDate() != null ? 
loan.getMaturityDate()
+                : getFinalBuyDownFeeAmortizationTransactionDate(loan);
+        LocalDate tillDatePlusOne = tillDate.plusDays(1);
+        if (tillDatePlusOne.isAfter(maturityDate)) {
+            tillDatePlusOne = maturityDate;
+        }
+
+        Money totalAmortization = Money.zero(loan.getCurrency());
+        for (LoanBuyDownFeeBalance balance : balances) {
+            List<LoanTransaction> adjustments = 
loanTransactionRepository.findAdjustments(balance.getLoanTransaction());
+            Money amortizationTillDate = 
BuyDownFeeAmortizationUtil.calculateTotalAmortizationTillDate(balance, 
adjustments, maturityDate,
+                    
loan.getLoanProductRelatedDetail().getBuyDownFeeStrategy(), tillDatePlusOne, 
loan.getCurrency());
+            totalAmortization = totalAmortization.add(amortizationTillDate);
+
+            
balance.setUnrecognizedAmount(balance.getAmount().subtract(MathUtil.nullToZero(balance.getAmountAdjustment()))
+                    .subtract(amortizationTillDate.getAmount()));
+        }
+
+        loanBuyDownFeesBalanceRepository.saveAll(balances);
+
+        BigDecimal totalAmortized = 
loanTransactionRepository.getAmortizedAmountBuyDownFee(loan);
+        BigDecimal totalAmortizationAmount = 
totalAmortization.getAmount().subtract(totalAmortized);
+
+        if (!MathUtil.isZero(totalAmortizationAmount)) {
+            LoanTransaction transaction = 
MathUtil.isGreaterThanZero(totalAmortizationAmount)
+                    ? LoanTransaction.buyDownFeeAmortization(loan, 
loan.getOffice(), tillDate, totalAmortizationAmount,
+                            externalIdFactory.create())
+                    : LoanTransaction.buyDownFeeAmortizationAdjustment(loan,
+                            Money.of(loan.getCurrency(), 
MathUtil.negate(totalAmortizationAmount)), tillDate, 
externalIdFactory.create());
+            loan.addLoanTransaction(transaction);
+
+            transaction = loanTransactionRepository.save(transaction);
+            loanTransactionRepository.flush();
+
+            if (addJournal) {
+                journalEntryPoster.postJournalEntries(loan, 
existingTransactionIds, existingReversedTransactionIds);
+            }
+
+            BusinessEvent<?> event = 
MathUtil.isGreaterThanZero(totalAmortizationAmount)
+                    ? new 
LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent(transaction)
+                    : new 
LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent(transaction);
+            businessEventNotifierService.notifyPostBusinessEvent(event);
+        }
+    }
+
+    private LocalDate getFinalBuyDownFeeAmortizationTransactionDate(final Loan 
loan) {
+        return switch (loan.getStatus()) {
+            case CLOSED_OBLIGATIONS_MET -> loan.getClosedOnDate();
+            case OVERPAID -> loan.getOverpaidOnDate();
+            case CLOSED_WRITTEN_OFF -> loan.getWrittenOffOnDate();
+            default -> throw new IllegalStateException("Unexpected value: " + 
loan.getStatus());
+        };
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingServiceImpl.java
index 17f0cf5343..e8bf678e13 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingServiceImpl.java
@@ -125,7 +125,7 @@ public class 
LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
 
         BigDecimal totalAmortizationAmount = BigDecimal.ZERO;
         for (LoanCapitalizedIncomeBalance balance : balances) {
-            List<LoanTransaction> adjustments = 
loanTransactionRepository.findAdjustmentsForCapitalizedIncome(balance.getLoanTransaction());
+            List<LoanTransaction> adjustments = 
loanTransactionRepository.findAdjustments(balance.getLoanTransaction());
             LocalDate maturityDate = loan.getMaturityDate() != null ? 
loan.getMaturityDate() : transactionDate;
             final Money amortizationTillDate = 
CapitalizedIncomeAmortizationUtil.calculateTotalAmortizationTillDate(balance, 
adjustments,
                     maturityDate, 
loan.getLoanProductRelatedDetail().getCapitalizedIncomeStrategy(), 
maturityDate, loan.getCurrency());
@@ -136,7 +136,7 @@ public class 
LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
             balance.setUnrecognizedAmount(BigDecimal.ZERO);
         }
 
-        BigDecimal amortizedAmount = 
loanTransactionRepository.getAmortizedAmount(loan);
+        BigDecimal amortizedAmount = 
loanTransactionRepository.getAmortizedAmountCapitalizedIncome(loan);
         BigDecimal totalUnrecognizedAmount = 
totalAmortizationAmount.subtract(amortizedAmount);
         if (MathUtil.isZero(totalUnrecognizedAmount)) {
             return Optional.empty();
@@ -203,7 +203,7 @@ public class 
LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
 
         Money totalAmortization = Money.zero(loan.getCurrency());
         for (LoanCapitalizedIncomeBalance balance : balances) {
-            List<LoanTransaction> adjustments = 
loanTransactionRepository.findAdjustmentsForCapitalizedIncome(balance.getLoanTransaction());
+            List<LoanTransaction> adjustments = 
loanTransactionRepository.findAdjustments(balance.getLoanTransaction());
             Money amortizationTillDate = 
CapitalizedIncomeAmortizationUtil.calculateTotalAmortizationTillDate(balance, 
adjustments,
                     maturityDate, 
loan.getLoanProductRelatedDetail().getCapitalizedIncomeStrategy(), 
tillDatePlusOne, loan.getCurrency());
             totalAmortization = totalAmortization.add(amortizationTillDate);
@@ -214,7 +214,7 @@ public class 
LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L
 
         loanCapitalizedIncomeBalanceRepository.saveAll(balances);
 
-        BigDecimal totalAmortized = 
loanTransactionRepository.getAmortizedAmount(loan);
+        BigDecimal totalAmortized = 
loanTransactionRepository.getAmortizedAmountCapitalizedIncome(loan);
         BigDecimal totalAmortizationAmount = 
totalAmortization.getAmount().subtract(totalAmortized);
 
         if (!MathUtil.isZero(totalAmortizationAmount)) {
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 00609a9999..a891a2aebf 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
@@ -123,9 +123,12 @@ import 
org.apache.fineract.portfolio.loanaccount.service.LoanArrearsAgingService
 import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
 import org.apache.fineract.portfolio.loanaccount.service.LoanAssemblerImpl;
 import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
+import 
org.apache.fineract.portfolio.loanaccount.service.LoanBuyDownFeeAmortizationProcessingService;
+import 
org.apache.fineract.portfolio.loanaccount.service.LoanBuyDownFeeAmortizationProcessingServiceImpl;
 import 
org.apache.fineract.portfolio.loanaccount.service.LoanCalculateRepaymentPastDueService;
 import 
org.apache.fineract.portfolio.loanaccount.service.LoanCapitalizedIncomeAmortizationEventService;
 import 
org.apache.fineract.portfolio.loanaccount.service.LoanCapitalizedIncomeAmortizationProcessingService;
+import 
org.apache.fineract.portfolio.loanaccount.service.LoanCapitalizedIncomeAmortizationProcessingServiceImpl;
 import org.apache.fineract.portfolio.loanaccount.service.LoanChargeAssembler;
 import 
org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadService;
 import 
org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
@@ -549,4 +552,25 @@ public class LoanAccountConfiguration {
                 loanCapitalizedIncomeAmortizationProcessingService);
     }
 
+    @Bean
+    
@ConditionalOnMissingBean(LoanCapitalizedIncomeAmortizationProcessingService.class)
+    public LoanCapitalizedIncomeAmortizationProcessingService 
loanCapitalizedIncomeAmortizationProcessingService(
+            final ConfigurationDomainService configurationDomainService, final 
LoanTransactionRepository loanTransactionRepository,
+            final LoanCapitalizedIncomeBalanceRepository 
loanCapitalizedIncomeBalanceRepository,
+            final BusinessEventNotifierService businessEventNotifierService, 
final LoanJournalEntryPoster journalEntryPoster,
+            final ExternalIdFactory externalIdFactory) {
+        return new 
LoanCapitalizedIncomeAmortizationProcessingServiceImpl(configurationDomainService,
 loanTransactionRepository,
+                loanCapitalizedIncomeBalanceRepository, 
businessEventNotifierService, journalEntryPoster, externalIdFactory);
+    }
+
+    @Bean
+    
@ConditionalOnMissingBean(LoanBuyDownFeeAmortizationProcessingService.class)
+    public LoanBuyDownFeeAmortizationProcessingService 
loanBuyDownFeeAmortizationProcessingService(
+            final LoanTransactionRepository loanTransactionRepository,
+            final LoanBuyDownFeesBalanceRepository 
loanBuyDownFeesBalanceRepository,
+            final BusinessEventNotifierService businessEventNotifierService, 
final LoanJournalEntryPoster journalEntryPoster,
+            final ExternalIdFactory externalIdFactory) {
+        return new 
LoanBuyDownFeeAmortizationProcessingServiceImpl(loanTransactionRepository, 
loanBuyDownFeesBalanceRepository,
+                businessEventNotifierService, journalEntryPoster, 
externalIdFactory);
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/util/BuyDownFeeAmortizationUtil.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/util/BuyDownFeeAmortizationUtil.java
new file mode 100644
index 0000000000..37459b5f0a
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/util/BuyDownFeeAmortizationUtil.java
@@ -0,0 +1,84 @@
+/**
+ * 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.util;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.util.Comparator;
+import java.util.List;
+import org.apache.fineract.infrastructure.core.service.DateUtils;
+import org.apache.fineract.infrastructure.core.service.MathUtil;
+import org.apache.fineract.organisation.monetary.domain.MonetaryCurrency;
+import org.apache.fineract.organisation.monetary.domain.Money;
+import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+
+public final class BuyDownFeeAmortizationUtil {
+
+    private BuyDownFeeAmortizationUtil() {}
+
+    public static Money calculateTotalAmortizationTillDate(final 
LoanBuyDownFeeBalance buyDownFeeBalance,
+            final List<LoanTransaction> adjustmentTransactions, final 
LocalDate maturityDate,
+            final LoanBuyDownFeeStrategy buyDownFeeStrategy, final LocalDate 
tillDate, final MonetaryCurrency currency) {
+        return switch (buyDownFeeStrategy) {
+            case EQUAL_AMORTIZATION -> 
calculateTotalAmortizationTillDateEqualAmortization(buyDownFeeBalance, 
adjustmentTransactions,
+                    maturityDate, tillDate, currency);
+        };
+    }
+
+    private static Money 
calculateTotalAmortizationTillDateEqualAmortization(LoanBuyDownFeeBalance 
balance,
+            List<LoanTransaction> adjustmentTransactions, LocalDate 
maturityDate, LocalDate tillDate, MonetaryCurrency currency) {
+
+        BigDecimal unrecognizedAmount = balance.getAmount();
+        BigDecimal totalAmortizationAmount = BigDecimal.ZERO;
+        BigDecimal overAmortizationCorrection = BigDecimal.ZERO;
+
+        List<LoanTransaction> sortedAdjustmentTransactions = 
adjustmentTransactions.stream()
+                
.sorted(Comparator.comparing(LoanTransaction::getDateOf)).toList();
+        LocalDate periodStart = balance.getDate();
+        for (LoanTransaction adjustmentTransaction : 
sortedAdjustmentTransactions) {
+            long daysUntilMaturity = 
DateUtils.getDifferenceInDays(periodStart, maturityDate);
+            long daysOfPeriod = DateUtils.getDifferenceInDays(periodStart, 
adjustmentTransaction.getDateOf());
+            BigDecimal periodAmortization = daysUntilMaturity == 0L ? 
BigDecimal.ZERO
+                    : 
unrecognizedAmount.multiply(BigDecimal.valueOf(daysOfPeriod)).divide(BigDecimal.valueOf(daysUntilMaturity),
+                            MoneyHelper.getMathContext());
+
+            totalAmortizationAmount = 
totalAmortizationAmount.add(periodAmortization);
+            unrecognizedAmount = 
unrecognizedAmount.subtract(periodAmortization).subtract(adjustmentTransaction.getAmount());
+            if (MathUtil.isLessThanZero(unrecognizedAmount)) {
+                overAmortizationCorrection = 
overAmortizationCorrection.add(unrecognizedAmount);
+                unrecognizedAmount = BigDecimal.ZERO;
+            }
+            periodStart = adjustmentTransaction.getDateOf();
+        }
+        if (periodStart.isBefore(tillDate)) {
+            long daysUntilMaturity = 
DateUtils.getDifferenceInDays(periodStart, maturityDate);
+            long daysOfPeriod = DateUtils.getDifferenceInDays(periodStart, 
tillDate);
+            BigDecimal periodAmortization = 
unrecognizedAmount.multiply(BigDecimal.valueOf(daysOfPeriod))
+                    .divide(BigDecimal.valueOf(daysUntilMaturity), 
MoneyHelper.getMathContext());
+            totalAmortizationAmount = 
totalAmortizationAmount.add(periodAmortization);
+        } else if (balance.getDate().equals(maturityDate)) {
+            totalAmortizationAmount = 
totalAmortizationAmount.add(unrecognizedAmount);
+        }
+
+        return Money.of(currency, 
totalAmortizationAmount.add(overAmortizationCorrection));
+    }
+}
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 4307ea39ee..5e627b4d8d 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
@@ -208,4 +208,5 @@
     <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" />
+    <include file="parts/0190_buy_down_fee_amortization.xml" 
relativeToChangelogFile="true" />
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0190_buy_down_fee_amortization.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0190_buy_down_fee_amortization.xml
new file mode 100644
index 0000000000..d9f53156b4
--- /dev/null
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0190_buy_down_fee_amortization.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd";>
+    <changeSet id="1" author="fineract">
+        <insert tableName="m_external_event_configuration">
+            <column name="type" 
value="LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent"/>
+            <column name="enabled" valueBoolean="false"/>
+        </insert>
+        <insert tableName="m_external_event_configuration">
+            <column name="type" 
value="LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent"/>
+            <column name="enabled" valueBoolean="false"/>
+        </insert>
+        <insert tableName="r_enum_value">
+            <column name="enum_name" value="transaction_type_enum"/>
+            <column name="enum_id" valueNumeric="42"/>
+            <column name="enum_message_property" value="Buy Down Fee 
Amortization"/>
+            <column name="enum_value" value="Buy Down Fee Amortization"/>
+            <column name="enum_type" valueBoolean="false"/>
+        </insert>
+        <insert tableName="r_enum_value">
+            <column name="enum_name" value="transaction_type_enum"/>
+            <column name="enum_id" valueNumeric="43"/>
+            <column name="enum_message_property" value="Buy Down Fee 
Amortization Adjustment"/>
+            <column name="enum_value" value="Buy Down Fee Amortization 
Adjustment"/>
+            <column name="enum_type" 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 9ac262fcab..3347b2162b 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
@@ -110,7 +110,9 @@ public class 
ExternalEventConfigurationValidationServiceTest {
                 
"LoanCapitalizedIncomeAdjustmentTransactionCreatedBusinessEvent", 
"LoanTransactionContractTerminationPostBusinessEvent",
                 
"LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent",
                 "LoanCapitalizedIncomeTransactionCreatedBusinessEvent", 
"LoanUndoContractTerminationBusinessEvent",
-                "LoanBuyDownFeeTransactionCreatedBusinessEvent", 
"LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent");
+                "LoanBuyDownFeeTransactionCreatedBusinessEvent", 
"LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent",
+                "LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent",
+                
"LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent");
 
         List<FineractPlatformTenant> tenants = Arrays
                 .asList(new FineractPlatformTenant(1L, "default", "Default 
Tenant", "Europe/Budapest", null));
@@ -202,7 +204,9 @@ public class 
ExternalEventConfigurationValidationServiceTest {
                 
"LoanCapitalizedIncomeAdjustmentTransactionCreatedBusinessEvent", 
"LoanTransactionContractTerminationPostBusinessEvent",
                 
"LoanCapitalizedIncomeAmortizationAdjustmentTransactionCreatedBusinessEvent",
                 "LoanCapitalizedIncomeTransactionCreatedBusinessEvent", 
"LoanUndoContractTerminationBusinessEvent",
-                "LoanBuyDownFeeTransactionCreatedBusinessEvent", 
"LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent");
+                "LoanBuyDownFeeTransactionCreatedBusinessEvent", 
"LoanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent",
+                "LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent",
+                
"LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent");
 
         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 786e207831..57288eaec0 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,22 +23,28 @@ import static 
org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 import java.math.BigDecimal;
+import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
 import java.util.List;
+import java.util.Optional;
 import java.util.UUID;
+import java.util.concurrent.atomic.AtomicReference;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
+import org.apache.fineract.client.models.PostClientsResponse;
 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.BusinessStepHelper;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.Utils;
 import org.apache.fineract.integrationtests.common.accounting.Account;
 import 
org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
@@ -425,4 +431,146 @@ public class LoanBuyDownFeeTest extends 
BaseLoanIntegrationTest {
                         
.transactionAmount(amount).externalId(buyDownFeeExternalId).note("Buy Down Fee 
Transaction"));
         return response.getResourceId();
     }
+
+    @Test
+    public void testBuyDownFeeDailyAmortization() {
+        new BusinessStepHelper().updateSteps("LOAN_CLOSE_OF_BUSINESS", 
"APPLY_CHARGE_TO_OVERDUE_LOANS", "LOAN_DELINQUENCY_CLASSIFICATION",
+                "CHECK_LOAN_REPAYMENT_DUE", "CHECK_LOAN_REPAYMENT_OVERDUE", 
"CHECK_DUE_INSTALLMENTS", "UPDATE_LOAN_ARREARS_AGING",
+                "ADD_PERIODIC_ACCRUAL_ENTRIES", "ACCRUAL_ACTIVITY_POSTING", 
"CAPITALIZED_INCOME_AMORTIZATION", "BUY_DOWN_FEE_AMORTIZATION",
+                "LOAN_INTEREST_RECALCULATION", 
"EXTERNAL_ASSET_OWNER_TRANSFER");
+
+        final AtomicReference<Long> loanIdRef = new AtomicReference<>();
+        final AtomicReference<Long> buyDownFeeTransactionIdIdRef = new 
AtomicReference<>();
+
+        final PostClientsResponse client = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+
+        final PostLoanProductsResponse loanProductsResponse = 
loanProductHelper.createLoanProduct(create4IProgressive()
+                
.enableBuyDownFee(true).buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT)
+                
.buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION)
+                
.buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE)
+                
.buyDownExpenseAccountId(buyDownExpenseAccount.getAccountID().longValue())
+                
.deferredIncomeLiabilityAccountId(deferredIncomeLiabilityAccount.getAccountID().longValue())
+                
.incomeFromBuyDownAccountId(feeIncomeAccount.getAccountID().longValue()));
+
+        runAt("1 January 2024", () -> {
+            Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), 
loanProductsResponse.getResourceId(), "1 January 2024",
+                    500.0, 7.0, 3, null);
+            loanIdRef.set(loanId);
+
+            disburseLoan(loanId, BigDecimal.valueOf(100), "1 January 2024");
+            PostLoansLoanIdTransactionsResponse transactionsResponse = 
loanTransactionHelper.makeLoanBuyDownFee(loanId, "1 January 2024",
+                    50.0);
+            
buyDownFeeTransactionIdIdRef.set(transactionsResponse.getResourceId());
+        });
+        runAt("31 January 2024", () -> {
+            Long loanId = loanIdRef.get();
+            executeInlineCOB(loanId);
+
+            // summarized amortization
+            verifyTransactions(loanId, //
+                    transaction(100.0, "Disbursement", "01 January 2024"), //
+                    transaction(0.55, "Accrual", "30 January 2024"), //
+                    transaction(50.0, "Buy Down Fee", "01 January 2024"), //
+                    transaction(16.48, "Buy Down Fee Amortization", "30 
January 2024"));
+        });
+        runAt("1 February 2024", () -> {
+            Long loanId = loanIdRef.get();
+            executeInlineCOB(loanId);
+
+            // daily amortization
+            verifyTransactions(loanId, //
+                    transaction(100.0, "Disbursement", "01 January 2024"), //
+                    transaction(50.0, "Buy Down Fee", "01 January 2024"), //
+                    transaction(0.55, "Accrual", "30 January 2024"), //
+                    transaction(16.48, "Buy Down Fee Amortization", "30 
January 2024"), //
+                    transaction(0.01, "Accrual", "31 January 2024"), //
+                    transaction(0.55, "Buy Down Fee Amortization", "31 January 
2024"));
+
+            loanTransactionHelper.buyDownFeeAdjustment(loanId, 
buyDownFeeTransactionIdIdRef.get(), "1 February 2024", 10.0);
+        });
+        runAt("2 February 2024", () -> {
+            Long loanId = loanIdRef.get();
+            executeInlineCOB(loanId);
+
+            // not backdated and not large buy down fee adjustment -> lowered 
daily amount
+            verifyTransactions(loanId, //
+                    transaction(100.0, "Disbursement", "01 January 2024"), //
+                    transaction(50.0, "Buy Down Fee", "01 January 2024"), //
+                    transaction(0.55, "Accrual", "30 January 2024"), //
+                    transaction(16.48, "Buy Down Fee Amortization", "30 
January 2024"), //
+                    transaction(0.01, "Accrual", "31 January 2024"), //
+                    transaction(0.55, "Buy Down Fee Amortization", "31 January 
2024"), //
+                    transaction(10.0, "Buy Down Fee Adjustment", "01 February 
2024"), //
+                    transaction(0.02, "Accrual", "01 February 2024"), //
+                    transaction(0.39, "Buy Down Fee Amortization", "01 
February 2024"));
+
+            loanTransactionHelper.buyDownFeeAdjustment(loanId, 
buyDownFeeTransactionIdIdRef.get(), "10 January 2024", 10.0);
+        });
+        runAt("3 February 2024", () -> {
+            Long loanId = loanIdRef.get();
+            executeInlineCOB(loanId);
+
+            // backdated buy down fee adjustment -> amortization adjustment
+            verifyTransactions(loanId, //
+                    transaction(100.0, "Disbursement", "01 January 2024"), //
+                    transaction(50.0, "Buy Down Fee", "01 January 2024"), //
+                    transaction(10.0, "Buy Down Fee Adjustment", "10 January 
2024"), //
+                    transaction(0.55, "Accrual", "30 January 2024"), //
+                    transaction(16.48, "Buy Down Fee Amortization", "30 
January 2024"), //
+                    transaction(0.01, "Accrual", "31 January 2024"), //
+                    transaction(0.55, "Buy Down Fee Amortization", "31 January 
2024"), //
+                    transaction(10.0, "Buy Down Fee Adjustment", "01 February 
2024"), //
+                    transaction(0.02, "Accrual", "01 February 2024"), //
+                    transaction(0.39, "Buy Down Fee Amortization", "01 
February 2024"), //
+                    transaction(0.02, "Accrual", "02 February 2024"), //
+                    transaction(2.55, "Buy Down Fee Amortization Adjustment", 
"02 February 2024"));
+
+            loanTransactionHelper.buyDownFeeAdjustment(loanId, 
buyDownFeeTransactionIdIdRef.get(), "03 February 2024", 20.0);
+        });
+        runAt("4 February 2024", () -> {
+            Long loanId = loanIdRef.get();
+            executeInlineCOB(loanId);
+
+            // large (more than remaining unrecognized (15.13)) buy down fee 
adjustment -> amortization adjustment
+            verifyTransactions(loanId, //
+                    transaction(100.0, "Disbursement", "01 January 2024"), //
+                    transaction(50.0, "Buy Down Fee", "01 January 2024"), //
+                    transaction(10.0, "Buy Down Fee Adjustment", "10 January 
2024"), //
+                    transaction(0.55, "Accrual", "30 January 2024"), //
+                    transaction(16.48, "Buy Down Fee Amortization", "30 
January 2024"), //
+                    transaction(0.01, "Accrual", "31 January 2024"), //
+                    transaction(0.55, "Buy Down Fee Amortization", "31 January 
2024"), //
+                    transaction(10.0, "Buy Down Fee Adjustment", "01 February 
2024"), //
+                    transaction(0.02, "Accrual", "01 February 2024"), //
+                    transaction(0.39, "Buy Down Fee Amortization", "01 
February 2024"), //
+                    transaction(0.02, "Accrual", "02 February 2024"), //
+                    transaction(2.55, "Buy Down Fee Amortization Adjustment", 
"02 February 2024"), //
+                    transaction(20.0, "Buy Down Fee Adjustment", "03 February 
2024"), //
+                    transaction(0.02, "Accrual", "03 February 2024"), //
+                    transaction(4.87, "Buy Down Fee Amortization Adjustment", 
"03 February 2024"));
+
+            // Check journal entries of amortization and amortization 
adjustment
+            GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanId);
+
+            Optional<GetLoansLoanIdTransactions> amortizationTransactionOpt = 
loanDetails.getTransactions().stream()
+                    .filter(transaction -> LocalDate.of(2024, 2, 
1).equals(transaction.getDate())
+                            && 
transaction.getType().getBuyDownFeeAmortization())
+                    .findFirst();
+            Assertions.assertTrue(amortizationTransactionOpt.isPresent());
+
+            verifyTRJournalEntries(amortizationTransactionOpt.get().getId(), //
+                    journalEntry(0.39, feeIncomeAccount, "CREDIT"), //
+                    journalEntry(0.39, deferredIncomeLiabilityAccount, 
"DEBIT"));
+
+            Optional<GetLoansLoanIdTransactions> 
amortizationAdjustmentTransactionOpt = loanDetails.getTransactions().stream()
+                    .filter(transaction -> LocalDate.of(2024, 2, 
3).equals(transaction.getDate())
+                            && 
transaction.getType().getBuyDownFeeAmortizationAdjustment())
+                    .findFirst();
+            
Assertions.assertTrue(amortizationAdjustmentTransactionOpt.isPresent());
+
+            
verifyTRJournalEntries(amortizationAdjustmentTransactionOpt.get().getId(), //
+                    journalEntry(4.87, deferredIncomeLiabilityAccount, 
"CREDIT"), //
+                    journalEntry(4.87, feeIncomeAccount, "DEBIT"));
+        });
+    }
 }
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 a8c4300cb5..bc4c92951b 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
@@ -646,6 +646,17 @@ public class ExternalEventConfigurationHelper {
         loanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent.put("enabled", 
false);
         defaults.add(loanBuyDownFeeAdjustmentTransactionCreatedBusinessEvent);
 
+        Map<String, Object> 
loanBuyDownFeeAmortizationTransactionCreatedBusinessEvent = new HashMap<>();
+        loanBuyDownFeeAmortizationTransactionCreatedBusinessEvent.put("type", 
"LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent");
+        
loanBuyDownFeeAmortizationTransactionCreatedBusinessEvent.put("enabled", false);
+        
defaults.add(loanBuyDownFeeAmortizationTransactionCreatedBusinessEvent);
+
+        Map<String, Object> 
loanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent = new 
HashMap<>();
+        
loanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent.put("type",
+                
"LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent");
+        
loanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent.put("enabled",
 false);
+        
defaults.add(loanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent);
+
         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 bec059a9f3..96ac70a249 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
@@ -3083,6 +3083,11 @@ public class LoanTransactionHelper {
         return 
Calls.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction(loanId,
 request, "buyDownFee"));
     }
 
+    public PostLoansLoanIdTransactionsResponse makeLoanBuyDownFee(Long loanId, 
String date, double amount) {
+        return makeLoanBuyDownFee(loanId, new 
PostLoansLoanIdTransactionsRequest().dateFormat("dd MMMM 
yyyy").transactionDate(date)
+                .locale("en").transactionAmount(amount));
+    }
+
     public List<AdvancedPaymentData> getAdvancedPaymentAllocationRules(final 
Integer loanId) {
         return 
Calls.ok(FineractClientHelper.getFineractClient().legacy.getAdvancedPaymentAllocationRulesOfLoan(loanId.longValue()));
     }

Reply via email to