FINERACT-2311: Buy down fee - final amortization

- early repayment
- write off
- charge-off
This commit is contained in:
adam.magyari
2025-07-15 10:46:55 +02:00
committed by Adam Saghy
parent 35c51278f9
commit 880580f736
8 changed files with 319 additions and 15 deletions
@@ -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
@@ -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);
}
@@ -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);
}
}
@@ -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);
}
@@ -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);
}
}
}
}
@@ -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();
@@ -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);
}
}
}
@@ -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);
}
}