diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java index c96372919..bdd78fb3f 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java @@ -124,6 +124,7 @@ public enum DefaultLoanProduct implements LoanProduct { LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME, // LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME_FEE, // LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSAL_CAPITALIZED_INCOME, // + LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC_CAPITALIZED_INCOME, // ; @Override diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanProductsRequestFactory.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanProductsRequestFactory.java index 77cb4f9b4..1da1db9e7 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanProductsRequestFactory.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/factory/LoanProductsRequestFactory.java @@ -1628,4 +1628,15 @@ public class LoanProductsRequestFactory { .incomeFromCapitalizationAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME));// } + public PostLoanProductsRequest defaultLoanProductsRequestLP2ChargeOffReasonToExpenseAccountMappingsWithCapitalizedIncome() { + return defaultLoanProductsRequestLP2ChargeOffReasonToExpenseAccountMappings()// + .enableIncomeCapitalization(true)// + .enableAutoRepaymentForDownPayment(false)// + .capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION)// + .capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)// + .capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.INTEREST)// + .deferredIncomeLiabilityAccountId(accountTypeResolver.resolve(DefaultAccountType.DEFERRED_CAPITALIZED_INCOME))// + .incomeFromCapitalizationAccountId(accountTypeResolver.resolve(DefaultAccountType.INTEREST_INCOME));// + } + } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java index 12722d52f..46026d6d9 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/helper/ErrorMessageHelper.java @@ -521,6 +521,13 @@ public final class ErrorMessageHelper { resourceId, line, actual, expected); } + public static String wrongValueInLineInJournalEntry(String resourceId, int line, List> actualList, List expected) { + String actual = actualList.stream().map(Object::toString).collect(Collectors.joining(System.lineSeparator())); + return String.format("%nWrong value in Journal entries of resource %s line %s." // + + "%nActual values for the possible transactions in line (with the same date) are: %n%s %nExpected values in line: %n%s", + resourceId, line, actual, expected); + } + public static String wrongDataInJournalEntriesGlAccountType(int line, String actual, String expected) { return String.format("Wrong data in Journal entries, line %s / GL account type. " // + "Actual value is: %s - But expected value is: %s", line, actual, expected); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java index 7c8fd0df8..56dfec061 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java @@ -2509,6 +2509,36 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ TestContext.INSTANCE.set( TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME_FEE, responseLoanProductsRequestLP2ProgressiveAdvPayment36030InterestRecalcCapitalizedIncomeFee); + + // LP2 + zero-interest chargeOff behaviour + progressive loan schedule + horizontal + delinquent charge-off + // reason to GL account mapping + interest recalculation + // capitalized income enabled + final String name102 = DefaultLoanProduct.LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC_CAPITALIZED_INCOME + .getName(); + final PostLoanProductsRequest loanProductsRequestPrgAdvZeroIntChargeOffDelinquentReasonIntRecalcCapitalizedIncome = loanProductsRequestFactory + .defaultLoanProductsRequestLP2ChargeOffReasonToExpenseAccountMappingsWithCapitalizedIncome()// + .name(name102)// + .isInterestRecalculationEnabled(true)// + .preClosureInterestCalculationStrategy(1)// + .rescheduleStrategyMethod(4)// + .interestRecalculationCompoundingMethod(0)// + .recalculationRestFrequencyType(2)// + .recalculationRestFrequencyInterval(1)// + .transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION.getValue())// + .loanScheduleType("PROGRESSIVE") // + .loanScheduleProcessingType("HORIZONTAL")// + .interestRateFrequencyType(3)// + .maxInterestRatePerPeriod(10.0)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));// + final Response responseLoanProductsRequestPrgAdvZeroIntChargeOffDelinquentReasonIntRecalcCapitalizedIncome = loanProductsApi + .createLoanProduct(loanProductsRequestPrgAdvZeroIntChargeOffDelinquentReasonIntRecalcCapitalizedIncome).execute(); + TestContext.INSTANCE.set( + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_ZERO_INT_CHARGE_OFF_DELINQUENT_REASON_INT_RECALC_CAPITALIZED_INCOME, + responseLoanProductsRequestPrgAdvZeroIntChargeOffDelinquentReasonIntRecalcCapitalizedIncome); } public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule, diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java index 8216b67f7..74fefcae0 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/common/JournalEntriesStepDef.java @@ -26,6 +26,7 @@ import java.io.IOException; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import lombok.extern.slf4j.Slf4j; import org.apache.fineract.client.models.GetJournalEntriesTransactionIdResponse; @@ -138,6 +139,82 @@ public class JournalEntriesStepDef extends AbstractStepDef { } } + @Then("Reversed loan capitalized income amortization transaction has the following Journal entries:") + public void capitalizedIncomeAmortizationJournalEntryDataCheck(final DataTable table) { + final long capitalizedIncomeAmortizationId = testContext().get(TestContextKey.LOAN_CAPITALIZED_INCOME_AMORTIZATION_ID); + final Response loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + assert loanResponse.body() != null; + final long loanId = loanResponse.body().getLoanId(); + final String resourceId = String.valueOf(loanId); + + List journalLinesActualList; + final String transactionId = "L" + capitalizedIncomeAmortizationId; + Response journalEntryDataResponse = null; + try { + journalEntryDataResponse = journalEntriesApi.retrieveAll1(// + null, // + null, // + null, // + null, // + null, // + null, // + null, // + transactionId, // + null, // + null, // + null, // + null, // + null, // + null, // + null, // + loanId, // + null, // + null, // + true// + ).execute(); + ErrorHelper.checkSuccessfulApiCall(journalEntryDataResponse); + } catch (IOException e) { + log.error("Exception", e); + } + assert journalEntryDataResponse != null; + assert journalEntryDataResponse.body() != null; + journalLinesActualList = journalEntryDataResponse.body().getPageItems(); + + final List> data = table.asLists(); + for (int i = 1; i < data.size(); i++) { + final List> possibleActualValuesList = new ArrayList<>(); + final List expectedValues = data.get(i); + boolean containsAnyExpected = false; + + for (int j = 0; j < Objects.requireNonNull(journalLinesActualList).size(); j++) { + final JournalEntryTransactionItem journalLinesActual = journalLinesActualList.get(j); + final List actualValues = new ArrayList<>(); + assert journalLinesActual.getGlAccountType() != null; + actualValues.add( + journalLinesActual.getGlAccountType().getValue() == null ? null : journalLinesActual.getGlAccountType().getValue()); + actualValues.add(journalLinesActual.getGlAccountCode() == null ? null : journalLinesActual.getGlAccountCode()); + actualValues.add(journalLinesActual.getGlAccountName() == null ? null : journalLinesActual.getGlAccountName()); + assert journalLinesActual.getEntryType() != null; + actualValues + .add("DEBIT".equals(journalLinesActual.getEntryType().getValue()) ? String.valueOf(journalLinesActual.getAmount()) + : null); + actualValues + .add("CREDIT".equals(journalLinesActual.getEntryType().getValue()) ? String.valueOf(journalLinesActual.getAmount()) + : null); + possibleActualValuesList.add(actualValues); + + final boolean containsExpectedValues = possibleActualValuesList.stream() + .anyMatch(actualValue -> actualValue.equals(expectedValues)); + if (containsExpectedValues) { + containsAnyExpected = true; + } + } + assertThat(containsAnyExpected) + .as(ErrorMessageHelper.wrongValueInLineInJournalEntry(resourceId, i, possibleActualValuesList, expectedValues)) + .isTrue(); + } + } + @Then("In Loan transactions the replayed {string} transaction with date {string} has a reverted transaction pair with the following Journal entries:") public void revertedJournalEntryDataCheck(String transactionType, String transactionDate, DataTable table) throws IOException { DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT); 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 8e3272fbb..fbf42686d 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 @@ -1692,6 +1692,17 @@ public class LoanStepDef extends AbstractStepDef { ErrorHelper.checkSuccessfulApiCall(chargeOffResponse); Long transactionId = chargeOffResponse.body().getResourceId(); + + final DateTimeFormatter formatter = DateTimeFormatter.ofPattern(DATE_FORMAT); + final Response loanDetailsResponse = loansApi.retrieveLoan(loanId, false, "transactions", "", "").execute(); + assert loanDetailsResponse.body() != null; + final List transactions = loanDetailsResponse.body().getTransactions(); + final Optional transactionsMatch = transactions.stream() + .filter(t -> formatter.format(t.getDate()).equals(transactionDate) && t.getType().getCapitalizedIncomeAmortization()) + .findFirst(); + if (transactionsMatch.isPresent()) { + testContext().set(TestContextKey.LOAN_CAPITALIZED_INCOME_AMORTIZATION_ID, transactionsMatch.get().getId()); + } eventAssertion.assertEvent(LoanChargeOffEvent.class, transactionId).extractingData(LoanTransactionDataV1::getLoanId) .isEqualTo(loanId).extractingData(LoanTransactionDataV1::getId).isEqualTo(chargeOffResponse.body().getResourceId()); } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java index 06b6e104f..845ed6f7c 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java @@ -149,6 +149,7 @@ public abstract class TestContextKey { public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME = "loanProductCreateResponseLP2ProgressiveAdvPayment36030InterestRecalcCapitalizedIncome"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_CAPITALIZED_INCOME_FEE = "loanProductCreateResponseLP2ProgressiveAdvPayment36030InterestRecalcCapitalizedIncomeFee"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALC_DAILY_MULTIDISBURSAL_CAPITALIZED_INCOME = "loanProductCreateResponseLP2ProgressiveAdvPayment36030InterestRecalcMultidisbursalCapitalizedIncome"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_ZERO_INT_CHARGE_OFF_DELINQUENT_REASON_INT_RECALC_CAPITALIZED_INCOME = "loanProductCreateResponseLP2AdvancedPaymentZeroInterestChargeOffDelinquentReasonInterestRecalculationCapitalizedIncome"; public static final String LP2_NO_INTEREST_RECALCULATION_CHARGEBACK_ALLOCATION_INTEREST_FIRST_RESPONSE = "loanProductCreateResponseLP2NoInterestRecalculationChargebackAllocationInterestFirst"; public static final String LP2_NO_INTEREST_RECALCULATION_CHARGEBACK_ALLOCATION_PRINCIPAL_FIRST_RESPONSE = "loanProductCreateResponseLP2NoInterestRecalculationChargebackAllocationPrincipalFirst"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_ACCELERATE_MATURITY_CHARGE_OFF_BEHAVIOUR_LAST_INSTALLMENT_STRATEGY = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationAccelerateMaturityChargeOffBehaviourLastInstallmentStrategy"; @@ -209,4 +210,5 @@ public abstract class TestContextKey { public static final String LOAN_TRANSACTION_RESPONSE = "loanTransactionResponse"; public static final String LOAN_CAPITALIZED_INCOME_RESPONSE = "loanCapitalizedIncomeResponse"; public static final String LOAN_DISBURSEMENT_DETAIL_RESPONSE = "loanDisbursementDetailResponse"; + public static final String LOAN_CAPITALIZED_INCOME_AMORTIZATION_ID = "loanCapitalizedIncomeAmortizationId"; } 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 667e3d7ab..4fb32bd2a 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanCapitalizedIncome.feature @@ -269,6 +269,7 @@ Feature: Capitalized Income | EXPENSE | e4 | Written off | | 100.0 | | LIABILITY | 145024 | Deferred Capitalized Income | 100.0 | | Then LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent is raised on "02 January 2024" + @TestRailId:C3648 Scenario: Verify capitalized income: daily amortization - Capitalized Income type: interest When Admin sets the business date to "01 January 2024" @@ -528,3 +529,194 @@ Feature: Capitalized Income | INCOME | 404000 | Interest Income | | 0.55 | | LIABILITY | 145024 | Deferred Capitalized Income | 0.55 | | And Loan Capitalized Income Amortization Transaction Created Business Event is created on "31 March 2024" + + @TestRailId:C3661 + Scenario: As a user I want to add capitalized income to a progressive loan after disbursement and then charge-off the loan with "delinquent" reason - amortization in case of loan charge-off 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_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC_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 | + Then Admin can successfully set Fraud flag to the loan + When Admin sets the business date to "03 February 2024" + And Admin does charge-off the loan with reason "DELINQUENT" on "03 February 2024" + Then Loan Transactions tab has a "CHARGE_OFF" transaction with date "03 February 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 900.0 | + | ASSET | 112603 | Interest/Fee Receivable | | 4.0 | + | EXPENSE | 744007 | Credit Loss/Bad Debt | 900.0 | | + | INCOME | 404001 | Interest Income Charge Off | 4.0 | | + 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 | + | 03 February 2024 | Accrual | 4.0 | 0.0 | 4.0 | 0.0 | 0.0 | 0.0 | false | + | 03 February 2024 | Charge-off | 904.0 | 900.0 | 4.0 | 0.0 | 0.0 | 0.0 | false | + | 03 February 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 "03 February 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | EXPENSE | 744007 | Credit Loss/Bad Debt | | 100.0 | + | LIABILITY | 145024 | Deferred Capitalized Income | 100.0 | | + Then LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent is raised on "03 February 2024" + + @TestRailId:C3662 + Scenario: As a user I want to add capitalized income to a progressive loan after disbursement and then charge-off a fraud loan - amortization in case of loan charge-off 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_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC_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 | + Then Admin can successfully set Fraud flag to the loan + When Admin sets the business date to "03 February 2024" + And Admin does charge-off the loan on "03 February 2024" + Then Loan Transactions tab has a "CHARGE_OFF" transaction with date "03 February 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 900.0 | + | ASSET | 112603 | Interest/Fee Receivable | | 4.0 | + | EXPENSE | 744037 | Credit Loss/Bad Debt-Fraud | 900.0 | | + | INCOME | 404001 | Interest Income Charge Off | 4.0 | | + 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 | + | 03 February 2024 | Accrual | 4.0 | 0.0 | 4.0 | 0.0 | 0.0 | 0.0 | false | + | 03 February 2024 | Charge-off | 904.0 | 900.0 | 4.0 | 0.0 | 0.0 | 0.0 | false | + | 03 February 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 "03 February 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | EXPENSE | 744037 | Credit Loss/Bad Debt-Fraud | | 100.0 | + | LIABILITY | 145024 | Deferred Capitalized Income | 100.0 | | + Then LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent is raised on "03 February 2024" + + @TestRailId:C3663 + Scenario: As a user I want to add capitalized income to a progressive loan after disbursement and then charge-off the loan - amortization in case of loan charge-off 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_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC_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 | + Then Admin can successfully set Fraud flag to the loan + When Admin sets the business date to "03 February 2024" + And Admin does charge-off the loan with reason "DELINQUENT" on "03 February 2024" + Then Loan Transactions tab has a "CHARGE_OFF" transaction with date "03 February 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 900.0 | + | ASSET | 112603 | Interest/Fee Receivable | | 4.0 | + | EXPENSE | 744007 | Credit Loss/Bad Debt | 900.0 | | + | INCOME | 404001 | Interest Income Charge Off | 4.0 | | + 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 | + | 03 February 2024 | Accrual | 4.0 | 0.0 | 4.0 | 0.0 | 0.0 | 0.0 | false | + | 03 February 2024 | Charge-off | 904.0 | 900.0 | 4.0 | 0.0 | 0.0 | 0.0 | false | + | 03 February 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 "03 February 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | EXPENSE | 744007 | Credit Loss/Bad Debt | | 100.0 | + | LIABILITY | 145024 | Deferred Capitalized Income | 100.0 | | + Then LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent is raised on "03 February 2024" + + @TestRailId:C3664 + Scenario: As a user I want to add capitalized income to a progressive loan after disbursement and then undo the charge-off transaction with "delinquent" reason - amortization in case of loan charge-off event should also be reversed + 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_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC_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 | + Then Admin can successfully set Fraud flag to the loan + When Admin sets the business date to "03 February 2024" + And Admin does charge-off the loan with reason "DELINQUENT" on "03 February 2024" + Then Loan Transactions tab has a "CHARGE_OFF" transaction with date "03 February 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 900.0 | + | ASSET | 112603 | Interest/Fee Receivable | | 4.0 | + | EXPENSE | 744007 | Credit Loss/Bad Debt | 900.0 | | + | INCOME | 404001 | Interest Income Charge Off | 4.0 | | + 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 | + | 03 February 2024 | Accrual | 4.0 | 0.0 | 4.0 | 0.0 | 0.0 | 0.0 | false | + | 03 February 2024 | Charge-off | 904.0 | 900.0 | 4.0 | 0.0 | 0.0 | 0.0 | false | + | 03 February 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 "03 February 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | EXPENSE | 744007 | Credit Loss/Bad Debt | | 100.0 | + | LIABILITY | 145024 | Deferred Capitalized Income | 100.0 | | + Then LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent is raised on "03 February 2024" + Then Admin does a charge-off undo the loan + Then Loan Transactions tab has a "CHARGE_OFF" transaction with date "03 February 2024" which has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | ASSET | 112601 | Loans Receivable | | 900.0 | + | ASSET | 112603 | Interest/Fee Receivable | | 4.0 | + | EXPENSE | 744007 | Credit Loss/Bad Debt | 900.0 | | + | INCOME | 404001 | Interest Income Charge Off | 4.0 | | + | ASSET | 112601 | Loans Receivable | 900.0 | | + | ASSET | 112603 | Interest/Fee Receivable | 4.0 | | + | EXPENSE | 744007 | Credit Loss/Bad Debt | | 900.0 | + | INCOME | 404001 | Interest Income Charge Off | | 4.0 | + 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 | + | 03 February 2024 | Accrual | 4.0 | 0.0 | 4.0 | 0.0 | 0.0 | 0.0 | false | + | 03 February 2024 | Charge-off | 904.0 | 900.0 | 4.0 | 0.0 | 0.0 | 0.0 | true | + Then Reversed loan capitalized income amortization transaction has the following Journal entries: + | Type | Account code | Account name | Debit | Credit | + | EXPENSE | 744007 | Credit Loss/Bad Debt | | 100.0 | + | LIABILITY | 145024 | Deferred Capitalized Income | 100.0 | | + | EXPENSE | 744007 | Credit Loss/Bad Debt | 100.0 | | + | LIABILITY | 145024 | Deferred Capitalized Income | | 100.0 | diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 47b4bb5b1..4f22c7489 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -2167,7 +2167,8 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom { public LoanTransaction getLastUserTransaction() { return getLoanTransactions().stream() // - .filter(t -> t.isNotReversed() && !(t.isAccrual() || t.isAccrualAdjustment() || t.isIncomePosting())) // + .filter(t -> t.isNotReversed() + && !(t.isAccrual() || t.isAccrualAdjustment() || t.isIncomePosting() || t.isCapitalizedIncomeAmortization())) // .reduce((first, second) -> second) // .orElse(null); } 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 dc34a3493..9ca1949e6 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 @@ -646,6 +646,10 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom 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/service/LoanCapitalizedIncomeAmortizationEventService.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationEventService.java index af9cf3a3f..b98915775 100644 --- 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 @@ -24,9 +24,12 @@ 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.domain.loan.transaction.LoanChargeOffPostBusinessEvent; +import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoChargeOffBusinessEvent; 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; +import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction; @Slf4j @RequiredArgsConstructor @@ -39,6 +42,9 @@ public class LoanCapitalizedIncomeAmortizationEventService { public void addListeners() { businessEventNotifierService.addPreBusinessEventListener(LoanCloseBusinessEvent.class, new LoanCloseListener()); businessEventNotifierService.addPostBusinessEventListener(LoanBalanceChangedBusinessEvent.class, new LoanBalanceChangedListener()); + businessEventNotifierService.addPostBusinessEventListener(LoanChargeOffPostBusinessEvent.class, new LoanChargeOffEventListener()); + businessEventNotifierService.addPostBusinessEventListener(LoanUndoChargeOffBusinessEvent.class, + new LoanUndoChargeOffEventListener()); } private final class LoanCloseListener implements BusinessEventListener { @@ -68,4 +74,32 @@ public class LoanCapitalizedIncomeAmortizationEventService { } } } + + private final class LoanChargeOffEventListener implements BusinessEventListener { + + @Override + public void onBusinessEvent(final LoanChargeOffPostBusinessEvent event) { + final LoanTransaction loanTransaction = event.get(); + final Loan loan = loanTransaction.getLoan(); + if (loan.getLoanProductRelatedDetail().isEnableIncomeCapitalization() && loan.isChargedOff() && loanTransaction.isChargeOff()) { + log.debug("Loan charge-off on capitalized income amortization for loan {}", loan.getId()); + loanCapitalizedIncomeAmortizationProcessingService.processCapitalizedIncomeAmortizationOnLoanChargeOff(loan); + } + } + } + + private final class LoanUndoChargeOffEventListener implements BusinessEventListener { + + @Override + public void onBusinessEvent(final LoanUndoChargeOffBusinessEvent event) { + final LoanTransaction loanTransaction = event.get(); + final Loan loan = loanTransaction.getLoan(); + final LoanStatus status = loan.getStatus(); + if (loan.getLoanProductRelatedDetail().isEnableIncomeCapitalization() && loanTransaction.getTypeOf().isChargeOff() + && !(loan.isChargedOff() || status.isClosedObligationsMet() || status.isClosedWrittenOff() || status.isOverpaid())) { + log.debug("Loan undo charge-off on capitalized income amortization for loan {}", loan.getId()); + loanCapitalizedIncomeAmortizationProcessingService.processCapitalizedIncomeAmortizationOnLoanUndoChargeOff(loanTransaction); + } + } + } } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingServiceImpl.java index 9feba20c5..373b47902 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanCapitalizedIncomeAmortizationProcessingServiceImpl.java @@ -20,9 +20,12 @@ package org.apache.fineract.portfolio.loanaccount.service; import java.math.BigDecimal; import java.time.LocalDate; +import java.util.List; import lombok.RequiredArgsConstructor; import org.apache.fineract.infrastructure.configuration.domain.ConfigurationDomainService; import org.apache.fineract.infrastructure.core.domain.ExternalId; +import org.apache.fineract.infrastructure.core.service.DateUtils; +import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent; 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; @@ -42,31 +45,84 @@ public class LoanCapitalizedIncomeAmortizationProcessingServiceImpl implements L private final LoanTransactionRepository loanTransactionRepository; private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository; private final BusinessEventNotifierService businessEventNotifierService; + private final LoanJournalEntryPoster journalEntryPoster; @Override @Transactional public void processCapitalizedIncomeAmortizationOnLoanClosure(@NotNull final Loan loan) { - final LocalDate finalAmortizationTransactionDate = getFinalCapitalizedIncomeAmortizationTransactionDate(loan); - ExternalId externalId = ExternalId.empty(); - BigDecimal totalUnrecognizedAmountCapitalizedIncomeAmount = BigDecimal.ZERO; + final LocalDate transactionDate = getFinalCapitalizedIncomeAmortizationTransactionDate(loan); + final LoanTransaction amortizationTransaction = createCapitalizedIncomeAmortizationTransaction(loan, transactionDate, false); + businessEventNotifierService + .notifyPostBusinessEvent(new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(amortizationTransaction)); + } - final boolean isExternalIdAutoGenerationEnabled = configurationDomainService.isExternalIdAutoGenerationEnabled(); - if (isExternalIdAutoGenerationEnabled) { + @Override + @Transactional + public void processCapitalizedIncomeAmortizationOnLoanChargeOff(@NotNull final Loan loan) { + final List existingTransactionIds = loan.findExistingTransactionIds(); + final List existingReversedTransactionIds = loan.findExistingReversedTransactionIds(); + + LocalDate transactionDate = loan.getChargedOffOnDate(); + if (transactionDate == null) { + transactionDate = DateUtils.getBusinessLocalDate(); + } + + final LoanTransaction amortizationTransaction = createCapitalizedIncomeAmortizationTransaction(loan, transactionDate, true); + journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); + businessEventNotifierService + .notifyPostBusinessEvent(new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(amortizationTransaction)); + } + + private LoanTransaction createCapitalizedIncomeAmortizationTransaction(final Loan loan, final LocalDate transactionDate, + final boolean isChargeOff) { + ExternalId externalId = ExternalId.empty(); + BigDecimal totalUnrecognizedAmount = BigDecimal.ZERO; + + if (configurationDomainService.isExternalIdAutoGenerationEnabled()) { externalId = ExternalId.generate(); } - for (LoanCapitalizedIncomeBalance balance : loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loan.getId())) { - totalUnrecognizedAmountCapitalizedIncomeAmount = totalUnrecognizedAmountCapitalizedIncomeAmount - .add(balance.getUnrecognizedAmount()); + final List balances = loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loan.getId()); + for (LoanCapitalizedIncomeBalance balance : balances) { + final BigDecimal unrecognizedAmount = balance.getUnrecognizedAmount(); + totalUnrecognizedAmount = totalUnrecognizedAmount.add(unrecognizedAmount); + if (isChargeOff) { + balance.setChargedOffAmount(unrecognizedAmount); + } 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)); + final LoanTransaction amortizationTransaction = LoanTransaction.capitalizedIncomeAmortization(loan, loan.getOffice(), + transactionDate, totalUnrecognizedAmount, externalId); + + loan.addLoanTransaction(amortizationTransaction); + loanTransactionRepository.saveAndFlush(amortizationTransaction); + + return amortizationTransaction; + } + + @Override + @Transactional + public void processCapitalizedIncomeAmortizationOnLoanUndoChargeOff(@NotNull final LoanTransaction loanTransaction) { + final Loan loan = loanTransaction.getLoan(); + final List existingTransactionIds = loan.findExistingTransactionIds(); + final List existingReversedTransactionIds = loan.findExistingReversedTransactionIds(); + + loan.getLoanTransactions().stream().filter(LoanTransaction::isCapitalizedIncomeAmortization) + .filter(transaction -> transaction.getTransactionDate().equals(loanTransaction.getTransactionDate())) + .forEach(transaction -> { + transaction.reverse(); + final LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(transaction); + businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data)); + + }); + + for (LoanCapitalizedIncomeBalance balance : loanCapitalizedIncomeBalanceRepository.findAllByLoanId(loan.getId())) { + balance.setUnrecognizedAmount(balance.getChargedOffAmount()); + balance.setChargedOffAmount(BigDecimal.ZERO); + } + + journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); } private LocalDate getFinalCapitalizedIncomeAmortizationTransactionDate(final Loan loan) {