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

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


The following commit(s) were added to refs/heads/develop by this push:
     new 65538495aa FINERACT-2347: Introduce classification field on loan 
transactions
65538495aa is described below

commit 65538495aa301246d305451c6fd9d5254b0cd424
Author: Jose Alberto Hernandez <[email protected]>
AuthorDate: Fri Aug 22 16:07:50 2025 -0500

    FINERACT-2347: Introduce classification field on loan transactions
---
 .../main/avro/loan/v1/LoanTransactionDataV1.avsc   |   8 ++
 .../apache/fineract/test/helper/CodeHelper.java    |   4 +
 .../fineract/test/stepdef/loan/LoanStepDef.java    | 112 +++++++++++++++++++++
 .../resources/features/LoanBuyDownFees.feature     |  36 +++++++
 .../features/LoanCapitalizedIncome.feature         |  37 +++++++
 .../api/LoanTransactionApiConstants.java           |   4 +
 .../api/LoanTransactionsApiResourceSwagger.java    |  19 ++++
 .../loanaccount/data/LoanTransactionData.java      |   6 +-
 .../loanaccount/domain/LoanTransaction.java        |   7 ++
 .../loanaccount/mapper/LoanTransactionMapper.java  |   2 +
 .../serialization/LoanTransactionValidator.java    |   2 +
 .../tenant/module/loan/module-changelog-master.xml |   1 +
 ...1032_add_classification_to_loan_transaction.xml |  41 ++++++++
 .../loanaccount/data/LoanTransactionDataTest.java  |   4 +-
 .../BuyDownFeeWritePlatformServiceImpl.java        |  16 +++
 .../CapitalizedIncomeWritePlatformServiceImpl.java |  14 +++
 .../ProgressiveLoanTransactionValidatorImpl.java   |  24 ++++-
 .../ProgressiveLoanAccountConfiguration.java       |   7 +-
 .../infrastructure/codes/api/CodesApiResource.java |  17 ++++
 .../api/LoanTransactionsApiResource.java           |   2 +-
 .../LoanTransactionValidatorImpl.java              |  18 ++++
 .../service/LoanReadPlatformServiceImpl.java       |  29 ++++--
 .../starter/LoanAccountConfiguration.java          |   6 +-
 .../LoanCapitalizedIncomeTest.java                 |  24 ++++-
 .../integrationtests/LoanTransactionTest.java      |  13 +++
 .../common/loans/LoanTransactionHelper.java        |   6 ++
 .../integrationtests/common/system/CodeHelper.java |   4 +
 27 files changed, 442 insertions(+), 21 deletions(-)

diff --git 
a/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc 
b/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc
index d9df8b18c4..2b59fd23a9 100644
--- a/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc
+++ b/fineract-avro-schemas/src/main/avro/loan/v1/LoanTransactionDataV1.avsc
@@ -263,6 +263,14 @@
                 "null",
                 "boolean"
             ]
+        },
+        {
+            "default": null,
+            "name": "classification",
+            "type": [
+                "null",
+                "org.apache.fineract.avro.generic.v1.CodeValueDataV1"
+            ]
         }
     ]
 }
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/CodeHelper.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/CodeHelper.java
index 8d6203cc26..d0a75faf7b 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/CodeHelper.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/CodeHelper.java
@@ -60,4 +60,8 @@ public class CodeHelper {
         return codesApi.retrieveCodes().execute().body().stream().filter(r -> 
name.equals(r.getName())).findAny()
                 .orElseThrow(() -> new IllegalArgumentException("Code with 
name " + name + " has not been found"));
     }
+
+    public Response<PostCodeValueDataResponse> createCodeValue(Long codeId, 
PostCodeValuesDataRequest request) throws IOException {
+        return codeValuesApi.createCodeValue(codeId, request).execute();
+    }
 }
diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
index 9193440837..6273fac69e 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java
@@ -67,6 +67,8 @@ import 
org.apache.fineract.client.models.BuyDownFeeAmortizationDetails;
 import org.apache.fineract.client.models.CapitalizedIncomeDetails;
 import org.apache.fineract.client.models.DeleteLoansLoanIdResponse;
 import org.apache.fineract.client.models.DisbursementDetail;
+import org.apache.fineract.client.models.GetCodeValuesDataResponse;
+import org.apache.fineract.client.models.GetCodesResponse;
 import org.apache.fineract.client.models.GetLoanProductsChargeOffReasonOptions;
 import org.apache.fineract.client.models.GetLoanProductsProductIdResponse;
 import org.apache.fineract.client.models.GetLoanProductsResponse;
@@ -90,6 +92,8 @@ import 
org.apache.fineract.client.models.OldestCOBProcessedLoanDTO;
 import org.apache.fineract.client.models.PaymentAllocationOrder;
 import 
org.apache.fineract.client.models.PostAddAndDeleteDisbursementDetailRequest;
 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.PostLoansDisbursementData;
 import org.apache.fineract.client.models.PostLoansLoanIdRequest;
 import org.apache.fineract.client.models.PostLoansLoanIdResponse;
@@ -108,6 +112,7 @@ import 
org.apache.fineract.client.models.PutLoansAvailableDisbursementAmountResp
 import org.apache.fineract.client.models.PutLoansLoanIdRequest;
 import org.apache.fineract.client.models.PutLoansLoanIdResponse;
 import org.apache.fineract.client.services.BusinessDateManagementApi;
+import org.apache.fineract.client.services.CodeValuesApi;
 import org.apache.fineract.client.services.LoanBuyDownFeesApi;
 import org.apache.fineract.client.services.LoanCapitalizedIncomeApi;
 import org.apache.fineract.client.services.LoanCobCatchUpApi;
@@ -236,6 +241,9 @@ public class LoanStepDef extends AbstractStepDef {
     @Autowired
     private CodeHelper codeHelper;
 
+    @Autowired
+    private CodeValuesApi codeValuesApi;
+
     @Autowired
     private LoanInterestPauseApi loanInterestPauseApi;
 
@@ -4771,6 +4779,35 @@ public class LoanStepDef extends AbstractStepDef {
         ErrorHelper.checkSuccessfulApiCall(capitalizedIncomeResponse);
     }
 
+    @And("Admin adds capitalized income with {string} payment type to the loan 
on {string} with {string} EUR transaction amount and {string} classification")
+    public void adminAddsCapitalizedIncomeWithClassification(final String 
transactionPaymentType, final String transactionDate,
+            final String amount, final String classificationCodeName) throws 
IOException {
+        final Response<PostLoansLoanIdTransactionsResponse> 
capitalizedIncomeResponse = addCapitalizedIncomeWithClassification(
+                transactionPaymentType, transactionDate, amount, 
classificationCodeName);
+        testContext().set(TestContextKey.LOAN_CAPITALIZED_INCOME_RESPONSE, 
capitalizedIncomeResponse);
+        ErrorHelper.checkSuccessfulApiCall(capitalizedIncomeResponse);
+    }
+
+    public Response<PostLoansLoanIdTransactionsResponse> 
addCapitalizedIncomeWithClassification(final String transactionPaymentType,
+            final String transactionDate, final String amount, final String 
classificationCodeName) throws IOException {
+        final Response<PostLoansResponse> loanResponse = 
testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+        final long loanId = loanResponse.body().getLoanId();
+
+        final DefaultPaymentType paymentType = 
DefaultPaymentType.valueOf(transactionPaymentType);
+        final Long paymentTypeValue = paymentTypeResolver.resolve(paymentType);
+
+        // Get classification code value
+        final Long classificationId = 
getClassificationCodeValueId(classificationCodeName);
+
+        final PostLoansLoanIdTransactionsRequest capitalizedIncomeRequest = 
LoanRequestFactory.defaultCapitalizedIncomeRequest()
+                
.transactionDate(transactionDate).transactionAmount(Double.valueOf(amount)).paymentTypeId(paymentTypeValue)
+                .externalId("EXT-CAP-INC-" + 
UUID.randomUUID()).classificationId(classificationId);
+
+        final Response<PostLoansLoanIdTransactionsResponse> 
capitalizedIncomeResponse = loanTransactionsApi
+                .executeLoanTransaction(loanId, capitalizedIncomeRequest, 
"capitalizedIncome").execute();
+        return capitalizedIncomeResponse;
+    }
+
     public Response<PostLoansLoanIdTransactionsResponse> 
adjustCapitalizedIncome(final String transactionPaymentType,
             final String transactionDate, final String amount, final Long 
transactionId) throws IOException {
 
@@ -5167,6 +5204,35 @@ public class LoanStepDef extends AbstractStepDef {
         ErrorHelper.checkSuccessfulApiCall(buyDownFeesIncomeResponse);
     }
 
+    @When("Admin adds buy down fee with {string} payment type to the loan on 
{string} with {string} EUR transaction amount and {string} classification")
+    public void adminAddsBuyDownFeeWithClassification(final String 
transactionPaymentType, final String transactionDate,
+            final String amount, final String classificationCodeName) throws 
IOException {
+        final Response<PostLoansLoanIdTransactionsResponse> 
buyDownFeesIncomeResponse = addBuyDownFeeWithClassification(
+                transactionPaymentType, transactionDate, amount, 
classificationCodeName);
+        testContext().set(TestContextKey.LOAN_BUY_DOWN_FEE_RESPONSE, 
buyDownFeesIncomeResponse);
+        ErrorHelper.checkSuccessfulApiCall(buyDownFeesIncomeResponse);
+    }
+
+    public Response<PostLoansLoanIdTransactionsResponse> 
addBuyDownFeeWithClassification(final String transactionPaymentType,
+            final String transactionDate, final String amount, final String 
classificationCodeName) throws IOException {
+        final Response<PostLoansResponse> loanResponse = 
testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+        final long loanId = loanResponse.body().getLoanId();
+
+        final DefaultPaymentType paymentType = 
DefaultPaymentType.valueOf(transactionPaymentType);
+        final Long paymentTypeValue = paymentTypeResolver.resolve(paymentType);
+
+        // Get classification code value
+        final Long classificationId = 
getClassificationCodeValueId(classificationCodeName);
+
+        final PostLoansLoanIdTransactionsRequest buyDownFeeRequest = 
LoanRequestFactory.defaultBuyDownFeeIncomeRequest()
+                
.transactionDate(transactionDate).transactionAmount(Double.valueOf(amount)).paymentTypeId(paymentTypeValue)
+                .externalId("EXT-BUY-DOWN-FEE" + 
UUID.randomUUID()).classificationId(classificationId);
+
+        final Response<PostLoansLoanIdTransactionsResponse> buyDownFeeResponse 
= loanTransactionsApi
+                .executeLoanTransaction(loanId, buyDownFeeRequest, 
"buyDownFee").execute();
+        return buyDownFeeResponse;
+    }
+
     @And("Admin adds buy down fee adjustment with {string} payment type to the 
loan on {string} with {string} EUR transaction amount")
     public void adminAddsBuyDownFeesAdjustmentToTheLoan(final String 
transactionPaymentType, final String transactionDate,
             final String amount) throws IOException {
@@ -5553,4 +5619,50 @@ public class LoanStepDef extends AbstractStepDef {
         
eventAssertion.assertEventRaised(LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent.class,
                 buyDownFeeAmortizationAdjustmentTransactionId);
     }
+
+    @And("Loan Transactions tab has a {string} transaction with date {string} 
which has classification code value {string}")
+    public void loanTransactionHasClassification(String transactionType, 
String expectedDate, String expectedClassification)
+            throws IOException {
+        Response<PostLoansResponse> loanCreateResponse = 
testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
+        long loanId = loanCreateResponse.body().getLoanId();
+
+        Response<GetLoansLoanIdResponse> loanDetailsResponse = 
loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute();
+        ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse);
+
+        List<GetLoansLoanIdTransactions> transactions = 
loanDetailsResponse.body().getTransactions();
+        GetLoansLoanIdTransactions transaction = transactions.stream()
+                .filter(t -> transactionType.equals(t.getType().getValue()) && 
expectedDate.equals(FORMATTER.format(t.getDate())))
+                .findFirst().orElseThrow(
+                        () -> new IllegalStateException(String.format("No %s 
transaction found on %s", transactionType, expectedDate)));
+
+        // Get detailed transaction information including classification
+        Response<GetLoansLoanIdTransactionsTransactionIdResponse> 
transactionDetailsResponse = loanTransactionsApi
+                .retrieveTransaction(loanId, transaction.getId(), 
null).execute();
+        ErrorHelper.checkSuccessfulApiCall(transactionDetailsResponse);
+
+        GetLoansLoanIdTransactionsTransactionIdResponse transactionDetails = 
transactionDetailsResponse.body();
+        
assertThat(transactionDetails.getClassification()).as(String.format("%s 
transaction should have classification", transactionType))
+                .isNotNull();
+        
assertThat(transactionDetails.getClassification().getName()).as("Classification 
name should match expected value")
+                .isEqualTo(expectedClassification);
+    }
+
+    private Long getClassificationCodeValueId(String classificationName) 
throws IOException {
+        final GetCodesResponse code = 
codeHelper.retrieveCodeByName(classificationName);
+
+        // Delete all existing code values for this classification
+        List<GetCodeValuesDataResponse> existingCodeValues = 
codeValuesApi.retrieveAllCodeValues(code.getId()).execute().body();
+        for (GetCodeValuesDataResponse codeValue : existingCodeValues) {
+            codeValuesApi.deleteCodeValue(code.getId(), 
codeValue.getId()).execute();
+        }
+
+        // Create a new code value under the classification code
+        PostCodeValuesDataRequest codeValueRequest = new 
PostCodeValuesDataRequest().name(classificationName + "_value").isActive(true)
+                .position(1);
+
+        Response<PostCodeValueDataResponse> response = 
codeHelper.createCodeValue(code.getId(), codeValueRequest);
+        ErrorHelper.checkSuccessfulApiCall(response);
+
+        return response.body().getSubResourceId();
+    }
 }
diff --git 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanBuyDownFees.feature 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanBuyDownFees.feature
index bc2442f803..2c469309b4 100644
--- 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanBuyDownFees.feature
+++ 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanBuyDownFees.feature
@@ -3204,3 +3204,39 @@ Feature:Feature: Buy Down Fees
       | Date            | Fee Amount | Amortized Amount | Not Yet Amortized 
Amount | Adjusted Amount | Charged Off Amount |
       | 01 January 2024 | 50.0       | 50.0             | 0.0                  
    | 0.0             | 0.0                |
     And LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent is created 
on "01 April 2024"
+
+  @TestRailId:C4004
+  Scenario: Verify buy down fee transaction creation with classification field 
set
+    When Admin sets the business date to "01 January 2024"
+    And Admin creates a client with random data
+    And Admin creates a fully customized loan with the following data:
+      | LoanProduct                                              | submitted 
on date | with Principal | ANNUAL interest rate % | interest type     | 
interest calculation period | amortization type  | loanTermFrequency | 
loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | 
numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | 
interest free period | Payment strategy            |
+      | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_BUYDOWN_FEES | 01 January 
2024   | 100            | 7                      | DECLINING_BALANCE | DAILY    
                   | EQUAL_INSTALLMENTS | 3                 | MONTHS            
    | 1              | MONTHS                 | 3                  | 0          
             | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "01 January 2024" with "100" 
amount and expected disbursement date on "01 January 2024"
+    And Admin successfully disburse the loan on "01 January 2024" with "100" 
EUR transaction amount
+    Then Loan status will be "ACTIVE"
+    When Admin adds buy down fee with "AUTOPAY" payment type to the loan on 
"01 January 2024" with "50" EUR transaction amount and 
"buydown_fee_transaction_classification" classification
+    Then Loan Repayment schedule has 3 periods, with the following data for 
periods:
+      | Nr | Days | Date             | Paid date | Balance of loan | Principal 
due | Interest | Fees | Penalties | Due   | Paid | In advance | Late | 
Outstanding |
+      |    |      | 01 January 2024  |           | 100.0           |           
    |          | 0.0  |           | 0.0   | 0.0  |            |      |          
   |
+      | 1  | 31   | 01 February 2024 |           | 66.86           | 33.14     
    | 0.58     | 0.0  | 0.0       | 33.72 | 0.0  | 0.0        | 0.0  | 33.72    
   |
+      | 2  | 29   | 01 March 2024    |           | 33.53           | 33.33     
    | 0.39     | 0.0  | 0.0       | 33.72 | 0.0  | 0.0        | 0.0  | 33.72    
   |
+      | 3  | 31   | 01 April 2024    |           | 0.0             | 33.53     
    | 0.2      | 0.0  | 0.0       | 33.73 | 0.0  | 0.0        | 0.0  | 33.73    
   |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due    | Paid | In 
advance | Late | Outstanding |
+      | 100.0         | 1.17     | 0.0  | 0.0       | 101.17 | 0.0  | 0.0      
  | 0.0  | 101.17      |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type | Amount | Principal | Interest | 
Fees | Penalties | Loan Balance | Reverted |
+      | 01 January 2024  | Disbursement     | 100.0  | 0.0       | 0.0      | 
0.0  | 0.0       | 100.0        | false    |
+      | 01 January 2024  | Buy Down Fee     | 50.0   | 0.0       | 0.0      | 
0.0  | 0.0       | 0.0          | false    |
+    And Loan Transactions tab has a "BUY_DOWN_FEE" transaction with date "01 
January 2024" which has the following Journal entries:
+      | Type      | Account code | Account name                | Debit | 
Credit |
+      | EXPENSE   | 450280       | Buy Down Expense            | 50.0  |       
 |
+      | LIABILITY | 145024       | Deferred Capitalized Income |       | 50.0  
 |
+    And Loan Transactions tab has a "Buy Down Fee" transaction with date "01 
January 2024" which has classification code value 
"buydown_fee_transaction_classification_value"
+    And Buy down fee contains the following data:
+      | Date            | Fee Amount | Amortized Amount | Not Yet Amortized 
Amount | Adjusted Amount | Charged Off Amount |
+      | 01 January 2024 | 50.0       | 0.0              | 50.0                 
    | 0.0             | 0.0                |
+    And LoanBuyDownFeeTransactionCreatedBusinessEvent is created on "01 
January 2024"
+    And Admin adds buy down fee adjustment with "AUTOPAY" payment type to the 
loan on "01 January 2024" with "25" EUR transaction amount
+    And Loan Transactions tab has a "Buy Down Fee Adjustment" transaction with 
date "01 January 2024" which has classification code value 
"buydown_fee_transaction_classification_value"
diff --git 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature
 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature
index b7951e3951..0350b40e5c 100644
--- 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature
+++ 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature
@@ -7529,3 +7529,40 @@ Feature: Capitalized Income
     When Admin successfully disburse the loan on "2 January 2024" with "300" 
EUR transaction amount
   # Available amount = 1200 - 900 - 300 - 0 = 0
     Then Loan's available disbursement amount is "0.0"
+
+  @TestRailId:C4005
+  Scenario: Verify capitalized income transaction creation with classification 
field set
+    When Admin sets the business date to "1 January 2024"
+    And Admin creates a client with random data
+    And Admin creates a fully customized loan with the following data:
+      | LoanProduct                                                    | 
submitted on date | with Principal | ANNUAL interest rate % | interest type | 
interest calculation period | amortization type  | loanTermFrequency | 
loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | 
numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | 
interest free period | Payment strategy            |
+      | LP2_PROGRESSIVE_ADVANCED_PAYMENT_ALLOCATION_CAPITALIZED_INCOME | 1 
January 2024    | 1000.0         | 0                      | FLAT          | 
SAME_AS_REPAYMENT_PERIOD    | EQUAL_INSTALLMENTS | 30                | DAYS     
             | 30             | DAYS                   | 1                  | 0 
                      | 0                      | 0                    | 
ADVANCED_PAYMENT_ALLOCATION |
+    And Admin successfully approves the loan on "1 January 2024" with "1000" 
amount and expected disbursement date on "1 January 2024"
+    And Admin successfully disburse the loan on "1 January 2024" with "900" 
EUR transaction amount
+    Then Loan status will be "ACTIVE"
+    When Admin sets the business date to "2 January 2024"
+    And Admin adds capitalized income with "AUTOPAY" payment type to the loan 
on "2 January 2024" with "100" EUR transaction amount and 
"capitalized_income_transaction_classification" classification
+    Then Loan Repayment schedule has 2 periods, with the following data for 
periods:
+      | Nr | Days | Date            | Paid date       | Balance of loan | 
Principal due | Interest | Fees | Penalties | Due   | Paid  | In advance | Late 
| Outstanding |
+      |    |      | 01 January 2024 |                 | 900.0           |      
         |          | 0.0  |           | 0.0   | 0.0   |            |      |    
         |
+      | 1  | 0    | 01 January 2024 | 01 January 2024 | 675.0           | 
225.0         | 0.0      | 0.0  | 0.0       | 225.0 | 225.0 | 0.0        | 0.0  
| 0.0         |
+      |    |      | 02 January 2024 |                 | 100.0           |      
         |          | 0.0  |           | 0.0   | 0.0   |            |      |    
         |
+      | 2  | 30   | 31 January 2024 |                 | 0.0             | 
775.0         | 0.0      | 0.0  | 0.0       | 775.0 | 0.0   | 0.0        | 0.0  
| 775.0       |
+    And Loan Repayment schedule has the following data in Total row:
+      | Principal due | Interest | Fees | Penalties | Due    | Paid  | In 
advance | Late | Outstanding |
+      | 1000.0        | 0.0      | 0.0  | 0.0       | 1000.0 | 225.0 | 0.0     
   | 0.0  | 775.0       |
+    And Loan Transactions tab has the following data:
+      | Transaction date | Transaction Type   | Amount | Principal | Interest 
| Fees | Penalties | Loan Balance | Reverted |
+      | 01 January 2024  | Disbursement       | 900.0  | 0.0       | 0.0      
| 0.0  | 0.0       | 900.0        | false    |
+      | 01 January 2024  | Down Payment       | 225.0  | 225.0     | 0.0      
| 0.0  | 0.0       | 675.0        | false    |
+      | 02 January 2024  | Capitalized Income | 100.0  | 100.0     | 0.0      
| 0.0  | 0.0       | 775.0        | false    |
+    And Loan Transactions tab has a "CAPITALIZED_INCOME" transaction with date 
"02 January 2024" which has the following Journal entries:
+      | Type      | Account code | Account name                 | Debit  | 
Credit |
+      | ASSET     | 112601       | Loans Receivable             | 100.0  |     
   |
+      | LIABILITY | 145024       | Deferred Capitalized Income  |        | 
100.0  |
+    And Loan Transactions tab has a "Capitalized Income" transaction with date 
"02 January 2024" which has classification code value 
"capitalized_income_transaction_classification_value"
+    And Deferred Capitalized Income contains the following data:
+      | Amount | Amortized Amount | Unrecognized Amount | Adjusted Amount | 
Charged Off Amount |
+      | 100.0  | 0.0              | 100.0               | 0.0             | 
0.0                |
+    And Admin adds capitalized income adjustment with "AUTOPAY" payment type 
to the loan on "02 January 2024" with "50" EUR transaction amount
+    And Loan Transactions tab has a "Capitalized Income Adjustment" 
transaction with date "02 January 2024" which has classification code value 
"capitalized_income_transaction_classification_value"
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionApiConstants.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionApiConstants.java
index 6617def508..258635d957 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionApiConstants.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionApiConstants.java
@@ -60,4 +60,8 @@ public interface LoanTransactionApiConstants {
         buyDownFeeAmortization, //
         buyDownFeeAmortizationAdjustment, //
     }
+
+    String TRANSACTION_CLASSIFICATIONID_PARAMNAME = "classificationId";
+    String CAPITALIZED_INCOME_CLASSIFICATION_CODE = 
"capitalized_income_transaction_classification";
+    String BUY_DOWN_FEE_CLASSIFICATION_CODE = 
"buydown_fee_transaction_classification";
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
index 8929d7b430..dd0bb22c1b 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResourceSwagger.java
@@ -32,6 +32,20 @@ final class LoanTransactionsApiResourceSwagger {
 
     private LoanTransactionsApiResourceSwagger() {}
 
+    static final class GetCodeValuesDataResponse {
+
+        private GetCodeValuesDataResponse() {}
+
+        @Schema(example = "1")
+        public Long id;
+        @Schema(example = "Passport")
+        public String name;
+        @Schema(example = "Passport information")
+        public String description;
+        @Schema(example = "0")
+        public Integer position;
+    }
+
     @Schema(description = "GetLoansLoanIdTransactionsTemplateResponse")
     public static final class GetLoansLoanIdTransactionsTemplateResponse {
 
@@ -110,6 +124,8 @@ final class LoanTransactionsApiResourceSwagger {
         public List<GetPaymentTypeOptions> paymentTypeOptions;
         @Schema(example = "200.000000")
         public Double netDisbursalAmount;
+
+        public List<GetCodeValuesDataResponse> classificationOptions;
     }
 
     public static final class GetLoanCurrency {
@@ -263,6 +279,7 @@ final class LoanTransactionsApiResourceSwagger {
         public Set<GetLoanTransactionRelation> transactionRelations;
         public Set<GetLoansLoanIdLoanChargePaidByData> loanChargePaidByList;
         public PaymentDetailData paymentDetailData;
+        public GetCodeValuesDataResponse classification;
 
         static final class PaymentDetailData {
 
@@ -354,6 +371,8 @@ final class LoanTransactionsApiResourceSwagger {
         // command=reAge END
         @Schema(description = "Optional. Controls whether Interest Refund 
transaction should be created for this refund. If not provided, loan product 
config is used.", example = "false")
         public Boolean interestRefundCalculation;
+        @Schema(example = "1")
+        public Long classificationId;
     }
 
     @Schema(description = "PostLoansLoanIdTransactionsResponse")
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
index 0e669052f1..c0a4ab510a 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionData.java
@@ -113,6 +113,8 @@ public class LoanTransactionData implements Serializable {
     private List<LoanTransactionRelationData> transactionRelations;
 
     private Collection<CodeValueData> chargeOffReasonOptions = null;
+    private Collection<CodeValueData> classificationOptions = null;
+    private CodeValueData classification;
 
     public static LoanTransactionData importInstance(BigDecimal 
repaymentAmount, LocalDate lastRepaymentDate, Long repaymentTypeId,
             Integer rowIndex, String locale, String dateFormat) {
@@ -162,10 +164,10 @@ public class LoanTransactionData implements Serializable {
 
     public static LoanTransactionData 
loanTransactionDataForCreditTemplate(final LoanTransactionEnumData 
transactionType,
             final LocalDate transactionDate, final BigDecimal 
transactionAmount, final Collection<PaymentTypeData> paymentOptions,
-            final CurrencyData currency) {
+            final CurrencyData currency, List<CodeValueData> 
classificationOptions) {
         return 
builder().type(transactionType).date(transactionDate).amount(transactionAmount).paymentTypeOptions(paymentOptions)
                 
.currency(currency).externalLoanId(ExternalId.empty()).externalId(ExternalId.empty()).reversalExternalId(ExternalId.empty())
-                .manuallyReversed(false).build();
+                
.manuallyReversed(false).classificationOptions(classificationOptions).build();
     }
 
     public static LoanTransactionData 
loanTransactionDataForDisbursalTemplate(final LoanTransactionEnumData 
transactionType,
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index 327e491ac9..230d3f3dde 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
@@ -39,6 +39,7 @@ import java.util.Set;
 import java.util.function.Predicate;
 import lombok.Getter;
 import lombok.Setter;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
 import 
org.apache.fineract.infrastructure.core.domain.AbstractAuditableWithUTCDateTimeCustom;
 import org.apache.fineract.infrastructure.core.domain.ExternalId;
 import org.apache.fineract.infrastructure.core.service.DateUtils;
@@ -144,6 +145,11 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom<Long
     @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = 
FetchType.LAZY, mappedBy = "loanTransaction")
     private LoanReAgeParameter loanReAgeParameter;
 
+    @Setter
+    @ManyToOne(fetch = FetchType.LAZY)
+    @JoinColumn(name = "classification_cv_id")
+    private CodeValue classification;
+
     protected LoanTransaction() {}
 
     public static LoanTransaction incomePosting(final Loan loan, final Office 
office, final LocalDate dateOf, final BigDecimal amount,
@@ -312,6 +318,7 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom<Long
         if (LoanTransactionType.REAGE.equals(loanTransaction.getTypeOf())) {
             
newTransaction.setLoanReAgeParameter(loanTransaction.getLoanReAgeParameter().getCopy(newTransaction));
         }
+        newTransaction.setClassification(loanTransaction.getClassification());
         return newTransaction;
     }
 
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionMapper.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionMapper.java
index be938e810d..57eb2498fc 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionMapper.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanTransactionMapper.java
@@ -33,6 +33,7 @@ public interface LoanTransactionMapper {
     @Mapping(target = "loanRepaymentScheduleInstallments", ignore = true)
     @Mapping(target = "writeOffReasonOptions", ignore = true)
     @Mapping(target = "chargeOffReasonOptions", ignore = true)
+    @Mapping(target = "classificationOptions", ignore = true)
     @Mapping(target = "paymentTypeOptions", ignore = true)
     @Mapping(target = "overpaymentPortion", ignore = true)
     @Mapping(target = "transfer", ignore = true)
@@ -63,5 +64,6 @@ public interface LoanTransactionMapper {
     @Mapping(target = "bankNumber", ignore = true)
     @Mapping(target = "accountId", ignore = true)
     @Mapping(target = "transactionAmount", ignore = true)
+    @Mapping(target = "classification", expression = 
"java(loanTransaction.getClassification() != null ? 
loanTransaction.getClassification().toData() : null)")
     LoanTransactionData mapLoanTransaction(LoanTransaction loanTransaction);
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java
index d3592f8a0d..43527f9a33 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidator.java
@@ -96,4 +96,6 @@ public interface LoanTransactionValidator {
     void validateReversalExternalId(DataValidatorBuilder baseDataValidator, 
JsonElement element);
 
     void validateManualInterestRefundTransaction(String json);
+
+    void validateClassificationCodeValue(String codeName, Long 
transactionClassificationId, DataValidatorBuilder baseDataValidator);
 }
diff --git 
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
 
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
index b805e53b7f..97127b644c 100644
--- 
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
+++ 
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/module-changelog-master.xml
@@ -54,4 +54,5 @@
   <include relativeToChangelogFile="true" 
file="parts/1029_add_installment_amount_in_multiples_of_to_loan.xml"/>
   <include relativeToChangelogFile="true" 
file="parts/1030_add_loan_undo_contract_termination_event.xml"/>
   <include relativeToChangelogFile="true" 
file="parts/1031_loan_merchant_buy_down_fee.xml"/>
+  <include relativeToChangelogFile="true" 
file="parts/1032_add_classification_to_loan_transaction.xml"/>
 </databaseChangeLog>
diff --git 
a/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1032_add_classification_to_loan_transaction.xml
 
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1032_add_classification_to_loan_transaction.xml
new file mode 100644
index 0000000000..2b4e8e91b7
--- /dev/null
+++ 
b/fineract-loan/src/main/resources/db/changelog/tenant/module/loan/parts/1032_add_classification_to_loan_transaction.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.3.xsd";>
+
+    <changeSet author="fineract" id="1">
+        <addColumn tableName="m_loan_transaction">
+            <column defaultValueComputed="NULL" name="classification_cv_id" 
type="BIGINT"/>
+        </addColumn>
+    </changeSet>
+    <changeSet author="fineract" id="2">
+        <insert tableName="m_code">
+            <column name="code_name" 
value="capitalized_income_transaction_classification"/>
+            <column name="is_system_defined" valueBoolean="true"/>
+        </insert>
+        <insert tableName="m_code">
+            <column name="code_name" 
value="buydown_fee_transaction_classification"/>
+            <column name="is_system_defined" valueBoolean="true"/>
+        </insert>
+    </changeSet>
+</databaseChangeLog>
diff --git 
a/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionDataTest.java
 
b/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionDataTest.java
index 9b690341f2..4ee6a17f0f 100644
--- 
a/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionDataTest.java
+++ 
b/fineract-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionDataTest.java
@@ -280,10 +280,11 @@ public class LoanTransactionDataTest {
         BigDecimal transactionAmount = new BigDecimal("3000.00");
         Collection<PaymentTypeData> paymentOptions = mock(Collection.class);
         CurrencyData currency = new CurrencyData("USD", "US Dollar", 2, 0, 
"$", "USD");
+        List<CodeValueData> classificationOptions = mock(List.class);
 
         // When
         LoanTransactionData result = 
LoanTransactionData.loanTransactionDataForCreditTemplate(transactionType, 
transactionDate,
-                transactionAmount, paymentOptions, currency);
+                transactionAmount, paymentOptions, currency, 
classificationOptions);
 
         // Then
         assertEquals(transactionType, result.getType());
@@ -295,6 +296,7 @@ public class LoanTransactionDataTest {
         assertEquals(ExternalId.empty(), result.getExternalId());
         assertEquals(ExternalId.empty(), result.getExternalLoanId());
         assertEquals(ExternalId.empty(), result.getReversalExternalId());
+        assertEquals(classificationOptions, result.getClassificationOptions());
     }
 
     @Test
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java
index 2a623ae506..35c850fcd9 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/BuyDownFeeWritePlatformServiceImpl.java
@@ -26,6 +26,7 @@ import java.util.Optional;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import 
org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
@@ -40,6 +41,7 @@ import org.apache.fineract.portfolio.client.domain.Client;
 import org.apache.fineract.portfolio.client.exception.ClientNotActiveException;
 import org.apache.fineract.portfolio.group.domain.Group;
 import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
+import 
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
@@ -65,6 +67,7 @@ public class BuyDownFeeWritePlatformServiceImpl implements 
BuyDownFeePlatformSer
     private final ExternalIdFactory externalIdFactory;
     private final LoanBuyDownFeeBalanceRepository 
loanBuyDownFeeBalanceRepository;
     private final BusinessEventNotifierService businessEventNotifierService;
+    private final CodeValueRepository codeValueRepository;
 
     @Transactional
     @Override
@@ -93,6 +96,9 @@ public class BuyDownFeeWritePlatformServiceImpl implements 
BuyDownFeePlatformSer
         // Add to loan (NO schedule recalculation as per requirements)
         loan.addLoanTransaction(buyDownFeeTransaction);
 
+        // Add Loan Transaction classification
+        addClassificationCodeToTransaction(command, 
LoanTransactionApiConstants.BUY_DOWN_FEE_CLASSIFICATION_CODE, 
buyDownFeeTransaction);
+
         // Save transaction
         loanTransactionRepository.saveAndFlush(buyDownFeeTransaction);
 
@@ -150,6 +156,8 @@ public class BuyDownFeeWritePlatformServiceImpl implements 
BuyDownFeePlatformSer
         
buyDownFeeAdjustment.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(buyDownFeeAdjustment,
                 originalBuyDownFee.get(), 
LoanTransactionRelationTypeEnum.ADJUSTMENT));
 
+        // Inherit from the target transaction the classification
+        
buyDownFeeAdjustment.setClassification(originalBuyDownFee.get().getClassification());
         // Add transaction to loan
         loan.addLoanTransaction(buyDownFeeAdjustment);
 
@@ -203,4 +211,12 @@ public class BuyDownFeeWritePlatformServiceImpl implements 
BuyDownFeePlatformSer
         
buyDownFeeBalance.setUnrecognizedAmount(buyDownFeeTransaction.getAmount());
         loanBuyDownFeeBalanceRepository.saveAndFlush(buyDownFeeBalance);
     }
+
+    private void addClassificationCodeToTransaction(final JsonCommand command, 
final String codeName, LoanTransaction loanTransaction) {
+        final Long transactionClassificationId = command
+                
.longValueOfParameterNamed(LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME);
+        if (transactionClassificationId != null) {
+            
loanTransaction.setClassification(codeValueRepository.findByCodeNameAndId(codeName,
 transactionClassificationId));
+        }
+    }
 }
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java
index fe4112a1c3..431f01b196 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/CapitalizedIncomeWritePlatformServiceImpl.java
@@ -25,6 +25,7 @@ import java.util.Map;
 import java.util.Optional;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.CommandProcessingResult;
 import 
org.apache.fineract.infrastructure.core.data.CommandProcessingResultBuilder;
@@ -37,6 +38,7 @@ import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction
 import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanCapitalizedIncomeTransactionCreatedBusinessEvent;
 import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
 import org.apache.fineract.organisation.monetary.domain.Money;
+import 
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
 import 
org.apache.fineract.portfolio.loanaccount.domain.LoanLifecycleStateMachine;
@@ -66,6 +68,7 @@ public class CapitalizedIncomeWritePlatformServiceImpl 
implements CapitalizedInc
     private final LoanBalanceService loanBalanceService;
     private final LoanLifecycleStateMachine loanLifecycleStateMachine;
     private final BusinessEventNotifierService businessEventNotifierService;
+    private final CodeValueRepository codeValueRepository;
 
     @Transactional
     @Override
@@ -86,6 +89,9 @@ public class CapitalizedIncomeWritePlatformServiceImpl 
implements CapitalizedInc
                 transactionDate, txnExternalId);
         // Update loan with capitalized income
         loan.addLoanTransaction(capitalizedIncomeTransaction);
+        // Add Loan Transaction classification
+        addClassificationCodeToTransaction(command, 
LoanTransactionApiConstants.CAPITALIZED_INCOME_CLASSIFICATION_CODE,
+                capitalizedIncomeTransaction);
         // Recalculate loan transactions
         recalculateLoanTransactions(loan, transactionDate, 
capitalizedIncomeTransaction);
         // Save and flush (PK is set)
@@ -137,6 +143,7 @@ public class CapitalizedIncomeWritePlatformServiceImpl 
implements CapitalizedInc
                 Money.of(loan.getCurrency(), transactionAmount), 
paymentDetail, transactionDate, txnExternalId);
         
capitalizedIncomeAdjustment.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(capitalizedIncomeAdjustment,
                 capitalizedIncome.get(), 
LoanTransactionRelationTypeEnum.ADJUSTMENT));
+        
capitalizedIncomeAdjustment.setClassification(capitalizedIncome.get().getClassification());
         loan.addLoanTransaction(capitalizedIncomeAdjustment);
         recalculateLoanTransactions(loan, transactionDate, 
capitalizedIncomeAdjustment);
         LoanTransaction savedCapitalizedIncomeAdjustment = 
loanTransactionRepository.saveAndFlush(capitalizedIncomeAdjustment);
@@ -205,4 +212,11 @@ public class CapitalizedIncomeWritePlatformServiceImpl 
implements CapitalizedInc
         
capitalizedIncomeBalanceRepository.saveAndFlush(capitalizedIncomeBalance);
     }
 
+    private void addClassificationCodeToTransaction(final JsonCommand command, 
final String codeName, LoanTransaction loanTransaction) {
+        final Long transactionClassificationId = command
+                
.longValueOfParameterNamed(LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME);
+        if (transactionClassificationId != null) {
+            
loanTransaction.setClassification(codeValueRepository.findByCodeNameAndId(codeName,
 transactionClassificationId));
+        }
+    }
 }
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java
index 51688ae080..085904d2cd 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/ProgressiveLoanTransactionValidatorImpl.java
@@ -45,6 +45,7 @@ import 
org.apache.fineract.infrastructure.core.service.MathUtil;
 import org.apache.fineract.organisation.holiday.domain.Holiday;
 import org.apache.fineract.organisation.workingdays.domain.WorkingDays;
 import org.apache.fineract.portfolio.common.service.Validator;
+import 
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
 import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
 import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
@@ -149,6 +150,11 @@ public class ProgressiveLoanTransactionValidatorImpl 
implements ProgressiveLoanT
                 }
             }
 
+            final Long transactionClassificationId = fromApiJsonHelper
+                    
.extractLongNamed(LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME,
 element);
+            
loanTransactionValidator.validateClassificationCodeValue(LoanTransactionApiConstants.CAPITALIZED_INCOME_CLASSIFICATION_CODE,
+                    transactionClassificationId, baseDataValidator);
+
             validatePaymentDetails(baseDataValidator, element);
             validateNote(baseDataValidator, element);
             validateExternalId(baseDataValidator, element);
@@ -276,7 +282,8 @@ public class ProgressiveLoanTransactionValidatorImpl 
implements ProgressiveLoanT
     }
 
     private static final List<String> 
BUY_DOWN_FEE_TRANSACTION_SUPPORTED_PARAMETERS = List
-            .of(new String[] { "transactionDate", "dateFormat", "locale", 
"transactionAmount", "paymentTypeId", "note", "externalId" });
+            .of(new String[] { "transactionDate", "dateFormat", "locale", 
"transactionAmount", "paymentTypeId", "note", "externalId",
+                    
LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME });
 
     @Override
     public void validateBuyDownFee(JsonCommand command, Long loanId) {
@@ -313,6 +320,11 @@ public class ProgressiveLoanTransactionValidatorImpl 
implements ProgressiveLoanT
         final BigDecimal transactionAmount = 
this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("transactionAmount", 
element);
         
baseDataValidator.reset().parameter("transactionAmount").value(transactionAmount).notNull().positiveAmount();
 
+        final Long transactionClassificationId = fromApiJsonHelper
+                
.extractLongNamed(LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME,
 element);
+        
loanTransactionValidator.validateClassificationCodeValue(LoanTransactionApiConstants.BUY_DOWN_FEE_CLASSIFICATION_CODE,
+                transactionClassificationId, baseDataValidator);
+
         throwExceptionIfValidationWarningsExist(dataValidationErrors);
     }
 
@@ -576,9 +588,15 @@ public class ProgressiveLoanTransactionValidatorImpl 
implements ProgressiveLoanT
         loanTransactionValidator.validateManualInterestRefundTransaction(json);
     }
 
+    @Override
+    public void validateClassificationCodeValue(final String codeName, final 
Long transactionClassificationId,
+            DataValidatorBuilder baseDataValidator) {
+        loanTransactionValidator.validateClassificationCodeValue(codeName, 
transactionClassificationId, baseDataValidator);
+    }
+
     private Set<String> getCapitalizedIncomeParameters() {
-        return new HashSet<>(
-                Arrays.asList("transactionDate", "dateFormat", "locale", 
"transactionAmount", "paymentTypeId", "note", "externalId"));
+        return new HashSet<>(Arrays.asList("transactionDate", "dateFormat", 
"locale", "transactionAmount", "paymentTypeId", "note",
+                "externalId", 
LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME));
     }
 
     private Set<String> getCapitalizedIncomeAdjustmentParameters() {
diff --git 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java
 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java
index 291b02b8f5..eb70e364ed 100644
--- 
a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java
+++ 
b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/ProgressiveLoanAccountConfiguration.java
@@ -18,6 +18,7 @@
  */
 package org.apache.fineract.portfolio.loanaccount.starter;
 
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
 import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
 import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
 import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
@@ -57,11 +58,12 @@ public class ProgressiveLoanAccountConfiguration {
             NoteWritePlatformService noteWritePlatformService, 
ExternalIdFactory externalIdFactory,
             LoanCapitalizedIncomeBalanceRepository 
capitalizedIncomeBalanceRepository,
             ReprocessLoanTransactionsService reprocessLoanTransactionsService, 
LoanBalanceService loanBalanceService,
-            LoanLifecycleStateMachine loanLifecycleStateMachine, 
BusinessEventNotifierService businessEventNotifierService) {
+            LoanLifecycleStateMachine loanLifecycleStateMachine, 
BusinessEventNotifierService businessEventNotifierService,
+            CodeValueRepository codeValueRepository) {
         return new 
CapitalizedIncomeWritePlatformServiceImpl(loanTransactionValidator, 
loanAssembler, loanTransactionRepository,
                 paymentDetailWritePlatformService, journalEntryPoster, 
noteWritePlatformService, externalIdFactory,
                 capitalizedIncomeBalanceRepository, 
reprocessLoanTransactionsService, loanBalanceService, loanLifecycleStateMachine,
-                businessEventNotifierService);
+                businessEventNotifierService, codeValueRepository);
     }
 
     @Bean
@@ -89,4 +91,5 @@ public class ProgressiveLoanAccountConfiguration {
             LoanCapitalizedIncomeBalanceRepository 
loanCapitalizedIncomeBalanceRepository) {
         return new CapitalizedIncomeBalanceReadServiceImpl(loanRepository, 
loanCapitalizedIncomeBalanceRepository);
     }
+
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodesApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodesApiResource.java
index 9fc63d8683..409bf3bf10 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodesApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/codes/api/CodesApiResource.java
@@ -123,6 +123,23 @@ public class CodesApiResource {
         return this.toApiJsonSerializer.serialize(settings, code, 
RESPONSE_DATA_PARAMETERS);
     }
 
+    @GET
+    @Path("name/{codeName}")
+    @Consumes({ MediaType.APPLICATION_JSON })
+    @Produces({ MediaType.APPLICATION_JSON })
+    @Operation(summary = "Retrieve a Code", description = "Returns the details 
of a Code.\n" + "\n" + "Example Requests:\n" + "\n"
+            + "codes/1")
+    @ApiResponses({
+            @ApiResponse(responseCode = "200", description = "OK", content = 
@Content(schema = @Schema(implementation = 
CodesApiResourceSwagger.GetCodesResponse.class))) })
+    public String retrieveCodeByName(@PathParam("codeName") 
@Parameter(description = "codeName") final String codeName,
+            @Context final UriInfo uriInfo) {
+
+        final CodeData code = this.readPlatformService.retriveCode(codeName);
+
+        final ApiRequestJsonSerializationSettings settings = 
this.apiRequestParameterHelper.process(uriInfo.getQueryParameters());
+        return this.toApiJsonSerializer.serialize(settings, code, 
RESPONSE_DATA_PARAMETERS);
+    }
+
     @PUT
     @Path("{codeId}")
     @Consumes({ MediaType.APPLICATION_JSON })
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
index 22ed1e9110..f8c45fcc53 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
@@ -93,7 +93,7 @@ public class LoanTransactionsApiResource {
     public static final String CAPITALIZED_INCOME = "capitalizedIncome";
     public static final String INTEREST_REFUND_COMMAND_VALUE = 
"interest-refund";
     private final Set<String> responseDataParameters = new 
HashSet<>(Arrays.asList("id", "type", "date", "currency", "amount", 
"externalId",
-            LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME, 
LoanApiConstants.REVERSED_ON_DATE_PARAMNAME));
+            LoanApiConstants.REVERSAL_EXTERNAL_ID_PARAMNAME, 
LoanApiConstants.REVERSED_ON_DATE_PARAMNAME, "classification"));
 
     private static final String RESOURCE_NAME_FOR_PERMISSIONS = "LOAN";
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java
index f75e69daf4..42ff8fefcd 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanTransactionValidatorImpl.java
@@ -35,6 +35,8 @@ import java.util.Set;
 import java.util.stream.Collectors;
 import lombok.AllArgsConstructor;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.fineract.infrastructure.codes.domain.CodeValue;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
 import org.apache.fineract.infrastructure.core.data.ApiParameterError;
 import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
@@ -67,6 +69,7 @@ import org.apache.fineract.portfolio.common.service.Validator;
 import org.apache.fineract.portfolio.group.domain.Group;
 import org.apache.fineract.portfolio.group.exception.GroupNotActiveException;
 import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import 
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
 import org.apache.fineract.portfolio.loanaccount.data.HolidayDetailDTO;
 import org.apache.fineract.portfolio.loanaccount.data.ScheduleGeneratorDTO;
 import org.apache.fineract.portfolio.loanaccount.domain.Loan;
@@ -107,6 +110,7 @@ public final class LoanTransactionValidatorImpl implements 
LoanTransactionValida
     private final CalendarInstanceRepository calendarInstanceRepository;
     private final LoanDownPaymentTransactionValidator 
loanDownPaymentTransactionValidator;
     private final LoanDisbursementValidator loanDisbursementValidator;
+    private final CodeValueRepository codeValueRepository;
 
     private void throwExceptionIfValidationWarningsExist(final 
List<ApiParameterError> dataValidationErrors) {
         if (!dataValidationErrors.isEmpty()) {
@@ -1120,4 +1124,18 @@ public final class LoanTransactionValidatorImpl 
implements LoanTransactionValida
         validatePaymentDetails(baseDataValidator, element);
         throwExceptionIfValidationWarningsExist(dataValidationErrors);
     }
+
+    @Override
+    public void validateClassificationCodeValue(final String codeName, final 
Long transactionClassificationId,
+            DataValidatorBuilder baseDataValidator) {
+        
baseDataValidator.reset().parameter(LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME)
+                
.value(transactionClassificationId).ignoreIfNull().positiveAmount();
+        if (transactionClassificationId != null) {
+            final CodeValue codeValue = 
codeValueRepository.findByCodeNameAndId(codeName, transactionClassificationId);
+            if (codeValue == null) {
+                
baseDataValidator.reset().parameter(LoanTransactionApiConstants.TRANSACTION_CLASSIFICATIONID_PARAMNAME)
+                        .failWithCode("code.value.classification.not.exists", 
"Code value does not exists in the code " + codeName);
+            }
+        }
+    }
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
index f9e609b251..44a6badd49 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanReadPlatformServiceImpl.java
@@ -94,6 +94,7 @@ import 
org.apache.fineract.portfolio.group.data.GroupGeneralData;
 import org.apache.fineract.portfolio.group.data.GroupRoleData;
 import org.apache.fineract.portfolio.group.service.GroupReadPlatformService;
 import org.apache.fineract.portfolio.loanaccount.api.LoanApiConstants;
+import 
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
 import org.apache.fineract.portfolio.loanaccount.data.DisbursementData;
 import org.apache.fineract.portfolio.loanaccount.data.LoanAccountData;
 import 
org.apache.fineract.portfolio.loanaccount.data.LoanApplicationTimelineData;
@@ -485,6 +486,7 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
 
         LoanTransactionData loanTransactionData = null;
         Collection<PaymentTypeData> paymentOptions = null;
+        List<CodeValueData> classificationOptions = null;
         BigDecimal transactionAmount = BigDecimal.ZERO;
         switch (transactionType) {
             case CAPITALIZED_INCOME:
@@ -499,15 +501,19 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
                         : loan.getApprovedPrincipal();
                 transactionAmount = 
transactionAmount.subtract(loan.getDisbursedAmount()).subtract(capitalizedIncomeBalance);
                 paymentOptions = 
this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+                classificationOptions = this.codeValueReadPlatformService
+                        
.retrieveCodeValuesByCode(LoanTransactionApiConstants.CAPITALIZED_INCOME_CLASSIFICATION_CODE);
                 loanTransactionData = 
LoanTransactionData.loanTransactionDataForCreditTemplate(
                         LoanEnumerations.transactionType(transactionType), 
DateUtils.getBusinessLocalDate(), transactionAmount,
-                        paymentOptions, retriveLoanCurrencyData(loanId));
+                        paymentOptions, retriveLoanCurrencyData(loanId), 
classificationOptions);
             break;
             case BUY_DOWN_FEE:
                 paymentOptions = 
this.paymentTypeReadPlatformService.retrieveAllPaymentTypes();
+                classificationOptions = this.codeValueReadPlatformService
+                        
.retrieveCodeValuesByCode(LoanTransactionApiConstants.BUY_DOWN_FEE_CLASSIFICATION_CODE);
                 loanTransactionData = 
LoanTransactionData.loanTransactionDataForCreditTemplate(
                         LoanEnumerations.transactionType(transactionType), 
DateUtils.getBusinessLocalDate(), transactionAmount,
-                        paymentOptions, retriveLoanCurrencyData(loanId));
+                        paymentOptions, retriveLoanCurrencyData(loanId), 
classificationOptions);
             break;
             case CAPITALIZED_INCOME_ADJUSTMENT:
                 final LoanCapitalizedIncomeBalance 
loanCapitalizedIncomeBalance = loanCapitalizedIncomeBalanceRepository
@@ -518,7 +524,7 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
                                 
.subtract(MathUtil.nullToZero(loanCapitalizedIncomeBalance.getAmountAdjustment()));
                 loanTransactionData = 
LoanTransactionData.loanTransactionDataForCreditTemplate(
                         LoanEnumerations.transactionType(transactionType), 
DateUtils.getBusinessLocalDate(), transactionAmount,
-                        paymentOptions, retriveLoanCurrencyData(loanId));
+                        paymentOptions, retriveLoanCurrencyData(loanId), 
classificationOptions);
             break;
             case BUY_DOWN_FEE_ADJUSTMENT:
                 final LoanBuyDownFeeBalance loanBuyDownFeeBalance = 
loanBuyDownFeeBalanceRepository.findByLoanIdAndLoanTransactionId(loanId,
@@ -528,7 +534,7 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
                         : 
loanBuyDownFeeBalance.getAmount().subtract(MathUtil.nullToZero(loanBuyDownFeeBalance.getAmountAdjustment()));
                 loanTransactionData = 
LoanTransactionData.loanTransactionDataForCreditTemplate(
                         LoanEnumerations.transactionType(transactionType), 
DateUtils.getBusinessLocalDate(), transactionAmount,
-                        paymentOptions, retriveLoanCurrencyData(loanId));
+                        paymentOptions, retriveLoanCurrencyData(loanId), 
classificationOptions);
             break;
             default:
                 loanTransactionData = 
LoanTransactionData.templateOnTop(retrieveLoanTransactionTemplate(loanId),
@@ -1667,13 +1673,15 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
                     + " fromtran.transaction_date as fromTransferDate, 
fromtran.amount as fromTransferAmount,"
                     + " fromtran.description as fromTransferDescription, "
                     + " totran.id as toTransferId, totran.is_reversed as 
toTransferReversed, "
-                    + " totran.transaction_date as toTransferDate, 
totran.amount as toTransferAmount,"
+                    + " totran.transaction_date as toTransferDate, 
totran.amount as toTransferAmount, "
+                    + " clcv.id as classCodeId, clcv.code_value as 
classCodeValue, "
                     + " totran.description as toTransferDescription from 
m_loan l join m_loan_transaction tr on tr.loan_id = l.id "
                     + " join m_currency rc on rc." + 
sqlGenerator.escape("code") + " = l.currency_code "
                     + " left JOIN m_payment_detail pd ON tr.payment_detail_id 
= pd.id"
                     + " left join m_payment_type pt on pd.payment_type_id = 
pt.id left join m_office office on office.id=tr.office_id"
                     + " left join m_account_transfer_transaction fromtran on 
fromtran.from_loan_transaction_id = tr.id "
-                    + " left join m_account_transfer_transaction totran on 
totran.to_loan_transaction_id = tr.id ";
+                    + " left join m_account_transfer_transaction totran on 
totran.to_loan_transaction_id = tr.id "
+                    + " left join m_code_value clcv on clcv.id = 
tr.classification_cv_id ";
         }
 
         @Override
@@ -1700,6 +1708,8 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
 
             PaymentDetailData paymentDetailData = null;
 
+            final CodeValueData classificationData = 
CodeValueData.instance(rs.getLong("classCodeId"), 
rs.getString("classCodeValue"));
+
             final Long paymentTypeId = JdbcSupport.getLong(rs, "paymentType");
             if (paymentTypeId != null) {
                 final String typeName = rs.getString("paymentTypeName");
@@ -1759,7 +1769,7 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
                     
.overpaymentPortion(overPaymentPortion).unrecognizedIncomePortion(unrecognizedIncomePortion).externalId(externalId)
                     
.transfer(transfer).outstandingLoanBalance(outstandingLoanBalance).submittedOnDate(submittedOnDate)
                     
.manuallyReversed(manuallyReversed).reversalExternalId(reversalExternalId).reversedOnDate(reversedOnDate).loanId(loanId)
-                    .externalLoanId(externalLoanId).build();
+                    
.externalLoanId(externalLoanId).classification(classificationData).build();
         }
     }
 
@@ -2491,8 +2501,9 @@ public class LoanReadPlatformServiceImpl implements 
LoanReadPlatformService, Loa
         final Collection<PaymentTypeData> paymentTypeOptions = 
paymentTypeReadPlatformService.retrieveAllPaymentTypes();
         final LoanTransactionEnumData transactionType = 
LoanEnumerations.transactionType(LoanTransactionType.INTEREST_REFUND);
 
-        return 
LoanTransactionData.loanTransactionDataForCreditTemplate(transactionType, 
targetTxn.getTransactionDate(),
-                interestRefundAmount, paymentTypeOptions, 
loan.getCurrency().toData());
+        return 
LoanTransactionData.builder().transactionType(LoanTransactionType.INTEREST_REFUND.name()).type(transactionType)
+                
.date(targetTxn.getTransactionDate()).amount(interestRefundAmount).paymentTypeOptions(paymentTypeOptions)
+                .currency(loan.getCurrency().toData()).build();
     }
 
     @Override
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
index 7a1e2f2637..c55a94cfdc 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
@@ -20,6 +20,7 @@ package org.apache.fineract.portfolio.loanaccount.starter;
 
 import org.apache.fineract.cob.service.LoanAccountLockService;
 import 
org.apache.fineract.infrastructure.accountnumberformat.domain.AccountNumberFormatRepositoryWrapper;
+import org.apache.fineract.infrastructure.codes.domain.CodeValueRepository;
 import 
org.apache.fineract.infrastructure.codes.domain.CodeValueRepositoryWrapper;
 import 
org.apache.fineract.infrastructure.codes.service.CodeValueReadPlatformService;
 import 
org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
@@ -391,10 +392,11 @@ public class LoanAccountConfiguration {
             LoanAssembler loanAssembler, LoanTransactionRepository 
loanTransactionRepository,
             PaymentDetailWritePlatformService 
paymentDetailWritePlatformService, LoanJournalEntryPoster 
loanJournalEntryPoster,
             NoteWritePlatformService noteWritePlatformService, 
ExternalIdFactory externalIdFactory,
-            LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository, 
BusinessEventNotifierService businessEventNotifierService) {
+            LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository, 
BusinessEventNotifierService businessEventNotifierService,
+            CodeValueRepository codeValueRepository) {
         return new 
BuyDownFeeWritePlatformServiceImpl(loanTransactionValidator, loanAssembler, 
loanTransactionRepository,
                 paymentDetailWritePlatformService, loanJournalEntryPoster, 
noteWritePlatformService, externalIdFactory,
-                loanBuyDownFeeBalanceRepository, businessEventNotifierService);
+                loanBuyDownFeeBalanceRepository, businessEventNotifierService, 
codeValueRepository);
     }
 
     @Bean
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 9c95110d24..e0e1ef1ee0 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
@@ -29,10 +29,14 @@ import java.util.List;
 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.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.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;
@@ -45,6 +49,7 @@ import org.apache.fineract.integrationtests.common.Utils;
 import 
org.apache.fineract.integrationtests.common.externalevents.LoanAdjustTransactionBusinessEvent;
 import 
org.apache.fineract.integrationtests.common.externalevents.LoanBusinessEvent;
 import 
org.apache.fineract.integrationtests.common.externalevents.LoanTransactionBusinessEvent;
+import 
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -185,6 +190,11 @@ public class LoanCapitalizedIncomeTest extends 
BaseLoanIntegrationTest {
                         
.incomeFromCapitalizationAccountId(feeIncomeAccount.getAccountID().longValue())
                         
.capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.FEE));
 
+        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));
+        final Long classificationId = classificationCode.getSubResourceId();
+
         runAt("1 April 2024", () -> {
             Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), 
loanProductsResponse.getResourceId(), "1 January 2024",
                     500.0, 7.0, 3, null);
@@ -192,9 +202,15 @@ public class LoanCapitalizedIncomeTest extends 
BaseLoanIntegrationTest {
 
             disburseLoan(loanId, BigDecimal.valueOf(100), "1 January 2024");
             PostLoansLoanIdTransactionsResponse capitalizedIncomeResponse = 
loanTransactionHelper.addCapitalizedIncome(loanId,
-                    "1 January 2024", 50.0);
+                    "1 January 2024", 50.0, classificationId);
             
capitalizedIncomeIdRef.set(capitalizedIncomeResponse.getResourceId());
 
+            // Validate Loan Transaction classification set value
+            GetLoansLoanIdTransactionsTransactionIdResponse transactionDetails 
= loanTransactionHelper.getLoanTransactionDetails(loanId,
+                    capitalizedIncomeIdRef.get());
+            assertNotNull(transactionDetails.getClassification());
+            assertEquals(classificationId, 
transactionDetails.getClassification().getId());
+
             PostLoansLoanIdTransactionsResponse 
capitalizedIncomeAdjustmentResponse = loanTransactionHelper
                     .capitalizedIncomeAdjustment(loanId, 
capitalizedIncomeIdRef.get(), "1 April 2024", 50.0);
             assertNotNull(capitalizedIncomeAdjustmentResponse.getLoanId());
@@ -222,6 +238,12 @@ public class LoanCapitalizedIncomeTest extends 
BaseLoanIntegrationTest {
                     journalEntry(49.71, loansReceivableAccount, "CREDIT"), //
                     journalEntry(0.29, interestReceivableAccount, "CREDIT") //
             );
+
+            // Validate Loan Transaction classification Inherit
+            transactionDetails = 
loanTransactionHelper.getLoanTransactionDetails(loanId,
+                    capitalizedIncomeAdjustmentResponse.getResourceId());
+            assertNotNull(transactionDetails.getClassification());
+            assertEquals(classificationId, 
transactionDetails.getClassification().getId());
         });
     }
 
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionTest.java
index 7e24d12c5e..df505ccc2c 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionTest.java
@@ -26,15 +26,20 @@ import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.atomic.AtomicReference;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.client.models.GetCodesResponse;
 import org.apache.fineract.client.models.GetLoansLoanIdTransactionsResponse;
 import 
org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse;
 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.TransactionType;
 import org.apache.fineract.integrationtests.common.ClientHelper;
+import org.apache.fineract.integrationtests.common.Utils;
+import 
org.apache.fineract.portfolio.loanaccount.api.LoanTransactionApiConstants;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
@@ -103,6 +108,9 @@ public class LoanTransactionTest extends 
BaseLoanIntegrationTest {
     @Test
     public void 
testGetLoanTransactionTemplateForCapitalizedIncomeWithOverAppliedAmount() {
         final PostClientsResponse client = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+        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));
 
         final PostLoanProductsResponse loanProductsResponse = loanProductHelper
                 
.createLoanProduct(create4IProgressive().enableIncomeCapitalization(true)
@@ -127,6 +135,7 @@ public class LoanTransactionTest extends 
BaseLoanIntegrationTest {
             assertEquals("loanTransactionType." + capitalizedIncomeCommand, 
transactionTemplate.getType().getCode());
             assertEquals(transactionTemplate.getAmount(), 415);
             assertThat(transactionTemplate.getPaymentTypeOptions().size() > 0);
+            assertThat(transactionTemplate.getClassificationOptions().size() > 
0);
         });
     }
 
@@ -242,6 +251,9 @@ public class LoanTransactionTest extends 
BaseLoanIntegrationTest {
     @Test
     public void testGetLoanTransactionTemplateForBuyDownFee() {
         final PostClientsResponse client = 
clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
+        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));
 
         final PostLoanProductsResponse loanProductsResponse = loanProductHelper
                 
.createLoanProduct(create4IProgressive().enableIncomeCapitalization(true)
@@ -266,6 +278,7 @@ public class LoanTransactionTest extends 
BaseLoanIntegrationTest {
             assertEquals("loanTransactionType." + buyDownFeeCommand, 
transactionTemplate.getType().getCode());
             assertEquals(transactionTemplate.getAmount(), 0);
             assertThat(transactionTemplate.getPaymentTypeOptions().size() > 0);
+            assertThat(transactionTemplate.getClassificationOptions().size() > 
0);
         });
     }
 }
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
index 39a75fcce7..276713ad53 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/LoanTransactionHelper.java
@@ -930,6 +930,12 @@ public class LoanTransactionHelper {
                 .transactionDate(transactionDate).dateFormat("dd MMMM 
yyyy").locale("en"));
     }
 
+    public PostLoansLoanIdTransactionsResponse addCapitalizedIncome(final Long 
loanId, final String transactionDate, final double amount,
+            final Long classificationId) {
+        return addCapitalizedIncome(loanId, new 
PostLoansLoanIdTransactionsRequest().transactionAmount(amount)
+                .transactionDate(transactionDate).dateFormat("dd MMMM 
yyyy").locale("en").classificationId(classificationId));
+    }
+
     public Response<CommandProcessingResult> createInterestPause(Long loanId, 
String startDate, String endDate) {
         log.info("Creating interest pause for Loan {} from {} to {}", loanId, 
startDate, endDate);
         return 
Calls.executeU(FineractClientHelper.getFineractClient().loanInterestPauseApi.createInterestPause(loanId,
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/CodeHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/CodeHelper.java
index a7951c169c..fe063ec80f 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/CodeHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/system/CodeHelper.java
@@ -388,4 +388,8 @@ public final class CodeHelper {
     public List<GetCodesResponse> retrieveCodes() {
         return 
Calls.ok(FineractClientHelper.getFineractClient().codes.retrieveCodes());
     }
+
+    public GetCodesResponse retrieveCodeByName(final String codeName) {
+        return 
Calls.ok(FineractClientHelper.getFineractClient().codes.retrieveCodeByName(codeName));
+    }
 }

Reply via email to