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 5a0f75a21..950e2d37e 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-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent.java new file mode 100644 index 000000000..ad0c08b13 --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/messaging/event/loan/transaction/LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent.java @@ -0,0 +1,27 @@ +/** + * 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.test.messaging.event.loan.transaction; + +public class LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent extends AbstractLoanTransactionEvent { + + @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 375c43cc6..84a70944e 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 loanCreateResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + long loanId = loanCreateResponse.body().getLoanId(); + + Response loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute(); + ErrorHelper.checkSuccessfulApiCall(loanDetailsResponse); + + List 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 89ae9f7e2..6d9d79062 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-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent.java b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent.java new file mode 100644 index 000000000..d76d69733 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/infrastructure/event/business/domain/loan/transaction/LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent.java @@ -0,0 +1,35 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.fineract.infrastructure.event.business.domain.loan.transaction; + +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; + +public class LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent extends LoanTransactionBusinessEvent { + + 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 bb0de9cf8..70de65491 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 86bcf7ef7..784136c05 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 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 bc0aa9a30..573b45703 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 cbd74830b..dc34a3493 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 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 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 c2d3916b9..f840e5bab 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 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-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingService.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingService.java new file mode 100644 index 000000000..0567a3fa6 --- /dev/null +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingService.java @@ -0,0 +1,27 @@ +/** + * 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 org.apache.fineract.portfolio.loanaccount.domain.Loan; +import org.springframework.lang.NonNull; + +public interface LoanCapitalizedIncomeAmortizationProcessingService { + + 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 9093c8642..f3c77f572 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 8b7fd8933..abe9389f9 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 b4e26d448..300ba5e47 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 722966e21..cb6a759f9 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 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 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 2a410513e..68dbc254b 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 0b146908a..c3d053d07 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 a42326221..6dd45dcec 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 000000000..af9cf3a3f --- /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 { + + @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 { + + @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 000000000..9feba20c5 --- /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 10f65716c..2d4b51cf5 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 09122d942..bfb36e98f 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 @@ + 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 000000000..7b0005071 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0176_add_capitalized_income_amortization_transaction.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + 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 7280364f7..1e8ddf379 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 d1e501c58..00e59f0af 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 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 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 2a9cfc132..03f91075d 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 loanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent = new HashMap<>(); + loanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent.put("type", + "LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent"); + loanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent.put("enabled", false); + defaults.add(loanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent); + return defaults; }