From 2531f99cd55b3aa75f8767958ce7f841e473c5ed Mon Sep 17 00:00:00 2001 From: Oleksii Novikov Date: Thu, 5 Dec 2024 18:14:42 +0200 Subject: [PATCH] FINERACT-2148: Update instalments interest with zero after charge off with interest recalculation enabled --- .../data/loanproduct/DefaultLoanProduct.java | 1 + .../LoanProductGlobalInitializerStep.java | 17 + .../test/stepdef/loan/LoanStepDef.java | 29 ++ .../fineract/test/support/TestContextKey.java | 1 + .../resources/features/LoanChargeOff.feature | 310 ++++++++++++++++++ .../portfolio/loanaccount/domain/Loan.java | 6 +- ...RepaymentScheduleTransactionProcessor.java | 8 +- ...edPaymentScheduleTransactionProcessor.java | 63 +++- ...WritePlatformServiceJpaRepositoryImpl.java | 12 +- 9 files changed, 440 insertions(+), 7 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 0951115bbd..5783e61946 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 @@ -73,6 +73,7 @@ public enum DefaultLoanProduct implements LoanProduct { LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_WHOLE_TERM, // LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_IR_DAILY_TILL_PRECLOSE_LAST_INSTALLMENT_STRATEGY, // LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_INTEREST_RECALCULATION, // + LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF_BEHAVIOUR, // ; @Override 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 6200b34ef7..5bd692f3ac 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 @@ -1088,6 +1088,23 @@ public void initialize() throws Exception { TestContext.INSTANCE.set( TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADVANCED_CUSTOM_PAYMENT_ALLOCATION_INTEREST_RECALCULATION_DAILY_EMI_360_30_MULTIDISBURSE, responseLoanProductsRequestLP2AdvCustomPaymentAllocationInterestRecalculationDaily36030MultiDisburse); + + // LP2 + interest recalculation + zero-interest chargeOff behaviour + progressive loan schedule + horizontal + // (LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF_BEHAVIOUR) + final String name51 = DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF_BEHAVIOUR + .getName(); + + final PostLoanProductsRequest loanProductsRequestAdvCustomZeroInterestChargeOffBehaviourProgressiveLoanSchedule = loanProductsRequestFactory + .defaultLoanProductsRequestLP2InterestDailyRecalculation()// + .name(name51)// + .paymentAllocation(List.of(// + createPaymentAllocation("DEFAULT", "NEXT_INSTALLMENT"))) + .chargeOffBehaviour("ZERO_INTEREST");// + final Response responseLoanProductsRequestAdvCustomZeroInterestChargeOffBehaviourProgressiveLoanSchedule = loanProductsApi + .createLoanProduct(loanProductsRequestAdvCustomZeroInterestChargeOffBehaviourProgressiveLoanSchedule).execute(); + TestContext.INSTANCE.set( + TestContextKey.DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF_BEHAVIOUR, + responseLoanProductsRequestAdvCustomZeroInterestChargeOffBehaviourProgressiveLoanSchedule); } public static AdvancedPaymentData createPaymentAllocation(String transactionType, String futureInstallmentAllocationRule, diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java index 20d50d2af6..b691ac573b 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanStepDef.java @@ -19,6 +19,7 @@ package org.apache.fineract.test.stepdef.loan; import static org.apache.fineract.test.data.TransactionProcessingStrategyCode.ADVANCED_PAYMENT_ALLOCATION; +import static org.apache.fineract.test.data.loanproduct.DefaultLoanProduct.LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF_BEHAVIOUR; import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertNotNull; @@ -2759,6 +2760,34 @@ public void createFullyCustomizedLoanWithInterestRateFrequency(final List clientResponse = testContext().get(TestContextKey.CLIENT_CREATE_RESPONSE); + final Long clientId = clientResponse.body().getClientId(); + + final DefaultLoanProduct product = DefaultLoanProduct + .valueOf(LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF_BEHAVIOUR.getName()); + final Long loanProductId = loanProductResolver.resolve(product); + + final PostLoansRequest loansRequest = loanRequestFactory.defaultLoansRequest(clientId).productId(loanProductId) + .principal(new BigDecimal(100)).numberOfRepayments(6).submittedOnDate(date).expectedDisbursementDate(date) + .loanTermFrequency(6)// + .loanTermFrequencyType(LoanTermFrequencyType.MONTHS.value)// + .repaymentEvery(1)// + .repaymentFrequencyType(RepaymentFrequencyType.MONTHS.value)// + .interestRateFrequencyType(3)// + .interestRatePerPeriod(new BigDecimal(7))// + .interestType(InterestType.DECLINING_BALANCE.value)// + .interestCalculationPeriodType(InterestCalculationPeriodTime.DAILY.value)// + .transactionProcessingStrategyCode(ADVANCED_PAYMENT_ALLOCATION.value); + + final Response response = loansApi.calculateLoanScheduleOrSubmitLoanApplication(loansRequest, "").execute(); + testContext().set(TestContextKey.LOAN_CREATE_RESPONSE, response); + ErrorHelper.checkSuccessfulApiCall(response); + + eventCheckHelper.createLoanEventCheck(response); + } + private void performLoanDisbursementAndVerifyStatus(final long loanId, final PostLoansLoanIdRequest disburseRequest) throws IOException { final Response loanDisburseResponse = loansApi.stateTransitions(loanId, disburseRequest, "disburse") 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 81f3aa1201..7fe0293307 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 @@ -104,6 +104,7 @@ public abstract class TestContextKey { public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP1_ADVANCED_PAYMENT_ALLOCATION_PROGRESSIVE_LOAN_SCHEDULE_HORIZONTAL = "loanProductCreateResponseLP1ProgressiveLoanScheduleHorizontal"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_360_30_INTEREST_RECALCULATION_DAILY_TILL_PRECLOSE_WHOLE_TERM = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmi36030InterestRecalculationSameAsRepTillPreCloseWholeTerm"; public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_EMI_ACTUAL_ACTUAL_INTEREST_REFUND_INTEREST_RECALCULATION = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyEmiActualActualInterestRefundFInterestRecalculation"; + public static final String DEFAULT_LOAN_PRODUCT_CREATE_RESPONSE_LP2_ADV_PYMNT_INTEREST_DAILY_INTEREST_RECALCULATION_ZERO_INTEREST_CHARGE_OFF_BEHAVIOUR = "loanProductCreateResponseLP2AdvancedPaymentInterestDailyInterestRecalculationZeroInterestChargeOffBehaviour"; public static final String CHARGE_FOR_LOAN_PERCENT_LATE_CREATE_RESPONSE = "ChargeForLoanPercentLateCreateResponse"; public static final String CHARGE_FOR_LOAN_PERCENT_LATE_AMOUNT_PLUS_INTEREST_CREATE_RESPONSE = "ChargeForLoanPercentLateAmountPlusInterestCreateResponse"; public static final String CHARGE_FOR_LOAN_PERCENT_PROCESSING_CREATE_RESPONSE = "ChargeForLoanPercentProcessingCreateResponse"; diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeOff.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeOff.feature index 546cdc6575..28bd5583bd 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeOff.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanChargeOff.feature @@ -1611,3 +1611,313 @@ Feature: Charge-off Then Loan Repayment schedule has the following data in Total row: | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | | 1000 | 12.22 | 0 | 0 | 1012.22 | 0 | 0 | 0 | 1012.22 | + + Scenario: Charge-off on due date when loan behaviour is zero-interest and interestRecalculation = true + When Admin sets the business date to "1 March 2023" + And Admin creates a client with random data + When Admin creates a new zero charge-off Loan with date: "1 January 2023" + Then Loan Repayment schedule has 6 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 2023 | | 100.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2023 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 01 March 2023 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2023 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2023 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2023 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2023 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 2.05 | 0 | 0 | 102.05 | 0 | 0 | 0 | 102.05 | + And Admin successfully approves the loan on "1 January 2023" with "100" amount and expected disbursement date on "1 January 2023" + And Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount + And Admin does charge-off the loan on "1 March 2023" + Then Loan Repayment schedule has 6 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 2023 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2023 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 01 March 2023 | | 67.14 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2023 | | 50.13 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2023 | | 33.12 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2023 | | 16.11 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2023 | | 0.0 | 16.11 | 0.0 | 0.0 | 0.0 | 16.11 | 0.0 | 0.0 | 0.0 | 16.11 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 1.16 | 0 | 0 | 101.16 | 0 | 0 | 0 | 101.16 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 01 March 2023 | Charge-off | 101.16 | 100.0 | 1.16 | 0.0 | 0.0 | 0.0 | + + Scenario: Charge-off after installment date when loan behaviour is zero-interest and interestRecalculation = true + When Admin sets the business date to "1 March 2023" + And Admin creates a client with random data + When Admin creates a new zero charge-off Loan with date: "1 January 2023" + Then Loan Repayment schedule has 6 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 2023 | | 100.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2023 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 01 March 2023 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2023 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2023 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2023 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2023 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 2.05 | 0 | 0 | 102.05 | 0 | 0 | 0 | 102.05 | + And Admin successfully approves the loan on "1 January 2023" with "100" amount and expected disbursement date on "1 January 2023" + And Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount + And Admin does charge-off the loan on "28 February 2023" + Then Loan Repayment schedule has 6 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 2023 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2023 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 01 March 2023 | | 67.12 | 16.45 | 0.56 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2023 | | 50.11 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2023 | | 33.1 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2023 | | 16.09 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2023 | | 0.0 | 16.09 | 0.0 | 0.0 | 0.0 | 16.09 | 0.0 | 0.0 | 0.0 | 16.09 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 1.14 | 0 | 0 | 101.14 | 0 | 0 | 0 | 101.14 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 28 February 2023 | Charge-off | 101.14 | 100.0 | 1.14 | 0.0 | 0.0 | 0.0 | + + Scenario: Charge-off in the middle of installment period when loan behaviour is zero-interest and interestRecalculation = true + When Admin sets the business date to "1 March 2023" + And Admin creates a client with random data + When Admin creates a new zero charge-off Loan with date: "1 January 2023" + Then Loan Repayment schedule has 6 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 2023 | | 100.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2023 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 01 March 2023 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2023 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2023 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2023 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2023 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 2.05 | 0 | 0 | 102.05 | 0 | 0 | 0 | 102.05 | + And Admin successfully approves the loan on "1 January 2023" with "100" amount and expected disbursement date on "1 January 2023" + And Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount + And Admin does charge-off the loan on "14 February 2023" + Then Loan Repayment schedule has 6 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 2023 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2023 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 01 March 2023 | | 66.83 | 16.74 | 0.27 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2023 | | 49.82 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2023 | | 32.81 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2023 | | 15.8 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2023 | | 0.0 | 15.8 | 0.0 | 0.0 | 0.0 | 15.8 | 0.0 | 0.0 | 0.0 | 15.8 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 0.85 | 0 | 0 | 100.85 | 0 | 0 | 0 | 100.85 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 14 February 2023 | Charge-off | 100.85 | 100.0 | 0.85 | 0.0 | 0.0 | 0.0 | + + Scenario: Charge-off after maturity date when loan behaviour is zero-interest and interestRecalculation = true + When Admin sets the business date to "1 August 2023" + And Admin creates a client with random data + When Admin creates a new zero charge-off Loan with date: "1 January 2023" + Then Loan Repayment schedule has 6 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 2023 | | 100.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2023 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 01 March 2023 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2023 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2023 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2023 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2023 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 2.05 | 0 | 0 | 102.05 | 0 | 0 | 0 | 102.05 | + And Admin successfully approves the loan on "1 January 2023" with "100" amount and expected disbursement date on "1 January 2023" + And Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount + And Admin does charge-off the loan on "15 July 2023" + Then Loan Repayment schedule has 6 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 2023 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2023 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 01 March 2023 | | 67.14 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2023 | | 50.71 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2023 | | 34.28 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2023 | | 17.85 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2023 | | 0.0 | 17.85 | 0.58 | 0.0 | 0.0 | 18.43 | 0.0 | 0.0 | 0.0 | 18.43 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 3.48 | 0 | 0 | 103.48 | 0 | 0 | 0 | 103.48 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 15 July 2023 | Charge-off | 103.48 | 100.0 | 3.48 | 0.0 | 0.0 | 0.0 | + + Scenario: Charge-off when charge is added before the charge-off date, loan behaviour is zero-interest and interestRecalculation = true + When Admin sets the business date to "1 March 2023" + And Admin creates a client with random data + When Admin creates a new zero charge-off Loan with date: "1 January 2023" + Then Loan Repayment schedule has 6 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 2023 | | 100.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2023 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 01 March 2023 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2023 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2023 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2023 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2023 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 2.05 | 0 | 0 | 102.05 | 0 | 0 | 0 | 102.05 | + And Admin successfully approves the loan on "1 January 2023" with "100" amount and expected disbursement date on "1 January 2023" + And Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount + When Admin adds "LOAN_SNOOZE_FEE" due date charge with "27 February 2023" due date and 3 EUR transaction amount + And Admin does charge-off the loan on "28 February 2023" + Then Loan Repayment schedule has 6 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 2023 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2023 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 01 March 2023 | | 67.12 | 16.45 | 0.56 | 3.0 | 0.0 | 20.01 | 0.0 | 0.0 | 0.0 | 20.01 | + | 3 | 31 | 01 April 2023 | | 50.11 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2023 | | 33.1 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2023 | | 16.09 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2023 | | 0.0 | 16.09 | 0.0 | 0.0 | 0.0 | 16.09 | 0.0 | 0.0 | 0.0 | 16.09 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 1.14 | 3.0 | 0 | 104.14 | 0 | 0 | 0 | 104.14 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 28 February 2023 | Charge-off | 104.14 | 100.0 | 1.14 | 3.0 | 0.0 | 0.0 | + + Scenario: Charge-off when charge is added on charge-off date, loan behaviour is zero-interest and interestRecalculation = true + When Admin sets the business date to "1 March 2023" + And Admin creates a client with random data + When Admin creates a new zero charge-off Loan with date: "1 January 2023" + Then Loan Repayment schedule has 6 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 2023 | | 100.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2023 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 01 March 2023 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2023 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2023 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2023 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2023 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 2.05 | 0 | 0 | 102.05 | 0 | 0 | 0 | 102.05 | + And Admin successfully approves the loan on "1 January 2023" with "100" amount and expected disbursement date on "1 January 2023" + And Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount + When Admin adds "LOAN_SNOOZE_FEE" due date charge with "28 February 2023" due date and 3 EUR transaction amount + And Admin does charge-off the loan on "28 February 2023" + Then Loan Repayment schedule has 6 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 2023 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2023 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 01 March 2023 | | 67.12 | 16.45 | 0.56 | 3.0 | 0.0 | 20.01 | 0.0 | 0.0 | 0.0 | 20.01 | + | 3 | 31 | 01 April 2023 | | 50.11 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2023 | | 33.1 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2023 | | 16.09 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2023 | | 0.0 | 16.09 | 0.0 | 0.0 | 0.0 | 16.09 | 0.0 | 0.0 | 0.0 | 16.09 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 1.14 | 3.0 | 0 | 104.14 | 0 | 0 | 0 | 104.14 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 28 February 2023 | Charge-off | 104.14 | 100.0 | 1.14 | 3.0 | 0.0 | 0.0 | + + Scenario: Charge-off when charge is added after the charge off date, loan behaviour is zero-interest and interestRecalculation = true + When Admin sets the business date to "1 March 2023" + And Admin creates a client with random data + When Admin creates a new zero charge-off Loan with date: "1 January 2023" + Then Loan Repayment schedule has 6 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 2023 | | 100.0 | | | 0.0 | | 0.0 | | | | 0.0 | + | 1 | 31 | 01 February 2023 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 01 March 2023 | | 67.05 | 16.52 | 0.49 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2023 | | 50.43 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2023 | | 33.71 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2023 | | 16.9 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2023 | | 0.0 | 16.9 | 0.1 | 0.0 | 0.0 | 17.0 | 0.0 | 0.0 | 0.0 | 17.0 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 2.05 | 0 | 0 | 102.05 | 0 | 0 | 0 | 102.05 | + And Admin successfully approves the loan on "1 January 2023" with "100" amount and expected disbursement date on "1 January 2023" + And Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount + When Admin adds "LOAN_SNOOZE_FEE" due date charge with "1 March 2023" due date and 3 EUR transaction amount + And Admin does charge-off the loan on "28 February 2023" + Then Loan Repayment schedule has 6 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 2023 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2023 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 01 March 2023 | | 67.12 | 16.45 | 0.56 | 3.0 | 0.0 | 20.01 | 0.0 | 0.0 | 0.0 | 20.01 | + | 3 | 31 | 01 April 2023 | | 50.11 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2023 | | 33.1 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2023 | | 16.09 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2023 | | 0.0 | 16.09 | 0.0 | 0.0 | 0.0 | 16.09 | 0.0 | 0.0 | 0.0 | 16.09 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 1.14 | 3.0 | 0 | 104.14 | 0 | 0 | 0 | 104.14 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 28 February 2023 | Charge-off | 101.14 | 100.0 | 1.14 | 0.0 | 0.0 | 0.0 | + + Scenario: Undo the charge-off when loan behaviour is zero-interest and interestRecalculation = true + When Admin sets the business date to "1 March 2023" + And Admin creates a client with random data + When Admin creates a new zero charge-off Loan with date: "1 January 2023" + And Admin successfully approves the loan on "1 January 2023" with "100" amount and expected disbursement date on "1 January 2023" + And Admin successfully disburse the loan on "1 January 2023" with "100" EUR transaction amount + Then Loan Repayment schedule has 6 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 2023 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2023 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 01 March 2023 | | 67.14 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2023 | | 50.52 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2023 | | 33.8 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2023 | | 16.99 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2023 | | 0.0 | 16.99 | 0.1 | 0.0 | 0.0 | 17.09 | 0.0 | 0.0 | 0.0 | 17.09 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 2.14 | 0 | 0 | 102.14 | 0 | 0 | 0 | 102.14 | + And Admin does charge-off the loan on "14 February 2023" + Then Loan Repayment schedule has 6 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 2023 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2023 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 01 March 2023 | | 66.83 | 16.74 | 0.27 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2023 | | 49.82 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2023 | | 32.81 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2023 | | 15.8 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2023 | | 0.0 | 15.8 | 0.0 | 0.0 | 0.0 | 15.8 | 0.0 | 0.0 | 0.0 | 15.8 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 0.85 | 0 | 0 | 100.85 | 0 | 0 | 0 | 100.85 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 14 February 2023 | Charge-off | 100.85 | 100.0 | 0.85 | 0.0 | 0.0 | 0.0 | + And Admin does a charge-off undo the loan + Then Loan Repayment schedule has 6 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 2023 | | 100.0 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 31 | 01 February 2023 | | 83.57 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 2 | 28 | 01 March 2023 | | 67.14 | 16.43 | 0.58 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 3 | 31 | 01 April 2023 | | 50.52 | 16.62 | 0.39 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 4 | 30 | 01 May 2023 | | 33.8 | 16.72 | 0.29 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 5 | 31 | 01 June 2023 | | 16.99 | 16.81 | 0.2 | 0.0 | 0.0 | 17.01 | 0.0 | 0.0 | 0.0 | 17.01 | + | 6 | 30 | 01 July 2023 | | 0.0 | 16.99 | 0.1 | 0.0 | 0.0 | 17.09 | 0.0 | 0.0 | 0.0 | 17.09 | + Then Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 100 | 2.14 | 0 | 0 | 102.14 | 0 | 0 | 0 | 102.14 | + Then Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | + | 01 January 2023 | Disbursement | 100.0 | 0.0 | 0.0 | 0.0 | 0.0 | 100.0 | + | 14 February 2023 | Charge-off | 100.85 | 100.0 | 0.85 | 0.0 | 0.0 | 0.0 | + + 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 280693da5f..3f850f29d8 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 @@ -1588,8 +1588,10 @@ public List retrieveListOfTransactionsForReprocessing() { } private static Predicate loanTransactionForReprocessingPredicate() { - return transaction -> transaction.isNotReversed() && (transaction.isChargeOff() || transaction.isReAge() - || transaction.isAccrualActivity() || transaction.isReAmortize() || !transaction.isNonMonetaryTransaction()); + return transaction -> (transaction.isNotReversed() && (transaction.isChargeOff() || transaction.isReAge() + || transaction.isAccrualActivity() || transaction.isReAmortize() || !transaction.isNonMonetaryTransaction())) + || (transaction.isReversed() && transaction.isChargeOff() && LoanChargeOffBehaviour.ZERO_INTEREST + .equals(transaction.getLoan().getLoanProductRelatedDetail().getChargeOffBehaviour())); } public List retrieveListOfTransactionsExcludeAccruals() { diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java index 3f1430c112..dbe127bfed 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/transactionprocessor/AbstractLoanRepaymentScheduleTransactionProcessor.java @@ -255,6 +255,7 @@ public void processLatestTransaction(final LoanTransaction loanTransaction, fina case WRITEOFF -> handleWriteOff(loanTransaction, ctx.getCurrency(), ctx.getInstallments()); case REFUND_FOR_ACTIVE_LOAN -> handleRefund(loanTransaction, ctx.getCurrency(), ctx.getInstallments(), ctx.getCharges()); case CHARGEBACK -> handleChargeback(loanTransaction, ctx); + case CHARGE_OFF -> handleChargeOff(loanTransaction, ctx); default -> { Money transactionAmountUnprocessed = handleTransactionAndCharges(loanTransaction, ctx.getCurrency(), ctx.getInstallments(), ctx.getCharges(), null, false); @@ -394,7 +395,7 @@ private void recalculateChargeOffTransaction(ChangedTransactionDetail changedTra principalPortion = principalPortion.plus(currentInstallment.getPrincipalOutstanding(currency)); interestPortion = interestPortion.plus(currentInstallment.getInterestOutstanding(currency)); feeChargesPortion = feeChargesPortion.plus(currentInstallment.getFeeChargesOutstanding(currency)); - penaltychargesPortion = penaltychargesPortion.plus(currentInstallment.getPenaltyChargesCharged(currency)); + penaltychargesPortion = penaltychargesPortion.plus(currentInstallment.getPenaltyChargesOutstanding(currency)); } } @@ -786,6 +787,11 @@ protected void handleChargeback(LoanTransaction loanTransaction, TransactionCtx processCreditTransaction(loanTransaction, ctx.getOverpaymentHolder(), ctx.getCurrency(), ctx.getInstallments()); } + private void handleChargeOff(LoanTransaction loanTransaction, TransactionCtx transactionCtx) { + recalculateChargeOffTransaction(transactionCtx.getChangedTransactionDetail(), loanTransaction, transactionCtx.getCurrency(), + transactionCtx.getInstallments()); + } + protected void handleCreditBalanceRefund(LoanTransaction loanTransaction, MonetaryCurrency currency, List installments, MoneyHolder overpaidAmountHolder) { processCreditTransaction(loanTransaction, overpaidAmountHolder, currency, installments); 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 c13b33e1f9..d08bb6288f 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 @@ -32,6 +32,7 @@ import java.math.BigDecimal; import java.math.MathContext; import java.time.LocalDate; +import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -68,6 +69,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.ChangedTransactionDetail; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanCharge; +import org.apache.fineract.portfolio.loanaccount.domain.LoanChargeOffBehaviour; import org.apache.fineract.portfolio.loanaccount.domain.LoanChargePaidBy; import org.apache.fineract.portfolio.loanaccount.domain.LoanCreditAllocationRule; import org.apache.fineract.portfolio.loanaccount.domain.LoanPaymentAllocationRule; @@ -1168,7 +1170,14 @@ private void handleOverpayment(Money overpaymentPortion, LoanTransaction loanTra } } - private void handleChargeOff(LoanTransaction loanTransaction, TransactionCtx transactionCtx) { + private void handleChargeOff(final LoanTransaction loanTransaction, final TransactionCtx transactionCtx) { + if (transactionCtx instanceof ProgressiveTransactionCtx progressiveTransactionCtx) { + if (LoanChargeOffBehaviour.ZERO_INTEREST.equals(loanTransaction.getLoan().getLoanProductRelatedDetail().getChargeOffBehaviour()) + && !loanTransaction.isReversed()) { + handleZeroInterestChargeOff(loanTransaction, progressiveTransactionCtx); + } + } + loanTransaction.resetDerivedComponents(); // determine how much is outstanding total and breakdown for principal, interest and charges Money principalPortion = Money.zero(transactionCtx.getCurrency()); @@ -1188,6 +1197,58 @@ private void handleChargeOff(LoanTransaction loanTransaction, TransactionCtx tra loanTransaction.updateComponentsAndTotal(principalPortion, interestPortion, feeChargesPortion, penaltychargesPortion); } + private void handleZeroInterestChargeOff(final LoanTransaction loanTransaction, + final ProgressiveTransactionCtx progressiveTransactionCtx) { + final LocalDate transactionDate = loanTransaction.getTransactionDate(); + final List installments = progressiveTransactionCtx.getInstallments(); + + if (!installments.isEmpty()) { + if (loanTransaction.getLoan().isInterestRecalculationEnabled()) { + installments.stream().filter(installment -> !installment.getFromDate().isAfter(transactionDate) + && installment.getDueDate().isAfter(transactionDate)).forEach(installment -> { + final BigDecimal newInterest = emiCalculator.getPeriodInterestTillDate(progressiveTransactionCtx.getModel(), + installment.getDueDate(), transactionDate).getAmount(); + final BigDecimal interestRemoved = installment.getInterestCharged().subtract(newInterest); + installment.updatePrincipal(MathUtil.nullToZero(installment.getPrincipal()).add(interestRemoved)); + installment.updateInterestCharged(newInterest); + }); + } else { + calculatePartialPeriodInterest(progressiveTransactionCtx, transactionDate); + } + + installments.stream().filter(installment -> installment.getFromDate().isAfter(transactionDate)).forEach(installment -> { + installment.updatePrincipal(MathUtil.nullToZero(installment.getPrincipal()).add(installment.getInterestCharged())); + installment.updateInterestCharged(BigDecimal.ZERO); + }); + + final BigDecimal totalInstallmentsPrincipal = installments.stream().map(LoanRepaymentScheduleInstallment::getPrincipal) + .reduce(ZERO, BigDecimal::add); + final Money amountToEditLastInstallment = loanTransaction.getLoan().getPrincipal().minus(totalInstallmentsPrincipal); + final int lastInstallmentIndex = installments.size() - 1; + installments.get(lastInstallmentIndex) + .updatePrincipal(installments.get(lastInstallmentIndex).getPrincipal().add(amountToEditLastInstallment.getAmount())); + } + } + + private void calculatePartialPeriodInterest(final ProgressiveTransactionCtx progressiveTransactionCtx, final LocalDate chargeOffDate) { + progressiveTransactionCtx.getInstallments().stream() + .filter(installment -> !installment.getFromDate().isAfter(chargeOffDate) && installment.getDueDate().isAfter(chargeOffDate)) + .forEach(installment -> { + final BigDecimal totalInterest = installment.getInterestOutstanding(progressiveTransactionCtx.getCurrency()) + .getAmount(); + final long totalDaysInPeriod = ChronoUnit.DAYS.between(installment.getFromDate(), installment.getDueDate()); + final long daysTillChargeOff = ChronoUnit.DAYS.between(installment.getFromDate(), chargeOffDate); + + final BigDecimal interestTillChargeOff = totalInterest + .divide(BigDecimal.valueOf(totalDaysInPeriod), MoneyHelper.getMathContext()) + .multiply(BigDecimal.valueOf(daysTillChargeOff)); + + final BigDecimal interestRemoved = totalInterest.subtract(interestTillChargeOff); + installment.updatePrincipal(MathUtil.nullToZero(installment.getPrincipal()).add(interestRemoved)); + installment.updateInterestCharged(interestTillChargeOff); + }); + } + private void handleChargePayment(LoanTransaction loanTransaction, TransactionCtx transactionCtx) { Money zero = Money.zero(transactionCtx.getCurrency()); Money feeChargesPortion = zero; diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java index a747373c73..a544fde4f8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/LoanWritePlatformServiceJpaRepositoryImpl.java @@ -3260,10 +3260,9 @@ public CommandProcessingResult chargeOff(JsonCommand command) { final List existingTransactionIds = loan.findExistingTransactionIds(); final List existingReversedTransactionIds = loan.findExistingReversedTransactionIds(); - LoanTransaction chargeOffTransaction = LoanTransaction.chargeOff(loan, transactionDate, txnExternalId); + final LoanTransaction chargeOffTransaction = LoanTransaction.chargeOff(loan, transactionDate, txnExternalId); - if (loan.isInterestBearing() && loan.isInterestRecalculationEnabled() - && DateUtils.isBefore(loan.getInterestRecalculatedOn(), DateUtils.getBusinessLocalDate())) { + if (loan.isInterestBearing() && loan.isInterestRecalculationEnabled()) { final ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, null, null); loanScheduleService.regenerateRepaymentScheduleWithInterestRecalculation(loan, scheduleGeneratorDTO); loan.addLoanTransaction(chargeOffTransaction); @@ -3278,6 +3277,10 @@ public CommandProcessingResult chargeOff(JsonCommand command) { } } else { loan.addLoanTransaction(chargeOffTransaction); + loan.getTransactionProcessor().processLatestTransaction(chargeOffTransaction, + new TransactionCtx(loan.getCurrency(), loan.getRepaymentScheduleInstallments(), loan.getActiveCharges(), + new MoneyHolder(loan.getTotalOverpaidAsMoney()), null)); + loan.updateLoanSummaryDerivedFields(); } loanTransactionRepository.saveAndFlush(chargeOffTransaction); @@ -3345,6 +3348,9 @@ public CommandProcessingResult undoChargeOff(JsonCommand command) { saveLoanWithDataIntegrityViolationChecks(loan); postJournalEntries(loan, existingTransactionIds, existingReversedTransactionIds); businessEventNotifierService.notifyPostBusinessEvent(new LoanUndoChargeOffBusinessEvent(chargedOffTransaction)); + + loan.reprocessTransactions(); + return new CommandProcessingResultBuilder() // .withOfficeId(loan.getOfficeId()) // .withClientId(loan.getClientId()) //