FINERACT-2232: Capitalized income - Daily amortization
This commit is contained in:
+3
-2
@@ -49,8 +49,9 @@ public class WorkFlowJobHelper {
|
||||
new BusinessStep().stepName("UPDATE_LOAN_ARREARS_AGING").order(6L), //
|
||||
new BusinessStep().stepName("ADD_PERIODIC_ACCRUAL_ENTRIES").order(7L), //
|
||||
new BusinessStep().stepName("ACCRUAL_ACTIVITY_POSTING").order(8L), //
|
||||
new BusinessStep().stepName("LOAN_INTEREST_RECALCULATION").order(9L), //
|
||||
new BusinessStep().stepName("EXTERNAL_ASSET_OWNER_TRANSFER").order(10L)//
|
||||
new BusinessStep().stepName("CAPITALIZED_INCOME_AMORTIZATION").order(9L), //
|
||||
new BusinessStep().stepName("LOAN_INTEREST_RECALCULATION").order(10L), //
|
||||
new BusinessStep().stepName("EXTERNAL_ASSET_OWNER_TRANSFER").order(11L)//
|
||||
);
|
||||
BusinessStepRequest request = new BusinessStepRequest().businessSteps(businessSteps);
|
||||
Response<Void> response = businessStepConfigurationApi.updateJobBusinessStepConfig(WORKFLOW_NAME_LOAN_CLOSE_OF_BUSINESS, request)
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* 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.cob.loan;
|
||||
|
||||
import jakarta.transaction.Transactional;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.fineract.accounting.journalentry.service.JournalEntryWritePlatformService;
|
||||
import org.apache.fineract.infrastructure.core.service.DateUtils;
|
||||
import org.apache.fineract.infrastructure.core.service.ExternalIdFactory;
|
||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||
import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.organisation.monetary.domain.MoneyHelper;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeDataDTO;
|
||||
import org.apache.fineract.portfolio.loanaccount.data.AccountingBridgeLoanTransactionDTO;
|
||||
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.mapper.LoanAccountingBridgeMapper;
|
||||
import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.util.CapitalizedIncomeAmortizationUtil;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class CapitalizedIncomeAmortizationBusinessStep implements LoanCOBBusinessStep {
|
||||
|
||||
private final LoanCapitalizedIncomeBalanceRepository capitalizedIncomeBalanceRepository;
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final JournalEntryWritePlatformService journalEntryWritePlatformService;
|
||||
private final LoanAccountingBridgeMapper loanAccountingBridgeMapper;
|
||||
private final LoanTransactionRepository loanTransactionRepository;
|
||||
private final ExternalIdFactory externalIdFactory;
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public Loan execute(Loan loan) {
|
||||
MathContext mc = MoneyHelper.getMathContext();
|
||||
LocalDate businessDate = DateUtils.getBusinessLocalDate();
|
||||
List<LoanCapitalizedIncomeBalance> balances = capitalizedIncomeBalanceRepository.findAllByLoanId(loan.getId());
|
||||
|
||||
BigDecimal totalAmortizationAmount = BigDecimal.ZERO;
|
||||
for (LoanCapitalizedIncomeBalance balance : balances) {
|
||||
long daysUntilMaturity = businessDate.until(loan.getMaturityDate(), ChronoUnit.DAYS);
|
||||
Money dailyAmortization = CapitalizedIncomeAmortizationUtil.calculateDailyAmortization(
|
||||
loan.getLoanProductRelatedDetail().getCapitalizedIncomeStrategy(), daysUntilMaturity, balance.getUnrecognizedAmount(),
|
||||
loan.getCurrency().toData());
|
||||
totalAmortizationAmount = totalAmortizationAmount.add(dailyAmortization.getAmount(), mc);
|
||||
balance.setUnrecognizedAmount(balance.getUnrecognizedAmount().subtract(dailyAmortization.getAmount(), mc));
|
||||
}
|
||||
|
||||
capitalizedIncomeBalanceRepository.saveAll(balances);
|
||||
|
||||
if (MathUtil.isGreaterThanZero(totalAmortizationAmount)) {
|
||||
LoanTransaction transaction = LoanTransaction.capitalizedIncomeAmortization(loan, loan.getOffice(), businessDate,
|
||||
totalAmortizationAmount, externalIdFactory.create());
|
||||
loan.addLoanTransaction(transaction);
|
||||
|
||||
transaction = loanTransactionRepository.save(transaction);
|
||||
loanTransactionRepository.flush();
|
||||
|
||||
final AccountingBridgeLoanTransactionDTO transactionDTO = loanAccountingBridgeMapper.mapToLoanTransactionData(transaction,
|
||||
loan.getCurrency().getCode());
|
||||
final AccountingBridgeDataDTO accountingBridgeData = new AccountingBridgeDataDTO(loan.getId(), loan.getLoanProduct().getId(),
|
||||
loan.getOfficeId(), loan.getCurrencyCode(), loan.getSummary().getTotalInterestCharged(),
|
||||
loan.isNoneOrCashOrUpfrontAccrualAccountingEnabledOnLoanProduct(),
|
||||
loan.isUpfrontAccrualAccountingEnabledOnLoanProduct(), loan.isPeriodicAccrualAccountingEnabledOnLoanProduct(), false,
|
||||
false, false, null, loan.isClosedWrittenOff(), List.of(transactionDTO));
|
||||
this.journalEntryWritePlatformService.createJournalEntriesForLoan(accountingBridgeData);
|
||||
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanCapitalizedIncomeAmortizationTransactionCreatedBusinessEvent(transaction));
|
||||
}
|
||||
|
||||
return loan;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEnumStyledName() {
|
||||
return "CAPITALIZED_INCOME_AMORTIZATION";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHumanReadableName() {
|
||||
return "Capitalized income amortization";
|
||||
}
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 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.integrationtests;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.apache.fineract.client.models.PostClientsResponse;
|
||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||
import org.apache.fineract.integrationtests.common.BusinessStepHelper;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class LoanCapitalizedIncomeTest extends BaseLoanIntegrationTest {
|
||||
|
||||
@BeforeAll
|
||||
public void setup() {
|
||||
new BusinessStepHelper().updateSteps("LOAN_CLOSE_OF_BUSINESS", "APPLY_CHARGE_TO_OVERDUE_LOANS", "LOAN_DELINQUENCY_CLASSIFICATION",
|
||||
"CHECK_LOAN_REPAYMENT_DUE", "CHECK_LOAN_REPAYMENT_OVERDUE", "CHECK_DUE_INSTALLMENTS", "UPDATE_LOAN_ARREARS_AGING",
|
||||
"ADD_PERIODIC_ACCRUAL_ENTRIES", "ACCRUAL_ACTIVITY_POSTING", "CAPITALIZED_INCOME_AMORTIZATION",
|
||||
"LOAN_INTEREST_RECALCULATION", "EXTERNAL_ASSET_OWNER_TRANSFER");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoanCapitalizedIncomeAmortization() {
|
||||
final AtomicReference<Long> loanIdRef = new AtomicReference<>();
|
||||
|
||||
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||
|
||||
final PostLoanProductsResponse loanProductsResponse = loanProductHelper
|
||||
.createLoanProduct(create4IProgressive().enableIncomeCapitalization(true)
|
||||
.capitalizedIncomeCalculationType(PostLoanProductsRequest.CapitalizedIncomeCalculationTypeEnum.FLAT)
|
||||
.capitalizedIncomeStrategy(PostLoanProductsRequest.CapitalizedIncomeStrategyEnum.EQUAL_AMORTIZATION)
|
||||
.deferredIncomeLiabilityAccountId(deferredIncomeLiabilityAccount.getAccountID().longValue())
|
||||
.incomeFromCapitalizationAccountId(feeIncomeAccount.getAccountID().longValue())
|
||||
.capitalizedIncomeType(PostLoanProductsRequest.CapitalizedIncomeTypeEnum.FEE));
|
||||
|
||||
runAt("1 January 2024", () -> {
|
||||
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "1 January 2024",
|
||||
500.0, 7.0, 3, null);
|
||||
loanIdRef.set(loanId);
|
||||
|
||||
disburseLoan(loanId, BigDecimal.valueOf(100), "1 January 2024");
|
||||
loanTransactionHelper.addCapitalizedIncome(loanId, "1 January 2024", 50.0);
|
||||
});
|
||||
runAt("2 January 2024", () -> {
|
||||
Long loanId = loanIdRef.get();
|
||||
executeInlineCOB(loanId);
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(100.0, "Disbursement", "01 January 2024"), //
|
||||
transaction(50.0, "Capitalized Income", "01 January 2024"), //
|
||||
transaction(0.55, "Capitalized Income Amortization", "01 January 2024") //
|
||||
);
|
||||
});
|
||||
runAt("3 January 2024", () -> {
|
||||
Long loanId = loanIdRef.get();
|
||||
executeInlineCOB(loanId);
|
||||
|
||||
verifyTransactions(loanId, //
|
||||
transaction(100.0, "Disbursement", "01 January 2024"), //
|
||||
transaction(50.0, "Capitalized Income", "01 January 2024"), //
|
||||
transaction(0.55, "Capitalized Income Amortization", "01 January 2024"), //
|
||||
transaction(0.02, "Accrual", "02 January 2024"), //
|
||||
transaction(0.55, "Capitalized Income Amortization", "02 January 2024") //
|
||||
);
|
||||
|
||||
verifyJournalEntries(loanId, //
|
||||
journalEntry(100, loansReceivableAccount, "DEBIT"), //
|
||||
journalEntry(100, fundSource, "CREDIT"), //
|
||||
journalEntry(50, loansReceivableAccount, "DEBIT"), //
|
||||
journalEntry(50, deferredIncomeLiabilityAccount, "CREDIT"), //
|
||||
journalEntry(0.55, deferredIncomeLiabilityAccount, "DEBIT"), //
|
||||
journalEntry(0.55, feeIncomeAccount, "CREDIT"), //
|
||||
journalEntry(0.02, interestReceivableAccount, "DEBIT"), //
|
||||
journalEntry(0.02, interestIncomeAccount, "CREDIT"), //
|
||||
journalEntry(0.55, deferredIncomeLiabilityAccount, "DEBIT"), //
|
||||
journalEntry(0.55, feeIncomeAccount, "CREDIT") //
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
+10
@@ -909,6 +909,16 @@ public class LoanTransactionHelper {
|
||||
"repayment"));
|
||||
}
|
||||
|
||||
public PostLoansLoanIdTransactionsResponse addCapitalizedIncome(final Long loanId, final PostLoansLoanIdTransactionsRequest request) {
|
||||
return Calls
|
||||
.ok(FineractClientHelper.getFineractClient().loanTransactions.executeLoanTransaction(loanId, request, "capitalizedIncome"));
|
||||
}
|
||||
|
||||
public PostLoansLoanIdTransactionsResponse addCapitalizedIncome(final Long loanId, final String transactionDate, final double amount) {
|
||||
return addCapitalizedIncome(loanId, new PostLoansLoanIdTransactionsRequest().transactionAmount(amount)
|
||||
.transactionDate(transactionDate).dateFormat("dd MMMM yyyy").locale("en"));
|
||||
}
|
||||
|
||||
// TODO: Rewrite to use fineract-client instead!
|
||||
// Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long,
|
||||
// org.apache.fineract.client.models.PostLoansLoanIdRequest)
|
||||
|
||||
Reference in New Issue
Block a user