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 e69378637d FINERACT-2232: Capitalized income - amortization in case of 
Preclose event
e69378637d is described below

commit e69378637d2e032cb2674c309dd5f839dc4347d3
Author: mariiaKraievska <[email protected]>
AuthorDate: Fri May 9 13:18:25 2025 +0300

    FINERACT-2232: Capitalized income - amortization in case of Preclose event
---
 .../apache/fineract/test/data/TransactionType.java |   3 +-
 ...mortizationTransactionCreatedBusinessEvent.java |  34 ++-----
 .../fineract/test/stepdef/loan/LoanStepDef.java    |  19 ++++
 .../features/LoanCapitalizedIncome.feature         | 102 +++++++++++++++++++++
 ...mortizationTransactionCreatedBusinessEvent.java |  40 +++-----
 .../api/LoanTransactionApiConstants.java           |   1 +
 .../loanaccount/data/AccountingBridgeDataDTO.java  |   1 +
 .../loanaccount/data/LoanTransactionEnumData.java  |   2 +
 .../loanaccount/domain/LoanTransaction.java        |  13 ++-
 .../loanaccount/domain/LoanTransactionType.java    |   2 +
 .../mapper/LoanAccountingBridgeMapper.java         |   9 +-
 ...talizedIncomeAmortizationProcessingService.java |  32 +------
 .../loanproduct/service/LoanEnumerations.java      |   3 +
 .../accounting/journalentry/data/LoanDTO.java      |   1 +
 .../service/AccountingProcessorHelper.java         |   3 +-
 .../AccrualBasedAccountingProcessorForLoan.java    |  53 ++++++++++-
 .../api/LoanTransactionsApiResource.java           |   1 +
 .../loanaccount/api/LoansApiResourceSwagger.java   |   4 +
 .../service/LoanAccrualsProcessingServiceImpl.java |   2 +-
 ...nCapitalizedIncomeAmortizationEventService.java |  71 ++++++++++++++
 ...zedIncomeAmortizationProcessingServiceImpl.java |  80 ++++++++++++++++
 .../starter/LoanAccountConfiguration.java          |  11 +++
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 ...capitalized_income_amortization_transaction.xml |  38 ++++++++
 .../CreateJournalEntriesForChargeOffLoanTest.java  |   2 +-
 ...nalEventConfigurationValidationServiceTest.java |   6 +-
 .../common/ExternalEventConfigurationHelper.java   |   6 ++
 27 files changed, 447 insertions(+), 93 deletions(-)

diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java
index 5a0f75a217..950e2d37ea 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/TransactionType.java
@@ -34,7 +34,8 @@ public enum TransactionType {
     CHARGE_ADJUSTMENT("chargeAdjustment"), //
     INTEREST_PAYMENT_WAIVER("interestPaymentWaiver"), //
     REPAYMENT_AT_DISBURSEMENT("repaymentAtDisbursement"), //
-    CAPITALIZED_INCOME("capitalizedIncome"),;
+    CAPITALIZED_INCOME("capitalizedIncome"), //
+    CAPITALIZED_INCOME_AMORTIZATION("capitalizedIncomeAmortization"),;
 
     public final String value;
 
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent.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-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent.java
index 8b7fd89335..ad0c08b138
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent.java
@@ -16,34 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.accounting.journalentry.data;
+package org.apache.fineract.test.messaging.event.loan.transaction;
 
-import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.Setter;
+public class LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent 
extends AbstractLoanTransactionEvent {
 
-@AllArgsConstructor
-@Getter
-public class LoanDTO {
-
-    @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;
+    @Override
+    public String getEventName() {
+        return 
"LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent";
+    }
 }
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 375c43cc63..84a70944e2 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
@@ -139,6 +139,7 @@ import 
org.apache.fineract.test.messaging.event.loan.transaction.BulkBusinessEve
 import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanAccrualAdjustmentTransactionBusinessEvent;
 import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanAccrualTransactionCreatedBusinessEvent;
 import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanAdjustTransactionBusinessEvent;
+import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent;
 import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanChargeAdjustmentPostBusinessEvent;
 import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanChargeOffEvent;
 import 
org.apache.fineract.test.messaging.event.loan.transaction.LoanChargeOffUndoEvent;
@@ -4116,4 +4117,22 @@ public class LoanStepDef extends AbstractStepDef {
                 
.isEqualTo(ErrorMessageHelper.addCapitalizedIncomeExceedApprovedAmountFailure());
     }
 
+    @Then("LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent is 
raised on {string}")
+    public void 
checkLoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(final 
String date) 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 finalAmortizationTransaction = 
transactions.stream()
+                .filter(t -> date.equals(FORMATTER.format(t.getDate())) && 
"Capitalized Income Amortization".equals(t.getType().getValue()))
+                .findFirst().orElseThrow(
+                        () -> new IllegalStateException(String.format("No 
Capitalized Income Amortization transaction found on %s", date)));
+        Long finalAmortizationTransactionId = 
finalAmortizationTransaction.getId();
+
+        
eventAssertion.assertEventRaised(LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent.class,
+                finalAmortizationTransactionId);
+    }
 }
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 89ae9f7e22..6d9d790625 100644
--- 
a/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature
+++ 
b/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature
@@ -167,3 +167,105 @@ Feature: Capitalized Income
       | Type      | Account code | Account name                 | Debit  | 
Credit |
       | ASSET     | 112601       | Loans Receivable             | 300.0  |     
   |
       | LIABILITY | 145024       | Deferred Capitalized Income  |        | 
300.0  |
+
+  @TestRailId:C3646
+  Scenario: As a user I want to add capitalized income to a progressive loan 
after disbursement and then make a full repayment - amortization in case of 
loan close event
+    When Admin sets the business date to "1 January 2024"
+    And Admin creates a client with random data
+    When 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_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME
 | 01 January 2024   | 1000           | 7                      | 
DECLINING_BALANCE | DAILY                       | EQUAL_INSTALLMENTS | 1        
         | MONTHS                | 1              | MONTHS                 | 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
+    Then 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    |
+      | 02 January 2024  | Capitalized Income | 100.0  | 100.0     | 0.0      
| 0.0  | 0.0       | 1000.0       | false    |
+    Then 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  |
+    When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on 
"2 January 2024" with 900.17 EUR transaction amount and system-generated 
Idempotency key
+    Then 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    |
+      | 02 January 2024  | Capitalized Income              | 100.0  | 100.0    
 | 0.0      | 0.0  | 0.0       | 1000.0       | false    |
+      | 02 January 2024  | Repayment                       | 900.17 | 900.0    
 | 0.17     | 0.0  | 0.0       | 100.0        | false    |
+      | 02 January 2024  | Accrual                         | 0.17   | 0.0      
 | 0.17     | 0.0  | 0.0       | 0.0          | false    |
+      | 02 January 2024  | Capitalized Income Amortization | 100.0  | 0.0      
 | 100.0    | 0.0  | 0.0       | 0.0          | false    |
+    Then Loan Transactions tab has a "CAPITALIZED_INCOME_AMORTIZATION" 
transaction with date "02 January 2024" which has the following Journal entries:
+      | Type      | Account code | Account name                 | Debit  | 
Credit |
+      | INCOME    | 404000       | Interest Income              |        | 
100.0  |
+      | LIABILITY | 145024       | Deferred Capitalized Income  | 100.0  |     
   |
+    Then LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent is 
raised on "02 January 2024"
+
+  @TestRailId:C3647
+  Scenario: As a user I want to add capitalized income to a progressive loan 
after disbursement and then overpay loan - amortization in case of loan close 
event
+    When Admin sets the business date to "1 January 2024"
+    And Admin creates a client with random data
+    When 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_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME
 | 01 January 2024   | 1000           | 7                      | 
DECLINING_BALANCE | DAILY                       | EQUAL_INSTALLMENTS | 1        
         | MONTHS                | 1              | MONTHS                 | 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
+    Then 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    |
+      | 02 January 2024  | Capitalized Income | 100.0  | 100.0     | 0.0      
| 0.0  | 0.0       | 1000.0       | false    |
+    Then 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  |
+    When Customer makes "REPAYMENT" transaction with "AUTOPAY" payment type on 
"2 January 2024" with 1000 EUR transaction amount and system-generated 
Idempotency key
+    Then 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    |
+      | 02 January 2024  | Capitalized Income              | 100.0  | 100.0    
 | 0.0      | 0.0  | 0.0       | 1000.0       | false    |
+      | 02 January 2024  | Repayment                       | 1000.0 | 900.0    
 | 0.17     | 0.0  | 0.0       | 100.0        | false    |
+      | 02 January 2024  | Accrual                         | 0.17   | 0.0      
 | 0.17     | 0.0  | 0.0       | 0.0          | false    |
+      | 02 January 2024  | Capitalized Income Amortization | 100.0  | 0.0      
 | 100.0    | 0.0  | 0.0       | 0.0          | false    |
+    Then Loan Transactions tab has a "CAPITALIZED_INCOME_AMORTIZATION" 
transaction with date "02 January 2024" which has the following Journal entries:
+      | Type      | Account code | Account name                 | Debit  | 
Credit |
+      | INCOME    | 404000       | Interest Income              |        | 
100.0  |
+      | LIABILITY | 145024       | Deferred Capitalized Income  | 100.0  |     
   |
+    Then LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent is 
raised on "02 January 2024"
+
+  @TestRailId:C3651
+  Scenario: As a user I want to add capitalized income to a progressive loan 
after disbursement and then write off loan - amortization in case of loan close 
event
+    When Admin sets the business date to "1 January 2024"
+    And Admin creates a client with random data
+    When 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_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME
 | 01 January 2024   | 1000           | 7                      | 
DECLINING_BALANCE | DAILY                       | EQUAL_INSTALLMENTS | 1        
         | MONTHS                | 1              | MONTHS                 | 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
+    Then 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    |
+      | 02 January 2024  | Capitalized Income | 100.0  | 100.0     | 0.0      
| 0.0  | 0.0       | 1000.0       | false    |
+    Then 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 Admin does write-off the loan on "02 January 2024"
+    Then Loan status will be "CLOSED_WRITTEN_OFF"
+    Then 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    |
+      | 02 January 2024  | Capitalized Income              | 100.0  | 100.0    
 | 0.0      | 0.0  | 0.0       | 1000.0       | false    |
+      | 02 January 2024  | Close (as written-off)          | 905.25 | 900.0    
 | 5.25     | 0.0  | 0.0       | 100.0        | false    |
+      | 02 January 2024  | Capitalized Income Amortization | 100.0  | 0.0      
 | 100.0    | 0.0  | 0.0       | 0.0          | false    |
+    Then Loan Transactions tab has a "CAPITALIZED_INCOME_AMORTIZATION" 
transaction with date "02 January 2024" which has the following Journal entries:
+      | Type      | Account code | Account name                 | Debit  | 
Credit |
+      | EXPENSE   | e4           | Written off                  |        | 
100.0  |
+      | LIABILITY | 145024       | Deferred Capitalized Income  | 100.0  |     
   |
+    Then LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent is 
raised on "02 January 2024"
\ No newline at end of file
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
 
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent.java
old mode 100755
new mode 100644
similarity index 51%
copy from 
fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
copy to 
fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent.java
index 8b7fd89335..d76d69733c
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent.java
@@ -16,34 +16,20 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.accounting.journalentry.data;
+package 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction;
 
-import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.Setter;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
 
-@AllArgsConstructor
-@Getter
-public class LoanDTO {
+public class LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent 
extends LoanTransactionBusinessEvent {
 
-    @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 static final String TYPE = 
"LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent";
+
+    public 
LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(LoanTransaction
 value) {
+        super(value);
+    }
+
+    @Override
+    public String getType() {
+        return TYPE;
+    }
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionApiConstants.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionApiConstants.java
index bb0de9cf81..70de654919 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
@@ -53,5 +53,6 @@ public interface LoanTransactionApiConstants {
         interestRefund, //
         accrualAdjustment, //
         capitalizedIncome, //
+        capitalizedIncomeAmortization, //
     }
 }
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 86bcf7ef75..784136c05d 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
@@ -44,6 +44,7 @@ public class AccountingBridgeDataDTO {
     private boolean isChargeOff;
     private boolean isFraud;
     private Long chargeOffReasonCodeValue;
+    private boolean isWrittenOff;
     private List<AccountingBridgeLoanTransactionDTO> newLoanTransactions = new 
ArrayList<>();
 
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
index bc0aa9a307..573b45703b 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/data/LoanTransactionEnumData.java
@@ -68,6 +68,7 @@ public class LoanTransactionEnumData implements Serializable {
     private final boolean interestRefund;
     private final boolean accrualAdjustment;
     private final boolean capitalizedIncome;
+    private final boolean capitalizedIncomeAmortization;
 
     public LoanTransactionEnumData(final Long id, final String code, final 
String value) {
         this.id = id;
@@ -105,6 +106,7 @@ public class LoanTransactionEnumData implements 
Serializable {
         this.interestRefund = 
Long.valueOf(LoanTransactionType.INTEREST_REFUND.getValue()).equals(this.id);
         this.accrualAdjustment = 
Long.valueOf(LoanTransactionType.ACCRUAL_ADJUSTMENT.getValue()).equals(this.id);
         this.capitalizedIncome = 
Long.valueOf(LoanTransactionType.CAPITALIZED_INCOME.getValue()).equals(this.id);
+        this.capitalizedIncomeAmortization = 
Long.valueOf(LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION.getValue()).equals(this.id);
     }
 
     public boolean isRepaymentType() {
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java
index cbd74830ba..dc34a34938 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
@@ -312,6 +312,16 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom<Long
                 amount.getAmount(), null, null, null, null, false, 
paymentDetail, externalId);
     }
 
+    public static LoanTransaction capitalizedIncomeAmortization(final Loan 
loan, final Office office, final LocalDate dateOf,
+            final BigDecimal amount, final ExternalId externalId) {
+        return switch 
(loan.getLoanProductRelatedDetail().getCapitalizedIncomeType()) {
+            case FEE -> new LoanTransaction(loan, office, 
LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION, dateOf, amount, null, null,
+                    amount, null, null, false, null, externalId);
+            case INTEREST -> new LoanTransaction(loan, office, 
LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION, dateOf, amount, null,
+                    amount, null, null, null, false, null, externalId);
+        };
+    }
+
     public LoanTransaction copyTransactionPropertiesAndMappings() {
         LoanTransaction newTransaction = copyTransactionProperties(this);
         
newTransaction.updateLoanTransactionToRepaymentScheduleMappings(loanTransactionToRepaymentScheduleMappings);
@@ -746,7 +756,8 @@ public class LoanTransaction extends 
AbstractAuditableWithUTCDateTimeCustom<Long
                 || type == LoanTransactionType.ACCRUAL_ACTIVITY || type == 
LoanTransactionType.APPROVE_TRANSFER
                 || type == LoanTransactionType.INITIATE_TRANSFER || type == 
LoanTransactionType.REJECT_TRANSFER
                 || type == LoanTransactionType.WITHDRAW_TRANSFER || type == 
LoanTransactionType.CHARGE_OFF
-                || type == LoanTransactionType.REAMORTIZE || type == 
LoanTransactionType.REAGE);
+                || type == LoanTransactionType.REAMORTIZE || type == 
LoanTransactionType.REAGE
+                || type == 
LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION);
     }
 
     public void updateOutstandingLoanBalance(BigDecimal 
outstandingLoanBalance) {
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
index 709d3cf0ec..195b4e4363 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionType.java
@@ -68,6 +68,7 @@ public enum LoanTransactionType {
     INTEREST_REFUND(33, "loanTransactionType.interestRefund"), //
     ACCRUAL_ADJUSTMENT(34, "loanTransactionType.accrualAdjustment"), //
     CAPITALIZED_INCOME(35, "loanTransactionType.capitalizedIncome"), //
+    CAPITALIZED_INCOME_AMORTIZATION(36, 
"loanTransactionType.capitalizedIncomeAmortization"), //
     ;
 
     private final Integer value;
@@ -119,6 +120,7 @@ public enum LoanTransactionType {
             case 33 -> LoanTransactionType.INTEREST_REFUND;
             case 34 -> LoanTransactionType.ACCRUAL_ADJUSTMENT;
             case 35 -> LoanTransactionType.CAPITALIZED_INCOME;
+            case 36 -> LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION;
             default -> LoanTransactionType.INVALID;
         };
     }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanAccountingBridgeMapper.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanAccountingBridgeMapper.java
index c2d3916b97..f840e5babe 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanAccountingBridgeMapper.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/mapper/LoanAccountingBridgeMapper.java
@@ -55,12 +55,14 @@ public class LoanAccountingBridgeMapper {
         AccountingBridgeDataDTO beforeChargeOff = new 
AccountingBridgeDataDTO(loan.getId(), loan.productId(), loan.getOfficeId(),
                 currencyCode, loan.getSummary().getTotalInterestCharged(), 
loan.isCashBasedAccountingEnabledOnLoanProduct(),
                 loan.isUpfrontAccrualAccountingEnabledOnLoanProduct(), 
loan.isPeriodicAccrualAccountingEnabledOnLoanProduct(),
-                isAccountTransfer, false, loan.isFraud(), 
loan.fetchChargeOffReasonId(), newLoanTransactionsBeforeChargeOff);
+                isAccountTransfer, false, loan.isFraud(), 
loan.fetchChargeOffReasonId(), loan.isClosedWrittenOff(),
+                newLoanTransactionsBeforeChargeOff);
 
         AccountingBridgeDataDTO afterChargeOff = new 
AccountingBridgeDataDTO(loan.getId(), loan.productId(), loan.getOfficeId(),
                 currencyCode, loan.getSummary().getTotalInterestCharged(), 
loan.isCashBasedAccountingEnabledOnLoanProduct(),
                 loan.isUpfrontAccrualAccountingEnabledOnLoanProduct(), 
loan.isPeriodicAccrualAccountingEnabledOnLoanProduct(),
-                isAccountTransfer, true, loan.isFraud(), 
loan.fetchChargeOffReasonId(), newLoanTransactionsAfterChargeOff);
+                isAccountTransfer, true, loan.isFraud(), 
loan.fetchChargeOffReasonId(), loan.isClosedWrittenOff(),
+                newLoanTransactionsAfterChargeOff);
 
         List<AccountingBridgeDataDTO> result = new ArrayList<>();
         result.add(beforeChargeOff);
@@ -83,7 +85,8 @@ public class LoanAccountingBridgeMapper {
         return new AccountingBridgeDataDTO(loan.getId(), loan.productId(), 
loan.getOfficeId(), currencyCode,
                 loan.getSummary().getTotalInterestCharged(), 
loan.isCashBasedAccountingEnabledOnLoanProduct(),
                 loan.isUpfrontAccrualAccountingEnabledOnLoanProduct(), 
loan.isPeriodicAccrualAccountingEnabledOnLoanProduct(),
-                isAccountTransfer, loan.isChargedOff(), loan.isFraud(), 
loan.fetchChargeOffReasonId(), newLoanTransactions);
+                isAccountTransfer, loan.isChargedOff(), loan.isFraud(), 
loan.fetchChargeOffReasonId(), loan.isClosedWrittenOff(),
+                newLoanTransactions);
     }
 
     public AccountingBridgeLoanTransactionDTO mapToLoanTransactionData(final 
LoanTransaction transaction, final String currencyCode) {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingService.java
old mode 100755
new mode 100644
similarity index 51%
copy from 
fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
copy to 
fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingService.java
index 8b7fd89335..0567a3fa6c
--- 
a/fineract-provider/src/main/java/org/apache/fineract/accounting/journalentry/data/LoanDTO.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingService.java
@@ -16,34 +16,12 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.fineract.accounting.journalentry.data;
+package org.apache.fineract.portfolio.loanaccount.service;
 
-import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.Setter;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.springframework.lang.NonNull;
 
-@AllArgsConstructor
-@Getter
-public class LoanDTO {
+public interface LoanCapitalizedIncomeAmortizationProcessingService {
 
-    @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;
+    void processCapitalizedIncomeAmortizationOnLoanClosure(@NonNull Loan loan);
 }
diff --git 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
index 9093c8642b..f3c77f5725 100644
--- 
a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
+++ 
b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanproduct/service/LoanEnumerations.java
@@ -327,6 +327,9 @@ public final class LoanEnumerations {
                     LoanTransactionType.ACCRUAL_ADJUSTMENT.getCode(), "Accrual 
Adjustment");
             case CAPITALIZED_INCOME -> new 
LoanTransactionEnumData(LoanTransactionType.CAPITALIZED_INCOME.getValue().longValue(),
                     LoanTransactionType.CAPITALIZED_INCOME.getCode(), 
"Capitalized Income");
+            case CAPITALIZED_INCOME_AMORTIZATION ->
+                new 
LoanTransactionEnumData(LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION.getValue().longValue(),
+                        
LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION.getCode(), "Capitalized 
Income Amortization");
         };
     }
 
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 8b7fd89335..abe9389f94 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
@@ -46,4 +46,5 @@ public class LoanDTO {
     @Setter
     private boolean markedAsFraud;
     private Long chargeOffReasonCodeValue;
+    private boolean markedAsWrittenOff;
 }
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 b4e26d4484..300ba5e47d 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
@@ -107,6 +107,7 @@ public class AccountingProcessorHelper {
         boolean isLoanMarkedAsChargeOff = accountingBridgeData.isChargeOff();
         boolean isLoanMarkedAsFraud = accountingBridgeData.isFraud();
         final Long chargeOffReasonCodeValue = 
accountingBridgeData.getChargeOffReasonCodeValue();
+        final boolean isLoanMarkedAsWrittenOff = 
accountingBridgeData.isWrittenOff();
         final boolean cashBasedAccountingEnabled = 
accountingBridgeData.isCashBasedAccountingEnabled();
         final boolean upfrontAccrualBasedAccountingEnabled = 
accountingBridgeData.isUpfrontAccrualBasedAccountingEnabled();
         final boolean periodicAccrualBasedAccountingEnabled = 
accountingBridgeData.isPeriodicAccrualBasedAccountingEnabled();
@@ -169,7 +170,7 @@ public class AccountingProcessorHelper {
 
         return new LoanDTO(loanId, loanProductId, officeId, currencyCode, 
cashBasedAccountingEnabled, upfrontAccrualBasedAccountingEnabled,
                 periodicAccrualBasedAccountingEnabled, newLoanTransactions, 
isLoanMarkedAsChargeOff, isLoanMarkedAsFraud,
-                chargeOffReasonCodeValue);
+                chargeOffReasonCodeValue, isLoanMarkedAsWrittenOff);
     }
 
     public ProductToGLAccountMapping getChargeOffMappingByCodeValue(Long 
loanProductId, PortfolioProductType productType,
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 722966e21d..cb6a759f96 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
@@ -127,6 +127,10 @@ public class AccrualBasedAccountingProcessorForLoan 
implements AccountingProcess
             if (transactionType.isCapitalizedIncome()) {
                 createJournalEntriesForCapitalizedIncome(loanDTO, 
loanTransactionDTO, office);
             }
+            // Handle Capitalized Income Amortization
+            if (transactionType.isCapitalizedIncomeAmortization()) {
+                createJournalEntriesForCapitalizedIncomeAmortization(loanDTO, 
loanTransactionDTO, office);
+            }
         }
     }
 
@@ -141,7 +145,7 @@ public class AccrualBasedAccountingProcessorForLoan 
implements AccountingProcess
         final LocalDate transactionDate = 
loanTransactionDTO.getTransactionDate();
         final BigDecimal principalAmount = loanTransactionDTO.getPrincipal();
         final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
-        GLAccountBalanceHolder glAccountBalanceHolder = new 
GLAccountBalanceHolder();
+        final GLAccountBalanceHolder glAccountBalanceHolder = new 
GLAccountBalanceHolder();
 
         if (MathUtil.isGreaterThanZero(principalAmount)) {
             populateCreditDebitMaps(loanProductId, principalAmount, 
paymentTypeId,
@@ -167,6 +171,53 @@ public class AccrualBasedAccountingProcessorForLoan 
implements AccountingProcess
         }
     }
 
+    private void createJournalEntriesForCapitalizedIncomeAmortization(final 
LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
+            final Office office) {
+        // loan properties
+        final Long loanProductId = loanDTO.getLoanProductId();
+        final Long loanId = loanDTO.getLoanId();
+        final String currencyCode = loanDTO.getCurrencyCode();
+        final boolean isLoanWrittenOff = loanDTO.isMarkedAsWrittenOff();
+
+        // transaction properties
+        final String transactionId = loanTransactionDTO.getTransactionId();
+        final LocalDate transactionDate = 
loanTransactionDTO.getTransactionDate();
+        final BigDecimal interestAmount = loanTransactionDTO.getInterest();
+        final BigDecimal feesAmount = loanTransactionDTO.getFees();
+        final Long paymentTypeId = loanTransactionDTO.getPaymentTypeId();
+        final GLAccountBalanceHolder glAccountBalanceHolder = new 
GLAccountBalanceHolder();
+
+        // interest payment
+        final AccrualAccountsForLoan creditAccountType = isLoanWrittenOff ? 
AccrualAccountsForLoan.LOSSES_WRITTEN_OFF
+                : AccrualAccountsForLoan.INCOME_FROM_CAPITALIZATION;
+        if (MathUtil.isGreaterThanZero(interestAmount)) {
+            populateCreditDebitMaps(loanProductId, interestAmount, 
paymentTypeId, creditAccountType.getValue(),
+                    
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), 
glAccountBalanceHolder);
+        }
+        // handle fees payment
+        if (MathUtil.isGreaterThanZero(feesAmount)) {
+            populateCreditDebitMaps(loanProductId, feesAmount, paymentTypeId, 
creditAccountType.getValue(),
+                    
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), 
glAccountBalanceHolder);
+        }
+
+        // create credit entries
+        for (Map.Entry<Long, BigDecimal> creditEntry : 
glAccountBalanceHolder.getCreditBalances().entrySet()) {
+            if (MathUtil.isGreaterThanZero(creditEntry.getValue())) {
+                GLAccount glAccount = 
glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey());
+                this.helper.createCreditJournalEntryForLoan(office, 
currencyCode, loanId, transactionId, transactionDate,
+                        creditEntry.getValue(), glAccount);
+            }
+        }
+        // create debit entries
+        for (Map.Entry<Long, BigDecimal> debitEntry : 
glAccountBalanceHolder.getDebitBalances().entrySet()) {
+            if (MathUtil.isGreaterThanZero(debitEntry.getValue())) {
+                GLAccount glAccount = 
glAccountBalanceHolder.getGlAccountMap().get(debitEntry.getKey());
+                this.helper.createDebitJournalEntryForLoan(office, 
currencyCode, loanId, transactionId, transactionDate,
+                        debitEntry.getValue(), glAccount);
+            }
+        }
+    }
+
     private void 
createJournalEntriesForInterestPaymentWaiverOrInterestRefund(LoanDTO loanDTO, 
LoanTransactionDTO loanTransactionDTO,
             Office office) {
         final Long loanProductId = loanDTO.getLoanProductId();
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoanTransactionsApiResource.java
index 2a410513e6..68dbc254be 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
@@ -528,6 +528,7 @@ public class LoanTransactionsApiResource {
             case interestRefund -> LoanTransactionType.INTEREST_REFUND;
             case accrualAdjustment -> LoanTransactionType.ACCRUAL_ADJUSTMENT;
             case capitalizedIncome -> LoanTransactionType.CAPITALIZED_INCOME;
+            case capitalizedIncomeAmortization -> 
LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION;
             default ->
                 throw new InvalidLoanTransactionTypeException("transaction", 
transactionTypeParam.name(), "Unknown transaction type");
         };
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
index 0b146908aa..c3d053d073 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/api/LoansApiResourceSwagger.java
@@ -722,6 +722,10 @@ final class LoansApiResourceSwagger {
                 public boolean chargeAdjustment;
                 @Schema(example = "false")
                 public boolean chargeoff;
+                @Schema(example = "false")
+                public boolean capitalizedIncome;
+                @Schema(example = "false")
+                public boolean capitalizedIncomeAmortization;
             }
 
             static final class GetLoansLoanIdPaymentDetailData {
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java
index a423262210..6dd45dceca 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanAccrualsProcessingServiceImpl.java
@@ -440,7 +440,7 @@ public class LoanAccrualsProcessingServiceImpl implements 
LoanAccrualsProcessing
                     loan.getOfficeId(), loan.getCurrencyCode(), 
loan.getSummary().getTotalInterestCharged(),
                     
loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct(),
                     loan.isUpfrontAccrualAccountingEnabledOnLoanProduct(), 
loan.isPeriodicAccrualAccountingEnabledOnLoanProduct(), false,
-                    false, false, null, newTransactionDTOs);
+                    false, false, null, false, newTransactionDTOs);
             
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
         }
     }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationEventService.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationEventService.java
new file mode 100644
index 0000000000..af9cf3a3f0
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationEventService.java
@@ -0,0 +1,71 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import jakarta.annotation.PostConstruct;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.fineract.infrastructure.event.business.BusinessEventListener;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.LoanCloseBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
+
+@Slf4j
+@RequiredArgsConstructor
+public class LoanCapitalizedIncomeAmortizationEventService {
+
+    private final BusinessEventNotifierService businessEventNotifierService;
+    private final LoanCapitalizedIncomeAmortizationProcessingService 
loanCapitalizedIncomeAmortizationProcessingService;
+
+    @PostConstruct
+    public void addListeners() {
+        
businessEventNotifierService.addPreBusinessEventListener(LoanCloseBusinessEvent.class,
 new LoanCloseListener());
+        
businessEventNotifierService.addPostBusinessEventListener(LoanBalanceChangedBusinessEvent.class,
 new LoanBalanceChangedListener());
+    }
+
+    private final class LoanCloseListener implements 
BusinessEventListener<LoanCloseBusinessEvent> {
+
+        @Override
+        public void onBusinessEvent(final LoanCloseBusinessEvent event) {
+            final Loan loan = event.get();
+            final LoanStatus status = loan.getStatus();
+            if 
(loan.getLoanProductRelatedDetail().isEnableIncomeCapitalization()
+                    && (status.isClosedObligationsMet() || 
status.isClosedWrittenOff() || status.isOverpaid())) {
+                log.debug("Loan closure on capitalized income amortization for 
loan {}", loan.getId());
+                
loanCapitalizedIncomeAmortizationProcessingService.processCapitalizedIncomeAmortizationOnLoanClosure(loan);
+            }
+        }
+    }
+
+    private final class LoanBalanceChangedListener implements 
BusinessEventListener<LoanBalanceChangedBusinessEvent> {
+
+        @Override
+        public void onBusinessEvent(final LoanBalanceChangedBusinessEvent 
event) {
+            final Loan loan = event.get();
+            final LoanStatus status = loan.getStatus();
+            if 
(loan.getLoanProductRelatedDetail().isEnableIncomeCapitalization()
+                    && (status.isClosedObligationsMet() || 
status.isClosedWrittenOff() || status.isOverpaid())) {
+                log.debug("Loan balance change on capitalized income 
amortization for loan {}", loan.getId());
+                
loanCapitalizedIncomeAmortizationProcessingService.processCapitalizedIncomeAmortizationOnLoanClosure(loan);
+            }
+        }
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingServiceImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingServiceImpl.java
new file mode 100644
index 0000000000..9feba20c57
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingServiceImpl.java
@@ -0,0 +1,80 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.portfolio.loanaccount.service;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import lombok.RequiredArgsConstructor;
+import 
org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService;
+import org.apache.fineract.infrastructure.core.domain.ExternalId;
+import 
org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent;
+import 
org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
+import org.apache.fineract.portfolio.loanaccount.domain.Loan;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanCapitalizedIncomeBalance;
+import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
+import 
org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
+import 
org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.stereotype.Component;
+import org.springframework.transaction.annotation.Transactional;
+
+@Component
+@RequiredArgsConstructor
+public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements 
LoanCapitalizedIncomeAmortizationProcessingService {
+
+    private final ConfigurationDomainService configurationDomainService;
+    private final LoanTransactionRepository loanTransactionRepository;
+    private final LoanCapitalizedIncomeBalanceRepository 
loanCapitalizedIncomeBalanceRepository;
+    private final BusinessEventNotifierService businessEventNotifierService;
+
+    @Override
+    @Transactional
+    public void processCapitalizedIncomeAmortizationOnLoanClosure(@NotNull 
final Loan loan) {
+        final LocalDate finalAmortizationTransactionDate = 
getFinalCapitalizedIncomeAmortizationTransactionDate(loan);
+        ExternalId externalId = ExternalId.empty();
+        BigDecimal totalUnrecognizedAmountCapitalizedIncomeAmount = 
BigDecimal.ZERO;
+
+        final boolean isExternalIdAutoGenerationEnabled = 
configurationDomainService.isExternalIdAutoGenerationEnabled();
+        if (isExternalIdAutoGenerationEnabled) {
+            externalId = ExternalId.generate();
+        }
+
+        for (LoanCapitalizedIncomeBalance balance : 
loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loan.getId())) {
+            totalUnrecognizedAmountCapitalizedIncomeAmount = 
totalUnrecognizedAmountCapitalizedIncomeAmount
+                    .add(balance.getUnrecognizedAmount());
+            balance.setUnrecognizedAmount(BigDecimal.ZERO);
+        }
+
+        final LoanTransaction finalCapitalizedIncomeAmortization = 
LoanTransaction.capitalizedIncomeAmortization(loan, loan.getOffice(),
+                finalAmortizationTransactionDate, 
totalUnrecognizedAmountCapitalizedIncomeAmount, externalId);
+        loan.addLoanTransaction(finalCapitalizedIncomeAmortization);
+        
loanTransactionRepository.saveAndFlush(finalCapitalizedIncomeAmortization);
+        businessEventNotifierService.notifyPostBusinessEvent(
+                new 
LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(finalCapitalizedIncomeAmortization));
+    }
+
+    private LocalDate 
getFinalCapitalizedIncomeAmortizationTransactionDate(final Loan loan) {
+        return switch (loan.getStatus()) {
+            case CLOSED_OBLIGATIONS_MET -> loan.getClosedOnDate();
+            case OVERPAID -> loan.getOverpaidOnDate();
+            case CLOSED_WRITTEN_OFF -> loan.getWrittenOffOnDate();
+            default -> throw new IllegalStateException("Unexpected value: " + 
loan.getStatus());
+        };
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
 
b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/starter/LoanAccountConfiguration.java
index 10f65716c0..2d4b51cf59 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
@@ -119,6 +119,8 @@ import 
org.apache.fineract.portfolio.loanaccount.service.LoanArrearsAgingService
 import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
 import org.apache.fineract.portfolio.loanaccount.service.LoanAssemblerImpl;
 import 
org.apache.fineract.portfolio.loanaccount.service.LoanCalculateRepaymentPastDueService;
+import 
org.apache.fineract.portfolio.loanaccount.service.LoanCapitalizedIncomeAmortizationEventService;
+import 
org.apache.fineract.portfolio.loanaccount.service.LoanCapitalizedIncomeAmortizationProcessingService;
 import org.apache.fineract.portfolio.loanaccount.service.LoanChargeAssembler;
 import 
org.apache.fineract.portfolio.loanaccount.service.LoanChargePaidByReadService;
 import 
org.apache.fineract.portfolio.loanaccount.service.LoanChargeReadPlatformService;
@@ -505,4 +507,13 @@ public class LoanAccountConfiguration {
         return new LoanAccountServiceImpl(loanRepositoryWrapper, 
loanTransactionRepository);
     }
 
+    @Bean
+    
@ConditionalOnMissingBean(LoanCapitalizedIncomeAmortizationEventService.class)
+    public LoanCapitalizedIncomeAmortizationEventService 
loanCapitalizedIncomeAmortizationEventService(
+            BusinessEventNotifierService businessEventNotifierService,
+            LoanCapitalizedIncomeAmortizationProcessingService 
loanCapitalizedIncomeAmortizationProcessingService) {
+        return new 
LoanCapitalizedIncomeAmortizationEventService(businessEventNotifierService,
+                loanCapitalizedIncomeAmortizationProcessingService);
+    }
+
 }
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 09122d942f..bfb36e98fc 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
@@ -194,4 +194,5 @@
     <include file="parts/0173_user_change_pwd.xml" 
relativeToChangelogFile="true" />
     <include file="parts/0174_loan_product_add_capitalized_income_type.xml" 
relativeToChangelogFile="true" />
     <include file="parts/0175_add_fk_acc_product_mapping.xml" 
relativeToChangelogFile="true" />
+    <include 
file="parts/0176_add_capitalized_income_amortization_transaction.xml" 
relativeToChangelogFile="true" />
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0176_add_capitalized_income_amortization_transaction.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0176_add_capitalized_income_amortization_transaction.xml
new file mode 100644
index 0000000000..7b00050710
--- /dev/null
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0176_add_capitalized_income_amortization_transaction.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd";>
+    <changeSet id="1" author="fineract">
+        <insert tableName="m_external_event_configuration">
+            <column name="type" 
value="LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent"/>
+            <column name="enabled" valueBoolean="false"/>
+        </insert>
+        <insert tableName="r_enum_value">
+            <column name="enum_name" value="transaction_type_enum"/>
+            <column name="enum_id" valueNumeric="36"/>
+            <column name="enum_message_property" value="Capitalized Income 
Amortization"/>
+            <column name="enum_value" value="Capitalized Income Amortization"/>
+            <column name="enum_type" valueBoolean="false"/>
+        </insert>
+    </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 7280364f78..1e8ddf379f 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
@@ -72,7 +72,7 @@ class CreateJournalEntriesForChargeOffLoanTest {
                 transactionType, new BigDecimal("500.00"), new 
BigDecimal("500.00"), null, null, null, null, false, Collections.emptyList(),
                 Collections.emptyList(), false, "", null, null, null, null);
 
-        loanDTO = new LoanDTO(1L, 1L, 1L, "USD", false, true, true, 
List.of(loanTransactionDTO), false, false, chargeOffReasonId);
+        loanDTO = new LoanDTO(1L, 1L, 1L, "USD", false, true, true, 
List.of(loanTransactionDTO), false, false, chargeOffReasonId, false);
     }
 
     @Test
diff --git 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java
 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java
index d1e501c589..00e59f0afd 100644
--- 
a/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java
+++ 
b/fineract-provider/src/test/java/org/apache/fineract/infrastructure/event/external/service/ExternalEventConfigurationValidationServiceTest.java
@@ -105,7 +105,8 @@ public class 
ExternalEventConfigurationValidationServiceTest {
                 "LoanReAmortizeBusinessEvent", 
"LoanUndoReAmortizeBusinessEvent", 
"LoanTransactionInterestPaymentWaiverPreBusinessEvent",
                 "LoanTransactionInterestPaymentWaiverPostBusinessEvent", 
"LoanTransactionAccrualActivityPostBusinessEvent",
                 "LoanTransactionAccrualActivityPreBusinessEvent", 
"LoanTransactionInterestRefundPostBusinessEvent",
-                "LoanTransactionInterestRefundPreBusinessEvent", 
"LoanAccrualAdjustmentTransactionBusinessEvent");
+                "LoanTransactionInterestRefundPreBusinessEvent", 
"LoanAccrualAdjustmentTransactionBusinessEvent",
+                
"LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent");
 
         List<FineractPlatformTenant> tenants = Arrays
                 .asList(new FineractPlatformTenant(1L, "default", "Default 
Tenant", "Europe/Budapest", null));
@@ -191,7 +192,8 @@ public class 
ExternalEventConfigurationValidationServiceTest {
                 "LoanReAmortizeBusinessEvent", 
"LoanUndoReAmortizeBusinessEvent", 
"LoanTransactionInterestPaymentWaiverPreBusinessEvent",
                 "LoanTransactionInterestPaymentWaiverPostBusinessEvent", 
"LoanTransactionAccrualActivityPostBusinessEvent",
                 "LoanTransactionAccrualActivityPreBusinessEvent", 
"LoanTransactionInterestRefundPostBusinessEvent",
-                "LoanTransactionInterestRefundPreBusinessEvent", 
"LoanAccrualAdjustmentTransactionBusinessEvent");
+                "LoanTransactionInterestRefundPreBusinessEvent", 
"LoanAccrualAdjustmentTransactionBusinessEvent",
+                
"LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent");
 
         List<FineractPlatformTenant> tenants = Arrays
                 .asList(new FineractPlatformTenant(1L, "default", "Default 
Tenant", "Europe/Budapest", null));
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
index 2a9cfc1320..03f91075df 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/ExternalEventConfigurationHelper.java
@@ -583,6 +583,12 @@ public class ExternalEventConfigurationHelper {
         loanTransactionInterestRefundPreBusinessEvent.put("enabled", false);
         defaults.add(loanTransactionInterestRefundPreBusinessEvent);
 
+        Map<String, Object> 
loanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent = new 
HashMap<>();
+        
loanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent.put("type",
+                
"LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent");
+        
loanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent.put("enabled", 
false);
+        
defaults.add(loanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent);
+
         return defaults;
     }
 

Reply via email to