FINERACT-2311: Buy down fee - final amortization
- early repayment - write off - charge-off
This commit is contained in:
@@ -327,12 +327,17 @@ Feature:Feature: Buy Down Fees
|
||||
When Loan Pay-off is made on "1 March 2024"
|
||||
Then Loan's all installments have obligations met
|
||||
Then Loan Transactions tab has the following data:
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
||||
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
|
||||
| 01 January 2024 | Buy Down Fee | 50.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
|
||||
| 01 February 2024 | Repayment | 33.72 | 33.14 | 0.58 | 0.0 | 0.0 | 66.86 | false |
|
||||
| 01 March 2024 | Repayment | 67.25 | 66.86 | 0.39 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 01 March 2024 | Accrual | 0.97 | 0.0 | 0.97 | 0.0 | 0.0 | 0.0 | false |
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
||||
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
|
||||
| 01 January 2024 | Buy Down Fee | 50.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
|
||||
| 01 February 2024 | Repayment | 33.72 | 33.14 | 0.58 | 0.0 | 0.0 | 66.86 | false |
|
||||
| 01 March 2024 | Repayment | 67.25 | 66.86 | 0.39 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 01 March 2024 | Accrual | 0.97 | 0.0 | 0.97 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 01 March 2024 | Buy Down Fee Amortization | 50.0 | 0.0 | 50.0 | 0.0 | 0.0 | 0.0 | false |
|
||||
And Loan Transactions tab has a "BUY_DOWN_FEE_AMORTIZATION" transaction with date "01 March 2024" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| INCOME | 450281 | Income From Buy Down | | 50.0 |
|
||||
| LIABILITY | 145024 | Deferred Capitalized Income | 50.0 | |
|
||||
|
||||
@TestRailId:C3828
|
||||
Scenario: Verify loan with Buy Down fees and early payoff and daily amortization - UC2.2
|
||||
@@ -508,6 +513,11 @@ Feature:Feature: Buy Down Fees
|
||||
|
||||
| 01 March 2024 | Repayment | 67.25 | 66.86 | 0.39 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 01 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 01 March 2024 | Buy Down Fee Amortization | 17.03 | 0.0 | 17.03 | 0.0 | 0.0 | 0.0 | false |
|
||||
And Loan Transactions tab has a "BUY_DOWN_FEE_AMORTIZATION" transaction with date "01 March 2024" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| INCOME | 450281 | Income From Buy Down | | 17.03 |
|
||||
| LIABILITY | 145024 | Deferred Capitalized Income | 17.03 | |
|
||||
|
||||
@TestRailId:C3772
|
||||
Scenario: Verify loan with Buy Down fees and charge-off - UC3.1
|
||||
@@ -556,12 +566,18 @@ Feature:Feature: Buy Down Fees
|
||||
| EXPENSE | 744007 | Credit Loss/Bad Debt | 66.86 | |
|
||||
| INCOME | 404001 | Interest Income Charge Off | 0.59 | |
|
||||
Then Loan Transactions tab has the following data:
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
||||
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
|
||||
| 01 January 2024 | Buy Down Fee | 50.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
|
||||
| 01 February 2024 | Repayment | 33.72 | 33.14 | 0.58 | 0.0 | 0.0 | 66.86 | false |
|
||||
| 01 March 2024 | Accrual | 0.97 | 0.0 | 0.97 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 01 March 2024 | Charge-off | 67.45 | 66.86 | 0.59 | 0.0 | 0.0 | 0.0 | false |
|
||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
||||
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
|
||||
| 01 January 2024 | Buy Down Fee | 50.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
|
||||
| 01 February 2024 | Repayment | 33.72 | 33.14 | 0.58 | 0.0 | 0.0 | 66.86 | false |
|
||||
| 01 March 2024 | Buy Down Fee Amortization | 33.52 | 0.0 | 33.52 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 01 March 2024 | Accrual | 0.97 | 0.0 | 0.97 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 01 March 2024 | Charge-off | 67.45 | 66.86 | 0.59 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 01 March 2024 | Buy Down Fee Amortization | 16.48 | 0.0 | 16.48 | 0.0 | 0.0 | 0.0 | false |
|
||||
And Loan Transactions tab has a "BUY_DOWN_FEE_AMORTIZATION" transaction with date "01 March 2024" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| EXPENSE | 744007 | Credit Loss/Bad Debt | | 16.48 |
|
||||
| LIABILITY | 145024 | Deferred Capitalized Income | 16.48 | |
|
||||
|
||||
When Loan Pay-off is made on "1 March 2024"
|
||||
Then Loan's all installments have obligations met
|
||||
@@ -745,8 +761,14 @@ Feature:Feature: Buy Down Fees
|
||||
| 29 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 29 February 2024 | Buy Down Fee Amortization | 0.55 | 0.0 | 0.55 | 0.0 | 0.0 | 0.0 | false |
|
||||
|
||||
| 01 March 2024 | Buy Down Fee Amortization | 0.55 | 0.0 | 0.55 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 01 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 01 March 2024 | Charge-off | 67.45 | 66.86 | 0.59 | 0.0 | 0.0 | 0.0 | false |
|
||||
| 01 March 2024 | Buy Down Fee Amortization | 16.48 | 0.0 | 16.48 | 0.0 | 0.0 | 0.0 | false |
|
||||
And Loan Transactions tab has a "BUY_DOWN_FEE_AMORTIZATION" transaction with date "01 March 2024" which has the following Journal entries:
|
||||
| Type | Account code | Account name | Debit | Credit |
|
||||
| EXPENSE | 744007 | Credit Loss/Bad Debt | | 16.48 |
|
||||
| LIABILITY | 145024 | Deferred Capitalized Income | 16.48 | |
|
||||
|
||||
When Loan Pay-off is made on "1 March 2024"
|
||||
Then Loan's all installments have obligations met
|
||||
|
||||
+3
-2
@@ -1755,8 +1755,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
||||
|
||||
public LoanTransaction getLastUserTransaction() {
|
||||
return getLoanTransactions().stream() //
|
||||
.filter(t -> t.isNotReversed()
|
||||
&& !(t.isAccrual() || t.isAccrualAdjustment() || t.isIncomePosting() || t.isCapitalizedIncomeAmortization())) //
|
||||
.filter(t -> t.isNotReversed() && !(t.isAccrual() || t.isAccrualAdjustment() || t.isIncomePosting()
|
||||
|| t.isCapitalizedIncomeAmortization() || t.isCapitalizedIncomeAmortizationAdjustment()
|
||||
|| t.isBuyDownFeeAmortization() || t.isBuyDownFeeAmortizationAdjustment())) //
|
||||
.reduce((first, second) -> second) //
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
+16
@@ -706,6 +706,10 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
||||
return LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION.equals(getTypeOf()) && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isCapitalizedIncomeAmortizationAdjustment() {
|
||||
return LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT.equals(getTypeOf()) && isNotReversed();
|
||||
}
|
||||
|
||||
public boolean isCapitalizedIncomeAdjustment() {
|
||||
return LoanTransactionType.CAPITALIZED_INCOME_ADJUSTMENT.equals(getTypeOf()) && isNotReversed();
|
||||
}
|
||||
@@ -998,4 +1002,16 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
||||
public boolean isBuyDownFee() {
|
||||
return LoanTransactionType.BUY_DOWN_FEE.equals(this.typeOf);
|
||||
}
|
||||
|
||||
public boolean isBuyDownFeeAdjustment() {
|
||||
return LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT.equals(this.typeOf);
|
||||
}
|
||||
|
||||
public boolean isBuyDownFeeAmortization() {
|
||||
return LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION.equals(this.typeOf);
|
||||
}
|
||||
|
||||
public boolean isBuyDownFeeAmortizationAdjustment() {
|
||||
return LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT.equals(this.typeOf);
|
||||
}
|
||||
}
|
||||
|
||||
+7
@@ -20,9 +20,16 @@ package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
public interface LoanBuyDownFeeAmortizationProcessingService {
|
||||
|
||||
void processBuyDownFeeAmortizationTillDate(@NonNull Loan loan, @NonNull LocalDate tillDate, boolean addJournal);
|
||||
|
||||
void processBuyDownFeeAmortizationOnLoanClosure(@NonNull Loan loan, boolean addJournal);
|
||||
|
||||
void processBuyDownFeeAmortizationOnLoanChargeOff(@NonNull Loan loan, @NonNull LoanTransaction chargeOffTransaction);
|
||||
|
||||
void processBuyDownFeeAmortizationOnLoanUndoChargeOff(@NonNull LoanTransaction loanTransaction);
|
||||
}
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 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.core.service.DateUtils;
|
||||
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.LoanChargeOffPreBusinessEvent;
|
||||
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
|
||||
public class LoanBuyDownFeeAmortizationEventService {
|
||||
|
||||
private final BusinessEventNotifierService businessEventNotifierService;
|
||||
private final LoanBuyDownFeeAmortizationProcessingService loanBuyDownFeeAmortizationProcessingService;
|
||||
|
||||
@PostConstruct
|
||||
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());
|
||||
businessEventNotifierService.addPreBusinessEventListener(LoanChargeOffPreBusinessEvent.class, new LoanChargeOffPreEventListener());
|
||||
}
|
||||
|
||||
private final class LoanCloseListener implements BusinessEventListener<LoanCloseBusinessEvent> {
|
||||
|
||||
@Override
|
||||
public void onBusinessEvent(final LoanCloseBusinessEvent event) {
|
||||
final Loan loan = event.get();
|
||||
final LoanStatus status = loan.getStatus();
|
||||
if (loan.getLoanProductRelatedDetail().isEnableBuyDownFee()
|
||||
&& (status.isClosedObligationsMet() || status.isClosedWrittenOff() || status.isOverpaid())) {
|
||||
log.debug("Loan closure on buy down fee amortization for loan {}", loan.getId());
|
||||
loanBuyDownFeeAmortizationProcessingService.processBuyDownFeeAmortizationOnLoanClosure(loan, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class LoanBalanceChangedListener implements BusinessEventListener<LoanBalanceChangedBusinessEvent> {
|
||||
|
||||
@Override
|
||||
public void onBusinessEvent(final LoanBalanceChangedBusinessEvent event) {
|
||||
final Loan loan = event.get();
|
||||
final LoanStatus status = loan.getStatus();
|
||||
if (loan.getLoanProductRelatedDetail().isEnableBuyDownFee()
|
||||
&& (status.isClosedObligationsMet() || status.isClosedWrittenOff() || status.isOverpaid())) {
|
||||
log.debug("Loan balance change on buy down fee amortization for loan {}", loan.getId());
|
||||
loanBuyDownFeeAmortizationProcessingService.processBuyDownFeeAmortizationOnLoanClosure(loan, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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().isEnableBuyDownFee() && loan.isChargedOff() && loanTransaction.isChargeOff()) {
|
||||
log.debug("Loan charge-off on buy down fee amortization for loan {}", loan.getId());
|
||||
loanBuyDownFeeAmortizationProcessingService.processBuyDownFeeAmortizationOnLoanChargeOff(loan, loanTransaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class LoanChargeOffPreEventListener implements BusinessEventListener<LoanChargeOffPreBusinessEvent> {
|
||||
|
||||
@Override
|
||||
public void onBusinessEvent(final LoanChargeOffPreBusinessEvent event) {
|
||||
final Loan loan = event.get();
|
||||
if (loan.getLoanProductRelatedDetail().isEnableBuyDownFee()) {
|
||||
log.debug("Loan pre charge-off buy down fee amortization for loan {}", loan.getId());
|
||||
loanBuyDownFeeAmortizationProcessingService.processBuyDownFeeAmortizationTillDate(loan, DateUtils.getBusinessLocalDate(),
|
||||
true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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().isEnableBuyDownFee() && loanTransaction.getTypeOf().isChargeOff()
|
||||
&& !(loan.isChargedOff() || status.isClosedObligationsMet() || status.isClosedWrittenOff() || status.isOverpaid())) {
|
||||
log.debug("Loan undo charge-off on buy down fee amortization for loan {}", loan.getId());
|
||||
loanBuyDownFeeAmortizationProcessingService.processBuyDownFeeAmortizationOnLoanUndoChargeOff(loanTransaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+128
@@ -20,11 +20,16 @@ package org.apache.fineract.portfolio.loanaccount.service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fineract.infrastructure.core.domain.ExternalId;
|
||||
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.BusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanAdjustTransactionBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent;
|
||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
||||
@@ -32,6 +37,8 @@ import org.apache.fineract.organisation.monetary.domain.Money;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum;
|
||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeeBalanceRepository;
|
||||
import org.apache.fineract.portfolio.loanaccount.util.BuyDownFeeAmortizationUtil;
|
||||
@@ -102,6 +109,127 @@ public class LoanBuyDownFeeAmortizationProcessingServiceImpl implements LoanBuyD
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void processBuyDownFeeAmortizationOnLoanClosure(@NonNull final Loan loan, final boolean addJournal) {
|
||||
List<Long> existingTransactionIds = Collections.emptyList();
|
||||
List<Long> existingReversedTransactionIds = Collections.emptyList();
|
||||
if (addJournal) {
|
||||
existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
|
||||
existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
|
||||
}
|
||||
|
||||
final LocalDate transactionDate = getFinalBuyDownFeeAmortizationTransactionDate(loan);
|
||||
final Optional<LoanTransaction> amortizationTransaction = createBuyDownFeeAmortizationTransaction(loan, transactionDate, false,
|
||||
null);
|
||||
amortizationTransaction.ifPresent(loanTransaction -> {
|
||||
if (loanTransaction.isBuyDownFeeAmortization()) {
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent(loanTransaction));
|
||||
} else {
|
||||
businessEventNotifierService
|
||||
.notifyPostBusinessEvent(new LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent(loanTransaction));
|
||||
}
|
||||
});
|
||||
|
||||
if (addJournal) {
|
||||
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void processBuyDownFeeAmortizationOnLoanChargeOff(@NonNull final Loan loan,
|
||||
@NonNull final LoanTransaction chargeOffTransaction) {
|
||||
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
|
||||
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
|
||||
|
||||
LocalDate transactionDate = loan.getChargedOffOnDate();
|
||||
if (transactionDate == null) {
|
||||
transactionDate = DateUtils.getBusinessLocalDate();
|
||||
}
|
||||
|
||||
final Optional<LoanTransaction> amortizationTransaction = createBuyDownFeeAmortizationTransaction(loan, transactionDate, true,
|
||||
chargeOffTransaction);
|
||||
if (amortizationTransaction.isPresent()) {
|
||||
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
if (amortizationTransaction.get().isBuyDownFeeAmortization()) {
|
||||
businessEventNotifierService.notifyPostBusinessEvent(
|
||||
new LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent(amortizationTransaction.get()));
|
||||
} else {
|
||||
businessEventNotifierService.notifyPostBusinessEvent(
|
||||
new LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent(amortizationTransaction.get()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void processBuyDownFeeAmortizationOnLoanUndoChargeOff(@NonNull final LoanTransaction loanTransaction) {
|
||||
final Loan loan = loanTransaction.getLoan();
|
||||
final List<Long> existingTransactionIds = loanTransactionRepository.findTransactionIdsByLoan(loan);
|
||||
final List<Long> existingReversedTransactionIds = loanTransactionRepository.findReversedTransactionIdsByLoan(loan);
|
||||
|
||||
loan.getLoanTransactions().stream().filter(LoanTransaction::isBuyDownFeeAmortization)
|
||||
.filter(transaction -> transaction.getTransactionDate().equals(loanTransaction.getTransactionDate())
|
||||
&& transaction.getLoanTransactionRelations().stream()
|
||||
.anyMatch(rel -> LoanTransactionRelationTypeEnum.RELATED.equals(rel.getRelationType())
|
||||
&& rel.getToTransaction().equals(loanTransaction)))
|
||||
.forEach(transaction -> {
|
||||
transaction.reverse();
|
||||
final LoanAdjustTransactionBusinessEvent.Data data = new LoanAdjustTransactionBusinessEvent.Data(transaction);
|
||||
businessEventNotifierService.notifyPostBusinessEvent(new LoanAdjustTransactionBusinessEvent(data));
|
||||
|
||||
});
|
||||
|
||||
for (LoanBuyDownFeeBalance balance : loanBuyDownFeeBalanceRepository.findAllByLoanId(loan.getId())) {
|
||||
balance.setUnrecognizedAmount(balance.getChargedOffAmount());
|
||||
balance.setChargedOffAmount(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
journalEntryPoster.postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds);
|
||||
}
|
||||
|
||||
private Optional<LoanTransaction> createBuyDownFeeAmortizationTransaction(final Loan loan, final LocalDate transactionDate,
|
||||
final boolean isChargeOff, final LoanTransaction chargeOffTransaction) {
|
||||
ExternalId externalId = externalIdFactory.create();
|
||||
|
||||
final List<LoanBuyDownFeeBalance> balances = loanBuyDownFeeBalanceRepository.findAllByLoanId(loan.getId());
|
||||
|
||||
BigDecimal totalAmortizationAmount = BigDecimal.ZERO;
|
||||
for (LoanBuyDownFeeBalance balance : balances) {
|
||||
List<LoanTransaction> adjustments = loanTransactionRepository.findAdjustments(balance.getLoanTransaction());
|
||||
LocalDate maturityDate = loan.getMaturityDate() != null ? loan.getMaturityDate() : transactionDate;
|
||||
final Money amortizationTillDate = BuyDownFeeAmortizationUtil.calculateTotalAmortizationTillDate(balance, adjustments,
|
||||
maturityDate, loan.getLoanProductRelatedDetail().getBuyDownFeeStrategy(), maturityDate, loan.getCurrency());
|
||||
totalAmortizationAmount = totalAmortizationAmount.add(amortizationTillDate.getAmount());
|
||||
if (isChargeOff) {
|
||||
balance.setChargedOffAmount(balance.getUnrecognizedAmount());
|
||||
}
|
||||
balance.setUnrecognizedAmount(BigDecimal.ZERO);
|
||||
}
|
||||
|
||||
BigDecimal amortizedAmount = loanTransactionRepository.getAmortizedAmountBuyDownFee(loan);
|
||||
BigDecimal totalUnrecognizedAmount = totalAmortizationAmount.subtract(amortizedAmount);
|
||||
if (MathUtil.isZero(totalUnrecognizedAmount)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
final LoanTransaction amortizationTransaction = MathUtil.isGreaterThanZero(totalUnrecognizedAmount)
|
||||
? LoanTransaction.buyDownFeeAmortization(loan, loan.getOffice(), transactionDate, totalUnrecognizedAmount, externalId)
|
||||
: LoanTransaction.buyDownFeeAmortizationAdjustment(loan,
|
||||
Money.of(loan.getCurrency(), MathUtil.negate(totalUnrecognizedAmount)), transactionDate, externalId);
|
||||
if (isChargeOff) {
|
||||
amortizationTransaction.getLoanTransactionRelations().add(LoanTransactionRelation.linkToTransaction(amortizationTransaction,
|
||||
chargeOffTransaction, LoanTransactionRelationTypeEnum.RELATED));
|
||||
}
|
||||
|
||||
loan.addLoanTransaction(amortizationTransaction);
|
||||
loanTransactionRepository.saveAndFlush(amortizationTransaction);
|
||||
|
||||
return Optional.of(amortizationTransaction);
|
||||
}
|
||||
|
||||
private LocalDate getFinalBuyDownFeeAmortizationTransactionDate(final Loan loan) {
|
||||
return switch (loan.getStatus()) {
|
||||
case CLOSED_OBLIGATIONS_MET -> loan.getClosedOnDate();
|
||||
|
||||
+1
-1
@@ -100,7 +100,7 @@ public class LoanCapitalizedIncomeAmortizationEventService {
|
||||
if (loan.getLoanProductRelatedDetail().isEnableIncomeCapitalization()) {
|
||||
log.debug("Loan pre charge-off capitalized income amortization for loan {}", loan.getId());
|
||||
loanCapitalizedIncomeAmortizationProcessingService.processCapitalizedIncomeAmortizationTillDate(loan,
|
||||
DateUtils.getBusinessLocalDate(), false);
|
||||
DateUtils.getBusinessLocalDate(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -123,6 +123,7 @@ 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.LoanBalanceService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanBuyDownFeeAmortizationEventService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanBuyDownFeeAmortizationProcessingService;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanBuyDownFeeAmortizationProcessingServiceImpl;
|
||||
import org.apache.fineract.portfolio.loanaccount.service.LoanCalculateRepaymentPastDueService;
|
||||
@@ -573,4 +574,12 @@ public class LoanAccountConfiguration {
|
||||
return new LoanBuyDownFeeAmortizationProcessingServiceImpl(loanTransactionRepository, loanBuyDownFeeBalanceRepository,
|
||||
businessEventNotifierService, journalEntryPoster, externalIdFactory);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(LoanBuyDownFeeAmortizationEventService.class)
|
||||
public LoanBuyDownFeeAmortizationEventService loanBuyDownFeeAmortizationEventService(
|
||||
BusinessEventNotifierService businessEventNotifierService,
|
||||
LoanBuyDownFeeAmortizationProcessingService loanBuyDownFeeAmortizationProcessingService) {
|
||||
return new LoanBuyDownFeeAmortizationEventService(businessEventNotifierService, loanBuyDownFeeAmortizationProcessingService);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user