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

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

commit cb70555485ad401b6c88d708aa2e60c9c5c6dafe
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Mon Sep 8 21:05:22 2025 -0500

    FINERACT-2374: Advance Accounting rule for classification type
---
 .../domain/ProductToGLAccountMapping.java          |  14 +-
 .../ProductToGLAccountMappingRepository.java       |  21 ++-
 .../service/ProductToGLAccountMappingHelper.java   | 160 +++++++++++++++++++++
 ...oductToGLAccountMappingReadPlatformService.java |   5 +
 ...tToGLAccountMappingReadPlatformServiceImpl.java |  51 +++++--
 .../accounting/common/AccountingConstants.java     |   7 +-
 .../journalentry/data/AdvancedMappingtDTO.java     |  32 +----
 .../data/ClassificationToGLAccountData.java        |  36 +++++
 .../codes/api/CodeValuesApiResourceSwagger.java    |   6 +-
 .../codes/api/CodesApiResourceSwagger.java         |   0
 .../codes/mapper/CodeValueMapper.java              |  41 ++----
 .../LoanProductToGLAccountMappingHelper.java       |  24 ++++
 .../loanaccount/data/AccountingBridgeDataDTO.java  |   3 +
 ...oanAmortizationAllocationMappingRepository.java |   9 ++
 .../domain/LoanTransactionRepository.java          |   8 ++
 .../api/LoanProductsApiResourceSwagger.java        |  54 +++++--
 .../loanproduct/data/LoanProductData.java          |  23 ++-
 .../handler/CreateLoanProductCommandHandler.java   |   9 +-
 .../accounting/journalentry/data/LoanDTO.java      |   2 +
 .../service/AccountingProcessorHelper.java         |  31 +++-
 .../AccrualBasedAccountingProcessorForLoan.java    | 114 +++++++++++++--
 ...EntryWritePlatformServiceJpaRepositoryImpl.java |  37 ++++-
 .../AccountingJournalEntryConfiguration.java       |   8 +-
 ...ToGLAccountMappingWritePlatformServiceImpl.java |  12 ++
 .../loanproduct/api/LoanProductsApiResource.java   |  20 ++-
 .../serialization/LoanProductDataValidator.java    |  74 +++++++++-
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 ...dd_classification_id_to_acc_product_mapping.xml |  52 +++++++
 .../CreateJournalEntriesForChargeOffLoanTest.java  |   2 +-
 .../integrationtests/LoanBuyDownFeeTest.java       | 134 +++++++++++++++--
 .../LoanCapitalizedIncomeTest.java                 | 105 ++++++++++++++
 31 files changed, 975 insertions(+), 120 deletions(-)

diff --git 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java
 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java
index 1f602e90d5..12edabfae4 100644
--- 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java
+++ 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMapping.java
@@ -68,10 +68,20 @@ public class ProductToGLAccountMapping extends 
AbstractPersistableCustom<Long> {
     @JoinColumn(name = "charge_off_reason_id", nullable = true)
     private CodeValue chargeOffReason;
 
+    @ManyToOne
+    @JoinColumn(name = "capitalized_income_classification_id", nullable = true)
+    private CodeValue capitalizedIncomeClassification;
+
+    @ManyToOne
+    @JoinColumn(name = "buydown_fee_classification_id", nullable = true)
+    private CodeValue buydownFeeClassification;
+
     public static ProductToGLAccountMapping createNew(final GLAccount 
glAccount, final Long productId, final int productType,
-            final int financialAccountType, final CodeValue chargeOffReason) {
+            final int financialAccountType, final CodeValue chargeOffReason, 
final CodeValue capitalizedIncomeClassification,
+            final CodeValue buydownFeeClassification) {
 
         return new 
ProductToGLAccountMapping().setGlAccount(glAccount).setProductId(productId).setProductType(productType)
-                
.setFinancialAccountType(financialAccountType).setChargeOffReason(chargeOffReason);
+                
.setFinancialAccountType(financialAccountType).setChargeOffReason(chargeOffReason)
+                
.setCapitalizedIncomeClassification(capitalizedIncomeClassification).setBuydownFeeClassification(buydownFeeClassification);
     }
 }
diff --git 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java
 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java
index 2b2bc21172..917fbdffd7 100644
--- 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java
+++ 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/domain/ProductToGLAccountMappingRepository.java
@@ -35,7 +35,7 @@ public interface ProductToGLAccountMappingRepository
             @Param("productType") int productType, 
@Param("financialAccountType") int financialAccountType,
             @Param("chargeId") Long ChargeId);
 
-    @Query("select mapping from ProductToGLAccountMapping mapping where 
mapping.productId =:productId and mapping.productType =:productType and 
mapping.financialAccountType=:financialAccountType and mapping.paymentType is 
NULL and mapping.charge is NULL and mapping.chargeOffReason is NULL")
+    @Query("select mapping from ProductToGLAccountMapping mapping where 
mapping.productId =:productId and mapping.productType =:productType and 
mapping.financialAccountType=:financialAccountType and mapping.paymentType is 
NULL and mapping.charge is NULL and mapping.chargeOffReason is NULL and 
mapping.capitalizedIncomeClassification is NULL and 
mapping.buydownFeeClassification is NULL")
     ProductToGLAccountMapping 
findCoreProductToFinAccountMapping(@Param("productId") Long productId, 
@Param("productType") int productType,
             @Param("financialAccountType") int financialAccountType);
 
@@ -70,7 +70,7 @@ public interface ProductToGLAccountMappingRepository
     ProductToGLAccountMapping findChargeOffReasonMapping(@Param("productId") 
Long productId, @Param("productType") Integer productType,
             @Param("chargeOffReasonId") Long chargeOffReasonId);
 
-    @Query("select mapping from ProductToGLAccountMapping mapping where 
mapping.productId =:productId AND mapping.productType =:productType AND 
mapping.charge IS NULL AND mapping.paymentType IS NULL AND 
mapping.chargeOffReason IS NULL")
+    @Query("select mapping from ProductToGLAccountMapping mapping where 
mapping.productId =:productId AND mapping.productType =:productType AND 
mapping.charge IS NULL AND mapping.paymentType IS NULL AND 
mapping.chargeOffReason IS NULL AND mapping.capitalizedIncomeClassification is 
NULL AND mapping.buydownFeeClassification is NULL")
     List<ProductToGLAccountMapping> findAllRegularMappings(@Param("productId") 
Long productId, @Param("productType") Integer productType);
 
     @Query("select mapping from ProductToGLAccountMapping mapping where 
mapping.productId =:productId and mapping.productType =:productType and 
mapping.paymentType is not NULL")
@@ -82,4 +82,21 @@ public interface ProductToGLAccountMappingRepository
 
     @Query("select mapping from ProductToGLAccountMapping mapping where 
mapping.productId =:productId AND mapping.productType =:productType AND 
mapping.charge.penalty = FALSE")
     List<ProductToGLAccountMapping> findAllFeeMappings(@Param("productId") 
Long productId, @Param("productType") Integer productType);
+
+    @Query("select mapping from ProductToGLAccountMapping mapping where 
mapping.productId =:productId and mapping.productType =:productType and 
mapping.capitalizedIncomeClassification is not NULL")
+    List<ProductToGLAccountMapping> 
findAllCapitalizedIncomeClassificationsMappings(@Param("productId") Long 
productId,
+            @Param("productType") int productType);
+
+    @Query("select mapping from ProductToGLAccountMapping mapping where 
mapping.productId =:productId and mapping.productType =:productType and 
mapping.buydownFeeClassification is not NULL")
+    List<ProductToGLAccountMapping> 
findAllBuyDownFeeClassificationsMappings(@Param("productId") Long productId,
+            @Param("productType") int productType);
+
+    @Query("select mapping from ProductToGLAccountMapping mapping where 
mapping.buydownFeeClassification.id = :classificationId AND mapping.productId = 
:productId AND mapping.productType = :productType")
+    ProductToGLAccountMapping 
findBuydownFeeClassificationMapping(@Param("productId") Long productId,
+            @Param("productType") Integer productType, 
@Param("classificationId") Long classificationId);
+
+    @Query("select mapping from ProductToGLAccountMapping mapping where 
mapping.capitalizedIncomeClassification.id = :classificationId AND 
mapping.productId = :productId AND mapping.productType = :productType")
+    ProductToGLAccountMapping 
findCapitalizedIncomeClassificationMapping(@Param("productId") Long productId,
+            @Param("productType") Integer productType, 
@Param("classificationId") Long classificationId);
+
 }
diff --git 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java
 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java
index dcd9ec44ca..b37580c872 100644
--- 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java
+++ 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingHelper.java
@@ -227,6 +227,30 @@ public class ProductToGLAccountMappingHelper {
         }
     }
 
+    public void saveClassificationToGLAccountMappings(final JsonCommand 
command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes, final PortfolioProductType 
portfolioProductType,
+            final LoanProductAccountingParams classificationParameter) {
+
+        final String arrayName = classificationParameter.getValue();
+        final JsonArray classificationToIncomeAccountMappingArray = 
this.fromApiJsonHelper.extractJsonArrayNamed(arrayName, element);
+
+        if (classificationToIncomeAccountMappingArray != null) {
+            if (changes != null) {
+                changes.put(arrayName, command.jsonFragment(arrayName));
+            }
+
+            for (int i = 0; i < 
classificationToIncomeAccountMappingArray.size(); i++) {
+                final JsonObject jsonObject = 
classificationToIncomeAccountMappingArray.get(i).getAsJsonObject();
+                final Long classificationId = 
jsonObject.get(LoanProductAccountingParams.CLASSIFICATION_CODE_VALUE_ID.getValue())
+                        .getAsLong();
+                final Long incomeAccountId = 
jsonObject.get(LoanProductAccountingParams.INCOME_ACCOUNT_ID.getValue()).getAsLong();
+
+                saveClassificationToIncomeMapping(productId, classificationId, 
incomeAccountId, portfolioProductType,
+                        classificationParameter);
+            }
+        }
+    }
+
     /**
      * @param command
      * @param element
@@ -448,6 +472,75 @@ public class ProductToGLAccountMappingHelper {
         }
     }
 
+    public void updateClassificationToGLAccountMappings(final JsonCommand 
command, final JsonElement element, final Long productId,
+            final Map<String, Object> changes, final PortfolioProductType 
portfolioProductType,
+            final LoanProductAccountingParams classificationParameter) {
+
+        final List<ProductToGLAccountMapping> 
existingClassificationToGLAccountMappings = classificationParameter
+                
.equals(LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS)
+                        ? 
this.accountMappingRepository.findAllCapitalizedIncomeClassificationsMappings(productId,
+                                portfolioProductType.getValue())
+                        : 
this.accountMappingRepository.findAllBuyDownFeeClassificationsMappings(productId,
+                                portfolioProductType.getValue());
+
+        final JsonArray classificationToGLAccountMappingArray = 
this.fromApiJsonHelper
+                .extractJsonArrayNamed(classificationParameter.getValue(), 
element);
+
+        final Map<Long, Long> inputClassificationToGLAccountMap = new 
HashMap<>();
+
+        final Set<Long> existingClassifications = new HashSet<>();
+        if (classificationToGLAccountMappingArray != null) {
+            if (changes != null) {
+                changes.put(classificationParameter.getValue(), 
command.jsonFragment(classificationParameter.getValue()));
+            }
+
+            for (int i = 0; i < classificationToGLAccountMappingArray.size(); 
i++) {
+                final JsonObject jsonObject = 
classificationToGLAccountMappingArray.get(i).getAsJsonObject();
+                final Long incomeGlAccountId = 
jsonObject.get(LoanProductAccountingParams.INCOME_ACCOUNT_ID.getValue()).getAsLong();
+                final Long classificationCodeValueId = 
jsonObject.get(LoanProductAccountingParams.CLASSIFICATION_CODE_VALUE_ID.getValue())
+                        .getAsLong();
+                
inputClassificationToGLAccountMap.put(classificationCodeValueId, 
incomeGlAccountId);
+            }
+
+            // If input map is empty, delete all existing mappings
+            if (inputClassificationToGLAccountMap.isEmpty()) {
+                
this.accountMappingRepository.deleteAllInBatch(existingClassificationToGLAccountMappings);
+            } else {
+                for (final ProductToGLAccountMapping 
existingClassificationToGLAccountMapping : 
existingClassificationToGLAccountMappings) {
+                    final Long currentClassificationId = 
classificationParameter
+                            
.equals(LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS)
+                                    ? 
existingClassificationToGLAccountMapping.getCapitalizedIncomeClassification().getId()
+                                    : 
existingClassificationToGLAccountMapping.getBuydownFeeClassification().getId();
+
+                    if (currentClassificationId != null) {
+                        existingClassifications.add(currentClassificationId);
+                        // update existing mappings (if required)
+                        if 
(inputClassificationToGLAccountMap.containsKey(currentClassificationId)) {
+                            final Long newGLAccountId = 
inputClassificationToGLAccountMap.get(currentClassificationId);
+                            if 
(!newGLAccountId.equals(existingClassificationToGLAccountMapping.getGlAccount().getId()))
 {
+                                final Optional<GLAccount> glAccount = 
accountRepository.findById(newGLAccountId);
+                                if (glAccount.isPresent()) {
+                                    
existingClassificationToGLAccountMapping.setGlAccount(glAccount.get());
+                                    
this.accountMappingRepository.saveAndFlush(existingClassificationToGLAccountMapping);
+                                }
+                            }
+                        } // deleted previous record
+                        else {
+                            
this.accountMappingRepository.delete(existingClassificationToGLAccountMapping);
+                        }
+                    }
+                }
+
+                // only the newly added
+                for (Map.Entry<Long, Long> entry : 
inputClassificationToGLAccountMap.entrySet().stream()
+                        .filter(e -> 
!existingClassifications.contains(e.getKey())).toList()) {
+                    saveClassificationToIncomeMapping(productId, 
entry.getKey(), entry.getValue(), portfolioProductType,
+                            classificationParameter);
+                }
+            }
+        }
+    }
+
     /**
      * @param productId
      *
@@ -514,6 +607,39 @@ public class ProductToGLAccountMappingHelper {
         }
     }
 
+    private void saveClassificationToIncomeMapping(final Long productId, final 
Long classificationId, final Long incomeAccountId,
+            final PortfolioProductType portfolioProductType, final 
LoanProductAccountingParams classificationParameter) {
+
+        final Optional<GLAccount> glAccount = 
accountRepository.findById(incomeAccountId);
+
+        boolean classificationMappingExists = false;
+        if 
(classificationParameter.equals(LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS))
 {
+            classificationMappingExists = this.accountMappingRepository
+                    
.findAllCapitalizedIncomeClassificationsMappings(productId, 
portfolioProductType.getValue()).stream()
+                    .anyMatch(mapping -> 
mapping.getCapitalizedIncomeClassification().getId().equals(classificationId));
+        } else {
+            classificationMappingExists = this.accountMappingRepository
+                    .findAllBuyDownFeeClassificationsMappings(productId, 
portfolioProductType.getValue()).stream()
+                    .anyMatch(mapping -> 
mapping.getBuydownFeeClassification().getId().equals(classificationId));
+        }
+
+        final Optional<CodeValue> codeValueOptional = 
codeValueRepository.findById(classificationId);
+
+        if (glAccount.isPresent() && !classificationMappingExists && 
codeValueOptional.isPresent()) {
+            final ProductToGLAccountMapping accountMapping = new 
ProductToGLAccountMapping().setGlAccount(glAccount.get())
+                    
.setProductId(productId).setProductType(portfolioProductType.getValue())
+                    
.setFinancialAccountType(CashAccountsForLoan.CLASSIFICATION_INCOME.getValue());
+
+            if 
(classificationParameter.equals(LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS))
 {
+                
accountMapping.setCapitalizedIncomeClassification(codeValueOptional.get());
+            } else {
+                
accountMapping.setBuydownFeeClassification(codeValueOptional.get());
+            }
+
+            this.accountMappingRepository.saveAndFlush(accountMapping);
+        }
+    }
+
     private List<GLAccountType> getAllowedAccountTypesForFeeMapping() {
         List<GLAccountType> allowedAccountTypes = new ArrayList<>();
         allowedAccountTypes.add(GLAccountType.INCOME);
@@ -610,4 +736,38 @@ public class ProductToGLAccountMappingHelper {
             throw new PlatformApiDataValidationException(validationErrors);
         }
     }
+
+    public void validateClassificationMappingsInDatabase(final 
List<JsonObject> mappings, final String dataCodeName) {
+        final List<ApiParameterError> validationErrors = new ArrayList<>();
+
+        for (JsonObject jsonObject : mappings) {
+            final Long incomeGlAccountId = 
this.fromApiJsonHelper.extractLongNamed(LoanProductAccountingParams.INCOME_ACCOUNT_ID.getValue(),
+                    jsonObject);
+            final Long classificationCodeValueId = this.fromApiJsonHelper
+                    
.extractLongNamed(LoanProductAccountingParams.CLASSIFICATION_CODE_VALUE_ID.getValue(),
 jsonObject);
+
+            // Validation: classificationCodeValueId must exist in the database
+            final CodeValue codeValue = 
this.codeValueRepository.findByCodeNameAndId(dataCodeName, 
classificationCodeValueId);
+            if (codeValue == null) {
+                
validationErrors.add(ApiParameterError.parameterError("validation.msg.classification.invalid",
+                        "Classification with ID " + classificationCodeValueId 
+ " does not exist", dataCodeName));
+            }
+
+            // Validation: expenseGLAccountId must exist as a valid Expense GL 
account
+            final Optional<GLAccount> glAccount = 
accountRepository.findById(incomeGlAccountId);
+
+            if (glAccount.isEmpty() || 
!GLAccountType.fromInt(glAccount.get().getType()).isIncomeType()) {
+                
validationErrors.add(ApiParameterError.parameterError("validation.msg.glaccount.not.found",
+                        "GL Account with ID " + incomeGlAccountId + " does not 
exist or is not an Income GL account",
+                        
LoanProductAccountingParams.INCOME_ACCOUNT_ID.getValue()));
+
+            }
+        }
+
+        // Throw all collected validation errors, if any
+        if (!validationErrors.isEmpty()) {
+            throw new PlatformApiDataValidationException(validationErrors);
+        }
+    }
+
 }
diff --git 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java
 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java
index 6c89ab1a3f..22ccc37c13 100644
--- 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java
+++ 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformService.java
@@ -20,8 +20,10 @@ package 
org.apache.fineract.accounting.producttoaccountmapping.service;
 
 import java.util.List;
 import java.util.Map;
+import 
org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams;
 import 
org.apache.fineract.accounting.producttoaccountmapping.data.ChargeOffReasonToGLAccountMapper;
 import 
org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
+import 
org.apache.fineract.accounting.producttoaccountmapping.data.ClassificationToGLAccountData;
 import 
org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
 
 public interface ProductToGLAccountMappingReadPlatformService {
@@ -49,4 +51,7 @@ public interface ProductToGLAccountMappingReadPlatformService 
{
     List<ChargeToGLAccountMapper> 
fetchFeeToIncomeAccountMappingsForShareProduct(Long productId);
 
     List<ChargeOffReasonToGLAccountMapper> 
fetchChargeOffReasonMappingsForLoanProduct(Long loanProductId);
+
+    List<ClassificationToGLAccountData> 
fetchClassificationMappingsForLoanProduct(Long loanProductId,
+            LoanProductAccountingParams classificationParameter);
 }
diff --git 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java
 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java
index 6e2c44c237..1fcb7bf6d1 100644
--- 
a/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java
+++ 
b/fineract-accounting/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/service/ProductToGLAccountMappingReadPlatformServiceImpl.java
@@ -30,6 +30,7 @@ import 
org.apache.fineract.accounting.common.AccountingConstants.CashAccountsFor
 import 
org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings;
 import 
org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForShares;
 import 
org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingDataParams;
+import 
org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams;
 import 
org.apache.fineract.accounting.common.AccountingConstants.SavingProductAccountingDataParams;
 import 
org.apache.fineract.accounting.common.AccountingConstants.SharesProductAccountingParams;
 import org.apache.fineract.accounting.common.AccountingRuleType;
@@ -37,14 +38,15 @@ import 
org.apache.fineract.accounting.common.AccountingValidations;
 import org.apache.fineract.accounting.glaccount.data.GLAccountData;
 import 
org.apache.fineract.accounting.producttoaccountmapping.data.ChargeOffReasonToGLAccountMapper;
 import 
org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
+import 
org.apache.fineract.accounting.producttoaccountmapping.data.ClassificationToGLAccountData;
 import 
org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
 import 
org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMapping;
 import 
org.apache.fineract.accounting.producttoaccountmapping.domain.ProductToGLAccountMappingRepository;
 import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.mapper.CodeValueMapper;
 import org.apache.fineract.portfolio.PortfolioProductType;
 import org.apache.fineract.portfolio.charge.data.ChargeData;
 import org.apache.fineract.portfolio.paymenttype.data.PaymentTypeData;
-import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Service;
 
 @Slf4j
@@ -52,9 +54,8 @@ import org.springframework.stereotype.Service;
 @RequiredArgsConstructor
 public class ProductToGLAccountMappingReadPlatformServiceImpl implements 
ProductToGLAccountMappingReadPlatformService {
 
-    private final JdbcTemplate jdbcTemplate;
-
     private final ProductToGLAccountMappingRepository 
productToGLAccountMappingRepository;
+    private final CodeValueMapper codeValueMapper;
 
     @Override
     public Map<String, Object> fetchAccountMappingDetailsForLoanProduct(final 
Long loanProductId, final Integer accountingType) {
@@ -283,14 +284,7 @@ public class 
ProductToGLAccountMappingReadPlatformServiceImpl implements Product
             final String glAccountName = mapping.getGlAccount().getName();
             final String glCode = mapping.getGlAccount().getGlCode();
             final GLAccountData chargeOffExpenseAccount = new 
GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode);
-            final Long chargeOffReasonId = 
mapping.getChargeOffReason().getId();
-            final String codeValue = mapping.getChargeOffReason().getLabel();
-            final String codeDescription = 
mapping.getChargeOffReason().getDescription();
-            final Integer orderPosition = 
mapping.getChargeOffReason().getPosition();
-            final boolean isActive = mapping.getChargeOffReason().isActive();
-            final boolean isMandatory = 
mapping.getChargeOffReason().isMandatory();
-            final CodeValueData chargeOffReasonsCodeValue = 
CodeValueData.builder().id(chargeOffReasonId).name(codeValue)
-                    
.description(codeDescription).position(orderPosition).active(isActive).mandatory(isMandatory).build();
+            final CodeValueData chargeOffReasonsCodeValue = 
codeValueMapper.map(mapping.getChargeOffReason());
 
             final ChargeOffReasonToGLAccountMapper 
chargeOffReasonToGLAccountMapper = new ChargeOffReasonToGLAccountMapper()
                     
.setChargeOffReasonCodeValue(chargeOffReasonsCodeValue).setExpenseAccount(chargeOffExpenseAccount);
@@ -299,6 +293,35 @@ public class 
ProductToGLAccountMappingReadPlatformServiceImpl implements Product
         return chargeOffReasonToGLAccountMappers;
     }
 
+    private List<ClassificationToGLAccountData> 
fetchClassificationMappings(final PortfolioProductType portfolioProductType,
+            final Long loanProductId, LoanProductAccountingParams 
classificationParameter) {
+        final List<ProductToGLAccountMapping> mappings = 
classificationParameter
+                
.equals(LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS)
+                        ? 
productToGLAccountMappingRepository.findAllCapitalizedIncomeClassificationsMappings(loanProductId,
+                                portfolioProductType.getValue())
+                        : 
productToGLAccountMappingRepository.findAllBuyDownFeeClassificationsMappings(loanProductId,
+                                portfolioProductType.getValue());
+
+        
productToGLAccountMappingRepository.findAllChargeOffReasonsMappings(loanProductId,
 portfolioProductType.getValue());
+        List<ClassificationToGLAccountData> classificationToGLAccountMappers = 
mappings.isEmpty() ? null : new ArrayList<>();
+        for (final ProductToGLAccountMapping mapping : mappings) {
+            final Long glAccountId = mapping.getGlAccount().getId();
+            final String glAccountName = mapping.getGlAccount().getName();
+            final String glCode = mapping.getGlAccount().getGlCode();
+            final GLAccountData glAccountData = new 
GLAccountData().setId(glAccountId).setName(glAccountName).setGlCode(glCode);
+
+            final CodeValueData classificationCodeValue = 
classificationParameter
+                    
.equals(LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS)
+                            ? 
codeValueMapper.map(mapping.getCapitalizedIncomeClassification())
+                            : 
codeValueMapper.map(mapping.getBuydownFeeClassification());
+
+            final ClassificationToGLAccountData 
classificationToGLAccountMapper = new ClassificationToGLAccountData()
+                    
.setClassificationCodeValue(classificationCodeValue).setIncomeAccount(glAccountData);
+            
classificationToGLAccountMappers.add(classificationToGLAccountMapper);
+        }
+        return classificationToGLAccountMappers;
+    }
+
     @Override
     public Map<String, Object> fetchAccountMappingDetailsForShareProduct(Long 
productId, Integer accountingType) {
 
@@ -344,6 +367,12 @@ public class 
ProductToGLAccountMappingReadPlatformServiceImpl implements Product
         return fetchChargeOffReasonMappings(PortfolioProductType.LOAN, 
loanProductId);
     }
 
+    @Override
+    public List<ClassificationToGLAccountData> 
fetchClassificationMappingsForLoanProduct(Long loanProductId,
+            LoanProductAccountingParams classificationParameter) {
+        return fetchClassificationMappings(PortfolioProductType.LOAN, 
loanProductId, classificationParameter);
+    }
+
     private Map<String, Object> 
setAccrualPeriodicSavingsProductToGLAccountMaps(final 
List<ProductToGLAccountMapping> mappings) {
         final Map<String, Object> accountMappingDetails = new 
LinkedHashMap<>(8);
 
diff --git 
a/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java
 
b/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java
index 1c50780e88..7435a46d90 100644
--- 
a/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/accounting/common/AccountingConstants.java
@@ -53,7 +53,9 @@ public final class AccountingConstants {
         INCOME_FROM_CHARGE_OFF_PENALTY(18), //
         INCOME_FROM_GOODWILL_CREDIT_INTEREST(19), //
         INCOME_FROM_GOODWILL_CREDIT_FEES(20), //
-        INCOME_FROM_GOODWILL_CREDIT_PENALTY(21); //
+        INCOME_FROM_GOODWILL_CREDIT_PENALTY(21), //
+        CLASSIFICATION_INCOME(22), //
+        ;
 
         private final Integer value;
 
@@ -185,6 +187,9 @@ public final class AccountingConstants {
         INCOME_FROM_CAPITALIZATION("incomeFromCapitalizationAccountId"), //
         BUY_DOWN_EXPENSE("buyDownExpenseAccountId"), //
         INCOME_FROM_BUY_DOWN("incomeFromBuyDownAccountId"), //
+        
CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS("capitalizedIncomeClassificationToIncomeAccountMappings"),
 //
+        
BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS("buydownfeeClassificationToIncomeAccountMappings"),
 //
+        CLASSIFICATION_CODE_VALUE_ID("classificationCodeValueId"), //
         ;
 
         private final String value;
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
 
b/fineract-core/src/main/java/org/apache/fineract/accounting/journalentry/data/AdvancedMappingtDTO.java
similarity index 53%
copy from 
fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
copy to 
fineract-core/src/main/java/org/apache/fineract/accounting/journalentry/data/AdvancedMappingtDTO.java
index 925448d541..14234e6c23 100755
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/accounting/journalentry/data/AdvancedMappingtDTO.java
@@ -18,34 +18,14 @@
  */
 package org.apache.fineract.accounting.journalentry.data;
 
-import java.util.List;
-import lombok.AllArgsConstructor;
+import java.math.BigDecimal;
 import lombok.Getter;
-import lombok.Setter;
+import lombok.RequiredArgsConstructor;
 
-@AllArgsConstructor
+@RequiredArgsConstructor
 @Getter
-public class LoanDTO {
+public class AdvancedMappingtDTO {
 
-    @Setter
-    private Long loanId;
-    @Setter
-    private Long loanProductId;
-    @Setter
-    private Long officeId;
-    @Setter
-    private String currencyCode;
-    @Setter
-    private boolean cashBasedAccountingEnabled;
-    private final boolean upfrontAccrualBasedAccountingEnabled;
-    private final boolean periodicAccrualBasedAccountingEnabled;
-    @Setter
-    private List<LoanTransactionDTO> newLoanTransactions;
-    @Setter
-    private boolean markedAsChargeOff;
-    @Setter
-    private boolean markedAsFraud;
-    private Long chargeOffReasonCodeValue;
-    private boolean markedAsWrittenOff;
-    private boolean merchantBuyDownFee;
+    private final Long referenceValueId;
+    private final BigDecimal amount;
 }
diff --git 
a/fineract-core/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/data/ClassificationToGLAccountData.java
 
b/fineract-core/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/data/ClassificationToGLAccountData.java
new file mode 100644
index 0000000000..fcec6272cd
--- /dev/null
+++ 
b/fineract-core/src/main/java/org/apache/fineract/accounting/producttoaccountmapping/data/ClassificationToGLAccountData.java
@@ -0,0 +1,36 @@
+/**
+ * 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.accounting.producttoaccountmapping.data;
+
+import java.io.Serializable;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+
+@Data
+@NoArgsConstructor
+@Accessors(chain = true)
+public class ClassificationToGLAccountData implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+    private CodeValueData classificationCodeValue;
+    private GLAccountData incomeAccount;
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodeValuesApiResourceSwagger.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/codes/api/CodeValuesApiResourceSwagger.java
similarity index 95%
rename from 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodeValuesApiResourceSwagger.java
rename to 
fineract-core/src/main/java/org/apache/fineract/infrastructure/codes/api/CodeValuesApiResourceSwagger.java
index 2407ec3ba4..6d79420ef7 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodeValuesApiResourceSwagger.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/codes/api/CodeValuesApiResourceSwagger.java
@@ -23,7 +23,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
 /**
  * Created by sanyam on 30/7/17.
  */
-final class CodeValuesApiResourceSwagger {
+public final class CodeValuesApiResourceSwagger {
 
     private CodeValuesApiResourceSwagger() {
 
@@ -44,6 +44,10 @@ final class CodeValuesApiResourceSwagger {
         public String description;
         @Schema(example = "0")
         public Integer position;
+        @Schema(example = "true")
+        public Boolean active;
+        @Schema(example = "false")
+        public Boolean mandatory;
     }
 
     @Schema(description = "PostCodeValuesDataRequest")
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodesApiResourceSwagger.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/codes/api/CodesApiResourceSwagger.java
similarity index 100%
rename from 
fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodesApiResourceSwagger.java
rename to 
fineract-core/src/main/java/org/apache/fineract/infrastructure/codes/api/CodesApiResourceSwagger.java
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/codes/mapper/CodeValueMapper.java
old mode 100755
new mode 100644
similarity index 50%
copy from 
fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
copy to 
fineract-core/src/main/java/org/apache/fineract/infrastructure/codes/mapper/CodeValueMapper.java
index 925448d541..eca38f69a0
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
+++ 
b/fineract-core/src/main/java/org/apache/fineract/infrastructure/codes/mapper/CodeValueMapper.java
@@ -16,36 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.accounting.journalentry.data;
+package org.apache.fineract.infrastructure.codes.mapper;
 
 import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.Setter;
+import org.apache.fineract.infrastructure.codes.data.CodeValueData;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.core.config.MapstructMapperConfig;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
 
-@AllArgsConstructor
-@Getter
-public class LoanDTO {
+@Mapper(config = MapstructMapperConfig.class)
+public interface CodeValueMapper {
+
+    @Mapping(target = "name", source = "label")
+    CodeValueData map(CodeValue source);
+
+    List<CodeValueData> map(List<CodeValue> source);
 
-    @Setter
-    private Long loanId;
-    @Setter
-    private Long loanProductId;
-    @Setter
-    private Long officeId;
-    @Setter
-    private String currencyCode;
-    @Setter
-    private boolean cashBasedAccountingEnabled;
-    private final boolean upfrontAccrualBasedAccountingEnabled;
-    private final boolean periodicAccrualBasedAccountingEnabled;
-    @Setter
-    private List<LoanTransactionDTO> newLoanTransactions;
-    @Setter
-    private boolean markedAsChargeOff;
-    @Setter
-    private boolean markedAsFraud;
-    private Long chargeOffReasonCodeValue;
-    private boolean markedAsWrittenOff;
-    private boolean merchantBuyDownFee;
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/LoanProductToGLAccountMappingHelper.java
 
b/fineract-loan/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/LoanProductToGLAccountMappingHelper.java
index 44439a316f..80b64277e4 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/LoanProductToGLAccountMappingHelper.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/LoanProductToGLAccountMappingHelper.java
@@ -149,6 +149,30 @@ public class LoanProductToGLAccountMappingHelper extends 
ProductToGLAccountMappi
         updateChargeOffReasonToGLAccountMappings(command, element, productId, 
changes, PortfolioProductType.LOAN);
     }
 
+    public void 
saveCapitalizedIncomeClassificationToIncomeAccountMappings(final JsonCommand 
command, final JsonElement element,
+            final Long productId, final Map<String, Object> changes) {
+        saveClassificationToGLAccountMappings(command, element, productId, 
changes, PortfolioProductType.LOAN,
+                
LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
+    }
+
+    public void 
updateCapitalizedIncomeClassificationToIncomeAccountMappings(final JsonCommand 
command, final JsonElement element,
+            final Long productId, final Map<String, Object> changes) {
+        updateClassificationToGLAccountMappings(command, element, productId, 
changes, PortfolioProductType.LOAN,
+                
LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
+    }
+
+    public void saveBuyDownFeeClassificationToIncomeAccountMappings(final 
JsonCommand command, final JsonElement element,
+            final Long productId, final Map<String, Object> changes) {
+        saveClassificationToGLAccountMappings(command, element, productId, 
changes, PortfolioProductType.LOAN,
+                
LoanProductAccountingParams.BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
+    }
+
+    public void updateBuyDownFeeClassificationToIncomeAccountMappings(final 
JsonCommand command, final JsonElement element,
+            final Long productId, final Map<String, Object> changes) {
+        updateClassificationToGLAccountMappings(command, element, productId, 
changes, PortfolioProductType.LOAN,
+                
LoanProductAccountingParams.BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
+    }
+
     public void updateChargesToIncomeAccountMappings(final JsonCommand 
command, final JsonElement element, final Long productId,
             final Map<String, Object> changes) {
         // update both fee and penalty charges
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccountingBridgeDataDTO.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccountingBridgeDataDTO.java
index b68c9e18dd..1dbb6c4e17 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccountingBridgeDataDTO.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/AccountingBridgeDataDTO.java
@@ -25,6 +25,7 @@ import lombok.AllArgsConstructor;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
+import org.apache.fineract.accounting.journalentry.data.AdvancedMappingtDTO;
 
 @Getter
 @Setter
@@ -47,5 +48,7 @@ public class AccountingBridgeDataDTO {
     private boolean isWrittenOff;
     private List<AccountingBridgeLoanTransactionDTO> newLoanTransactions = new 
ArrayList<>();
     private boolean merchantBuyDownFee;
+    private List<AdvancedMappingtDTO> buydownFeeClassificationCodeValue;
+    private List<AdvancedMappingtDTO> capitalizedIncomeClassificationCodeValue;
 
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAmortizationAllocationMappingRepository.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAmortizationAllocationMappingRepository.java
index 22ad707baa..fd355087c6 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAmortizationAllocationMappingRepository.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanAmortizationAllocationMappingRepository.java
@@ -61,4 +61,13 @@ public interface LoanAmortizationAllocationMappingRepository
                     WHERE laam.baseLoanTransactionId = :baseLoanTransactionId 
AND laam.loanId = :loanId
             """)
     BigDecimal calculateAlreadyAmortizedAmount(@Param("baseLoanTransactionId") 
Long baseLoanTransactionId, @Param("loanId") Long loanId);
+
+    @Query("""
+                    SELECT laam FROM LoanAmortizationAllocationMapping laam
+                        JOIN LoanTransaction at ON at.id = 
laam.baseLoanTransactionId
+                    WHERE laam.amortizationLoanTransactionId = 
:amortizationLoanTransactionId
+                    AND laam.loanId = :loanId
+            """)
+    List<LoanAmortizationAllocationMapping> 
fetchLoanTransactionAllocationByAmortizationLoanTransactionId(
+            @Param("amortizationLoanTransactionId") Long 
amortizationLoanTransactionId, @Param("loanId") Long loanId);
 }
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 2fcdc83c7b..4862798bf7 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
@@ -24,6 +24,7 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Optional;
 import java.util.Set;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import 
org.apache.fineract.portfolio.loanaccount.data.CumulativeIncomeFromIncomePosting;
 import 
org.apache.fineract.portfolio.loanaccount.data.LoanScheduleDelinquencyData;
@@ -473,4 +474,11 @@ public interface LoanTransactionRepository extends 
JpaRepository<LoanTransaction
     boolean existsNonReversedByLoanAndTypeAndDate(@Param("loan") Loan loan, 
@Param("type") LoanTransactionType type,
             @Param("transactionDate") LocalDate transactionDate);
 
+    @Query("""
+            SELECT lt.classification
+            FROM LoanTransaction lt
+            WHERE lt.id = :transactionId
+            """)
+    CodeValue 
fetchClassificationCodeValueByTransactionId(@Param("transactionId") Long 
transactionId);
+
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
index 00fc321aeb..ccb47b2ffc 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResourceSwagger.java
@@ -24,6 +24,7 @@ import java.time.LocalDate;
 import java.util.List;
 import java.util.Set;
 import org.apache.fineract.accounting.glaccount.data.GLAccountData;
+import 
org.apache.fineract.infrastructure.codes.api.CodeValuesApiResourceSwagger.GetCodeValuesDataResponse;
 import org.apache.fineract.infrastructure.core.data.EnumOptionData;
 import org.apache.fineract.infrastructure.core.data.StringEnumOptionData;
 import org.apache.fineract.portfolio.delinquency.data.DelinquencyBucketData;
@@ -300,6 +301,8 @@ public final class LoanProductsApiResourceSwagger {
         public 
List<GetLoanProductsProductIdResponse.GetLoanPaymentChannelToFundSourceMappings>
 paymentChannelToFundSourceMappings;
         public List<LoanProductChargeToGLAccountMapper> 
feeToIncomeAccountMappings;
         public List<PostChargeOffReasonToExpenseAccountMappings> 
chargeOffReasonToExpenseAccountMappings;
+        public 
List<PostLoanProductsRequest.PostClassificationToIncomeAccountMappings> 
buydownfeeClassificationToIncomeAccountMappings;
+        public 
List<PostLoanProductsRequest.PostClassificationToIncomeAccountMappings> 
capitalizedIncomeClassificationToIncomeAccountMappings;
         public List<LoanProductChargeToGLAccountMapper> 
penaltyToIncomeAccountMappings;
 
         // Multi Disburse
@@ -378,6 +381,16 @@ public final class LoanProductsApiResourceSwagger {
             @Schema(example = "1")
             public Long expenseAccountId;
         }
+
+        static final class PostClassificationToIncomeAccountMappings {
+
+            private PostClassificationToIncomeAccountMappings() {}
+
+            @Schema(example = "1")
+            public Long classificationCodeValueId;
+            @Schema(example = "1")
+            public Long incomeAccountId;
+        }
     }
 
     @Schema(description = "PostLoanProductsResponse")
@@ -1168,6 +1181,8 @@ public final class LoanProductsApiResourceSwagger {
         public List<StringEnumOptionData> buyDownFeeCalculationTypeOptions;
         public List<StringEnumOptionData> buyDownFeeStrategyOptions;
         public List<StringEnumOptionData> buyDownFeeIncomeTypeOptions;
+        public List<GetCodeValuesDataResponse> 
capitalizedIncomeClassificationOptions;
+        public List<GetCodeValuesDataResponse> buydownFeeClassificationOptions;
     }
 
     @Schema(description = "GetLoanProductsProductIdResponse")
@@ -1294,6 +1309,26 @@ public final class LoanProductsApiResourceSwagger {
             public Long fundSourceAccountId;
         }
 
+        static final class GetGLAccountData {
+
+            private GetGLAccountData() {}
+
+            @Schema(example = "1")
+            public Long id;
+            @Schema(example = "Written off")
+            public String name;
+            @Schema(example = "e4")
+            public String glCode;
+        }
+
+        static final class GetClassificationToIncomeAccountMappings {
+
+            private GetClassificationToIncomeAccountMappings() {}
+
+            public GetCodeValuesDataResponse classificationCodeValue;
+            public GetGLAccountData incomeAccount;
+        }
+
         static final class GetChargeOffReasonToExpenseAccountMappings {
 
             private GetChargeOffReasonToExpenseAccountMappings() {}
@@ -1317,18 +1352,6 @@ public final class LoanProductsApiResourceSwagger {
                 @Schema(example = "false")
                 public Boolean mandatory;
             }
-
-            static final class GetGLAccountData {
-
-                private GetGLAccountData() {}
-
-                @Schema(example = "1")
-                public Long id;
-                @Schema(example = "Written off")
-                public String name;
-                @Schema(example = "e4")
-                public String glCode;
-            }
         }
 
         static final class GetLoanFeeToIncomeAccountMappings {
@@ -1516,6 +1539,11 @@ public final class LoanProductsApiResourceSwagger {
         public List<StringEnumOptionData> buyDownFeeCalculationTypeOptions;
         public List<StringEnumOptionData> buyDownFeeStrategyOptions;
         public List<StringEnumOptionData> buyDownFeeIncomeTypeOptions;
+        public List<GetCodeValuesDataResponse> 
capitalizedIncomeClassificationOptions;
+        public List<GetCodeValuesDataResponse> buydownFeeClassificationOptions;
+        public List<GetClassificationToIncomeAccountMappings> 
buydownFeeClassificationToIncomeAccountMappings;
+        public List<GetClassificationToIncomeAccountMappings> 
capitalizedIncomeClassificationToIncomeAccountMappings;
+
     }
 
     @Schema(description = "PutLoanProductsProductIdRequest")
@@ -1746,6 +1774,8 @@ public final class LoanProductsApiResourceSwagger {
         public 
List<GetLoanProductsProductIdResponse.GetLoanPaymentChannelToFundSourceMappings>
 paymentChannelToFundSourceMappings;
         public List<LoanProductChargeToGLAccountMapper> 
feeToIncomeAccountMappings;
         public 
List<PostLoanProductsRequest.PostChargeOffReasonToExpenseAccountMappings> 
chargeOffReasonToExpenseAccountMappings;
+        public 
List<PostLoanProductsRequest.PostClassificationToIncomeAccountMappings> 
buydownfeeClassificationToIncomeAccountMappings;
+        public 
List<PostLoanProductsRequest.PostClassificationToIncomeAccountMappings> 
capitalizedIncomeClassificationToIncomeAccountMappings;
         public List<LoanProductChargeToGLAccountMapper> 
penaltyToIncomeAccountMappings;
         @Schema(example = "false")
         public Boolean enableAccrualActivityPosting;
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
index ff33b5f513..87660df6d0 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/data/LoanProductData.java
@@ -32,6 +32,7 @@ import 
org.apache.fineract.accounting.common.AccountingRuleType;
 import org.apache.fineract.accounting.glaccount.data.GLAccountData;
 import 
org.apache.fineract.accounting.producttoaccountmapping.data.ChargeOffReasonToGLAccountMapper;
 import 
org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
+import 
org.apache.fineract.accounting.producttoaccountmapping.data.ClassificationToGLAccountData;
 import 
org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
 import org.apache.fineract.infrastructure.codes.data.CodeValueData;
 import org.apache.fineract.infrastructure.core.api.ApiFacingEnum;
@@ -261,6 +262,11 @@ public class LoanProductData implements Serializable {
     private List<StringEnumOptionData> buyDownFeeStrategyOptions;
     private List<StringEnumOptionData> buyDownFeeIncomeTypeOptions;
 
+    private final List<CodeValueData> capitalizedIncomeClassificationOptions;
+    private final List<CodeValueData> buydownFeeClassificationOptions;
+    private List<ClassificationToGLAccountData> 
capitalizedIncomeClassificationToIncomeAccountMappings;
+    private List<ClassificationToGLAccountData> 
buydownFeeClassificationToIncomeAccountMappings;
+
     /**
      * Used when returning lookup information about loan product for dropdowns.
      */
@@ -822,12 +828,16 @@ public class LoanProductData implements Serializable {
             final Collection<PaymentTypeToGLAccountMapper> 
paymentChannelToFundSourceMappings,
             final Collection<ChargeToGLAccountMapper> feeToGLAccountMappings,
             final Collection<ChargeToGLAccountMapper> 
penaltyToGLAccountMappings,
-            final List<ChargeOffReasonToGLAccountMapper> 
chargeOffReasonToGLAccountMappings) {
+            final List<ChargeOffReasonToGLAccountMapper> 
chargeOffReasonToGLAccountMappings,
+            final List<ClassificationToGLAccountData> 
capitalizedIncomeClassificationToIncomeAccountMappings,
+            final List<ClassificationToGLAccountData> 
buydownFeeClassificationToIncomeAccountMappings) {
         productData.accountingMappings = accountingMappings;
         productData.paymentChannelToFundSourceMappings = 
paymentChannelToFundSourceMappings;
         productData.feeToIncomeAccountMappings = feeToGLAccountMappings;
         productData.penaltyToIncomeAccountMappings = 
penaltyToGLAccountMappings;
         productData.chargeOffReasonToExpenseAccountMappings = 
chargeOffReasonToGLAccountMappings;
+        productData.capitalizedIncomeClassificationToIncomeAccountMappings = 
capitalizedIncomeClassificationToIncomeAccountMappings;
+        productData.buydownFeeClassificationToIncomeAccountMappings = 
buydownFeeClassificationToIncomeAccountMappings;
         return productData;
     }
 
@@ -1036,6 +1046,10 @@ public class LoanProductData implements Serializable {
         this.buyDownFeeCalculationTypeOptions = 
ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeCalculationType.class);
         this.buyDownFeeStrategyOptions = 
ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeStrategy.class);
         this.buyDownFeeIncomeTypeOptions = 
ApiFacingEnum.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeIncomeType.class);
+        this.capitalizedIncomeClassificationOptions = null;
+        this.buydownFeeClassificationOptions = null;
+        this.capitalizedIncomeClassificationToIncomeAccountMappings = null;
+        this.buydownFeeClassificationToIncomeAccountMappings = null;
     }
 
     public LoanProductData(final LoanProductData productData, final 
Collection<ChargeData> chargeOptions,
@@ -1065,7 +1079,8 @@ public class LoanProductData implements Serializable {
             final List<StringEnumOptionData> capitalizedIncomeStrategyOptions,
             final List<StringEnumOptionData> capitalizedIncomeTypeOptions,
             final List<StringEnumOptionData> buyDownFeeCalculationTypeOptions, 
final List<StringEnumOptionData> buyDownFeeStrategyOptions,
-            final List<StringEnumOptionData> buyDownFeeIncomeTypeOptions) {
+            final List<StringEnumOptionData> buyDownFeeIncomeTypeOptions, 
final List<CodeValueData> capitalizedIncomeClassificationOptions,
+            final List<CodeValueData> buydownFeeClassificationOptions) {
 
         this.id = productData.id;
         this.name = productData.name;
@@ -1246,6 +1261,10 @@ public class LoanProductData implements Serializable {
         this.buyDownFeeIncomeTypeOptions = buyDownFeeIncomeTypeOptions;
 
         this.merchantBuyDownFee = productData.isMerchantBuyDownFee();
+        this.capitalizedIncomeClassificationOptions = 
capitalizedIncomeClassificationOptions;
+        this.buydownFeeClassificationOptions = buydownFeeClassificationOptions;
+        this.buydownFeeClassificationToIncomeAccountMappings = 
productData.buydownFeeClassificationToIncomeAccountMappings;
+        this.capitalizedIncomeClassificationToIncomeAccountMappings = 
productData.capitalizedIncomeClassificationToIncomeAccountMappings;
     }
 
     private Collection<ChargeData> nullIfEmpty(final Collection<ChargeData> 
charges) {
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/handler/CreateLoanProductCommandHandler.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/handler/CreateLoanProductCommandHandler.java
index 72d6dbd174..98bd268664 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/handler/CreateLoanProductCommandHandler.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/handler/CreateLoanProductCommandHandler.java
@@ -18,30 +18,25 @@
  */
 package org.apache.fineract.portfolio.loanproduct.handler;
 
+import lombok.AllArgsConstructor;
 import org.apache.fineract.commands.annotation.CommandType;
 import org.apache.fineract.commands.handler.NewCommandSourceHandler;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import 
org.apache.fineract.portfolio.loanproduct.service.LoanProductWritePlatformService;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 @Service
+@AllArgsConstructor
 @CommandType(entity = "LOANPRODUCT", action = "CREATE")
 public class CreateLoanProductCommandHandler implements 
NewCommandSourceHandler {
 
     private final LoanProductWritePlatformService writePlatformService;
 
-    @Autowired
-    public CreateLoanProductCommandHandler(final 
LoanProductWritePlatformService writePlatformService) {
-        this.writePlatformService = writePlatformService;
-    }
-
     @Transactional
     @Override
     public CommandProcessingResult processCommand(final JsonCommand command) {
-
         return this.writePlatformService.createLoanProduct(command);
     }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
index 925448d541..c415e2a7f2 100755
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
@@ -48,4 +48,6 @@ public class LoanDTO {
     private Long chargeOffReasonCodeValue;
     private boolean markedAsWrittenOff;
     private boolean merchantBuyDownFee;
+    private List<AdvancedMappingtDTO> buydownFeeAdvancedMappingData;
+    private List<AdvancedMappingtDTO> capitalizedIncomeAdvancedMappingData;
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java
index 8e984cede1..2db1687b09 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/AccountingProcessorHelper.java
@@ -35,6 +35,7 @@ import 
org.apache.fineract.accounting.common.AccountingConstants.CashAccountsFor
 import 
org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForSavings;
 import 
org.apache.fineract.accounting.common.AccountingConstants.CashAccountsForShares;
 import 
org.apache.fineract.accounting.common.AccountingConstants.FinancialActivity;
+import 
org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams;
 import 
org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccount;
 import 
org.apache.fineract.accounting.financialactivityaccount.domain.FinancialActivityAccountRepositoryWrapper;
 import org.apache.fineract.accounting.glaccount.domain.GLAccount;
@@ -171,7 +172,9 @@ public class AccountingProcessorHelper {
 
         return new LoanDTO(loanId, loanProductId, officeId, currencyCode, 
cashBasedAccountingEnabled, upfrontAccrualBasedAccountingEnabled,
                 periodicAccrualBasedAccountingEnabled, newLoanTransactions, 
isLoanMarkedAsChargeOff, isLoanMarkedAsFraud,
-                chargeOffReasonCodeValue, isLoanMarkedAsWrittenOff, 
merchantBuyDownFee);
+                chargeOffReasonCodeValue, isLoanMarkedAsWrittenOff, 
merchantBuyDownFee,
+                accountingBridgeData.getBuydownFeeClassificationCodeValue(),
+                
accountingBridgeData.getCapitalizedIncomeClassificationCodeValue());
     }
 
     public ProductToGLAccountMapping getChargeOffMappingByCodeValue(Long 
loanProductId, PortfolioProductType productType,
@@ -179,6 +182,16 @@ public class AccountingProcessorHelper {
         return 
accountMappingRepository.findChargeOffReasonMapping(loanProductId, 
productType.getValue(), chargeOffReasonId);
     }
 
+    public ProductToGLAccountMapping getClassificationMappingByCodeValue(Long 
loanProductId, PortfolioProductType productType,
+            final Long classificationId, final String classificationType) {
+        if 
(LoanProductAccountingParams.BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS.getValue().equals(classificationType))
 {
+            return 
accountMappingRepository.findBuydownFeeClassificationMapping(loanProductId, 
productType.getValue(), classificationId);
+        } else {
+            return 
accountMappingRepository.findCapitalizedIncomeClassificationMapping(loanProductId,
 productType.getValue(),
+                    classificationId);
+        }
+    }
+
     public SavingsDTO populateSavingsDtoFromMap(final Map<String, Object> 
accountingBridgeData, final boolean cashBasedAccountingEnabled,
             final boolean accrualBasedAccountingEnabled) {
         final Long loanId = (Long) accountingBridgeData.get("savingsId");
@@ -472,6 +485,14 @@ public class AccountingProcessorHelper {
                 transactionId, transactionDate, amount);
     }
 
+    public void createJournalEntriesForLoan(final Office office, final String 
currencyCode, final Integer accountTypeToBeDebited,
+            final GLAccount accountToBeCredited, final Long loanProductId, 
final Long paymentTypeId, final Long loanId,
+            final String transactionId, final LocalDate transactionDate, final 
BigDecimal amount) {
+        int accountTypeToDebitId = accountTypeToBeDebited;
+        createJournalEntriesForLoan(office, currencyCode, 
accountTypeToDebitId, accountToBeCredited, loanProductId, paymentTypeId, loanId,
+                transactionId, transactionDate, amount);
+    }
+
     public void createSplitJournalEntriesForLoan(Office office, String 
currencyCode, List<JournalAmountHolder> splitAccountsHolder,
             JournalAmountHolder totalAccountHolder, Long loanProductId, Long 
paymentTypeId, Long loanId, String transactionId,
             LocalDate transactionDate) {
@@ -534,6 +555,14 @@ public class AccountingProcessorHelper {
         createCreditJournalEntryForLoan(office, currencyCode, creditAccount, 
loanId, transactionId, transactionDate, amount);
     }
 
+    private void createJournalEntriesForLoan(final Office office, final String 
currencyCode, final int accountTypeToDebitId,
+            final GLAccount creditAccount, final Long loanProductId, final 
Long paymentTypeId, final Long loanId,
+            final String transactionId, final LocalDate transactionDate, final 
BigDecimal amount) {
+        final GLAccount debitAccount = 
getLinkedGLAccountForLoanProduct(loanProductId, accountTypeToDebitId, 
paymentTypeId);
+        createDebitJournalEntryForLoan(office, currencyCode, debitAccount, 
loanId, transactionId, transactionDate, amount);
+        createCreditJournalEntryForLoan(office, currencyCode, creditAccount, 
loanId, transactionId, transactionDate, amount);
+    }
+
     private void createJournalEntriesForSavings(final Office office, final 
String currencyCode, final int accountTypeToDebitId,
             final int accountTypeToCreditId, final Long savingsProductId, 
final Long paymentTypeId, final Long savingsId,
             final String transactionId, final LocalDate transactionDate, final 
BigDecimal amount) {
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 40ebb2214f..18a91d5b8d 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
@@ -29,7 +29,9 @@ import lombok.RequiredArgsConstructor;
 import org.apache.fineract.accounting.closure.domain.GLClosure;
 import 
org.apache.fineract.accounting.common.AccountingConstants.AccrualAccountsForLoan;
 import 
org.apache.fineract.accounting.common.AccountingConstants.FinancialActivity;
+import 
org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams;
 import org.apache.fineract.accounting.glaccount.domain.GLAccount;
+import org.apache.fineract.accounting.journalentry.data.AdvancedMappingtDTO;
 import org.apache.fineract.accounting.journalentry.data.ChargePaymentDTO;
 import org.apache.fineract.accounting.journalentry.data.GLAccountBalanceHolder;
 import org.apache.fineract.accounting.journalentry.data.LoanDTO;
@@ -296,17 +298,57 @@ public class AccrualBasedAccountingProcessorForLoan 
implements AccountingProcess
         final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
         final GLAccountBalanceHolder glAccountBalanceHolder = new 
GLAccountBalanceHolder();
 
+        final List<AdvancedMappingtDTO> classificationCodeValues = 
loanDTO.getCapitalizedIncomeAdvancedMappingData();
+
         // interest payment
         final AccrualAccountsForLoan creditAccountType = isLoanWrittenOff ? 
AccrualAccountsForLoan.LOSSES_WRITTEN_OFF
                 : AccrualAccountsForLoan.INCOME_FROM_CAPITALIZATION;
         if (MathUtil.isGreaterThanZero(interestAmount)) {
-            populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId, creditAccountType.getValue(),
-                    
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), 
glAccountBalanceHolder);
+            if (classificationCodeValues.isEmpty()) {
+                populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId, creditAccountType.getValue(),
+                        
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), 
glAccountBalanceHolder);
+            } else {
+                
classificationCodeValues.stream().forEach(classificationCodeValue -> {
+                    ProductToGLAccountMapping mapping = null;
+                    if (classificationCodeValue.getReferenceValueId() != null) 
{
+                        mapping = 
fetchAdvanceAccountingMappingForCodeValue(loanProductId, 
classificationCodeValue.getReferenceValueId(),
+                                
LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS.getValue());
+                    }
+
+                    if (mapping == null) {
+                        populateCreditDebitMaps(loanProductId, 
classificationCodeValue.getAmount(), paymentTypeId,
+                                creditAccountType.getValue(), 
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(),
+                                glAccountBalanceHolder);
+                    } else {
+                        populateCreditDebitMaps(loanProductId, 
classificationCodeValue.getAmount(), paymentTypeId, mapping.getGlAccount(),
+                                
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);
+            if (classificationCodeValues.isEmpty()) {
+                populateCreditDebitMaps(loanProductId, feesAmount, 
paymentTypeId, creditAccountType.getValue(),
+                        
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), 
glAccountBalanceHolder);
+            } else {
+                
classificationCodeValues.stream().forEach(classificationCodeValue -> {
+                    ProductToGLAccountMapping mapping = null;
+                    if (classificationCodeValue.getReferenceValueId() != null) 
{
+                        mapping = 
fetchAdvanceAccountingMappingForCodeValue(loanProductId, 
classificationCodeValue.getReferenceValueId(),
+                                
LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS.getValue());
+                    }
+
+                    if (mapping == null) {
+                        populateCreditDebitMaps(loanProductId, 
classificationCodeValue.getAmount(), paymentTypeId,
+                                creditAccountType.getValue(), 
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(),
+                                glAccountBalanceHolder);
+                    } else {
+                        populateCreditDebitMaps(loanProductId, 
classificationCodeValue.getAmount(), paymentTypeId, mapping.getGlAccount(),
+                                
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), 
glAccountBalanceHolder);
+                    }
+                });
+            }
         }
 
         // create credit entries
@@ -327,6 +369,11 @@ public class AccrualBasedAccountingProcessorForLoan 
implements AccountingProcess
         }
     }
 
+    private ProductToGLAccountMapping 
fetchAdvanceAccountingMappingForCodeValue(final Long loanProductId, final Long 
codeValueId,
+            final String codeName) {
+        return helper.getClassificationMappingByCodeValue(loanProductId, 
PortfolioProductType.LOAN, codeValueId, codeName);
+    }
+
     private void 
createJournalEntriesForChargeOffLoanCapitalizedIncomeAmortization(final LoanDTO 
loanDTO,
             final LoanTransactionDTO loanTransactionDTO, final Office office) {
         // loan properties
@@ -493,17 +540,57 @@ public class AccrualBasedAccountingProcessorForLoan 
implements AccountingProcess
         final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
         final GLAccountBalanceHolder glAccountBalanceHolder = new 
GLAccountBalanceHolder();
 
+        final List<AdvancedMappingtDTO> classificationCodeValues = 
loanDTO.getBuydownFeeAdvancedMappingData();
+
         // 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);
+            if (classificationCodeValues.isEmpty()) {
+                populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId, creditAccountType.getValue(),
+                        
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), 
glAccountBalanceHolder);
+            } else {
+                
classificationCodeValues.stream().forEach(classificationCodeValue -> {
+                    ProductToGLAccountMapping mapping = null;
+                    if (classificationCodeValue.getReferenceValueId() != null) 
{
+                        mapping = 
fetchAdvanceAccountingMappingForCodeValue(loanProductId, 
classificationCodeValue.getReferenceValueId(),
+                                
LoanProductAccountingParams.BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS.getValue());
+                    }
+
+                    if (mapping == null) {
+                        populateCreditDebitMaps(loanProductId, 
classificationCodeValue.getAmount(), paymentTypeId,
+                                creditAccountType.getValue(), 
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(),
+                                glAccountBalanceHolder);
+                    } else {
+                        populateCreditDebitMaps(loanProductId, 
classificationCodeValue.getAmount(), paymentTypeId, mapping.getGlAccount(),
+                                
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);
+            if (classificationCodeValues.isEmpty()) {
+                populateCreditDebitMaps(loanProductId, feesAmount, 
paymentTypeId, creditAccountType.getValue(),
+                        
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), 
glAccountBalanceHolder);
+            } else {
+                
classificationCodeValues.stream().forEach(classificationCodeValue -> {
+                    ProductToGLAccountMapping mapping = null;
+                    if (classificationCodeValue.getReferenceValueId() != null) 
{
+                        mapping = 
fetchAdvanceAccountingMappingForCodeValue(loanProductId, 
classificationCodeValue.getReferenceValueId(),
+                                
LoanProductAccountingParams.BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS.getValue());
+                    }
+
+                    if (mapping == null) {
+                        populateCreditDebitMaps(loanProductId, 
classificationCodeValue.getAmount(), paymentTypeId,
+                                creditAccountType.getValue(), 
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(),
+                                glAccountBalanceHolder);
+                    } else {
+                        populateCreditDebitMaps(loanProductId, 
classificationCodeValue.getAmount(), paymentTypeId, mapping.getGlAccount(),
+                                
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), 
glAccountBalanceHolder);
+                    }
+                });
+            }
         }
 
         // create credit entries
@@ -800,6 +887,17 @@ public class AccrualBasedAccountingProcessorForLoan 
implements AccountingProcess
         }
     }
 
+    private void populateCreditDebitMaps(Long loanProductId, BigDecimal 
transactionPartAmount, Long paymentTypeId, GLAccount accountCredit,
+            Integer debitAccountType, GLAccountBalanceHolder 
glAccountBalanceHolder) {
+        if (MathUtil.isGreaterThanZero(transactionPartAmount)) {
+            // Resolve Credit
+            glAccountBalanceHolder.addToCredit(accountCredit, 
transactionPartAmount);
+            // Resolve Debit
+            GLAccount accountDebit = 
this.helper.getLinkedGLAccountForLoanProduct(loanProductId, debitAccountType, 
paymentTypeId);
+            glAccountBalanceHolder.addToDebit(accountDebit, 
transactionPartAmount);
+        }
+    }
+
     private void createJournalEntriesForChargeAdjustment(LoanDTO loanDTO, 
LoanTransactionDTO loanTransactionDTO, Office office) {
         final boolean isMarkedAsChargeOff = loanDTO.isMarkedAsChargeOff();
         if (isMarkedAsChargeOff) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java
index 95d3ecc5a8..973a279441 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/service/JournalEntryWritePlatformServiceJpaRepositoryImpl.java
@@ -44,6 +44,7 @@ import 
org.apache.fineract.accounting.glaccount.service.GLAccountReadPlatformSer
 import 
org.apache.fineract.accounting.journalentry.api.JournalEntryJsonInputParams;
 import org.apache.fineract.accounting.journalentry.command.JournalEntryCommand;
 import 
org.apache.fineract.accounting.journalentry.command.SingleDebitOrCreditEntryCommand;
+import org.apache.fineract.accounting.journalentry.data.AdvancedMappingtDTO;
 import org.apache.fineract.accounting.journalentry.data.ClientTransactionDTO;
 import org.apache.fineract.accounting.journalentry.data.LoanDTO;
 import org.apache.fineract.accounting.journalentry.data.SavingsDTO;
@@ -61,6 +62,7 @@ import 
org.apache.fineract.accounting.provisioning.domain.ProvisioningEntry;
 import org.apache.fineract.accounting.rule.domain.AccountingRule;
 import org.apache.fineract.accounting.rule.domain.AccountingRuleRepository;
 import 
org.apache.fineract.accounting.rule.exception.AccountingRuleNotFoundException;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
 import 
org.apache.fineract.infrastructure.configuration.api.GlobalConfigurationConstants;
 import 
org.apache.fineract.infrastructure.configuration.service.ConfigurationReadPlatformService;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
@@ -90,11 +92,14 @@ import 
org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO;
 import 
org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeLoanTransactionDTO;
 import org.apache.fineract.portfolio.loanaccount.data.LoanChargePaidByDTO;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanAmortizationAllocationMapping;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanAmortizationAllocationMappingRepository;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy;
 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.loanproduct.service.LoanEnumerations;
 import org.apache.fineract.portfolio.paymentdetail.domain.PaymentDetail;
 import 
org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
@@ -128,6 +133,8 @@ public class 
JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
     private final ConfigurationReadPlatformService 
configurationReadPlatformService;
     private final AccountingService accountingService;
     private final ExternalAssetOwnerRepository externalAssetOwnerRepository;
+    private final LoanAmortizationAllocationMappingRepository 
loanAmortizationAllocationMappingRepository;
+    private final LoanTransactionRepository loanTransactionRepository;
 
     @Transactional
     @Override
@@ -857,11 +864,39 @@ public class 
JournalEntryWritePlatformServiceJpaRepositoryImpl implements Journa
             }
         }
 
+        List<AdvancedMappingtDTO> buydownFeeAdvancedMappingData = null;
+        List<AdvancedMappingtDTO> capitalizedIncomeAdvancedMappingData = null;
+        if (loanTransaction.isBuyDownFeeAmortization()) {
+            buydownFeeAdvancedMappingData = 
getLoanTransactionClassificationId(loanTransaction);
+        } else if (loanTransaction.isCapitalizedIncomeAmortization()) {
+            capitalizedIncomeAdvancedMappingData = 
getLoanTransactionClassificationId(loanTransaction);
+        }
+
         return new AccountingBridgeDataDTO(loan.getId(), loan.productId(), 
loan.getOfficeId(), currencyCode,
                 loan.getSummary().getTotalInterestCharged(), 
loan.isCashBasedAccountingEnabledOnLoanProduct(),
                 loan.isUpfrontAccrualAccountingEnabledOnLoanProduct(), 
loan.isPeriodicAccrualAccountingEnabledOnLoanProduct(),
                 isAccountTransfer, wasChargedOffAtTransactionTime, 
loan.isFraud(), loan.fetchChargeOffReasonId(), loan.isClosedWrittenOff(),
-                transactions, 
loan.getLoanProductRelatedDetail().isMerchantBuyDownFee());
+                transactions, 
loan.getLoanProductRelatedDetail().isMerchantBuyDownFee(), 
buydownFeeAdvancedMappingData,
+                capitalizedIncomeAdvancedMappingData);
+    }
+
+    private List<AdvancedMappingtDTO> getLoanTransactionClassificationId(final 
LoanTransaction loanTransaction) {
+        List<AdvancedMappingtDTO> advancedMappingData = new 
ArrayList<AdvancedMappingtDTO>();
+        if (loanTransaction.isCapitalizedIncomeAmortization() || 
loanTransaction.isBuyDownFeeAmortization()) {
+            final List<LoanAmortizationAllocationMapping> 
loanTransactionAllocations = loanAmortizationAllocationMappingRepository
+                    
.fetchLoanTransactionAllocationByAmortizationLoanTransactionId(loanTransaction.getId(),
+                            loanTransaction.getLoan().getId());
+            
loanTransactionAllocations.stream().forEach(loanTransactionAllocation -> {
+                final CodeValue classification = loanTransactionRepository
+                        
.fetchClassificationCodeValueByTransactionId(loanTransactionAllocation.getBaseLoanTransactionId());
+                if (classification != null) {
+                    advancedMappingData.add(new 
AdvancedMappingtDTO(classification.getId(), 
loanTransactionAllocation.getAmount()));
+                } else {
+                    advancedMappingData.add(new AdvancedMappingtDTO(null, 
loanTransactionAllocation.getAmount()));
+                }
+            });
+        }
+        return advancedMappingData;
     }
 
     /**
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/starter/AccountingJournalEntryConfiguration.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/starter/AccountingJournalEntryConfiguration.java
index 397e486cef..57c1a1f2a0 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/starter/AccountingJournalEntryConfiguration.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/starter/AccountingJournalEntryConfiguration.java
@@ -49,6 +49,8 @@ import 
org.apache.fineract.organisation.office.domain.OfficeRepositoryWrapper;
 import 
org.apache.fineract.organisation.office.service.OfficeReadPlatformService;
 import 
org.apache.fineract.portfolio.account.service.AccountTransfersReadPlatformService;
 import org.apache.fineract.portfolio.charge.domain.ChargeRepositoryWrapper;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanAmortizationAllocationMappingRepository;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
 import 
org.apache.fineract.portfolio.paymentdetail.service.PaymentDetailWritePlatformService;
 import 
org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.context.annotation.Bean;
@@ -94,12 +96,14 @@ public class AccountingJournalEntryConfiguration {
             FinancialActivityAccountRepositoryWrapper 
financialActivityAccountRepositoryWrapper,
             CashBasedAccountingProcessorForClientTransactions 
accountingProcessorForClientTransactions,
             ConfigurationReadPlatformService configurationReadPlatformService, 
AccountingService accountingService,
-            ExternalAssetOwnerRepository externalAssetOwnerRepository) {
+            ExternalAssetOwnerRepository externalAssetOwnerRepository,
+            LoanAmortizationAllocationMappingRepository 
loanAmortizationAllocationMappingRepository,
+            LoanTransactionRepository loanTransactionRepository) {
         return new 
JournalEntryWritePlatformServiceJpaRepositoryImpl(glClosureRepository, 
glAccountRepository, glJournalEntryRepository,
                 officeRepositoryWrapper, accountingProcessorForLoanFactory, 
accountingProcessorForSavingsFactory,
                 accountingProcessorForSharesFactory, helper, 
fromApiJsonDeserializer, accountingRuleRepository,
                 glAccountReadPlatformService, organisationCurrencyRepository, 
context, paymentDetailWritePlatformService,
                 financialActivityAccountRepositoryWrapper, 
accountingProcessorForClientTransactions, configurationReadPlatformService,
-                accountingService, externalAssetOwnerRepository);
+                accountingService, externalAssetOwnerRepository, 
loanAmortizationAllocationMappingRepository, loanTransactionRepository);
     }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java
index bb3bc9a578..8ed70663e7 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/accounting/productaccountmapping/service/ProductToGLAccountMappingWritePlatformServiceImpl.java
@@ -140,6 +140,10 @@ public class 
ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
                 
this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command,
 element, loanProductId, null);
                 
this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command,
 element, loanProductId, null);
                 
this.loanProductToGLAccountMappingHelper.saveChargeOffReasonToExpenseAccountMappings(command,
 element, loanProductId, null);
+                
this.loanProductToGLAccountMappingHelper.saveBuyDownFeeClassificationToIncomeAccountMappings(command,
 element,
+                        loanProductId, null);
+                
this.loanProductToGLAccountMappingHelper.saveCapitalizedIncomeClassificationToIncomeAccountMappings(command,
 element,
+                        loanProductId, null);
             break;
             case ACCRUAL_UPFRONT:
                 // Fall Through
@@ -233,6 +237,10 @@ public class 
ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
                 
this.loanProductToGLAccountMappingHelper.savePaymentChannelToFundSourceMappings(command,
 element, loanProductId, null);
                 
this.loanProductToGLAccountMappingHelper.saveChargesToIncomeAccountMappings(command,
 element, loanProductId, null);
                 
this.loanProductToGLAccountMappingHelper.saveChargeOffReasonToExpenseAccountMappings(command,
 element, loanProductId, null);
+                
this.loanProductToGLAccountMappingHelper.saveBuyDownFeeClassificationToIncomeAccountMappings(command,
 element,
+                        loanProductId, null);
+                
this.loanProductToGLAccountMappingHelper.saveCapitalizedIncomeClassificationToIncomeAccountMappings(command,
 element,
+                        loanProductId, null);
             break;
         }
     }
@@ -411,6 +419,10 @@ public class 
ProductToGLAccountMappingWritePlatformServiceImpl implements Produc
             
this.loanProductToGLAccountMappingHelper.updateChargesToIncomeAccountMappings(command,
 element, loanProductId, changes);
             
this.loanProductToGLAccountMappingHelper.updateChargeOffReasonToExpenseAccountMappings(command,
 element, loanProductId,
                     changes);
+            
this.loanProductToGLAccountMappingHelper.updateBuyDownFeeClassificationToIncomeAccountMappings(command,
 element, loanProductId,
+                    changes);
+            
this.loanProductToGLAccountMappingHelper.updateCapitalizedIncomeClassificationToIncomeAccountMappings(command,
 element,
+                    loanProductId, changes);
         }
         return changes;
     }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
index 786a1fa7b6..6c981ba69a 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/api/LoanProductsApiResource.java
@@ -46,10 +46,12 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 import lombok.RequiredArgsConstructor;
+import 
org.apache.fineract.accounting.common.AccountingConstants.LoanProductAccountingParams;
 import 
org.apache.fineract.accounting.common.AccountingDropdownReadPlatformService;
 import org.apache.fineract.accounting.glaccount.data.GLAccountData;
 import 
org.apache.fineract.accounting.producttoaccountmapping.data.ChargeOffReasonToGLAccountMapper;
 import 
org.apache.fineract.accounting.producttoaccountmapping.data.ChargeToGLAccountMapper;
+import 
org.apache.fineract.accounting.producttoaccountmapping.data.ClassificationToGLAccountData;
 import 
org.apache.fineract.accounting.producttoaccountmapping.data.PaymentTypeToGLAccountMapper;
 import 
org.apache.fineract.accounting.producttoaccountmapping.service.ProductToGLAccountMappingReadPlatformService;
 import org.apache.fineract.commands.domain.CommandWrapper;
@@ -82,6 +84,7 @@ import 
org.apache.fineract.portfolio.floatingrates.service.FloatingRatesReadPlat
 import org.apache.fineract.portfolio.fund.data.FundData;
 import org.apache.fineract.portfolio.fund.service.FundReadPlatformService;
 import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import 
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
@@ -352,6 +355,8 @@ public class LoanProductsApiResource {
         Collection<ChargeToGLAccountMapper> feeToGLAccountMappings;
         Collection<ChargeToGLAccountMapper> penaltyToGLAccountMappings;
         List<ChargeOffReasonToGLAccountMapper> 
chargeOffReasonToGLAccountMappings;
+        List<ClassificationToGLAccountData> 
capitalizedIncomeClassificationToGLAccountMappings;
+        List<ClassificationToGLAccountData> 
buydowFeeClassificationToGLAccountMappings;
         if (loanProduct.hasAccountingEnabled()) {
             accountingMappings = 
this.accountMappingReadPlatformService.fetchAccountMappingDetailsForLoanProduct(productId,
                     loanProduct.getAccountingRule().getId().intValue());
@@ -362,8 +367,14 @@ public class LoanProductsApiResource {
                     
.fetchPenaltyToIncomeAccountMappingsForLoanProduct(productId);
             chargeOffReasonToGLAccountMappings = 
this.accountMappingReadPlatformService
                     .fetchChargeOffReasonMappingsForLoanProduct(productId);
+            capitalizedIncomeClassificationToGLAccountMappings = 
accountMappingReadPlatformService
+                    .fetchClassificationMappingsForLoanProduct(productId,
+                            
LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
+            buydowFeeClassificationToGLAccountMappings = 
accountMappingReadPlatformService.fetchClassificationMappingsForLoanProduct(
+                    productId, 
LoanProductAccountingParams.BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
             loanProduct = LoanProductData.withAccountingDetails(loanProduct, 
accountingMappings, paymentChannelToFundSourceMappings,
-                    feeToGLAccountMappings, penaltyToGLAccountMappings, 
chargeOffReasonToGLAccountMappings);
+                    feeToGLAccountMappings, penaltyToGLAccountMappings, 
chargeOffReasonToGLAccountMappings,
+                    capitalizedIncomeClassificationToGLAccountMappings, 
buydowFeeClassificationToGLAccountMappings);
         }
 
         if (settings.isTemplate()) {
@@ -464,6 +475,10 @@ public class LoanProductsApiResource {
                 
.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeStrategy.class);
         final List<StringEnumOptionData> buyDownFeeIncomeTypeOptions = 
ApiFacingEnum
                 
.getValuesAsStringEnumOptionDataList(LoanBuyDownFeeIncomeType.class);
+        final List<CodeValueData> capitalizedIncomeClassificationOptions = 
codeValueReadPlatformService
+                
.retrieveCodeValuesByCode(LoanTransactionApiConstants.CAPITALIZED_INCOME_CLASSIFICATION_CODE);
+        final List<CodeValueData> buydownFeeClassificationOptions = 
codeValueReadPlatformService
+                
.retrieveCodeValuesByCode(LoanTransactionApiConstants.BUY_DOWN_FEE_CLASSIFICATION_CODE);
 
         return new LoanProductData(productData, chargeOptions, penaltyOptions, 
paymentTypeOptions, currencyOptions, amortizationTypeOptions,
                 interestTypeOptions, interestCalculationPeriodTypeOptions, 
repaymentFrequencyTypeOptions, interestRateFrequencyTypeOptions,
@@ -477,7 +492,8 @@ public class LoanProductsApiResource {
                 LoanScheduleProcessingType.getValuesAsEnumOptionDataList(), 
creditAllocationTransactionTypes,
                 creditAllocationAllocationTypes, 
supportedInterestRefundTypesOptions, chargeOffBehaviourOptions, 
chargeOffReasonOptions,
                 daysInYearCustomStrategyOptions, 
capitalizedIncomeCalculationTypeOptions, capitalizedIncomeStrategyOptions,
-                capitalizedIncomeTypeOptions, 
buyDownFeeCalculationTypeOptions, buyDownFeeStrategyOptions, 
buyDownFeeIncomeTypeOptions);
+                capitalizedIncomeTypeOptions, 
buyDownFeeCalculationTypeOptions, buyDownFeeStrategyOptions, 
buyDownFeeIncomeTypeOptions,
+                capitalizedIncomeClassificationOptions, 
buydownFeeClassificationOptions);
     }
 
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
index 3371368a78..cd99af21d7 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanproduct/serialization/LoanProductDataValidator.java
@@ -49,6 +49,7 @@ import 
org.apache.fineract.infrastructure.core.service.MathUtil;
 import org.apache.fineract.portfolio.calendar.service.CalendarUtils;
 import org.apache.fineract.portfolio.common.domain.PeriodFrequencyType;
 import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import 
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeCalculationType;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeIncomeType;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeStrategy;
@@ -202,7 +203,10 @@ public final class LoanProductDataValidator {
             LoanProductConstants.ENABLE_BUY_DOWN_FEE_PARAM_NAME, 
LoanProductConstants.BUY_DOWN_FEE_CALCULATION_TYPE_PARAM_NAME,
             LoanProductConstants.BUY_DOWN_FEE_STRATEGY_PARAM_NAME, 
LoanProductConstants.BUY_DOWN_FEE_INCOME_TYPE_PARAM_NAME,
             LoanProductAccountingParams.BUY_DOWN_EXPENSE.getValue(), 
LoanProductAccountingParams.INCOME_FROM_BUY_DOWN.getValue(),
-            LoanProductConstants.MERCHANT_BUY_DOWN_FEE_PARAM_NAME));
+            LoanProductConstants.MERCHANT_BUY_DOWN_FEE_PARAM_NAME,
+            
LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS.getValue(),
 //
+            
LoanProductAccountingParams.BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS.getValue()
 //
+    ));
 
     private static final String[] SUPPORTED_LOAN_CONFIGURABLE_ATTRIBUTES = { 
LoanProductConstants.amortizationTypeParamName,
             LoanProductConstants.interestTypeParamName, 
LoanProductConstants.transactionProcessingStrategyCodeParamName,
@@ -744,7 +748,10 @@ public final class LoanProductDataValidator {
             validatePaymentChannelFundSourceMappings(baseDataValidator, 
element);
             validateChargeToIncomeAccountMappings(baseDataValidator, element);
             validateChargeOffToExpenseMappings(baseDataValidator, element);
-
+            validateClassificationToIncomeMappings(baseDataValidator, element,
+                    
LoanProductAccountingParams.BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
+            validateClassificationToIncomeMappings(baseDataValidator, element,
+                    
LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
         }
 
         if 
(AccountingValidations.isAccrualBasedAccounting(accountingRuleType)) {
@@ -1877,6 +1884,10 @@ public final class LoanProductDataValidator {
         validatePaymentChannelFundSourceMappings(baseDataValidator, element);
         validateChargeToIncomeAccountMappings(baseDataValidator, element);
         validateChargeOffToExpenseMappings(baseDataValidator, element);
+        validateClassificationToIncomeMappings(baseDataValidator, element,
+                
LoanProductAccountingParams.BUYDOWN_FEE_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
+        validateClassificationToIncomeMappings(baseDataValidator, element,
+                
LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS);
 
         validateMinMaxConstraints(element, baseDataValidator, loanProduct);
 
@@ -2109,6 +2120,65 @@ public final class LoanProductDataValidator {
         }
     }
 
+    private void validateClassificationToIncomeMappings(final 
DataValidatorBuilder baseDataValidator, final JsonElement element,
+            final LoanProductAccountingParams classificationParameter) {
+        String parameterName = classificationParameter.getValue();
+
+        if (this.fromApiJsonHelper.parameterExists(parameterName, element)) {
+            final JsonArray classificationToIncomeMappingArray = 
this.fromApiJsonHelper.extractJsonArrayNamed(parameterName, element);
+            if (classificationToIncomeMappingArray != null && 
classificationToIncomeMappingArray.size() > 0) {
+                Map<Long, Set<Long>> classificationToAccounts = new 
HashMap<>();
+                List<JsonObject> processedMappings = new ArrayList<>(); // 
Collect processed mappings for the new method
+
+                int i = 0;
+                do {
+                    final JsonObject jsonObject = 
classificationToIncomeMappingArray.get(i).getAsJsonObject();
+                    final Long incomeGlAccountId = this.fromApiJsonHelper
+                            
.extractLongNamed(LoanProductAccountingParams.INCOME_ACCOUNT_ID.getValue(), 
jsonObject);
+                    final Long classificationCodeValueId = 
this.fromApiJsonHelper
+                            
.extractLongNamed(LoanProductAccountingParams.CLASSIFICATION_CODE_VALUE_ID.getValue(),
 jsonObject);
+
+                    // Validate parameters locally
+                    baseDataValidator.reset()
+                            .parameter(parameterName + OPENING_SQUARE_BRACKET 
+ i + CLOSING_SQUARE_BRACKET + DOT
+                                    + 
LoanProductAccountingParams.INCOME_ACCOUNT_ID.getValue())
+                            
.value(incomeGlAccountId).notNull().integerGreaterThanZero();
+                    baseDataValidator.reset()
+                            .parameter(parameterName + OPENING_SQUARE_BRACKET 
+ i + CLOSING_SQUARE_BRACKET + DOT
+                                    + 
LoanProductAccountingParams.CLASSIFICATION_CODE_VALUE_ID.getValue())
+                            
.value(classificationCodeValueId).notNull().integerGreaterThanZero();
+
+                    // Handle duplicate classification and GL Account 
validation
+                    
classificationToAccounts.putIfAbsent(classificationCodeValueId, new 
HashSet<>());
+                    Set<Long> associatedAccounts = 
classificationToAccounts.get(classificationCodeValueId);
+
+                    if (associatedAccounts.contains(incomeGlAccountId)) {
+                        baseDataValidator.reset().parameter(parameterName + 
OPENING_SQUARE_BRACKET + i + CLOSING_SQUARE_BRACKET)
+                                
.failWithCode("duplicate.classification.and.glAccount");
+                    }
+                    associatedAccounts.add(incomeGlAccountId);
+
+                    if (associatedAccounts.size() > 1) {
+                        baseDataValidator.reset().parameter(parameterName + 
OPENING_SQUARE_BRACKET + i + CLOSING_SQUARE_BRACKET)
+                                
.failWithCode("multiple.glAccounts.for.classification");
+                    }
+
+                    // Collect mapping for additional validations
+                    processedMappings.add(jsonObject);
+
+                    i++;
+                } while (i < classificationToIncomeMappingArray.size());
+
+                // Call the new validation method for additional checks
+                final String dataCodeName = classificationParameter
+                        
.equals(LoanProductAccountingParams.CAPITALIZED_INCOME_CLASSIFICATION_TO_INCOME_ACCOUNT_MAPPINGS)
+                                ? 
LoanTransactionApiConstants.CAPITALIZED_INCOME_CLASSIFICATION_CODE
+                                : 
LoanTransactionApiConstants.BUY_DOWN_FEE_CLASSIFICATION_CODE;
+                
productToGLAccountMappingHelper.validateClassificationMappingsInDatabase(processedMappings,
 dataCodeName);
+            }
+        }
+    }
+
     public void validateMinMaxConstraints(final JsonElement element, final 
DataValidatorBuilder baseDataValidator,
             final LoanProduct loanProduct) {
         validatePrincipalMinMaxConstraint(element, loanProduct, 
baseDataValidator);
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 2cd6e8dea7..7b12aaab7d 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
@@ -216,4 +216,5 @@
     <include file="parts/0195_create_loan_amortization_allocation_mapping.xml" 
relativeToChangelogFile="true" />
     <include 
file="parts/0196_add_deleted_and_closed_to_buy_down_fee_balance.xml" 
relativeToChangelogFile="true" />
     <include 
file="parts/0197_add_deleted_and_closed_to_capitalized_income_balance.xml" 
relativeToChangelogFile="true" />
+    <include 
file="parts/0198_add_classification_id_to_acc_product_mapping.xml" 
relativeToChangelogFile="true" />
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0198_add_classification_id_to_acc_product_mapping.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0198_add_classification_id_to_acc_product_mapping.xml
new file mode 100644
index 0000000000..8eb87510c8
--- /dev/null
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0198_add_classification_id_to_acc_product_mapping.xml
@@ -0,0 +1,52 @@
+<?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">
+        <addColumn tableName="acc_product_mapping">
+            <column name="capitalized_income_classification_id" type="INT" 
defaultValueNumeric="NULL">
+                <constraints nullable="true"/>
+            </column>
+        </addColumn>
+        <addForeignKeyConstraint
+                baseTableName="acc_product_mapping"
+                baseColumnNames="capitalized_income_classification_id"
+                referencedTableName="m_code_value"
+                referencedColumnNames="id"
+                
constraintName="fk_acc_product_mapping_capitalized_income_classification"/>
+    </changeSet>
+    <changeSet id="2" author="fineract">
+        <addColumn tableName="acc_product_mapping">
+            <column name="buydown_fee_classification_id" type="INT" 
defaultValueNumeric="NULL">
+                <constraints nullable="true"/>
+            </column>
+        </addColumn>
+        <addForeignKeyConstraint
+                baseTableName="acc_product_mapping"
+                baseColumnNames="buydown_fee_classification_id"
+                referencedTableName="m_code_value"
+                referencedColumnNames="id"
+                
constraintName="fk_acc_product_mapping_buydown_fee_classification"/>
+    </changeSet>
+</databaseChangeLog>
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/accounting/journalentry/CreateJournalEntriesForChargeOffLoanTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/accounting/journalentry/CreateJournalEntriesForChargeOffLoanTest.java
index 52a3331f62..05faf8f603 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/accounting/journalentry/CreateJournalEntriesForChargeOffLoanTest.java
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/accounting/journalentry/CreateJournalEntriesForChargeOffLoanTest.java
@@ -73,7 +73,7 @@ class CreateJournalEntriesForChargeOffLoanTest {
                 Collections.emptyList(), false, "", null, null, null, null);
 
         loanDTO = new LoanDTO(1L, 1L, 1L, "USD", false, true, true, 
List.of(loanTransactionDTO), false, false, chargeOffReasonId, false,
-                false);
+                false, null, null);
     }
 
     @Test
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 e36fb78060..92ab6434f2 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
@@ -28,28 +28,38 @@ import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.time.format.DateTimeFormatter;
 import java.util.List;
+import java.util.Objects;
 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.BuyDownFeeAmortizationDetails;
+import org.apache.fineract.client.models.GetCodesResponse;
+import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
+import 
org.apache.fineract.client.models.PostClassificationToIncomeAccountMappings;
 import org.apache.fineract.client.models.PostClientsResponse;
+import org.apache.fineract.client.models.PostCodeValueDataResponse;
+import org.apache.fineract.client.models.PostCodeValuesDataRequest;
 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.client.models.PutLoanProductsProductIdRequest;
 import org.apache.fineract.client.util.CallFailedRuntimeException;
 import org.apache.fineract.integrationtests.common.BusinessStepHelper;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
 import 
org.apache.fineract.integrationtests.common.externalevents.BusinessEvent;
 import 
org.apache.fineract.integrationtests.common.externalevents.ExternalEventHelper;
 import 
org.apache.fineract.integrationtests.common.externalevents.LoanAdjustTransactionBusinessEvent;
 import 
org.apache.fineract.integrationtests.common.externalevents.LoanTransactionMinimalBusinessEvent;
+import 
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
 import org.junit.jupiter.api.AfterAll;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeEach;
@@ -88,7 +98,7 @@ public class LoanBuyDownFeeTest extends 
BaseLoanIntegrationTest {
         runAt("01 September 2024", () -> {
             clientId = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
             final PostLoanProductsResponse loanProductsResponse = 
loanProductHelper
-                    
.createLoanProduct(createProgressiveLoanProductWithBuyDownFee());
+                    
.createLoanProduct(createProgressiveLoanProductWithBuyDownFee(null));
 
             // Apply for the loan with proper progressive loan settings
             PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(clientId,
@@ -240,14 +250,16 @@ public class LoanBuyDownFeeTest extends 
BaseLoanIntegrationTest {
     /**
      * Creates a progressive loan product with buy down fee enabled
      */
-    private PostLoanProductsRequest 
createProgressiveLoanProductWithBuyDownFee() {
+    private PostLoanProductsRequest createProgressiveLoanProductWithBuyDownFee(
+            PostClassificationToIncomeAccountMappings 
buydownFeeClassificationAccountMappings) {
         // Create a progressive loan product with accrual-based accounting and 
proper GL mappings
-        return new 
PostLoanProductsRequest().name(Utils.uniqueRandomStringGenerator("BUY_DOWN_FEE_PROGRESSIVE_",
 6))
-                .shortName(Utils.uniqueRandomStringGenerator("", 
4)).description("Progressive loan product with buy down fee enabled")
-                
.includeInBorrowerCycle(false).useBorrowerCycle(false).currencyCode("USD").digitsAfterDecimal(2).principal(1000.0)
-                
.minPrincipal(100.0).maxPrincipal(10000.0).numberOfRepayments(12).minNumberOfRepayments(6).maxNumberOfRepayments(24)
-                
.repaymentEvery(1).repaymentFrequencyType(RepaymentFrequencyType.MONTHS_L).interestRatePerPeriod(10.0)
-                
.minInterestRatePerPeriod(0.0).maxInterestRatePerPeriod(120.0).interestRateFrequencyType(InterestRateFrequencyType.YEARS)
+        PostLoanProductsRequest postLoanProductsRequest = new 
PostLoanProductsRequest()
+                
.name(Utils.uniqueRandomStringGenerator("BUY_DOWN_FEE_PROGRESSIVE_", 
6)).shortName(Utils.uniqueRandomStringGenerator("", 4))
+                .description("Progressive loan product with buy down fee 
enabled").includeInBorrowerCycle(false).useBorrowerCycle(false)
+                
.currencyCode("USD").digitsAfterDecimal(2).principal(1000.0).minPrincipal(100.0).maxPrincipal(10000.0)
+                
.numberOfRepayments(12).minNumberOfRepayments(6).maxNumberOfRepayments(24).repaymentEvery(1)
+                
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS_L).interestRatePerPeriod(10.0).minInterestRatePerPeriod(0.0)
+                
.maxInterestRatePerPeriod(120.0).interestRateFrequencyType(InterestRateFrequencyType.YEARS)
                 
.amortizationType(AmortizationType.EQUAL_INSTALLMENTS).interestType(InterestType.DECLINING_BALANCE)
                 
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY).allowPartialPeriodInterestCalcualtion(false)
                 
.transactionProcessingStrategyCode("advanced-payment-allocation-strategy")
@@ -275,6 +287,11 @@ public class LoanBuyDownFeeTest extends 
BaseLoanIntegrationTest {
                 
.buyDownFeeCalculationType(PostLoanProductsRequest.BuyDownFeeCalculationTypeEnum.FLAT)
                 
.buyDownFeeStrategy(PostLoanProductsRequest.BuyDownFeeStrategyEnum.EQUAL_AMORTIZATION)
                 
.buyDownFeeIncomeType(PostLoanProductsRequest.BuyDownFeeIncomeTypeEnum.FEE).locale("en").dateFormat("dd
 MMMM yyyy");
+
+        if (buydownFeeClassificationAccountMappings != null) {
+            
postLoanProductsRequest.addBuydownfeeClassificationToIncomeAccountMappingsItem(buydownFeeClassificationAccountMappings);
+        }
+        return postLoanProductsRequest;
     }
 
     @Test
@@ -485,6 +502,15 @@ public class LoanBuyDownFeeTest extends 
BaseLoanIntegrationTest {
         return response.getResourceId();
     }
 
+    private Long addBuyDownFeeForLoan(Long loanId, Double amount, String date, 
Long classificationId) {
+        String buyDownFeeExternalId = UUID.randomUUID().toString();
+        PostLoansLoanIdTransactionsResponse response = 
loanTransactionHelper.makeLoanBuyDownFee(loanId,
+                new 
PostLoansLoanIdTransactionsRequest().dateFormat(DATETIME_PATTERN).transactionDate(date).locale("en")
+                        
.transactionAmount(amount).externalId(buyDownFeeExternalId).note("Buy Down Fee 
Transaction")
+                        .classificationId(classificationId));
+        return response.getResourceId();
+    }
+
     @Test
     public void testBuyDownFeeDailyAmortization() {
         final AtomicReference<Long> loanIdRef = new AtomicReference<>();
@@ -645,7 +671,7 @@ public class LoanBuyDownFeeTest extends 
BaseLoanIntegrationTest {
     @Test
     public void testRetrieveBuyDownFeeAmortizationDetails() {
         final PostClientsResponse client = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
-        final PostLoanProductsResponse loanProduct = 
loanProductHelper.createLoanProduct(createProgressiveLoanProductWithBuyDownFee());
+        final PostLoanProductsResponse loanProduct = 
loanProductHelper.createLoanProduct(createProgressiveLoanProductWithBuyDownFee(null));
 
         final long loanId = 
applyAndApproveProgressiveLoan(client.getClientId(), 
loanProduct.getResourceId(), "1 February 2024", 1000.0,
                 7.0, 6, null);
@@ -677,7 +703,7 @@ public class LoanBuyDownFeeTest extends 
BaseLoanIntegrationTest {
     public void testRetrieveBuyDownFeeAmortizationDetails_notEnabled() {
         final PostClientsResponse client = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
         final PostLoanProductsResponse loanProduct = loanProductHelper
-                
.createLoanProduct(createProgressiveLoanProductWithBuyDownFee().enableBuyDownFee(false));
+                
.createLoanProduct(createProgressiveLoanProductWithBuyDownFee(null).enableBuyDownFee(false));
 
         final long loanId = 
applyAndApproveProgressiveLoan(client.getClientId(), 
loanProduct.getResourceId(), "1 February 2024", 1000.0,
                 7.0, 6, null);
@@ -872,7 +898,7 @@ public class LoanBuyDownFeeTest extends 
BaseLoanIntegrationTest {
             // Add initial buy down fee
 
             final PostLoanProductsResponse loanProductsResponse = 
loanProductHelper
-                    
.createLoanProduct(createProgressiveLoanProductWithBuyDownFee().merchantBuyDownFee(false));
+                    
.createLoanProduct(createProgressiveLoanProductWithBuyDownFee(null).merchantBuyDownFee(false));
 
             // Apply for the loan with proper progressive loan settings
             PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(clientId,
@@ -917,4 +943,90 @@ public class LoanBuyDownFeeTest extends 
BaseLoanIntegrationTest {
         });
     }
 
+    @Test
+    public void testBuyDownFeeWithAdvanceAccountingMappings() {
+        final AtomicReference<Long> loanIdRef = new AtomicReference<>();
+        final AtomicReference<Long> classificationIdRef = new 
AtomicReference<>();
+        final AtomicReference<Account> classificationIncomeAccountRef = new 
AtomicReference<>();
+        runAt("10 September 2024", () -> {
+            deleteAllExternalEvents();
+
+            final AccountHelper accountHelper = new 
AccountHelper(this.requestSpec, this.responseSpec);
+            final Account classificationIncomeAccount = accountHelper
+                    
.createIncomeAccount(Utils.uniqueRandomStringGenerator("buydownfee_class_income_",
 6));
+            classificationIncomeAccountRef.set(classificationIncomeAccount);
+
+            final GetCodesResponse code = 
codeHelper.retrieveCodeByName(LoanTransactionApiConstants.BUY_DOWN_FEE_CLASSIFICATION_CODE);
+            final PostCodeValueDataResponse classificationCode = 
codeHelper.createCodeValue(code.getId(),
+                    new 
PostCodeValuesDataRequest().name(Utils.uniqueRandomStringGenerator("CLASS_", 
6)).isActive(true).position(10));
+            classificationIdRef.set(classificationCode.getSubResourceId());
+
+            // Loan Product create
+            final PostClassificationToIncomeAccountMappings 
classificationToIncomeMapping = new PostClassificationToIncomeAccountMappings()
+                    .classificationCodeValueId(classificationIdRef.get())
+                    
.incomeAccountId(classificationIncomeAccount.getAccountID().longValue());
+            final PostLoanProductsResponse loanProductsResponse = 
loanProductHelper
+                    
.createLoanProduct(createProgressiveLoanProductWithBuyDownFee(classificationToIncomeMapping));
+
+            GetLoanProductsProductIdResponse getLoanProductResponse = 
loanProductHelper
+                    
.retrieveLoanProductById(loanProductsResponse.getResourceId());
+            assertNotNull(getLoanProductResponse);
+            
assertNotNull(getLoanProductResponse.getBuydownFeeClassificationToIncomeAccountMappings());
+            Assertions.assertEquals(1, 
getLoanProductResponse.getBuydownFeeClassificationToIncomeAccountMappings().size());
+            Assertions.assertEquals(classificationIdRef.get(), 
getLoanProductResponse.getBuydownFeeClassificationToIncomeAccountMappings()
+                    .get(0).getClassificationCodeValue().getId());
+
+            final PostCodeValueDataResponse secClassificationCode = 
codeHelper.createCodeValue(code.getId(),
+                    new 
PostCodeValuesDataRequest().name(Utils.uniqueRandomStringGenerator("CLASS_", 
6)).isActive(true).position(10));
+            classificationIdRef.set(secClassificationCode.getSubResourceId());
+
+            // Loan Product update
+            final PutLoanProductsProductIdRequest putLoanProductRequest = new 
PutLoanProductsProductIdRequest();
+            
putLoanProductRequest.addBuydownfeeClassificationToIncomeAccountMappingsItem(
+                    new 
PostClassificationToIncomeAccountMappings().classificationCodeValueId(classificationIdRef.get())
+                            
.incomeAccountId(classificationIncomeAccount.getAccountID().longValue()));
+
+            
loanProductHelper.updateLoanProductById(loanProductsResponse.getResourceId(), 
putLoanProductRequest);
+            getLoanProductResponse = 
loanProductHelper.retrieveLoanProductById(loanProductsResponse.getResourceId());
+            assertNotNull(getLoanProductResponse);
+            
assertNotNull(getLoanProductResponse.getBuydownFeeClassificationToIncomeAccountMappings());
+            Assertions.assertEquals(1, 
getLoanProductResponse.getBuydownFeeClassificationToIncomeAccountMappings().size());
+            Assertions.assertEquals(classificationIdRef.get(), 
getLoanProductResponse.getBuydownFeeClassificationToIncomeAccountMappings()
+                    .get(0).getClassificationCodeValue().getId());
+
+            PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(clientId,
+                    loanProductsResponse.getResourceId(), "10 September 2024", 
1000.0, 10.0, 12, null));
+            loanId = postLoansResponse.getLoanId();
+            loanIdRef.set(loanId);
+            loanTransactionHelper.approveLoan(loanId, 
approveLoanRequest(1000.0, "10 September 2024"));
+            disburseLoan(loanId, BigDecimal.valueOf(1000.0), "10 September 
2024");
+
+            Long buyDownFeeTransactionId = addBuyDownFeeForLoan(loanId, 400.0, 
"10 September 2024", classificationIdRef.get());
+            assertNotNull(buyDownFeeTransactionId);
+        });
+
+        runAt("20 September 2024", () -> {
+            Long loanId = loanIdRef.get();
+            deleteAllExternalEvents();
+            executeInlineCOB(loanId);
+
+            Long buyDownFeeTransactionId = addBuyDownFeeForLoan(loanId, 50.0, 
"20 September 2024");
+            assertNotNull(buyDownFeeTransactionId);
+        });
+
+        runAt("30 September 2024", () -> {
+            Long loanId = loanIdRef.get();
+            deleteAllExternalEvents();
+            executeInlineCOB(loanId);
+
+            final GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanId);
+            final Optional<GetLoansLoanIdTransactions> optTx = 
loanDetails.getTransactions().stream()
+                    .filter(item -> 
Objects.equals(Utils.getDoubleValue(item.getAmount()), 1.23)
+                            && Objects.equals(item.getType().getValue(), "Buy 
Down Fee Amortization"))
+                    .findFirst();
+            verifyTRJournalEntries(optTx.get().getId(), 
debit(deferredIncomeLiabilityAccount, 1.23),
+                    credit(classificationIncomeAccountRef.get(), 1.09), // 
First BuyDown Fee With classification
+                    credit(feeIncomeAccount, 0.14)); // Second BuyDown Fee 
Without classification
+        });
+    }
 }
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java
index e0e1ef1ee0..01b16f443e 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanCapitalizedIncomeTest.java
@@ -26,14 +26,17 @@ import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.time.LocalDate;
 import java.util.List;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.concurrent.atomic.AtomicReference;
 import org.apache.fineract.client.models.CapitalizedIncomeDetails;
 import org.apache.fineract.client.models.GetCodesResponse;
+import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdTransactions;
 import 
org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
 import org.apache.fineract.client.models.LoanCapitalizedIncomeData;
+import 
org.apache.fineract.client.models.PostClassificationToIncomeAccountMappings;
 import org.apache.fineract.client.models.PostClientsResponse;
 import org.apache.fineract.client.models.PostCodeValueDataResponse;
 import org.apache.fineract.client.models.PostCodeValuesDataRequest;
@@ -42,10 +45,13 @@ 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.PostLoansResponse;
+import org.apache.fineract.client.models.PutLoanProductsProductIdRequest;
 import org.apache.fineract.client.util.CallFailedRuntimeException;
 import org.apache.fineract.integrationtests.common.BusinessStepHelper;
 import org.apache.fineract.integrationtests.common.ClientHelper;
 import org.apache.fineract.integrationtests.common.Utils;
+import org.apache.fineract.integrationtests.common.accounting.Account;
+import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
 import 
org.apache.fineract.integrationtests.common.externalevents.LoanAdjustTransactionBusinessEvent;
 import 
org.apache.fineract.integrationtests.common.externalevents.LoanBusinessEvent;
 import 
org.apache.fineract.integrationtests.common.externalevents.LoanTransactionBusinessEvent;
@@ -1179,4 +1185,103 @@ public class LoanCapitalizedIncomeTest extends 
BaseLoanIntegrationTest {
             );
         });
     }
+
+    @Test
+    public void testCapitalizedIncomeWithAdvanceAccountingMappings() {
+        final AtomicReference<Long> loanIdRef = new AtomicReference<>();
+        final AtomicReference<Long> classificationIdRef = new 
AtomicReference<>();
+        final AtomicReference<Account> classificationIncomeAccountRef = new 
AtomicReference<>();
+        runAt("10 September 2024", () -> {
+            deleteAllExternalEvents();
+            final PostClientsResponse client = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+
+            final AccountHelper accountHelper = new 
AccountHelper(this.requestSpec, this.responseSpec);
+            final Account classificationIncomeAccount = accountHelper
+                    
.createIncomeAccount(Utils.uniqueRandomStringGenerator("capitalizedincome_class_income_",
 6));
+            classificationIncomeAccountRef.set(classificationIncomeAccount);
+
+            final GetCodesResponse code = 
codeHelper.retrieveCodeByName(LoanTransactionApiConstants.CAPITALIZED_INCOME_CLASSIFICATION_CODE);
+            final PostCodeValueDataResponse classificationCode = 
codeHelper.createCodeValue(code.getId(),
+                    new 
PostCodeValuesDataRequest().name(Utils.uniqueRandomStringGenerator("CLASS_", 
6)).isActive(true).position(10));
+            classificationIdRef.set(classificationCode.getSubResourceId());
+
+            // Loan Product create
+            final PostClassificationToIncomeAccountMappings 
classificationToIncomeMapping = new PostClassificationToIncomeAccountMappings()
+                    .classificationCodeValueId(classificationIdRef.get())
+                    
.incomeAccountId(classificationIncomeAccount.getAccountID().longValue());
+
+            final PostLoanProductsResponse loanProductsResponse = 
loanProductHelper
+                    
.createLoanProduct(create4IProgressive().enableIncomeCapitalization(true)
+                            
.capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)
+                            
.capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION)
+                            
.deferredIncomeLiabilityAccountId(deferredIncomeLiabilityAccount.getAccountID().longValue())
+                            
.incomeFromCapitalizationAccountId(feeIncomeAccount.getAccountID().longValue())
+                            
.capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.FEE)
+                            
.addCapitalizedIncomeClassificationToIncomeAccountMappingsItem(classificationToIncomeMapping));
+
+            GetLoanProductsProductIdResponse getLoanProductResponse = 
loanProductHelper
+                    
.retrieveLoanProductById(loanProductsResponse.getResourceId());
+            assertNotNull(getLoanProductResponse);
+            
assertNotNull(getLoanProductResponse.getCapitalizedIncomeClassificationToIncomeAccountMappings());
+            Assertions.assertEquals(1, 
getLoanProductResponse.getCapitalizedIncomeClassificationToIncomeAccountMappings().size());
+            Assertions.assertEquals(classificationIdRef.get(), 
getLoanProductResponse
+                    
.getCapitalizedIncomeClassificationToIncomeAccountMappings().get(0).getClassificationCodeValue().getId());
+
+            final PostCodeValueDataResponse secClassificationCode = 
codeHelper.createCodeValue(code.getId(),
+                    new 
PostCodeValuesDataRequest().name(Utils.uniqueRandomStringGenerator("CLASS_", 
6)).isActive(true).position(10));
+            classificationIdRef.set(secClassificationCode.getSubResourceId());
+
+            // Loan Product update
+            final PutLoanProductsProductIdRequest putLoanProductRequest = new 
PutLoanProductsProductIdRequest();
+            
putLoanProductRequest.addCapitalizedIncomeClassificationToIncomeAccountMappingsItem(
+                    new 
PostClassificationToIncomeAccountMappings().classificationCodeValueId(classificationIdRef.get())
+                            
.incomeAccountId(classificationIncomeAccount.getAccountID().longValue()));
+
+            
loanProductHelper.updateLoanProductById(loanProductsResponse.getResourceId(), 
putLoanProductRequest);
+            getLoanProductResponse = 
loanProductHelper.retrieveLoanProductById(loanProductsResponse.getResourceId());
+            assertNotNull(getLoanProductResponse);
+            
assertNotNull(getLoanProductResponse.getCapitalizedIncomeClassificationToIncomeAccountMappings());
+            Assertions.assertEquals(1, 
getLoanProductResponse.getCapitalizedIncomeClassificationToIncomeAccountMappings().size());
+            Assertions.assertEquals(classificationIdRef.get(), 
getLoanProductResponse
+                    
.getCapitalizedIncomeClassificationToIncomeAccountMappings().get(0).getClassificationCodeValue().getId());
+
+            PostLoansResponse postLoansResponse = 
loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(client.getClientId(),
+                    loanProductsResponse.getResourceId(), "10 September 2024", 
1000.0, 10.0, 12, null));
+            Long loanId = postLoansResponse.getLoanId();
+            loanIdRef.set(loanId);
+            loanTransactionHelper.approveLoan(loanId, 
approveLoanRequest(1000.0, "10 September 2024"));
+            disburseLoan(loanId, BigDecimal.valueOf(1000.0), "10 September 
2024");
+
+            Long capitalizedIncomeTransactionId = loanTransactionHelper
+                    .addCapitalizedIncome(loanId, "10 September 2024", 100.0, 
classificationIdRef.get()).getResourceId();
+            assertNotNull(capitalizedIncomeTransactionId);
+        });
+
+        runAt("20 September 2024", () -> {
+            Long loanId = loanIdRef.get();
+            deleteAllExternalEvents();
+            executeInlineCOB(loanId);
+
+            Long capitalizedIncomeTransactionId = 
loanTransactionHelper.addCapitalizedIncome(loanId, "20 September 2024", 20.0)
+                    .getResourceId();
+            assertNotNull(capitalizedIncomeTransactionId);
+        });
+
+        runAt("30 September 2024", () -> {
+            Long loanId = loanIdRef.get();
+            deleteAllExternalEvents();
+            executeInlineCOB(loanId);
+
+            final GetLoansLoanIdResponse loanDetails = 
loanTransactionHelper.getLoanDetails(loanId);
+            final Optional<GetLoansLoanIdTransactions> optTx = 
loanDetails.getTransactions().stream()
+                    .filter(item -> 
Objects.equals(Utils.getDoubleValue(item.getAmount()), 0.33)
+                            && Objects.equals(item.getType().getValue(), 
"Capitalized Income Amortization"))
+                    .findFirst();
+            verifyTRJournalEntries(optTx.get().getId(), 
debit(deferredIncomeLiabilityAccount, 0.33),
+                    credit(classificationIncomeAccountRef.get(), 0.27), // 
First Capitalized Income With classification
+                    credit(feeIncomeAccount, 0.06)); // Second Capitalized 
Income Without classification
+
+        });
+    }
+
 }

Reply via email to