Skip to content

Commit a0b31bb

Browse files
Jose Alberto Hernandezadamsaghy
authored andcommitted
FINERACT-2326: Loan contract termination same disbursement date
1 parent fdb9e55 commit a0b31bb

File tree

9 files changed

+150
-35
lines changed

9 files changed

+150
-35
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: 55 additions & 22 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() //
@@ -4106,12 +4139,13 @@ public void initialize() throws Exception {
41064139
.execute();
41074140
TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_NO_OVERRIDES, responseNoOverrides);
41084141

4109-
// LP2 advanced + progressive loan schedule + horizontal + interest recalculation
4142+
// LP2 advanced custom payment allocation + progressive loan schedule + horizontal + interest recalculation
41104143
// Frequency for recalculate Outstanding Principal: Daily, Frequency Interval for recalculation: 1
4111-
String name148 = DefaultLoanProduct.LP2_ADV_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OF_ACCRUAL.getName();
4112-
PostLoanProductsRequest loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals = loanProductsRequestFactory
4144+
String name149 = DefaultLoanProduct.LP2_ADV_CUSTOM_PMT_ALLOC_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OFF_ACCRUAL
4145+
.getName();
4146+
PostLoanProductsRequest loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals = loanProductsRequestFactory
41134147
.defaultLoanProductsRequestLP2Emi()//
4114-
.name(name148)//
4148+
.name(name149)//
41154149
.supportedInterestRefundTypes(supportedInterestRefundTypes).installmentAmountInMultiplesOf(null) //
41164150
.daysInYearType(DaysInYearType.ACTUAL.value)//
41174151
.daysInMonthType(DaysInMonthType.ACTUAL.value)//
@@ -4124,22 +4158,24 @@ public void initialize() throws Exception {
41244158
.enableAccrualActivityPosting(true) //
41254159
.chargeOffBehaviour(ZERO_INTEREST.value)//
41264160
.paymentAllocation(List.of(//
4127-
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"))); //
4128-
Response<PostLoanProductsResponse> responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals = loanProductsApi
4161+
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), //
4162+
createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
4163+
createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
4164+
createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT"))); //
4165+
Response<PostLoanProductsResponse> responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals = loanProductsApi
41294166
.createLoanProduct(
4130-
loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals)
4167+
loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals)
41314168
.execute();
41324169
TestContext.INSTANCE.set(
4133-
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OF_ACCRUAL,
4134-
responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals);
4170+
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_CUSTOM_PMT_ALLOC_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OFF_ACCRUAL,
4171+
responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals);
41354172

4136-
// LP2 advanced custom payment allocation + progressive loan schedule + horizontal + interest recalculation
4173+
// LP2 advanced + progressive loan schedule + horizontal + interest recalculation
41374174
// Frequency for recalculate Outstanding Principal: Daily, Frequency Interval for recalculation: 1
4138-
String name149 = DefaultLoanProduct.LP2_ADV_CUSTOM_PMT_ALLOC_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OFF_ACCRUAL
4139-
.getName();
4140-
PostLoanProductsRequest loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals = loanProductsRequestFactory
4175+
String name150 = DefaultLoanProduct.LP2_ADV_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OF_ACCRUAL.getName();
4176+
PostLoanProductsRequest loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals = loanProductsRequestFactory
41414177
.defaultLoanProductsRequestLP2Emi()//
4142-
.name(name149)//
4178+
.name(name150)//
41434179
.supportedInterestRefundTypes(supportedInterestRefundTypes).installmentAmountInMultiplesOf(null) //
41444180
.daysInYearType(DaysInYearType.ACTUAL.value)//
41454181
.daysInMonthType(DaysInMonthType.ACTUAL.value)//
@@ -4152,17 +4188,14 @@ public void initialize() throws Exception {
41524188
.enableAccrualActivityPosting(true) //
41534189
.chargeOffBehaviour(ZERO_INTEREST.value)//
41544190
.paymentAllocation(List.of(//
4155-
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), //
4156-
createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
4157-
createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
4158-
createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT"))); //
4159-
Response<PostLoanProductsResponse> responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals = loanProductsApi
4191+
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"))); //
4192+
Response<PostLoanProductsResponse> responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals = loanProductsApi
41604193
.createLoanProduct(
4161-
loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals)
4194+
loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals)
41624195
.execute();
41634196
TestContext.INSTANCE.set(
4164-
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_CUSTOM_PMT_ALLOC_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OFF_ACCRUAL,
4165-
responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals);
4197+
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OF_ACCRUAL,
4198+
responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals);
41664199
}
41674200

41684201
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,

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
@@ -259,6 +259,7 @@ public abstract class TestContextKey {
259259
public static final String LOAN_INTEREST_REFUND_RESPONSE = "loanInterestRefundResponse";
260260
public static final String INTEREST_PAUSE_VARIATION_ID = "interestPauseVariationId";
261261
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationContractTermination";
262+
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationContractTerminationIntRecognition";
262263
public static final String LOAN_CONTRACT_TERMINATION_RESPONSE = "loanContractTerminationResponse";
263264
public static final String LOAN_UNDO_CONTRACT_TERMINATION_RESPONSE = "loanUndoContractTerminationResponse";
264265
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.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 |
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.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 |
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.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
1364+

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) {

0 commit comments

Comments
 (0)