FINERACT-2232: Capitalized income - amortization in case of Charge-off event
This commit is contained in:
committed by
Arnold Gálovics
parent
39062ad1a1
commit
4861ad19fd
+77
@@ -173,6 +173,16 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
|
||||
private void createJournalEntriesForCapitalizedIncomeAmortization(final LoanDTO loanDTO, final LoanTransactionDTO loanTransactionDTO,
|
||||
final Office office) {
|
||||
final boolean isMarkedAsChargeOff = loanDTO.isMarkedAsChargeOff();
|
||||
if (isMarkedAsChargeOff) {
|
||||
createJournalEntriesForChargeOffLoanCapitalizedIncomeAmortization(loanDTO, loanTransactionDTO, office);
|
||||
} else {
|
||||
createJournalEntriesForLoanCapitalizedIncomeAmortization(loanDTO, loanTransactionDTO, office);
|
||||
}
|
||||
}
|
||||
|
||||
private void createJournalEntriesForLoanCapitalizedIncomeAmortization(final LoanDTO loanDTO,
|
||||
final LoanTransactionDTO loanTransactionDTO, final Office office) {
|
||||
// loan properties
|
||||
final Long loanProductId = loanDTO.getLoanProductId();
|
||||
final Long loanId = loanDTO.getLoanId();
|
||||
@@ -218,6 +228,73 @@ public class AccrualBasedAccountingProcessorForLoan implements AccountingProcess
|
||||
}
|
||||
}
|
||||
|
||||
private void createJournalEntriesForChargeOffLoanCapitalizedIncomeAmortization(final LoanDTO loanDTO,
|
||||
final LoanTransactionDTO loanTransactionDTO, final Office office) {
|
||||
// loan properties
|
||||
final Long loanProductId = loanDTO.getLoanProductId();
|
||||
final boolean isMarkedFraud = loanDTO.isMarkedAsFraud();
|
||||
final Long loanId = loanDTO.getLoanId();
|
||||
final String currencyCode = loanDTO.getCurrencyCode();
|
||||
|
||||
// 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();
|
||||
final Long chargeOffReasonCodeValue = loanDTO.getChargeOffReasonCodeValue();
|
||||
|
||||
final ProductToGLAccountMapping mapping = chargeOffReasonCodeValue != null
|
||||
? helper.getChargeOffMappingByCodeValue(loanProductId, PortfolioProductType.LOAN, chargeOffReasonCodeValue)
|
||||
: null;
|
||||
|
||||
if (mapping != null) {
|
||||
final GLAccount accountDebit = this.helper.getLinkedGLAccountForLoanProduct(loanProductId,
|
||||
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), paymentTypeId);
|
||||
// handle interest payment
|
||||
if (MathUtil.isGreaterThanZero(interestAmount)) {
|
||||
glAccountBalanceHolder.addToCredit(mapping.getGlAccount(), interestAmount);
|
||||
glAccountBalanceHolder.addToDebit(accountDebit, interestAmount);
|
||||
}
|
||||
// handle fees payment
|
||||
if (MathUtil.isGreaterThanZero(feesAmount)) {
|
||||
glAccountBalanceHolder.addToCredit(mapping.getGlAccount(), feesAmount);
|
||||
glAccountBalanceHolder.addToDebit(accountDebit, feesAmount);
|
||||
}
|
||||
} else {
|
||||
final AccrualAccountsForLoan creditAccountType = isMarkedFraud ? AccrualAccountsForLoan.CHARGE_OFF_FRAUD_EXPENSE
|
||||
: AccrualAccountsForLoan.CHARGE_OFF_EXPENSE;
|
||||
// handle interest payment
|
||||
if (MathUtil.isGreaterThanZero(interestAmount)) {
|
||||
populateCreditDebitMaps(loanProductId, interestAmount, paymentTypeId, creditAccountType.getValue(),
|
||||
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), glAccountBalanceHolder);
|
||||
}
|
||||
// handle fees payment
|
||||
if (MathUtil.isGreaterThanZero(feesAmount)) {
|
||||
populateCreditDebitMaps(loanProductId, feesAmount, paymentTypeId, creditAccountType.getValue(),
|
||||
AccrualAccountsForLoan.DEFERRED_INCOME_LIABILITY.getValue(), glAccountBalanceHolder);
|
||||
}
|
||||
}
|
||||
|
||||
// create credit entries
|
||||
for (Map.Entry<Long, BigDecimal> creditEntry : glAccountBalanceHolder.getCreditBalances().entrySet()) {
|
||||
if (MathUtil.isGreaterThanZero(creditEntry.getValue())) {
|
||||
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(creditEntry.getKey());
|
||||
this.helper.createCreditJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate,
|
||||
creditEntry.getValue(), glAccount);
|
||||
}
|
||||
}
|
||||
// create debit entries
|
||||
for (Map.Entry<Long, BigDecimal> debitEntry : glAccountBalanceHolder.getDebitBalances().entrySet()) {
|
||||
if (MathUtil.isGreaterThanZero(debitEntry.getValue())) {
|
||||
GLAccount glAccount = glAccountBalanceHolder.getGlAccountMap().get(debitEntry.getKey());
|
||||
this.helper.createDebitJournalEntryForLoan(office, currencyCode, loanId, transactionId, transactionDate,
|
||||
debitEntry.getValue(), glAccount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createJournalEntriesForInterestPaymentWaiverOrInterestRefund(LoanDTO loanDTO, LoanTransactionDTO loanTransactionDTO,
|
||||
Office office) {
|
||||
final Long loanProductId = loanDTO.getLoanProductId();
|
||||
|
||||
+34
@@ -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<LoanCloseBusinessEvent> {
|
||||
@@ -68,4 +74,32 @@ public class LoanCapitalizedIncomeAmortizationEventService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class LoanChargeOffEventListener implements BusinessEventListener<LoanChargeOffPostBusinessEvent> {
|
||||
|
||||
@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<LoanUndoChargeOffBusinessEvent> {
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+70
-14
@@ -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<Long> existingTransactionIds = loan.findExistingTransactionIds();
|
||||
final List<Long> 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<LoanCapitalizedIncomeBalance> 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<Long> existingTransactionIds = loan.findExistingTransactionIds();
|
||||
final List<Long> 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) {
|
||||
|
||||
Reference in New Issue
Block a user