FINERACT-2326: Loan contract termination same disbursement date
This commit is contained in:
committed by
Adam Saghy
parent
fdb9e5518c
commit
a0b31bbb25
+1
@@ -146,6 +146,7 @@ public enum DefaultLoanProduct implements LoanProduct {
|
|||||||
LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC_CAPITALIZED_INCOME, //
|
LP2_ADV_PYMNT_ZERO_INTEREST_CHARGE_OFF_DELINQUENT_REASON_INTEREST_RECALC_CAPITALIZED_INCOME, //
|
||||||
LP2_ADV_PYMNT_360_30_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF_ACCRUAL_ACTIVITY, //
|
LP2_ADV_PYMNT_360_30_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF_ACCRUAL_ACTIVITY, //
|
||||||
LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION, //
|
LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION, //
|
||||||
|
LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION, //
|
||||||
LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_MULTIDISB_OVER_APPLIED_PERCENTAGE_CAPITALIZED_INCOME, //
|
LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_MULTIDISB_OVER_APPLIED_PERCENTAGE_CAPITALIZED_INCOME, //
|
||||||
LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_MULTIDISB_OVER_APPLIED_FLAT_CAPITALIZED_INCOME, //
|
LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_MULTIDISB_OVER_APPLIED_FLAT_CAPITALIZED_INCOME, //
|
||||||
LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_APPROVED_OVER_APPLIED_PERCENTAGE_CAPITALIZED_INCOME, //
|
LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_APPROVED_OVER_APPLIED_PERCENTAGE_CAPITALIZED_INCOME, //
|
||||||
|
|||||||
+60
-27
@@ -4058,6 +4058,39 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
|
|||||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY,
|
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_CUSTOM_PMT_ALLOC_PROGRESSIVE_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY,
|
||||||
responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmi36030InterestRecalculationDaily);
|
responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmi36030InterestRecalculationDaily);
|
||||||
|
|
||||||
|
// LP2 with progressive loan schedule + horizontal + interest EMI + 360/30 + multidisbursement +
|
||||||
|
// contract termination with interest recognition
|
||||||
|
// (LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION)
|
||||||
|
final String name148 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION
|
||||||
|
.getName();
|
||||||
|
|
||||||
|
final PostLoanProductsRequest loanProductsRequestAdvCustomContractTerminationProgressiveLoanScheduleIntRecalcRecog = loanProductsRequestFactory
|
||||||
|
.defaultLoanProductsRequestLP2InterestDailyRecalculation()//
|
||||||
|
.interestRecognitionOnDisbursementDate(true) //
|
||||||
|
.name(name148)//
|
||||||
|
.paymentAllocation(List.of(//
|
||||||
|
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT",
|
||||||
|
LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_PENALTY, //
|
||||||
|
LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_FEE, //
|
||||||
|
LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_INTEREST, //
|
||||||
|
LoanProductPaymentAllocationRule.AllocationTypesEnum.PAST_DUE_PRINCIPAL, //
|
||||||
|
LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_PENALTY, //
|
||||||
|
LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_FEE, //
|
||||||
|
LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_PRINCIPAL, //
|
||||||
|
LoanProductPaymentAllocationRule.AllocationTypesEnum.DUE_INTEREST, //
|
||||||
|
LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_PENALTY, //
|
||||||
|
LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_FEE, //
|
||||||
|
LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_PRINCIPAL, //
|
||||||
|
LoanProductPaymentAllocationRule.AllocationTypesEnum.IN_ADVANCE_INTEREST), //
|
||||||
|
createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), //
|
||||||
|
createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), //
|
||||||
|
createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT")));//
|
||||||
|
final Response<PostLoanProductsResponse> responseLoanProductsRequestAdvCustomContractTerminationProgressiveLoanScheduleIntRecalcRecog = loanProductsApi
|
||||||
|
.createLoanProduct(loanProductsRequestAdvCustomContractTerminationProgressiveLoanScheduleIntRecalcRecog).execute();
|
||||||
|
TestContext.INSTANCE.set(
|
||||||
|
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION,
|
||||||
|
responseLoanProductsRequestAdvCustomContractTerminationProgressiveLoanScheduleIntRecalcRecog);
|
||||||
|
|
||||||
// (LP1_WITH_OVERRIDES) - Loan product with all attribute overrides ENABLED
|
// (LP1_WITH_OVERRIDES) - Loan product with all attribute overrides ENABLED
|
||||||
final String nameWithOverrides = DefaultLoanProduct.LP1_WITH_OVERRIDES.getName();
|
final String nameWithOverrides = DefaultLoanProduct.LP1_WITH_OVERRIDES.getName();
|
||||||
final PostLoanProductsRequest loanProductsRequestWithOverrides = loanProductsRequestFactory.defaultLoanProductsRequestLP1() //
|
final PostLoanProductsRequest loanProductsRequestWithOverrides = loanProductsRequestFactory.defaultLoanProductsRequestLP1() //
|
||||||
@@ -4106,33 +4139,6 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
|
|||||||
.execute();
|
.execute();
|
||||||
TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_NO_OVERRIDES, responseNoOverrides);
|
TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_NO_OVERRIDES, responseNoOverrides);
|
||||||
|
|
||||||
// LP2 advanced + progressive loan schedule + horizontal + interest recalculation
|
|
||||||
// Frequency for recalculate Outstanding Principal: Daily, Frequency Interval for recalculation: 1
|
|
||||||
String name148 = DefaultLoanProduct.LP2_ADV_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OF_ACCRUAL.getName();
|
|
||||||
PostLoanProductsRequest loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals = loanProductsRequestFactory
|
|
||||||
.defaultLoanProductsRequestLP2Emi()//
|
|
||||||
.name(name148)//
|
|
||||||
.supportedInterestRefundTypes(supportedInterestRefundTypes).installmentAmountInMultiplesOf(null) //
|
|
||||||
.daysInYearType(DaysInYearType.ACTUAL.value)//
|
|
||||||
.daysInMonthType(DaysInMonthType.ACTUAL.value)//
|
|
||||||
.daysInYearCustomStrategy(FEB_29_PERIOD_ONLY.getValue()).isInterestRecalculationEnabled(true)//
|
|
||||||
.preClosureInterestCalculationStrategy(1)//
|
|
||||||
.rescheduleStrategyMethod(4)//
|
|
||||||
.interestRecalculationCompoundingMethod(0)//
|
|
||||||
.recalculationRestFrequencyType(2)//
|
|
||||||
.recalculationRestFrequencyInterval(1)//
|
|
||||||
.enableAccrualActivityPosting(true) //
|
|
||||||
.chargeOffBehaviour(ZERO_INTEREST.value)//
|
|
||||||
.paymentAllocation(List.of(//
|
|
||||||
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"))); //
|
|
||||||
Response<PostLoanProductsResponse> responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals = loanProductsApi
|
|
||||||
.createLoanProduct(
|
|
||||||
loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals)
|
|
||||||
.execute();
|
|
||||||
TestContext.INSTANCE.set(
|
|
||||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OF_ACCRUAL,
|
|
||||||
responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals);
|
|
||||||
|
|
||||||
// LP2 advanced custom payment allocation + progressive loan schedule + horizontal + interest recalculation
|
// LP2 advanced custom payment allocation + progressive loan schedule + horizontal + interest recalculation
|
||||||
// Frequency for recalculate Outstanding Principal: Daily, Frequency Interval for recalculation: 1
|
// Frequency for recalculate Outstanding Principal: Daily, Frequency Interval for recalculation: 1
|
||||||
String name149 = DefaultLoanProduct.LP2_ADV_CUSTOM_PMT_ALLOC_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OFF_ACCRUAL
|
String name149 = DefaultLoanProduct.LP2_ADV_CUSTOM_PMT_ALLOC_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OFF_ACCRUAL
|
||||||
@@ -4163,6 +4169,33 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ
|
|||||||
TestContext.INSTANCE.set(
|
TestContext.INSTANCE.set(
|
||||||
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_CUSTOM_PMT_ALLOC_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OFF_ACCRUAL,
|
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_CUSTOM_PMT_ALLOC_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OFF_ACCRUAL,
|
||||||
responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals);
|
responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals);
|
||||||
|
|
||||||
|
// LP2 advanced + progressive loan schedule + horizontal + interest recalculation
|
||||||
|
// Frequency for recalculate Outstanding Principal: Daily, Frequency Interval for recalculation: 1
|
||||||
|
String name150 = DefaultLoanProduct.LP2_ADV_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OF_ACCRUAL.getName();
|
||||||
|
PostLoanProductsRequest loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals = loanProductsRequestFactory
|
||||||
|
.defaultLoanProductsRequestLP2Emi()//
|
||||||
|
.name(name150)//
|
||||||
|
.supportedInterestRefundTypes(supportedInterestRefundTypes).installmentAmountInMultiplesOf(null) //
|
||||||
|
.daysInYearType(DaysInYearType.ACTUAL.value)//
|
||||||
|
.daysInMonthType(DaysInMonthType.ACTUAL.value)//
|
||||||
|
.daysInYearCustomStrategy(FEB_29_PERIOD_ONLY.getValue()).isInterestRecalculationEnabled(true)//
|
||||||
|
.preClosureInterestCalculationStrategy(1)//
|
||||||
|
.rescheduleStrategyMethod(4)//
|
||||||
|
.interestRecalculationCompoundingMethod(0)//
|
||||||
|
.recalculationRestFrequencyType(2)//
|
||||||
|
.recalculationRestFrequencyInterval(1)//
|
||||||
|
.enableAccrualActivityPosting(true) //
|
||||||
|
.chargeOffBehaviour(ZERO_INTEREST.value)//
|
||||||
|
.paymentAllocation(List.of(//
|
||||||
|
createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"))); //
|
||||||
|
Response<PostLoanProductsResponse> responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals = loanProductsApi
|
||||||
|
.createLoanProduct(
|
||||||
|
loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals)
|
||||||
|
.execute();
|
||||||
|
TestContext.INSTANCE.set(
|
||||||
|
TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OF_ACCRUAL,
|
||||||
|
responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,
|
public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule,
|
||||||
|
|||||||
+1
@@ -259,6 +259,7 @@ public abstract class TestContextKey {
|
|||||||
public static final String LOAN_INTEREST_REFUND_RESPONSE = "loanInterestRefundResponse";
|
public static final String LOAN_INTEREST_REFUND_RESPONSE = "loanInterestRefundResponse";
|
||||||
public static final String INTEREST_PAUSE_VARIATION_ID = "interestPauseVariationId";
|
public static final String INTEREST_PAUSE_VARIATION_ID = "interestPauseVariationId";
|
||||||
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationContractTermination";
|
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationContractTermination";
|
||||||
|
public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_CONTRACT_TERMINATION_INT_RECOGNITION = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationContractTerminationIntRecognition";
|
||||||
public static final String LOAN_CONTRACT_TERMINATION_RESPONSE = "loanContractTerminationResponse";
|
public static final String LOAN_CONTRACT_TERMINATION_RESPONSE = "loanContractTerminationResponse";
|
||||||
public static final String LOAN_UNDO_CONTRACT_TERMINATION_RESPONSE = "loanUndoContractTerminationResponse";
|
public static final String LOAN_UNDO_CONTRACT_TERMINATION_RESPONSE = "loanUndoContractTerminationResponse";
|
||||||
public static final String LOAN_BUY_DOWN_FEE_RESPONSE = "loanBuyDownFeeResponse";
|
public static final String LOAN_BUY_DOWN_FEE_RESPONSE = "loanBuyDownFeeResponse";
|
||||||
|
|||||||
@@ -1317,3 +1317,48 @@ Feature: Contract Termination
|
|||||||
| 31 March 2024 | Accrual Adjustment | 0.15 | 0.0 | 0.15 | 0.0 | 0.0 | 0.0 | false | false |
|
| 31 March 2024 | Accrual Adjustment | 0.15 | 0.0 | 0.15 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
| 31 March 2024 | Contract Termination | 57.37 | 57.05 | 0.32 | 0.0 | 0.0 | 0.0 | true | true |
|
| 31 March 2024 | Contract Termination | 57.37 | 57.05 | 0.32 | 0.0 | 0.0 | 0.0 | true | true |
|
||||||
And Global configuration "is-principal-compounding-disabled-for-overdue-loans" is disabled
|
And Global configuration "is-principal-compounding-disabled-for-overdue-loans" is disabled
|
||||||
|
|
||||||
|
@TestRailId:C4133
|
||||||
|
Scenario: Contract termination on disbursement date
|
||||||
|
When Admin sets the business date to "01 January 2025"
|
||||||
|
And Admin creates a client with random data
|
||||||
|
And Admin creates a fully customized loan with the following data:
|
||||||
|
| 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 |
|
||||||
|
| 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 |
|
||||||
|
And Admin successfully approves the loan on "01 January 2025" with "100" amount and expected disbursement date on "01 January 2025"
|
||||||
|
And Admin successfully disburse the loan on "01 January 2025" with "100" EUR transaction amount
|
||||||
|
And Admin successfully terminates loan contract
|
||||||
|
Then Loan Repayment schedule has 1 periods, with the following data for periods:
|
||||||
|
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| | | 01 January 2025 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 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 |
|
||||||
|
And Loan Repayment schedule has the following data in Total row:
|
||||||
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 |
|
||||||
|
And Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
|
| 01 January 2025 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false |
|
||||||
|
| 01 January 2025 | Contract Termination | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
|
||||||
|
@TestRailId:C4134
|
||||||
|
Scenario: Contract termination on disbursement date with interest recognition
|
||||||
|
When Admin sets the business date to "01 January 2025"
|
||||||
|
And Admin creates a client with random data
|
||||||
|
And Admin creates a fully customized loan with the following data:
|
||||||
|
| 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 |
|
||||||
|
| 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 |
|
||||||
|
And Admin successfully approves the loan on "01 January 2025" with "100" amount and expected disbursement date on "01 January 2025"
|
||||||
|
And Admin successfully disburse the loan on "01 January 2025" with "100" EUR transaction amount
|
||||||
|
And Admin successfully terminates loan contract
|
||||||
|
Then Loan Repayment schedule has 1 periods, with the following data for periods:
|
||||||
|
| Nr | Days | Date | Paid date | Balance of loan | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| | | 01 January 2025 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | |
|
||||||
|
| 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 |
|
||||||
|
And Loan Repayment schedule has the following data in Total row:
|
||||||
|
| Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding |
|
||||||
|
| 100.0 | 0.0 | 0.0 | 0.0 | 100.0 | 0.0 | 0.0 | 0.0 | 100.0 |
|
||||||
|
And Loan Transactions tab has the following data:
|
||||||
|
| Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed |
|
||||||
|
| 01 January 2025 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | false | false |
|
||||||
|
| 01 January 2025 | Contract Termination | 100.0 | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false |
|
||||||
|
|
||||||
|
|||||||
+11
-4
@@ -1410,10 +1410,17 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> {
|
|||||||
* @param date
|
* @param date
|
||||||
* @return a schedule installment is related to the provided date
|
* @return a schedule installment is related to the provided date
|
||||||
**/
|
**/
|
||||||
public LoanRepaymentScheduleInstallment getRelatedRepaymentScheduleInstallment(LocalDate date) {
|
public LoanRepaymentScheduleInstallment getRelatedRepaymentScheduleInstallment(final LocalDate date) {
|
||||||
return getRepaymentScheduleInstallment(
|
return getRepaymentScheduleInstallment(e -> (DateUtils.isDateInRangeFromExclusiveToInclusive(date, e.getFromDate(), e.getDueDate())
|
||||||
e -> (e.isFirstNormalInstallment() && DateUtils.isDateInRangeInclusive(date, e.getFromDate(), e.getDueDate()))
|
|| (e.isFirstNormalInstallment(getRepaymentScheduleInstallments())
|
||||||
|| DateUtils.isDateInRangeFromExclusiveToInclusive(date, e.getFromDate(), e.getDueDate()));
|
&& DateUtils.isDateInRangeInclusive(date, e.getFromDate(), e.getDueDate()))));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<LoanRepaymentScheduleInstallment> getInstallmentsUpToTransactionDate(final LocalDate transactionDate) {
|
||||||
|
return getRepaymentScheduleInstallments().stream()
|
||||||
|
.filter(i -> (transactionDate.isAfter(i.getFromDate())
|
||||||
|
|| (i.isFirstNormalInstallment(getRepaymentScheduleInstallments()) && !transactionDate.isBefore(i.getFromDate()))))
|
||||||
|
.collect(Collectors.toCollection(ArrayList::new));
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoanRepaymentScheduleInstallment fetchRepaymentScheduleInstallment(final Integer installmentNumber) {
|
public LoanRepaymentScheduleInstallment fetchRepaymentScheduleInstallment(final Integer installmentNumber) {
|
||||||
|
|||||||
+2
-3
@@ -1216,8 +1216,7 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa
|
|||||||
return value.setScale(6, MoneyHelper.getRoundingMode());
|
return value.setScale(6, MoneyHelper.getRoundingMode());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isFirstNormalInstallment() {
|
public boolean isFirstNormalInstallment(List<LoanRepaymentScheduleInstallment> installments) {
|
||||||
return loan.getRepaymentScheduleInstallments().stream().filter(rp -> !rp.isDownPayment()).findFirst().stream()
|
return installments.stream().filter(rp -> !rp.isDownPayment()).findFirst().stream().anyMatch(rp -> rp.equals(this));
|
||||||
.anyMatch(rp -> rp.equals(this));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-1
@@ -2255,5 +2255,4 @@ public final class LoanApplicationTerms {
|
|||||||
public void updateVariationDays(final long daysToAdd) {
|
public void updateVariationDays(final long daysToAdd) {
|
||||||
this.variationDays += daysToAdd;
|
this.variationDays += daysToAdd;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-5
@@ -1760,10 +1760,10 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
final Loan loan = loanTransaction.getLoan();
|
final Loan loan = loanTransaction.getLoan();
|
||||||
final LoanRepaymentScheduleInstallment currentInstallment = loan.getRelatedRepaymentScheduleInstallment(transactionDate);
|
final LoanRepaymentScheduleInstallment currentInstallment = loan.getRelatedRepaymentScheduleInstallment(transactionDate);
|
||||||
|
|
||||||
if (!installments.isEmpty() && transactionDate.isBefore(loan.getMaturityDate())) {
|
if (!installments.isEmpty() && transactionDate.isBefore(loan.getMaturityDate()) && currentInstallment != null) {
|
||||||
if (currentInstallment.isNotFullyPaidOff()) {
|
if (currentInstallment.isNotFullyPaidOff()) {
|
||||||
if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx
|
if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx
|
||||||
&& loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) {
|
&& loan.isInterestBearingAndInterestRecalculationEnabled()) {
|
||||||
final BigDecimal interestOutstanding = currentInstallment.getInterestOutstanding(loan.getCurrency()).getAmount();
|
final BigDecimal interestOutstanding = currentInstallment.getInterestOutstanding(loan.getCurrency()).getAmount();
|
||||||
final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(),
|
final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(),
|
||||||
currentInstallment.getDueDate(), transactionDate, true).getAmount();
|
currentInstallment.getDueDate(), transactionDate, true).getAmount();
|
||||||
@@ -1817,9 +1817,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep
|
|||||||
MathUtil.nullToZero(currentInstallment.getTotalPaidInAdvance()).add(futureTotalPaidInAdvance));
|
MathUtil.nullToZero(currentInstallment.getTotalPaidInAdvance()).add(futureTotalPaidInAdvance));
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<LoanRepaymentScheduleInstallment> installmentsUpToTransactionDate = installments.stream()
|
final List<LoanRepaymentScheduleInstallment> installmentsUpToTransactionDate = loan
|
||||||
.filter(installment -> transactionDate.isAfter(installment.getFromDate()))
|
.getInstallmentsUpToTransactionDate(transactionDate);
|
||||||
.collect(Collectors.toCollection(ArrayList::new));
|
|
||||||
|
|
||||||
final List<LoanTransaction> transactionsToBeReprocessed = loan.getLoanTransactions().stream()
|
final List<LoanTransaction> transactionsToBeReprocessed = loan.getLoanTransactions().stream()
|
||||||
.filter(transaction -> transaction.getTransactionDate().isBefore(transactionDate))
|
.filter(transaction -> transaction.getTransactionDate().isBefore(transactionDate))
|
||||||
|
|||||||
+31
@@ -18,13 +18,17 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.fineract.integrationtests;
|
package org.apache.fineract.integrationtests;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||||
import org.apache.fineract.client.models.PostClientsResponse;
|
import org.apache.fineract.client.models.PostClientsResponse;
|
||||||
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
import org.apache.fineract.client.models.PostLoanProductsResponse;
|
||||||
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
|
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
|
||||||
import org.apache.fineract.client.util.CallFailedRuntimeException;
|
import org.apache.fineract.client.util.CallFailedRuntimeException;
|
||||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||||
|
import org.apache.fineract.integrationtests.common.GlobalConfigurationHelper;
|
||||||
import org.apache.fineract.integrationtests.common.Utils;
|
import org.apache.fineract.integrationtests.common.Utils;
|
||||||
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
|
||||||
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
|
||||||
@@ -118,4 +122,31 @@ public class LoanContractTerminationTest extends BaseLoanIntegrationTest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoanContractTerminationSameDisbursementDate() {
|
||||||
|
final PostClientsResponse client = clientHelper.createClient(ClientHelper.defaultClientCreationRequest());
|
||||||
|
final GlobalConfigurationHelper globalConfigurationHelper = new GlobalConfigurationHelper();
|
||||||
|
|
||||||
|
runAt("1 January 2024", () -> {
|
||||||
|
|
||||||
|
PostLoanProductsResponse loanProductsResponse = loanProductHelper
|
||||||
|
.createLoanProduct(create4IProgressive().interestRecognitionOnDisbursementDate(false));
|
||||||
|
Long loanId = applyAndApproveProgressiveLoan(client.getClientId(), loanProductsResponse.getResourceId(), "1 January 2024",
|
||||||
|
500.0, 7.0, 6, (request) -> request.interestRecognitionOnDisbursementDate(false));
|
||||||
|
|
||||||
|
disburseLoan(loanId, BigDecimal.valueOf(100), "1 January 2024");
|
||||||
|
|
||||||
|
loanTransactionHelper.moveLoanState(loanId,
|
||||||
|
new PostLoansLoanIdRequest().note("Contract Termination Test").externalId(Utils.randomStringGenerator("", 20)),
|
||||||
|
"contractTermination");
|
||||||
|
|
||||||
|
verifyTransactions(loanId, //
|
||||||
|
transaction(100.0, "Disbursement", "01 January 2024"), //
|
||||||
|
transaction(100.0, "Contract Termination", "01 January 2024"));
|
||||||
|
|
||||||
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||||
|
assertEquals(BigDecimal.ZERO.stripTrailingZeros(), loanDetails.getSummary().getInterestCharged().stripTrailingZeros());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user