Skip to content

Commit 149b2ba

Browse files
author
Jose Alberto Hernandez
committed
FINERACT-2326: Loan contract termination same disbursement date
1 parent 5ea7c36 commit 149b2ba

File tree

16 files changed

+181
-18
lines changed

16 files changed

+181
-18
lines changed

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ public enum DefaultLoanProduct implements LoanProduct {
146146
LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC_CAPITALIZED_INCOME, //
147147
LP2_ADV_PYMNT_360_30_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF_ACCRUAL_ACTIVITY, //
148148
LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION, //
149+
LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION, //
149150
LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_MULTIDISB_OVER_APPLIED_PERCENTAGE_CAPITALIZED_INCOME, //
150151
LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_MULTIDISB_OVER_APPLIED_FLAT_CAPITALIZED_INCOME, //
151152
LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_APPROVED_OVER_APPLIED_PERCENTAGE_CAPITALIZED_INCOME, //

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4058,6 +4058,39 @@ public void initialize() throws Exception {
40584058
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY,
40594059
responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmi36030InterestRecalculationDaily);
40604060

4061+
// LP2 with progressive loan schedule + horizontal + interest EMI + 360/30 + multidisbursement +
4062+
// contract termination with interest recognition
4063+
// (LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION)
4064+
final String name148 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION
4065+
.getName();
4066+
4067+
final PostLoanProductsRequest loanProductsRequestAdvCustomContractTerminationProgressiveLoanScheduleIntRecalcRecog = loanProductsRequestFactory
4068+
.defaultLoanProductsRequestLP2InterestDailyRecalculation()//
4069+
.interestRecognitionOnDisbursementDate(true) //
4070+
.name(name148)//
4071+
.paymentAllocation(List.of(//
4072+
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT",
4073+
LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_PENALTY, //
4074+
LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_FEE, //
4075+
LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_INTEREST, //
4076+
LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_PRINCIPAL, //
4077+
LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_PENALTY, //
4078+
LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_FEE, //
4079+
LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_PRINCIPAL, //
4080+
LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_INTEREST, //
4081+
LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_PENALTY, //
4082+
LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_FEE, //
4083+
LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_PRINCIPAL, //
4084+
LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_INTEREST), //
4085+
createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
4086+
createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
4087+
createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));//
4088+
final Response<PostLoanProductsResponse> responseLoanProductsRequestAdvCustomContractTerminationProgressiveLoanScheduleIntRecalcRecog = loanProductsApi
4089+
.createLoanProduct(loanProductsRequestAdvCustomContractTerminationProgressiveLoanScheduleIntRecalcRecog).execute();
4090+
TestContext.INSTANCE.set(
4091+
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION,
4092+
responseLoanProductsRequestAdvCustomContractTerminationProgressiveLoanScheduleIntRecalcRecog);
4093+
40614094
// (LP1_WITH_OVERRIDES) - Loan product with all attribute overrides ENABLED
40624095
final String nameWithOverrides = DefaultLoanProduct.LP1_WITH_OVERRIDES.getName();
40634096
final PostLoanProductsRequest loanProductsRequestWithOverrides = loanProductsRequestFactory.defaultLoanProductsRequestLP1() //

fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ public abstract class TestContextKey {
257257
public static final String LOAN_CAPITALIZED_INCOME_ADJUSTMENT_RESPONSE = "loanCapitalizedIncomeAdjustmentResponse";
258258
public static final String LOAN_INTEREST_REFUND_RESPONSE = "loanInterestRefundResponse";
259259
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationContractTermination";
260+
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationContractTerminationIntRecognition";
260261
public static final String LOAN_CONTRACT_TERMINATION_RESPONSE = "loanContractTerminationResponse";
261262
public static final String LOAN_UNDO_CONTRACT_TERMINATION_RESPONSE = "loanUndoContractTerminationResponse";
262263
public static final String LOAN_BUY_DOWN_FEE_RESPONSE = "loanBuyDownFeeResponse";

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,3 +1317,48 @@ Feature: Contract Termination
13171317
| 31 March 2024 | Accrual Adjustment | 0.15 | 0.0 | 0.15 | 0.0 | 0.0 | 0.0 | false | false |
13181318
| 31 March 2024 | Contract Termination | 57.37 | 57.05 | 0.32 | 0.0 | 0.0 | 0.0 | true | true |
13191319
And Global configuration "is-principal-compounding-disabled-for-overdue-loans" is disabled
1320+
1321+
@TestRailId:C4133
1322+
Scenario: Contract termination on disbursement date
1323+
When Admin sets the business date to "01 January 2025"
1324+
And Admin creates a client with random data
1325+
And Admin creates a fully customized loan with the following data:
1326+
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
1327+
| LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION | 01 January 2025 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
1328+
And Admin successfully approves the loan on "01 January 2025" with "100" amount and expected disbursement date on "01 January 2025"
1329+
And Admin successfully disburse the loan on "01 January 2025" with "100" EUR transaction amount
1330+
And Admin successfully terminates loan contract
1331+
Then Loan Repayment schedule has 1 periods, with the following data for periods:
1332+
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
1333+
| | | 01 January 2025 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
1334+
| 1 | 0 | 01 January 2025 | | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 |
1335+
And Loan Repayment schedule has the following data in Total row:
1336+
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
1337+
| 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 |
1338+
And Loan Transactions tab has the following data:
1339+
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
1340+
| 01 January 2025 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false |
1341+
| 01 January 2025 | Contract Termination | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
1342+
1343+
@TestRailId:C4134
1344+
Scenario: Contract termination on disbursement date with interest recognition
1345+
When Admin sets the business date to "01 January 2025"
1346+
And Admin creates a client with random data
1347+
And Admin creates a fully customized loan with the following data:
1348+
| LoanProduct | submitted on date | with Principal | ANNUAL interest rate % | interest type | interest calculation period | amortization type | loanTermFrequency | loanTermFrequencyType | repaymentEvery | repaymentFrequencyType | numberOfRepayments | graceOnPrincipalPayment | graceOnInterestPayment | interest free period | Payment strategy |
1349+
| LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION | 01 January 2025 | 100 | 7 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 4 | MONTHS | 1 | MONTHS | 4 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION |
1350+
And Admin successfully approves the loan on "01 January 2025" with "100" amount and expected disbursement date on "01 January 2025"
1351+
And Admin successfully disburse the loan on "01 January 2025" with "100" EUR transaction amount
1352+
And Admin successfully terminates loan contract
1353+
Then Loan Repayment schedule has 1 periods, with the following data for periods:
1354+
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
1355+
| | | 01 January 2025 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
1356+
| 1 | 0 | 01 January 2025 | | 0.0 | 100.0 | 0.02 | 0.0 | 0.0 | 100.02| 0.0 | 0.0 | 0.0 | 100.02 |
1357+
And Loan Repayment schedule has the following data in Total row:
1358+
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
1359+
| 100.0 | 0.02 | 0.0 | 0.0 | 100.02 | 0.0 | 0.0 | 0.0 | 100.02 |
1360+
And Loan Transactions tab has the following data:
1361+
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
1362+
| 01 January 2025 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false |
1363+
| 01 January 2025 | Contract Termination | 100.02 | 100.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false |
1364+
| 01 January 2025 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false |

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

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,10 +1410,17 @@ public void addLoanRepaymentScheduleInstallment(final LoanRepaymentScheduleInsta
14101410
* @param date
14111411
* @return a schedule installment is related to the provided date
14121412
**/
1413-
public LoanRepaymentScheduleInstallment getRelatedRepaymentScheduleInstallment(LocalDate date) {
1414-
return getRepaymentScheduleInstallment(
1415-
e -> (e.isFirstNormalInstallment() && DateUtils.isDateInRangeInclusive(date, e.getFromDate(), e.getDueDate()))
1416-
|| DateUtils.isDateInRangeFromExclusiveToInclusive(date, e.getFromDate(), e.getDueDate()));
1413+
public LoanRepaymentScheduleInstallment getRelatedRepaymentScheduleInstallment(final LocalDate date) {
1414+
return getRepaymentScheduleInstallment(e -> (DateUtils.isDateInRangeFromExclusiveToInclusive(date, e.getFromDate(), e.getDueDate())
1415+
|| (e.isFirstNormalInstallment(getRepaymentScheduleInstallments())
1416+
&& DateUtils.isDateInRangeInclusive(date, e.getFromDate(), e.getDueDate()))));
1417+
}
1418+
1419+
public List<LoanRepaymentScheduleInstallment> getInstallmentsUpToTransactionDate(final LocalDate transactionDate) {
1420+
return getRepaymentScheduleInstallments().stream()
1421+
.filter(i -> (transactionDate.isAfter(i.getFromDate())
1422+
|| (i.isFirstNormalInstallment(getRepaymentScheduleInstallments()) && !transactionDate.isBefore(i.getFromDate()))))
1423+
.collect(Collectors.toCollection(ArrayList::new));
14171424
}
14181425

14191426
public LoanRepaymentScheduleInstallment fetchRepaymentScheduleInstallment(final Integer installmentNumber) {

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1216,8 +1216,7 @@ private BigDecimal setScaleAndDefaultToNullIfZero(final BigDecimal value) {
12161216
return value.setScale(6, MoneyHelper.getRoundingMode());
12171217
}
12181218

1219-
public boolean isFirstNormalInstallment() {
1220-
return loan.getRepaymentScheduleInstallments().stream().filter(rp -> !rp.isDownPayment()).findFirst().stream()
1221-
.anyMatch(rp -> rp.equals(this));
1219+
public boolean isFirstNormalInstallment(List<LoanRepaymentScheduleInstallment> installments) {
1220+
return installments.stream().filter(rp -> !rp.isDownPayment()).findFirst().stream().anyMatch(rp -> rp.equals(this));
12221221
}
12231222
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2255,4 +2255,8 @@ public void updateVariationDays(final long daysToAdd) {
22552255
this.variationDays += daysToAdd;
22562256
}
22572257

2258+
public boolean isInterestRecognitionOnDisbursementDate() {
2259+
return this.interestRecognitionOnDisbursementDate;
2260+
}
2261+
22582262
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ ChangedTransactionDetail processLatestTransaction(String transactionProcessingSt
4444

4545
ChangedTransactionDetail reprocessLoanTransactions(String transactionProcessingStrategyCode, LocalDate disbursementDate,
4646
List<LoanTransaction> repaymentsOrWaivers, MonetaryCurrency currency,
47-
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges);
47+
List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, Set<LoanCharge> charges,
48+
boolean interestRecognitionOnDisbursementDate);
4849

4950
LoanRepaymentScheduleTransactionProcessor getTransactionProcessor(String transactionProcessingStrategyCode);
5051

fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1845,13 +1845,15 @@ private void handleAccelerateMaturityDate(final LoanTransaction loanTransaction,
18451845
final Loan loan = loanTransaction.getLoan();
18461846
final LoanRepaymentScheduleInstallment currentInstallment = loan.getRelatedRepaymentScheduleInstallment(transactionDate);
18471847

1848-
if (!installments.isEmpty() && transactionDate.isBefore(loan.getMaturityDate())) {
1848+
if (!installments.isEmpty() && transactionDate.isBefore(loan.getMaturityDate()) && currentInstallment != null) {
18491849
if (currentInstallment.isNotFullyPaidOff()) {
18501850
if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx
1851-
&& loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) {
1851+
&& loan.isInterestBearingAndInterestRecalculationEnabled()) {
18521852
final BigDecimal interestOutstanding = currentInstallment.getInterestOutstanding(loan.getCurrency()).getAmount();
1853+
final LocalDate tillDate = (loan.getLoanProductRelatedDetail().isInterestRecognitionOnDisbursementDate()
1854+
&& transactionDate.equals(loan.getDisbursementDate())) ? transactionDate.plusDays(1L) : transactionDate;
18531855
final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(),
1854-
currentInstallment.getDueDate(), transactionDate, true).getAmount();
1856+
currentInstallment.getDueDate(), tillDate, true).getAmount();
18551857
if (interestOutstanding.compareTo(BigDecimal.ZERO) > 0 || newInterest.compareTo(BigDecimal.ZERO) > 0) {
18561858
currentInstallment.updateInterestCharged(newInterest);
18571859
}
@@ -1902,9 +1904,8 @@ private void handleAccelerateMaturityDate(final LoanTransaction loanTransaction,
19021904
MathUtil.nullToZero(currentInstallment.getTotalPaidInAdvance()).add(futureTotalPaidInAdvance));
19031905
}
19041906

1905-
final List<LoanRepaymentScheduleInstallment> installmentsUpToTransactionDate = installments.stream()
1906-
.filter(installment -> transactionDate.isAfter(installment.getFromDate()))
1907-
.collect(Collectors.toCollection(ArrayList::new));
1907+
final List<LoanRepaymentScheduleInstallment> installmentsUpToTransactionDate = loan
1908+
.getInstallmentsUpToTransactionDate(transactionDate);
19081909

19091910
final List<LoanTransaction> transactionsToBeReprocessed = loan.getLoanTransactions().stream()
19101911
.filter(transaction -> transaction.getTransactionDate().isBefore(transactionDate))

fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/ProgressiveLoanScheduleGenerator.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,9 @@ public OutstandingAmountsDTO calculatePrepaymentAmount(MonetaryCurrency currency
204204
transactionDate);
205205
ProgressiveLoanInterestScheduleModel model = savedModel
206206
.orElseGet(() -> processor.calculateInterestScheduleModel(loan.getId(), transactionDate));
207+
// final LocalDate tillDate = loanApplicationTerms.isInterestRecognitionOnDisbursementDate() ?
208+
// transactionDate.plusDays(1L)
209+
// : transactionDate;
207210
OutstandingDetails outstandingAmounts = emiCalculator.getOutstandingAmountsTillDate(model, transactionDate);
208211
// TODO: We should add all the past due outstanding amounts as well
209212
OutstandingAmountsDTO result = new OutstandingAmountsDTO(currency) //

0 commit comments

Comments
 (0)