This is an automated email from the ASF dual-hosted git repository. adamsaghy pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract.git
commit a0b31bbb2508ab32517ff50fbb3526470957e83f Author: Jose Alberto Hernandez <[email protected]> AuthorDate: Fri Oct 3 13:01:50 2025 -0500 FINERACT-2326: Loan contract termination same disbursement date --- .../test/data/loanproduct/DefaultLoanProduct.java | 1 + .../global/LoanProductGlobalInitializerStep.java | 77 +++++++++++++++------- .../fineract/test/support/TestContextKey.java | 1 + .../features/LoanContractTermination.feature | 45 +++++++++++++ .../portfolio/loanaccount/domain/Loan.java | 15 +++-- .../domain/LoanRepaymentScheduleInstallment.java | 5 +- .../loanschedule/domain/LoanApplicationTerms.java | 1 - ...dvancedPaymentScheduleTransactionProcessor.java | 9 ++- .../LoanContractTerminationTest.java | 31 +++++++++ 9 files changed, 150 insertions(+), 35 deletions(-) diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java index f8b9b3afd2..bec004ff72 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/data/loanproduct/DefaultLoanProduct.java @@ -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_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_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_FLAT_CAPITALIZED_INCOME, // LP2_ADV_PYMNT_INTEREST_DAILY_RECALC_EMI_360_30_APPROVED_OVER_APPLIED_PERCENTAGE_CAPITALIZED_INCOME, // diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java index 407448de53..0d4556ce6d 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/initializer/global/LoanProductGlobalInitializerStep.java @@ -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, 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 final String nameWithOverrides = DefaultLoanProduct.LP1_WITH_OVERRIDES.getName(); final PostLoanProductsRequest loanProductsRequestWithOverrides = loanProductsRequestFactory.defaultLoanProductsRequestLP1() // @@ -4106,12 +4139,13 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ .execute(); TestContext.INSTANCE.set(TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_NO_OVERRIDES, responseNoOverrides); - // LP2 advanced + 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 - String name148 = DefaultLoanProduct.LP2_ADV_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OF_ACCRUAL.getName(); - PostLoanProductsRequest loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals = loanProductsRequestFactory + String name149 = DefaultLoanProduct.LP2_ADV_CUSTOM_PMT_ALLOC_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OFF_ACCRUAL + .getName(); + PostLoanProductsRequest loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals = loanProductsRequestFactory .defaultLoanProductsRequestLP2Emi()// - .name(name148)// + .name(name149)// .supportedInterestRefundTypes(supportedInterestRefundTypes).installmentAmountInMultiplesOf(null) // .daysInYearType(DaysInYearType.ACTUAL.value)// .daysInMonthType(DaysInMonthType.ACTUAL.value)// @@ -4124,22 +4158,24 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ .enableAccrualActivityPosting(true) // .chargeOffBehaviour(ZERO_INTEREST.value)// .paymentAllocation(List.of(// - createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"))); // - Response<PostLoanProductsResponse> responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals = loanProductsApi + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // + createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // + createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // + createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT"))); // + Response<PostLoanProductsResponse> responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals = loanProductsApi .createLoanProduct( - loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals) + loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals) .execute(); TestContext.INSTANCE.set( - TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OF_ACCRUAL, - responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals); + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_CUSTOM_PMT_ALLOC_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OFF_ACCRUAL, + responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals); - // LP2 advanced custom payment allocation + progressive loan schedule + horizontal + interest recalculation + // LP2 advanced + progressive loan schedule + horizontal + interest recalculation // 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 - .getName(); - PostLoanProductsRequest loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals = loanProductsRequestFactory + String name150 = DefaultLoanProduct.LP2_ADV_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_RECALC_ZERO_CHARGE_OF_ACCRUAL.getName(); + PostLoanProductsRequest loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals = loanProductsRequestFactory .defaultLoanProductsRequestLP2Emi()// - .name(name149)// + .name(name150)// .supportedInterestRefundTypes(supportedInterestRefundTypes).installmentAmountInMultiplesOf(null) // .daysInYearType(DaysInYearType.ACTUAL.value)// .daysInMonthType(DaysInMonthType.ACTUAL.value)// @@ -4152,17 +4188,14 @@ public class LoanProductGlobalInitializerStep implements FineractGlobalInitializ .enableAccrualActivityPosting(true) // .chargeOffBehaviour(ZERO_INTEREST.value)// .paymentAllocation(List.of(// - createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"), // - createPaymentAllocation("GOODWILL_CREDIT", "LAST_INSTALLMENT"), // - createPaymentAllocation("MERCHANT_ISSUED_REFUND", "REAMORTIZATION"), // - createPaymentAllocation("PAYOUT_REFUND", "NEXT_INSTALLMENT"))); // - Response<PostLoanProductsResponse> responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals = loanProductsApi + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"))); // + Response<PostLoanProductsResponse> responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals = loanProductsApi .createLoanProduct( - loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals) + loanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffChargebackAccruals) .execute(); 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, - responseLoanProductsResponseAdvCustomPaymentAllocationProgressiveLoanInterestDailyEmiActualInterestRecalcZeroChargeOffAccruals); + 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, diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java index 30db0f017b..f294b9e893 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/support/TestContextKey.java @@ -259,6 +259,7 @@ public abstract class TestContextKey { public static final String LOAN_INTEREST_REFUND_RESPONSE = "loanInterestRefundResponse"; 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_INT_RECOGNITION = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationContractTerminationIntRecognition"; public static final String LOAN_CONTRACT_TERMINATION_RESPONSE = "loanContractTerminationResponse"; public static final String LOAN_UNDO_CONTRACT_TERMINATION_RESPONSE = "loanUndoContractTerminationResponse"; public static final String LOAN_BUY_DOWN_FEE_RESPONSE = "loanBuyDownFeeResponse"; diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature index 65cded5e73..641db7d926 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanContractTermination.feature @@ -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 | 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 + + @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 | + diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java index 31a6f971c0..601b8c6bba 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/Loan.java @@ -1410,10 +1410,17 @@ public class Loan extends AbstractAuditableWithUTCDateTimeCustom<Long> { * @param date * @return a schedule installment is related to the provided date **/ - public LoanRepaymentScheduleInstallment getRelatedRepaymentScheduleInstallment(LocalDate date) { - return getRepaymentScheduleInstallment( - e -> (e.isFirstNormalInstallment() && DateUtils.isDateInRangeInclusive(date, e.getFromDate(), e.getDueDate())) - || DateUtils.isDateInRangeFromExclusiveToInclusive(date, e.getFromDate(), e.getDueDate())); + public LoanRepaymentScheduleInstallment getRelatedRepaymentScheduleInstallment(final LocalDate date) { + return getRepaymentScheduleInstallment(e -> (DateUtils.isDateInRangeFromExclusiveToInclusive(date, e.getFromDate(), e.getDueDate()) + || (e.isFirstNormalInstallment(getRepaymentScheduleInstallments()) + && 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) { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java index 52e8dd8982..fb55acbbdd 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanRepaymentScheduleInstallment.java @@ -1216,8 +1216,7 @@ public class LoanRepaymentScheduleInstallment extends AbstractAuditableWithUTCDa return value.setScale(6, MoneyHelper.getRoundingMode()); } - public boolean isFirstNormalInstallment() { - return loan.getRepaymentScheduleInstallments().stream().filter(rp -> !rp.isDownPayment()).findFirst().stream() - .anyMatch(rp -> rp.equals(this)); + public boolean isFirstNormalInstallment(List<LoanRepaymentScheduleInstallment> installments) { + return installments.stream().filter(rp -> !rp.isDownPayment()).findFirst().stream().anyMatch(rp -> rp.equals(this)); } } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java index 560aa83d22..77a4e43865 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/domain/LoanApplicationTerms.java @@ -2255,5 +2255,4 @@ public final class LoanApplicationTerms { public void updateVariationDays(final long daysToAdd) { this.variationDays += daysToAdd; } - } diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java index 57221a6a47..bf06feaa5c 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/impl/AdvancedPaymentScheduleTransactionProcessor.java @@ -1760,10 +1760,10 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep final Loan loan = loanTransaction.getLoan(); 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 (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx - && loanTransaction.getLoan().isInterestBearingAndInterestRecalculationEnabled()) { + && loan.isInterestBearingAndInterestRecalculationEnabled()) { final BigDecimal interestOutstanding = currentInstallment.getInterestOutstanding(loan.getCurrency()).getAmount(); final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(), currentInstallment.getDueDate(), transactionDate, true).getAmount(); @@ -1817,9 +1817,8 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep MathUtil.nullToZero(currentInstallment.getTotalPaidInAdvance()).add(futureTotalPaidInAdvance)); } - final List<LoanRepaymentScheduleInstallment> installmentsUpToTransactionDate = installments.stream() - .filter(installment -> transactionDate.isAfter(installment.getFromDate())) - .collect(Collectors.toCollection(ArrayList::new)); + final List<LoanRepaymentScheduleInstallment> installmentsUpToTransactionDate = loan + .getInstallmentsUpToTransactionDate(transactionDate); final List<LoanTransaction> transactionsToBeReprocessed = loan.getLoanTransactions().stream() .filter(transaction -> transaction.getTransactionDate().isBefore(transactionDate)) diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanContractTerminationTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanContractTerminationTest.java index 80fc4b5de7..105fbf70d1 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanContractTerminationTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanContractTerminationTest.java @@ -18,13 +18,17 @@ */ package org.apache.fineract.integrationtests; +import static org.junit.jupiter.api.Assertions.assertEquals; + import java.math.BigDecimal; 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.PostLoanProductsResponse; import org.apache.fineract.client.models.PostLoansLoanIdRequest; import org.apache.fineract.client.util.CallFailedRuntimeException; 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.loans.LoanProductTestBuilder; 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()); + }); + } + }
