Skip to content

Commit 880580f

Browse files
magyari-adamadamsaghy
authored andcommitted
FINERACT-2311: Buy down fee - final amortization
- early repayment - write off - charge-off
1 parent 35c5127 commit 880580f

File tree

8 files changed

+319
-15
lines changed

8 files changed

+319
-15
lines changed

fineract-e2e-tests-runner/src/test/resources/features/LoanBuyDownFees.feature

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -327,12 +327,17 @@ Feature:Feature: Buy Down Fees
327327
When Loan Pay-off is made on "1 March 2024"
328328
Then Loan's all installments have obligations met
329329
Then Loan Transactions tab has the following data:
330-
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
331-
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
332-
| 01 January 2024 | Buy Down Fee | 50.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
333-
| 01 February 2024 | Repayment | 33.72 | 33.14 | 0.58 | 0.0 | 0.0 | 66.86 | false |
334-
| 01 March 2024 | Repayment | 67.25 | 66.86 | 0.39 | 0.0 | 0.0 | 0.0 | false |
335-
| 01 March 2024 | Accrual | 0.97 | 0.0 | 0.97 | 0.0 | 0.0 | 0.0 | false |
330+
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
331+
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
332+
| 01 January 2024 | Buy Down Fee | 50.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
333+
| 01 February 2024 | Repayment | 33.72 | 33.14 | 0.58 | 0.0 | 0.0 | 66.86 | false |
334+
| 01 March 2024 | Repayment | 67.25 | 66.86 | 0.39 | 0.0 | 0.0 | 0.0 | false |
335+
| 01 March 2024 | Accrual | 0.97 | 0.0 | 0.97 | 0.0 | 0.0 | 0.0 | false |
336+
| 01 March 2024 | Buy Down Fee Amortization | 50.0 | 0.0 | 50.0 | 0.0 | 0.0 | 0.0 | false |
337+
And Loan Transactions tab has a "BUY_DOWN_FEE_AMORTIZATION" transaction with date "01 March 2024" which has the following Journal entries:
338+
| Type | Account code | Account name | Debit | Credit |
339+
| INCOME | 450281 | Income From Buy Down | | 50.0 |
340+
| LIABILITY | 145024 | Deferred Capitalized Income | 50.0 | |
336341

337342
@TestRailId:C3828
338343
Scenario: Verify loan with Buy Down fees and early payoff and daily amortization - UC2.2
@@ -508,6 +513,11 @@ Feature:Feature: Buy Down Fees
508513

509514
| 01 March 2024 | Repayment | 67.25 | 66.86 | 0.39 | 0.0 | 0.0 | 0.0 | false |
510515
| 01 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false |
516+
| 01 March 2024 | Buy Down Fee Amortization | 17.03 | 0.0 | 17.03 | 0.0 | 0.0 | 0.0 | false |
517+
And Loan Transactions tab has a "BUY_DOWN_FEE_AMORTIZATION" transaction with date "01 March 2024" which has the following Journal entries:
518+
| Type | Account code | Account name | Debit | Credit |
519+
| INCOME | 450281 | Income From Buy Down | | 17.03 |
520+
| LIABILITY | 145024 | Deferred Capitalized Income | 17.03 | |
511521

512522
@TestRailId:C3772
513523
Scenario: Verify loan with Buy Down fees and charge-off - UC3.1
@@ -556,12 +566,18 @@ Feature:Feature: Buy Down Fees
556566
| EXPENSE | 744007 | Credit Loss/Bad Debt | 66.86 | |
557567
| INCOME | 404001 | Interest Income Charge Off | 0.59 | |
558568
Then Loan Transactions tab has the following data:
559-
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
560-
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
561-
| 01 January 2024 | Buy Down Fee | 50.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
562-
| 01 February 2024 | Repayment | 33.72 | 33.14 | 0.58 | 0.0 | 0.0 | 66.86 | false |
563-
| 01 March 2024 | Accrual | 0.97 | 0.0 | 0.97 | 0.0 | 0.0 | 0.0 | false |
564-
| 01 March 2024 | Charge-off | 67.45 | 66.86 | 0.59 | 0.0 | 0.0 | 0.0 | false |
569+
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted |
570+
| 01 January 2024 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
571+
| 01 January 2024 | Buy Down Fee | 50.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false |
572+
| 01 February 2024 | Repayment | 33.72 | 33.14 | 0.58 | 0.0 | 0.0 | 66.86 | false |
573+
| 01 March 2024 | Buy Down Fee Amortization | 33.52 | 0.0 | 33.52 | 0.0 | 0.0 | 0.0 | false |
574+
| 01 March 2024 | Accrual | 0.97 | 0.0 | 0.97 | 0.0 | 0.0 | 0.0 | false |
575+
| 01 March 2024 | Charge-off | 67.45 | 66.86 | 0.59 | 0.0 | 0.0 | 0.0 | false |
576+
| 01 March 2024 | Buy Down Fee Amortization | 16.48 | 0.0 | 16.48 | 0.0 | 0.0 | 0.0 | false |
577+
And Loan Transactions tab has a "BUY_DOWN_FEE_AMORTIZATION" transaction with date "01 March 2024" which has the following Journal entries:
578+
| Type | Account code | Account name | Debit | Credit |
579+
| EXPENSE | 744007 | Credit Loss/Bad Debt | | 16.48 |
580+
| LIABILITY | 145024 | Deferred Capitalized Income | 16.48 | |
565581

566582
When Loan Pay-off is made on "1 March 2024"
567583
Then Loan's all installments have obligations met
@@ -745,8 +761,14 @@ Feature:Feature: Buy Down Fees
745761
| 29 February 2024 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false |
746762
| 29 February 2024 | Buy Down Fee Amortization | 0.55 | 0.0 | 0.55 | 0.0 | 0.0 | 0.0 | false |
747763

764+
| 01 March 2024 | Buy Down Fee Amortization | 0.55 | 0.0 | 0.55 | 0.0 | 0.0 | 0.0 | false |
748765
| 01 March 2024 | Accrual | 0.01 | 0.0 | 0.01 | 0.0 | 0.0 | 0.0 | false |
749766
| 01 March 2024 | Charge-off | 67.45 | 66.86 | 0.59 | 0.0 | 0.0 | 0.0 | false |
767+
| 01 March 2024 | Buy Down Fee Amortization | 16.48 | 0.0 | 16.48 | 0.0 | 0.0 | 0.0 | false |
768+
And Loan Transactions tab has a "BUY_DOWN_FEE_AMORTIZATION" transaction with date "01 March 2024" which has the following Journal entries:
769+
| Type | Account code | Account name | Debit | Credit |
770+
| EXPENSE | 744007 | Credit Loss/Bad Debt | | 16.48 |
771+
| LIABILITY | 145024 | Deferred Capitalized Income | 16.48 | |
750772

751773
When Loan Pay-off is made on "1 March 2024"
752774
Then Loan's all installments have obligations met

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1755,8 +1755,9 @@ public List<LoanTransactionType> getSupportedInterestRefundTransactionTypes() {
17551755

17561756
public LoanTransaction getLastUserTransaction() {
17571757
return getLoanTransactions().stream() //
1758-
.filter(t -> t.isNotReversed()
1759-
&& !(t.isAccrual() || t.isAccrualAdjustment() || t.isIncomePosting() || t.isCapitalizedIncomeAmortization())) //
1758+
.filter(t -> t.isNotReversed() && !(t.isAccrual() || t.isAccrualAdjustment() || t.isIncomePosting()
1759+
|| t.isCapitalizedIncomeAmortization() || t.isCapitalizedIncomeAmortizationAdjustment()
1760+
|| t.isBuyDownFeeAmortization() || t.isBuyDownFeeAmortizationAdjustment())) //
17601761
.reduce((first, second) -> second) //
17611762
.orElse(null);
17621763
}

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,10 @@ public boolean isCapitalizedIncomeAmortization() {
706706
return LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION.equals(getTypeOf()) && isNotReversed();
707707
}
708708

709+
public boolean isCapitalizedIncomeAmortizationAdjustment() {
710+
return LoanTransactionType.CAPITALIZED_INCOME_AMORTIZATION_ADJUSTMENT.equals(getTypeOf()) && isNotReversed();
711+
}
712+
709713
public boolean isCapitalizedIncomeAdjustment() {
710714
return LoanTransactionType.CAPITALIZED_INCOME_ADJUSTMENT.equals(getTypeOf()) && isNotReversed();
711715
}
@@ -998,4 +1002,16 @@ public static LoanTransaction buyDownFee(final Loan loan, final Money amount, fi
9981002
public boolean isBuyDownFee() {
9991003
return LoanTransactionType.BUY_DOWN_FEE.equals(this.typeOf);
10001004
}
1005+
1006+
public boolean isBuyDownFeeAdjustment() {
1007+
return LoanTransactionType.BUY_DOWN_FEE_ADJUSTMENT.equals(this.typeOf);
1008+
}
1009+
1010+
public boolean isBuyDownFeeAmortization() {
1011+
return LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION.equals(this.typeOf);
1012+
}
1013+
1014+
public boolean isBuyDownFeeAmortizationAdjustment() {
1015+
return LoanTransactionType.BUY_DOWN_FEE_AMORTIZATION_ADJUSTMENT.equals(this.typeOf);
1016+
}
10011017
}

fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanBuyDownFeeAmortizationProcessingService.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,16 @@
2020

2121
import java.time.LocalDate;
2222
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
23+
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
2324
import org.springframework.lang.NonNull;
2425

2526
public interface LoanBuyDownFeeAmortizationProcessingService {
2627

2728
void processBuyDownFeeAmortizationTillDate(@NonNull Loan loan, @NonNull LocalDate tillDate, boolean addJournal);
29+
30+
void processBuyDownFeeAmortizationOnLoanClosure(@NonNull Loan loan, boolean addJournal);
31+
32+
void processBuyDownFeeAmortizationOnLoanChargeOff(@NonNull Loan loan, @NonNull LoanTransaction chargeOffTransaction);
33+
34+
void processBuyDownFeeAmortizationOnLoanUndoChargeOff(@NonNull LoanTransaction loanTransaction);
2835
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.fineract.portfolio.loanaccount.service;
20+
21+
import jakarta.annotation.PostConstruct;
22+
import lombok.RequiredArgsConstructor;
23+
import lombok.extern.slf4j.Slf4j;
24+
import org.apache.fineract.infrastructure.core.service.DateUtils;
25+
import org.apache.fineract.infrastructure.event.business.BusinessEventListener;
26+
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanBalanceChangedBusinessEvent;
27+
import org.apache.fineract.infrastructure.event.business.domain.loan.LoanCloseBusinessEvent;
28+
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPostBusinessEvent;
29+
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanChargeOffPreBusinessEvent;
30+
import org.apache.fineract.infrastructure.event.business.domain.loan.transaction.LoanUndoChargeOffBusinessEvent;
31+
import org.apache.fineract.infrastructure.event.business.service.BusinessEventNotifierService;
32+
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
33+
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
34+
import org.apache.fineract.portfolio.loanaccount.domain.LoanTransaction;
35+
36+
@Slf4j
37+
@RequiredArgsConstructor
38+
public class LoanBuyDownFeeAmortizationEventService {
39+
40+
private final BusinessEventNotifierService businessEventNotifierService;
41+
private final LoanBuyDownFeeAmortizationProcessingService loanBuyDownFeeAmortizationProcessingService;
42+
43+
@PostConstruct
44+
public void addListeners() {
45+
businessEventNotifierService.addPreBusinessEventListener(LoanCloseBusinessEvent.class, new LoanCloseListener());
46+
businessEventNotifierService.addPostBusinessEventListener(LoanBalanceChangedBusinessEvent.class, new LoanBalanceChangedListener());
47+
businessEventNotifierService.addPostBusinessEventListener(LoanChargeOffPostBusinessEvent.class, new LoanChargeOffEventListener());
48+
businessEventNotifierService.addPostBusinessEventListener(LoanUndoChargeOffBusinessEvent.class,
49+
new LoanUndoChargeOffEventListener());
50+
businessEventNotifierService.addPreBusinessEventListener(LoanChargeOffPreBusinessEvent.class, new LoanChargeOffPreEventListener());
51+
}
52+
53+
private final class LoanCloseListener implements BusinessEventListener<LoanCloseBusinessEvent> {
54+
55+
@Override
56+
public void onBusinessEvent(final LoanCloseBusinessEvent event) {
57+
final Loan loan = event.get();
58+
final LoanStatus status = loan.getStatus();
59+
if (loan.getLoanProductRelatedDetail().isEnableBuyDownFee()
60+
&& (status.isClosedObligationsMet() || status.isClosedWrittenOff() || status.isOverpaid())) {
61+
log.debug("Loan closure on buy down fee amortization for loan {}", loan.getId());
62+
loanBuyDownFeeAmortizationProcessingService.processBuyDownFeeAmortizationOnLoanClosure(loan, false);
63+
}
64+
}
65+
}
66+
67+
private final class LoanBalanceChangedListener implements BusinessEventListener<LoanBalanceChangedBusinessEvent> {
68+
69+
@Override
70+
public void onBusinessEvent(final LoanBalanceChangedBusinessEvent event) {
71+
final Loan loan = event.get();
72+
final LoanStatus status = loan.getStatus();
73+
if (loan.getLoanProductRelatedDetail().isEnableBuyDownFee()
74+
&& (status.isClosedObligationsMet() || status.isClosedWrittenOff() || status.isOverpaid())) {
75+
log.debug("Loan balance change on buy down fee amortization for loan {}", loan.getId());
76+
loanBuyDownFeeAmortizationProcessingService.processBuyDownFeeAmortizationOnLoanClosure(loan, true);
77+
}
78+
}
79+
}
80+
81+
private final class LoanChargeOffEventListener implements BusinessEventListener<LoanChargeOffPostBusinessEvent> {
82+
83+
@Override
84+
public void onBusinessEvent(final LoanChargeOffPostBusinessEvent event) {
85+
final LoanTransaction loanTransaction = event.get();
86+
final Loan loan = loanTransaction.getLoan();
87+
if (loan.getLoanProductRelatedDetail().isEnableBuyDownFee() && loan.isChargedOff() && loanTransaction.isChargeOff()) {
88+
log.debug("Loan charge-off on buy down fee amortization for loan {}", loan.getId());
89+
loanBuyDownFeeAmortizationProcessingService.processBuyDownFeeAmortizationOnLoanChargeOff(loan, loanTransaction);
90+
}
91+
}
92+
}
93+
94+
private final class LoanChargeOffPreEventListener implements BusinessEventListener<LoanChargeOffPreBusinessEvent> {
95+
96+
@Override
97+
public void onBusinessEvent(final LoanChargeOffPreBusinessEvent event) {
98+
final Loan loan = event.get();
99+
if (loan.getLoanProductRelatedDetail().isEnableBuyDownFee()) {
100+
log.debug("Loan pre charge-off buy down fee amortization for loan {}", loan.getId());
101+
loanBuyDownFeeAmortizationProcessingService.processBuyDownFeeAmortizationTillDate(loan, DateUtils.getBusinessLocalDate(),
102+
true);
103+
}
104+
}
105+
}
106+
107+
private final class LoanUndoChargeOffEventListener implements BusinessEventListener<LoanUndoChargeOffBusinessEvent> {
108+
109+
@Override
110+
public void onBusinessEvent(final LoanUndoChargeOffBusinessEvent event) {
111+
final LoanTransaction loanTransaction = event.get();
112+
final Loan loan = loanTransaction.getLoan();
113+
final LoanStatus status = loan.getStatus();
114+
if (loan.getLoanProductRelatedDetail().isEnableBuyDownFee() && loanTransaction.getTypeOf().isChargeOff()
115+
&& !(loan.isChargedOff() || status.isClosedObligationsMet() || status.isClosedWrittenOff() || status.isOverpaid())) {
116+
log.debug("Loan undo charge-off on buy down fee amortization for loan {}", loan.getId());
117+
loanBuyDownFeeAmortizationProcessingService.processBuyDownFeeAmortizationOnLoanUndoChargeOff(loanTransaction);
118+
}
119+
}
120+
}
121+
}

0 commit comments

Comments
 (0)