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"
|
When Loan Pay-off is made on "1 March 2024"
|
||||||
Then Loan's all installments have obligations met
|
Then Loan's all installments have obligations met
|
||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
| 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 | 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 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 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 | 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 | 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
|
@TestRailId:C3828
|
||||||
Scenario: Verify loan with Buy Down fees and early payoff and daily amortization - UC2.2
|
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 | 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 | 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
|
@TestRailId:C3772
|
||||||
Scenario: Verify loan with Buy Down fees and charge-off - UC3.1
|
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 | |
|
| EXPENSE | 744007 | Credit Loss/Bad Debt | 66.86 | |
|
||||||
| INCOME | 404001 | Interest Income Charge Off | 0.59 | |
|
| INCOME | 404001 | Interest Income Charge Off | 0.59 | |
|
||||||
Then Loan Transactions tab has the following data:
|
Then Loan Transactions tab has the following data:
|
||||||
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
|
| 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 | 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 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 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 | Buy Down Fee Amortization | 33.52 | 0.0 | 33.52 | 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 | 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"
|
When Loan Pay-off is made on "1 March 2024"
|
||||||
Then Loan's all installments have obligations met
|
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 | 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 |
|
| 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 | 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 | 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"
|
When Loan Pay-off is made on "1 March 2024"
|
||||||
Then Loan's all installments have obligations met
|
Then Loan's all installments have obligations met
|
||||||
|
|||||||
+3
-2
@@ -1755,8 +1755,9 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
|
|
||||||
public LoanTransaction getLastUserTransaction() {
|
public LoanTransaction getLastUserTransaction() {
|
||||||
return getLoanTransactions().stream() //
|
return getLoanTransactions().stream() //
|
||||||
.filter(t -> t.isNotReversed()
|
.filter(t -> t.isNotReversed() && !(t.isAccrual() || t.isAccrualAdjustment() || t.isIncomePosting()
|
||||||
&& !(t.isAccrual() || t.isAccrualAdjustment() || t.isIncomePosting() || t.isCapitalizedIncomeAmortization())) //
|
|| t.isCapitalizedIncomeAmortization() || t.isCapitalizedIncomeAmortizationAdjustment()
|
||||||
|
|| t.isBuyDownFeeAmortization() || t.isBuyDownFeeAmortizationAdjustment())) //
|
||||||
.reduce((first, second) -> second) //
|
.reduce((first, second) -> second) //
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|||||||
+16
@@ -706,6 +706,10 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
return LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION.equals(getTypeOf()) && isNotReversed();
|
return LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION.equals(getTypeOf()) && isNotReversed();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCapitalizedIncomeAmortizationAdjustment() {
|
||||||
|
return LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT.equals(getTypeOf()) && isNotReversed();
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isCapitalizedIncomeAdjustment() {
|
public boolean isCapitalizedIncomeAdjustment() {
|
||||||
return LoanTransactionType.CAPITALIZED_INCOME_ADJUSTMENT.equals(getTypeOf()) && isNotReversed();
|
return LoanTransactionType.CAPITALIZED_INCOME_ADJUSTMENT.equals(getTypeOf()) && isNotReversed();
|
||||||
}
|
}
|
||||||
@@ -998,4 +1002,16 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long
|
|||||||
public boolean isBuyDownFee() {
|
public boolean isBuyDownFee() {
|
||||||
return LoanTransactionType.BUY_DOWN_FEE.equals(this.typeOf);
|
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 java.time.LocalDate;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
|
|
||||||
public interface LoanBuyDownFeeAmortizationProcessingService {
|
public interface LoanBuyDownFeeAmortizationProcessingService {
|
||||||
|
|
||||||
void processBuyDownFeeAmortizationTillDate(@NonNull Loan loan, @NonNull LocalDate tillDate, boolean addJournal);
|
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.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
import lombok.RequiredArgsConstructor;
|
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.ExternalIdFactory;
|
||||||
import org.apache.fineract.infrastructure.core.service.MathUtil;
|
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.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.LoanBuyDownFeeAmortizationAdjustmentTransactionCreatedBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent;
|
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanBuyDownFeeAmortizationTransactionCreatedBusinessEvent;
|
||||||
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
|
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.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanBuyDownFeeBalance;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
|
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.domain.LoanTransactionRepository;
|
||||||
import org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeeBalanceRepository;
|
import org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeeBalanceRepository;
|
||||||
import org.apache.fineract.portfolio.loanaccount.util.BuyDownFeeAmortizationUtil;
|
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) {
|
private LocalDate getFinalBuyDownFeeAmortizationTransactionDate(final Loan loan) {
|
||||||
return switch (loan.getStatus()) {
|
return switch (loan.getStatus()) {
|
||||||
case CLOSED_OBLIGATIONS_MET -> loan.getClosedOnDate();
|
case CLOSED_OBLIGATIONS_MET -> loan.getClosedOnDate();
|
||||||
|
|||||||
+1
-1
@@ -100,7 +100,7 @@ public class LoanCapitalizedIncomeAmortizationEventService {
|
|||||||
if (loan.getLoanProductRelatedDetail().isEnableIncomeCapitalization()) {
|
if (loan.getLoanProductRelatedDetail().isEnableIncomeCapitalization()) {
|
||||||
log.debug("Loan pre charge-off capitalized income amortization for loan {}", loan.getId());
|
log.debug("Loan pre charge-off capitalized income amortization for loan {}", loan.getId());
|
||||||
loanCapitalizedIncomeAmortizationProcessingService.processCapitalizedIncomeAmortizationTillDate(loan,
|
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.LoanAssembler;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssemblerImpl;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAssemblerImpl;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService;
|
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.LoanBuyDownFeeAmortizationProcessingService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanBuyDownFeeAmortizationProcessingServiceImpl;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanBuyDownFeeAmortizationProcessingServiceImpl;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanCalculateRepaymentPastDueService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanCalculateRepaymentPastDueService;
|
||||||
@@ -573,4 +574,12 @@ public class LoanAccountConfiguration {
|
|||||||
return new LoanBuyDownFeeAmortizationProcessingServiceImpl(loanTransactionRepository, loanBuyDownFeeBalanceRepository,
|
return new LoanBuyDownFeeAmortizationProcessingServiceImpl(loanTransactionRepository, loanBuyDownFeeBalanceRepository,
|
||||||
businessEventNotifierService, journalEntryPoster, externalIdFactory);
|
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