This is an automated email from the ASF dual-hosted git repository. aleks pushed a commit to branch develop in repository https://gitbox.apache.org/repos/asf/fineract.git
commit 000ebb4c1d7904a8dcf9fc56ccd8cff070fc2e6d Author: Jose Alberto Hernandez <[email protected]> AuthorDate: Wed Aug 27 12:15:41 2025 -0500 FINERACT-2326: Reprocessing loan internal api for testing purpose --- .../fineract/client/util/FineractClient.java | 3 + .../apache/fineract/test/api/ApiConfiguration.java | 6 + .../test/stepdef/loan/LoanReprocessStepDef.java | 24 +++ .../resources/features/LoanAccrualActivity.feature | 174 +++++++++++++++++++++ .../features/LoanMerchantIssuedRefund.feature | 2 +- .../loanaccount/domain/LoanTransaction.java | 3 + .../domain/LoanTransactionRepository.java | 11 ++ ...dvancedPaymentScheduleTransactionProcessor.java | 19 ++- .../fineract/cob/api/InternalCOBApiResource.java | 11 ++ .../CollectionSheetReadPlatformServiceImpl.java | 6 +- .../service/CenterReadPlatformServiceImpl.java | 6 +- .../adjustment/LoanAdjustmentServiceImpl.java | 2 - ...PaymentAllocationLoanRepaymentScheduleTest.java | 146 +++++++++++++++++ .../LoanTransactionAccrualActivityPostingTest.java | 4 +- .../common/FineractClientHelper.java | 3 +- .../integrationtests/common/loans/CobHelper.java | 18 +-- 16 files changed, 414 insertions(+), 24 deletions(-) diff --git a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java index 64dc2daed4..c08b49ede0 100644 --- a/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java +++ b/fineract-client/src/main/java/org/apache/fineract/client/util/FineractClient.java @@ -82,6 +82,7 @@ import org.apache.fineract.client.services.ImagesApi; import org.apache.fineract.client.services.InlineJobApi; import org.apache.fineract.client.services.InterestRateChartApi; import org.apache.fineract.client.services.InterestRateSlabAKAInterestBandsApi; +import org.apache.fineract.client.services.InternalCobApi; import org.apache.fineract.client.services.JournalEntriesApi; import org.apache.fineract.client.services.ListReportMailingJobHistoryApi; import org.apache.fineract.client.services.LoanAccountLockApi; @@ -225,6 +226,7 @@ public final class FineractClient { public final HolidaysApi holidays; public final HooksApi hooks; public final ImagesApi images; + public final InternalCobApi internalCob; public final InterestRateChartApi interestRateCharts; public final InterestRateSlabAKAInterestBandsApi interestRateChartLabs; public final JournalEntriesApi journalEntries; @@ -356,6 +358,7 @@ public final class FineractClient { holidays = retrofit.create(HolidaysApi.class); hooks = retrofit.create(HooksApi.class); images = retrofit.create(ImagesApi.class); + internalCob = retrofit.create(InternalCobApi.class); interestRateCharts = retrofit.create(InterestRateChartApi.class); interestRateChartLabs = retrofit.create(InterestRateSlabAKAInterestBandsApi.class); journalEntries = retrofit.create(JournalEntriesApi.class); diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/api/ApiConfiguration.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/api/ApiConfiguration.java index 16d08a62e8..64ff69666d 100644 --- a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/api/ApiConfiguration.java +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/api/ApiConfiguration.java @@ -37,6 +37,7 @@ import org.apache.fineract.client.services.FundsApi; import org.apache.fineract.client.services.GeneralLedgerAccountApi; import org.apache.fineract.client.services.GlobalConfigurationApi; import org.apache.fineract.client.services.InlineJobApi; +import org.apache.fineract.client.services.InternalCobApi; import org.apache.fineract.client.services.JournalEntriesApi; import org.apache.fineract.client.services.LoanAccountLockApi; import org.apache.fineract.client.services.LoanBuyDownFeesApi; @@ -268,4 +269,9 @@ public class ApiConfiguration { public LoanCapitalizedIncomeApi loanCapitalizedIncomeApi() { return fineractClient.createService(LoanCapitalizedIncomeApi.class); } + + @Bean + public InternalCobApi internalCobApi() { + return fineractClient.createService(InternalCobApi.class); + } } diff --git a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReprocessStepDef.java b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReprocessStepDef.java new file mode 100644 index 0000000000..368885d33a --- /dev/null +++ b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanReprocessStepDef.java @@ -0,0 +1,24 @@ +package org.apache.fineract.test.stepdef.loan; + +import io.cucumber.java.en.When; +import java.io.IOException; +import org.apache.fineract.client.models.PostLoansResponse; +import org.apache.fineract.client.services.InternalCobApi; +import org.apache.fineract.test.stepdef.AbstractStepDef; +import org.apache.fineract.test.support.TestContextKey; +import org.springframework.beans.factory.annotation.Autowired; +import retrofit2.Response; + +public class LoanReprocessStepDef extends AbstractStepDef { + + @Autowired + private InternalCobApi internalCobApi; + + @When("Admin runs loan reprocess for Loan") + public void admin_runs_inline_COB_job_for_loan() throws IOException { + Response<PostLoansResponse> loanResponse = testContext().get(TestContextKey.LOAN_CREATE_RESPONSE); + long loanId = loanResponse.body().getLoanId(); + + internalCobApi.loanReprocess(loanId).execute(); + } +} diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualActivity.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualActivity.feature index 3ec5e65334..ca5d1549c0 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualActivity.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanAccrualActivity.feature @@ -7780,4 +7780,178 @@ Feature: LoanAccrualActivity When Loan Pay-off is made on "24 June 2025" Then Loan's all installments have obligations met + @TestRailId:C4052 + Scenario: Verify that no extra accrual activity will be created upon loan reprocessing with merchant issued refund and NSF fee + When Admin sets the business date to "13 June 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_EMI_360_30_INTEREST_REFUND_INTEREST_RECALC_ACCRUAL_ACTIVITY | 13 June 2025 | 135.94 | 11.32 | DECLINING_BALANCE | DAILY | EQUAL_INSTALLMENTS | 6 | MONTHS | 1 | MONTHS | 6 | 0 | 0 | 0 | ADVANCED_PAYMENT_ALLOCATION | + And Admin successfully approves the loan on "13 June 2025" with "135.94" amount and expected disbursement date on "13 June 2025" + And Admin successfully disburse the loan on "13 June 2025" with "135.94" 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 | + | | | 13 June 2025 | | 135.94 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 13 July 2025 | | 113.81 | 22.13 | 1.28 | 0.0 | 0.0 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | + | 2 | 31 | 13 August 2025 | | 91.47 | 22.34 | 1.07 | 0.0 | 0.0 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | + | 3 | 31 | 13 September 2025 | | 68.92 | 22.55 | 0.86 | 0.0 | 0.0 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | + | 4 | 30 | 13 October 2025 | | 46.16 | 22.76 | 0.65 | 0.0 | 0.0 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | + | 5 | 31 | 13 November 2025 | | 23.19 | 22.97 | 0.44 | 0.0 | 0.0 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | + | 6 | 30 | 13 December 2025 | | 0.0 | 23.19 | 0.22 | 0.0 | 0.0 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 135.94 | 4.52 | 0.0 | 0.0 | 140.46 | 0.0 | 0.0 | 0.0 | 140.46 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 13 June 2025 | Disbursement | 135.94 | 0.0 | 0.0 | 0.0 | 0.0 | 135.94 | false | false | +# --- First repayment on 22 June 2025 --- + When Admin sets the business date to "22 June 2025" + And Admin makes "REPAYMENT" transaction with "AUTOPAY" payment type on "22 June 2025" with 25.00 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 | + | | | 13 June 2025 | | 135.94 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 13 July 2025 | 22 June 2025 | 112.91 | 23.03 | 0.38 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 2 | 31 | 13 August 2025 | | 91.29 | 21.62 | 1.79 | 0.0 | 0.0 | 23.41 | 1.59 | 1.59 | 0.0 | 21.82 | + | 3 | 31 | 13 September 2025 | | 68.74 | 22.55 | 0.86 | 0.0 | 0.0 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | + | 4 | 30 | 13 October 2025 | | 45.98 | 22.76 | 0.65 | 0.0 | 0.0 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | + | 5 | 31 | 13 November 2025 | | 23.0 | 22.98 | 0.43 | 0.0 | 0.0 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | + | 6 | 30 | 13 December 2025 | | 0.0 | 23.0 | 0.22 | 0.0 | 0.0 | 23.22 | 0.0 | 0.0 | 0.0 | 23.22 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 135.94 | 4.33 | 0.0 | 0.0 | 140.27 | 25.0 | 25.0 | 0.0 | 115.27 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 13 June 2025 | Disbursement | 135.94 | 0.0 | 0.0 | 0.0 | 0.0 | 135.94 | false | false | + | 22 June 2025 | Repayment | 25.0 | 24.62 | 0.38 | 0.0 | 0.0 | 111.32 | false | false | +# --- Second repayment on 13 July 2025 --- + When Admin sets the business date to "13 July 2025" + And Admin makes "REPAYMENT" transaction with "AUTOPAY" payment type on "13 July 2025" with 23.41 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 | + | | | 13 June 2025 | | 135.94 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 13 July 2025 | 22 June 2025 | 112.91 | 23.03 | 0.38 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 2 | 31 | 13 August 2025 | 13 July 2025 | 90.24 | 22.67 | 0.74 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 3 | 31 | 13 September 2025 | | 68.51 | 21.73 | 1.68 | 0.0 | 0.0 | 23.41 | 1.59 | 1.59 | 0.0 | 21.82 | + | 4 | 30 | 13 October 2025 | | 45.75 | 22.76 | 0.65 | 0.0 | 0.0 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | + | 5 | 31 | 13 November 2025 | | 22.77 | 22.98 | 0.43 | 0.0 | 0.0 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | + | 6 | 30 | 13 December 2025 | | 0.0 |22.77 | 0.21 | 0.0 | 0.0 | 22.98 | 0.0 | 0.0 | 0.0 | 22.98 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 135.94 | 4.09 | 0.0 | 0.0 | 140.03 | 48.41 | 48.41 | 0.0 | 91.62 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 13 June 2025 | Disbursement | 135.94 | 0.0 | 0.0 | 0.0 | 0.0 | 135.94 | false | false | + | 22 June 2025 | Repayment | 25.0 | 24.62 | 0.38 | 0.0 | 0.0 | 111.32 | false | false | + | 13 July 2025 | Repayment | 23.41 | 22.67 | 0.74 | 0.0 | 0.0 | 88.65 | false | false | +# --- Merchant issued refund --- + When Admin sets the business date to "16 July 2025" + And Admin makes "MERCHANT_ISSUED_REFUND" transaction with "AUTOPAY" payment type on "16 July 2025" with 135.94 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 | + | | | 13 June 2025 | | 135.94 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 13 July 2025 | 22 June 2025 | 112.91 | 23.03 | 0.38 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 2 | 31 | 13 August 2025 | 13 July 2025 | 90.24 | 22.67 | 0.74 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 3 | 31 | 13 September 2025 | 16 July 2025 | 66.91 | 23.33 | 0.08 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 4 | 30 | 13 October 2025 | 16 July 2025 | 43.5 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 5 | 31 | 13 November 2025 | 16 July 2025 | 20.09 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 6 | 30 | 13 December 2025 | 16 July 2025 | 0.0 | 20.09 | 0.0 | 0.0 | 0.0 | 20.09 | 20.09 | 20.09 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 135.94 | 1.2 | 0.0 | 0.0 | 137.14 | 137.14 | 137.14 | 0.0 | 0.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 13 June 2025 | Disbursement | 135.94 | 0.0 | 0.0 | 0.0 | 0.0 | 135.94 | false | false | + | 22 June 2025 | Repayment | 25.0 | 24.62 | 0.38 | 0.0 | 0.0 | 111.32 | false | false | + | 13 July 2025 | Repayment | 23.41 | 22.67 | 0.74 | 0.0 | 0.0 | 88.65 | false | false | + | 13 July 2025 | Accrual Activity | 0.38 | 0.0 | 0.38 | 0.0 | 0.0 | 0.0 | false | false | + | 16 July 2025 | Merchant Issued Refund | 135.94 | 88.65 | 0.08 | 0.0 | 0.0 | 0.0 | false | false | + | 16 July 2025 | Interest Refund | 1.2 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | false | + | 16 July 2025 | Accrual | 1.2 | 0.0 | 1.2 | 0.0 | 0.0 | 0.0 | false | false | + | 16 July 2025 | Accrual Activity | 0.82 | 0.0 | 0.82 | 0.0 | 0.0 | 0.0 | false | false | + And Loan status will be "OVERPAID" + And Loan has 48.41 overpaid amount +# --- Undo repayment made on 13 July 2025 on 18 July 2025 --- + When Admin sets the business date to "18 July 2025" + And Customer undo "1"th "Repayment" transaction made on "13 July 2025" + 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 | + | | | 13 June 2025 | | 135.94 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 13 July 2025 | 22 June 2025 | 112.91 | 23.03 | 0.38 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 2 | 31 | 13 August 2025 | 16 July 2025 | 90.34 | 22.57 | 0.84 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 3 | 31 | 13 September 2025 | 16 July 2025 | 66.93 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 4 | 30 | 13 October 2025 | 16 July 2025 | 43.52 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 5 | 31 | 13 November 2025 | 16 July 2025 | 20.11 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 6 | 30 | 13 December 2025 | 16 July 2025 | 0.0 | 20.11 | 0.0 | 0.0 | 0.0 | 20.11 | 20.11 | 20.11 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 135.94 | 1.22 | 0.0 | 0.0 | 137.16 | 137.16 | 137.16 | 0.0 | 0.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 13 June 2025 | Disbursement | 135.94 | 0.0 | 0.0 | 0.0 | 0.0 | 135.94 | false | false | + | 22 June 2025 | Repayment | 25.0 | 24.62 | 0.38 | 0.0 | 0.0 | 111.32 | false | false | + | 13 July 2025 | Repayment | 23.41 | 22.67 | 0.74 | 0.0 | 0.0 | 88.65 | true | false | + | 13 July 2025 | Accrual Activity | 0.38 | 0.0 | 0.38 | 0.0 | 0.0 | 0.0 | false | false | + | 16 July 2025 | Accrual | 1.2 | 0.0 | 1.2 | 0.0 | 0.0 | 0.0 | false | false | + | 16 July 2025 | Merchant Issued Refund | 135.94 | 111.32 | 0.84 | 0.0 | 0.0 | 0.0 | false | true | + | 16 July 2025 | Interest Refund | 1.22 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | + | 16 July 2025 | Accrual Activity | 0.84 | 0.0 | 0.84 | 0.0 | 0.0 | 0.0 | false | true | + | 18 July 2025 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + And Loan status will be "OVERPAID" + And Loan has 25 overpaid amount +# --- Add NSF fee 18 on July 2025 --- + When Admin adds "LOAN_NSF_FEE" due date charge with "18 July 2025" due date and 2.8 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 | + | | | 13 June 2025 | | 135.94 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 13 July 2025 | 22 June 2025 | 112.91 | 23.03 | 0.38 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 2 | 31 | 13 August 2025 | 16 July 2025 | 90.34 | 22.57 | 0.84 | 0.0 | 2.8 | 26.21 | 26.21 | 26.21 | 0.0 | 0.0 | + | 3 | 31 | 13 September 2025 | 16 July 2025 | 66.93 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 4 | 30 | 13 October 2025 | 16 July 2025 | 43.52 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 5 | 31 | 13 November 2025 | 16 July 2025 | 20.11 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 6 | 30 | 13 December 2025 | 16 July 2025 | 0.0 | 20.11 | 0.0 | 0.0 | 0.0 | 20.11 | 20.11 | 20.11 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 135.94 | 1.22 | 0.0 | 2.8 | 139.96 | 139.96 | 139.96 | 0.0 | 0.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 13 June 2025 | Disbursement | 135.94 | 0.0 | 0.0 | 0.0 | 0.0 | 135.94 | false | false | + | 22 June 2025 | Repayment | 25.0 | 24.62 | 0.38 | 0.0 | 0.0 | 111.32 | false | false | + | 13 July 2025 | Repayment | 23.41 | 22.67 | 0.74 | 0.0 | 0.0 | 88.65 | true | false | + | 13 July 2025 | Accrual Activity | 0.38 | 0.0 | 0.38 | 0.0 | 0.0 | 0.0 | false | false | + | 16 July 2025 | Accrual | 1.2 | 0.0 | 1.2 | 0.0 | 0.0 | 0.0 | false | false | + | 16 July 2025 | Merchant Issued Refund | 135.94 | 111.32 | 0.84 | 0.0 | 2.8 | 0.0 | false | true | + | 16 July 2025 | Interest Refund | 1.22 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | + | 16 July 2025 | Accrual Activity | 3.64 | 0.0 | 0.84 | 0.0 | 2.8 | 0.0 | false | true | + | 18 July 2025 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 18 July 2025 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | + And Loan status will be "OVERPAID" + And Loan has 22.2 overpaid amount +# --- Reprocess the loan on 18 July 2025 --- + When Admin runs loan reprocess for 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 | + | | | 13 June 2025 | | 135.94 | | | 0.0 | | 0.0 | 0.0 | | | | + | 1 | 30 | 13 July 2025 | 22 June 2025 | 112.91 | 23.03 | 0.38 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 2 | 31 | 13 August 2025 | 16 July 2025 | 90.34 | 22.57 | 0.84 | 0.0 | 2.8 | 26.21 | 26.21 | 26.21 | 0.0 | 0.0 | + | 3 | 31 | 13 September 2025 | 16 July 2025 | 66.93 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 4 | 30 | 13 October 2025 | 16 July 2025 | 43.52 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 5 | 31 | 13 November 2025 | 16 July 2025 | 20.11 | 23.41 | 0.0 | 0.0 | 0.0 | 23.41 | 23.41 | 23.41 | 0.0 | 0.0 | + | 6 | 30 | 13 December 2025 | 16 July 2025 | 0.0 | 20.11 | 0.0 | 0.0 | 0.0 | 20.11 | 20.11 | 20.11 | 0.0 | 0.0 | + And Loan Repayment schedule has the following data in Total row: + | Principal due | Interest | Fees | Penalties | Due | Paid | In advance | Late | Outstanding | + | 135.94 | 1.22 | 0.0 | 2.8 | 139.96 | 139.96 | 139.96 | 0.0 | 0.0 | + And Loan Transactions tab has the following data: + | Transaction date | Transaction Type | Amount | Principal | Interest | Fees | Penalties | Loan Balance | Reverted | Replayed | + | 13 June 2025 | Disbursement | 135.94 | 0.0 | 0.0 | 0.0 | 0.0 | 135.94 | false | false | + | 22 June 2025 | Repayment | 25.0 | 24.62 | 0.38 | 0.0 | 0.0 | 111.32 | false | false | + | 13 July 2025 | Repayment | 23.41 | 22.67 | 0.74 | 0.0 | 0.0 | 88.65 | true | false | + | 13 July 2025 | Accrual Activity | 0.38 | 0.0 | 0.38 | 0.0 | 0.0 | 0.0 | false | false | + | 16 July 2025 | Accrual | 1.2 | 0.0 | 1.2 | 0.0 | 0.0 | 0.0 | false | false | + | 16 July 2025 | Merchant Issued Refund | 135.94 | 111.32 | 0.84 | 0.0 | 2.8 | 0.0 | false | true | + | 16 July 2025 | Interest Refund | 1.22 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | + | 16 July 2025 | Accrual Activity | 3.64 | 0.0 | 0.84 | 0.0 | 2.8 | 0.0 | false | true | + | 18 July 2025 | Accrual | 0.02 | 0.0 | 0.02 | 0.0 | 0.0 | 0.0 | false | false | + | 18 July 2025 | Accrual | 2.8 | 0.0 | 0.0 | 0.0 | 2.8 | 0.0 | false | false | + And Loan status will be "OVERPAID" + And Loan has 22.2 overpaid amount diff --git a/fineract-e2e-tests-runner/src/test/resources/features/LoanMerchantIssuedRefund.feature b/fineract-e2e-tests-runner/src/test/resources/features/LoanMerchantIssuedRefund.feature index ebb3adc892..2302f3a834 100644 --- a/fineract-e2e-tests-runner/src/test/resources/features/LoanMerchantIssuedRefund.feature +++ b/fineract-e2e-tests-runner/src/test/resources/features/LoanMerchantIssuedRefund.feature @@ -26,7 +26,7 @@ Feature: MerchantIssuedRefund | 22 May 2025 | Repayment | 63.85 | 62.57 | 1.28 | 0.0 | 0.0 | 113.83 | true | false | | 22 May 2025 | Accrual Activity | 1.69 | 0.0 | 1.69 | 0.0 | 0.0 | 0.0 | false | false | | 28 May 2025 | Accrual | 1.9 | 0.0 | 1.9 | 0.0 | 0.0 | 0.0 | false | false | - | 28 May 2025 | Interest Refund | 2.01 | 0.0 | 0.0 | 0.0 | 0.0 | 176.4 | false | true | + | 28 May 2025 | Interest Refund | 2.01 | 0.0 | 0.0 | 0.0 | 0.0 | 0.0 | false | true | | 28 May 2025 | Accrual | 0.11 | 0.0 | 0.11 | 0.0 | 0.0 | 0.0 | false | false | | 28 May 2025 | Merchant Issued Refund | 187.99 | 176.4 | 1.6 | 0.0 | 2.8 | 0.0 | false | true | | 28 May 2025 | Accrual Activity | 3.12 | 0.0 | 0.32 | 0.0 | 2.8 | 0.0 | false | true | diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java index f77a490ae1..cf5303c21c 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransaction.java @@ -319,6 +319,9 @@ public class LoanTransaction extends AbstractAuditableWithUTCDateTimeCustom<Long newTransaction.setLoanReAgeParameter(loanTransaction.getLoanReAgeParameter().getCopy(newTransaction)); } newTransaction.setClassification(loanTransaction.getClassification()); + if (loanTransaction.isAccrualActivity()) { + newTransaction.setCreatedBy(loanTransaction.getCreatedBy().get()); + } return newTransaction; } diff --git a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java index 41de177fe1..2fcdc83c7b 100644 --- a/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java +++ b/fineract-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/domain/LoanTransactionRepository.java @@ -325,6 +325,17 @@ public interface LoanTransactionRepository extends JpaRepository<LoanTransaction List<LoanTransaction> findNonReversedLoanAndTypeAndDates(@Param("loan") Loan loan, @Param("type") LoanTransactionType type, @Param("transactionDates") Set<LocalDate> transactionDates); + @Query(""" + SELECT lt FROM LoanTransaction lt + WHERE lt.loan = :loan + AND lt.reversed = false + AND lt.typeOf = :type + AND lt.dateOf = :transactionDate + ORDER BY lt.dateOf, lt.createdDate, lt.id + """) + List<LoanTransaction> findNonReversedLoanAndTypeAndDate(@Param("loan") Loan loan, @Param("type") LoanTransactionType type, + @Param("transactionDate") LocalDate transactionDate); + @Query(""" SELECT lt FROM LoanTransaction lt WHERE lt.loan = :loan 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 e6ef0a4f70..458431dcbd 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 @@ -91,6 +91,7 @@ import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelation; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRelationTypeEnum; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionRepository; import org.apache.fineract.portfolio.loanaccount.domain.LoanTransactionToRepaymentScheduleMapping; +import org.apache.fineract.portfolio.loanaccount.domain.SingleLoanChargeRepaymentScheduleProcessingWrapper; import org.apache.fineract.portfolio.loanaccount.domain.reaging.LoanReAgeParameter; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.AbstractLoanRepaymentScheduleTransactionProcessor; import org.apache.fineract.portfolio.loanaccount.domain.transactionprocessor.MoneyHolder; @@ -129,6 +130,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep private final LoanScheduleComponent loanSchedule; private final LoanTransactionRepository loanTransactionRepository; private final LoanChargeService loanChargeService; + private final SingleLoanChargeRepaymentScheduleProcessingWrapper loanChargeRepaymentScheduleProcessing; public AdvancedPaymentScheduleTransactionProcessor(final EMICalculator emiCalculator, final LoanRepositoryWrapper loanRepositoryWrapper, final InterestRefundService interestRefundService, final ExternalIdFactory externalIdFactory, @@ -142,6 +144,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep this.loanTransactionRepository = loanTransactionRepository; this.loanSchedule = loanSchedule; this.loanChargeService = loanChargeService; + this.loanChargeRepaymentScheduleProcessing = new SingleLoanChargeRepaymentScheduleProcessingWrapper(); } @Override @@ -233,6 +236,7 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep List<ChangeOperation> changeOperations = createSortedChangeList(loanTermVariations, loanTransactions, charges); + List<Long> loanChargeIdProcessed = new ArrayList<>(); List<LoanTransaction> overpaidTransactions = new ArrayList<>(); for (final ChangeOperation changeOperation : changeOperations) { if (changeOperation.isLoanTermVariationsData()) { @@ -240,6 +244,16 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep processLoanTermVariation(installments, interestRateChange, scheduleModel); } else if (changeOperation.isTransaction()) { LoanTransaction transaction = changeOperation.getLoanTransaction().get(); + if (loan.getStatus().isOverpaid() && transaction.isAccrualActivity()) { + for (LoanCharge loanCharge : ctx.getCharges()) { + if (loanCharge.isDueDateCharge() && !loanChargeIdProcessed.contains(loanCharge.getId()) + && !DateUtils.isAfter(loan.getClosedOnDate(), loanCharge.getDueLocalDate())) { + loanChargeRepaymentScheduleProcessing.reprocess(transaction.getLoan().getCurrency(), + transaction.getLoan().getDisbursementDate(), ctx.getInstallments(), loanCharge); + loanChargeIdProcessed.add(loanCharge.getId()); + } + } + } processSingleTransaction(transaction, ctx); transaction = getProcessedTransaction(changedTransactionDetail, transaction); ctx.getAlreadyProcessedTransactions().add(transaction); @@ -248,7 +262,9 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep } } else { LoanCharge loanCharge = changeOperation.getLoanCharge().get(); - processSingleCharge(loanCharge, currency, installments, disbursementDate); + if (!loanChargeIdProcessed.contains(loanCharge.getId())) { + processSingleCharge(loanCharge, currency, installments, disbursementDate); + } if (!loanCharge.isFullyPaid() && !overpaidTransactions.isEmpty()) { overpaidTransactions = processOverpaidTransactions(overpaidTransactions, ctx); } @@ -1137,7 +1153,6 @@ public class AdvancedPaymentScheduleTransactionProcessor extends AbstractLoanRep remainingTransactions.remove(transaction); if (processTransaction.isOverPaid()) { remainingTransactions.add(processTransaction); - break; } } return remainingTransactions; diff --git a/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java b/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java index 09284b2b9f..83e44eb433 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java +++ b/fineract-provider/src/main/java/org/apache/fineract/cob/api/InternalCOBApiResource.java @@ -21,6 +21,7 @@ package org.apache.fineract.cob.api; import com.google.gson.JsonElement; import com.google.gson.JsonParser; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; @@ -46,6 +47,7 @@ import org.apache.fineract.infrastructure.core.serialization.ToApiJsonSerializer import org.apache.fineract.infrastructure.core.service.ThreadLocalContextUtil; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepositoryWrapper; +import org.apache.fineract.portfolio.loanaccount.service.ReprocessLoanTransactionsService; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Component; @@ -54,6 +56,7 @@ import org.springframework.stereotype.Component; @Component @Path("/v1/internal/cob") @RequiredArgsConstructor +@Tag(name = "Internal COB", description = "Internal COB api for testing purpose") @Slf4j public class InternalCOBApiResource implements InitializingBean { @@ -63,6 +66,7 @@ public class InternalCOBApiResource implements InitializingBean { private final ApiRequestParameterHelper apiRequestParameterHelper; private final ToApiJsonSerializer<List> toApiJsonSerializerForList; private final LoanRepositoryWrapper loanRepositoryWrapper; + private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; protected DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATETIME_PATTERN); @@ -103,4 +107,11 @@ public class InternalCOBApiResource implements InitializingBean { loanRepositoryWrapper.save(loan); } + @POST + @Consumes({ MediaType.APPLICATION_JSON }) + @Path("loan-reprocess/{loanId}") + public void loanReprocess(@Context final UriInfo uriInfo, @PathParam("loanId") long loanId) { + reprocessLoanTransactionsService.reprocessTransactions(loanRepositoryWrapper.findOneWithNotFoundDetection(loanId)); + } + } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java index baa410f2f2..a5f8131070 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/collectionsheet/service/CollectionSheetReadPlatformServiceImpl.java @@ -236,8 +236,7 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe .append("ln.interest_repaid_derived As interestPaid, ") .append("sum(COALESCE((CASE WHEN ln.loan_status_id = 300 THEN ls.fee_charges_amount ELSE 0.0 END), 0.0) - COALESCE((CASE WHEN ln.loan_status_id = 300 THEN ls.fee_charges_completed_derived ELSE 0.0 END), 0.0)) As feeDue, ") .append("ln.fee_charges_repaid_derived As feePaid, ").append("ca.attendance_type_enum as attendanceTypeId ") - .append("FROM m_group gp ") - .append("LEFT JOIN m_office of ON of.id = gp.office_id AND of.hierarchy like :officeHierarchy ") + .append("FROM m_group gp ").append("LEFT JOIN m_office o ON o.id = gp.office_id AND o.hierarchy like :officeHierarchy ") .append("JOIN m_group_level gl ON gl.id = gp.level_Id ").append("LEFT JOIN m_staff sf ON sf.id = gp.staff_id ") .append("JOIN m_group_client gc ON gc.group_id = gp.id ").append("JOIN m_client cl ON cl.id = gc.client_id ") .append("LEFT JOIN m_loan ln ON cl.id = ln.client_id and ln.group_id=gp.id AND ln.group_id is not null AND ( ln.loan_status_id = 300 ) ") @@ -511,8 +510,7 @@ public class CollectionSheetReadPlatformServiceImpl implements CollectionSheetRe .append("rc.internationalized_name_code as currencyNameCode, ") .append("SUM(COALESCE(mss.deposit_amount,0) - coalesce(mss.deposit_amount_completed_derived,0)) as dueAmount ") - .append("FROM m_group gp ") - .append("LEFT JOIN m_office of ON of.id = gp.office_id AND of.hierarchy like :officeHierarchy ") + .append("FROM m_group gp ").append("LEFT JOIN m_office o ON o.id = gp.office_id AND o.hierarchy like :officeHierarchy ") .append("JOIN m_group_level gl ON gl.id = gp.level_Id ").append("LEFT JOIN m_staff sf ON sf.id = gp.staff_id ") .append("JOIN m_group_client gc ON gc.group_id = gp.id ").append("JOIN m_client cl ON cl.id = gc.client_id ") .append("JOIN m_savings_account sa ON sa.client_id=cl.id and sa.status_enum=300 ") diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java index 9175ef4449..08a62a84d8 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/group/service/CenterReadPlatformServiceImpl.java @@ -188,7 +188,7 @@ public class CenterReadPlatformServiceImpl implements CenterReadPlatformService + " g.hierarchy as hierarchy, c.id as calendarId, ci.id as calendarInstanceId, ci.entity_id as entityId," + " ci.entity_type_enum as entityTypeId, c.title as title, c.description as description," + "c.location as location, c.start_date as startDate, c.end_date as endDate, c.recurrence as recurrence,c.meeting_time as meetingTime," - + "sum(CASE WHEN l.loan_status_id=300 and lrs.duedate = ? THEN COALESCE(lrs.principal_amount,0)) + (COALESCE(lrs.interest_amount,0) ELSE 0 END)) as installmentDue," + + "sum(CASE WHEN l.loan_status_id=300 and lrs.duedate = ? THEN COALESCE(lrs.principal_amount,0) + COALESCE(lrs.interest_amount,0) ELSE 0 END) as installmentDue," + "sum(CASE WHEN l.loan_status_id=300 and lrs.duedate = ? THEN COALESCE(lrs.principal_completed_derived,0) + COALESCE(lrs.interest_completed_derived,0) ELSE 0 END) as totalCollected," + "sum(CASE WHEN l.loan_status_id=300 and lrs.duedate <= ? THEN COALESCE(lrs.principal_amount,0) + COALESCE(lrs.interest_amount,0) ELSE 0 END)" + "- sum(CASE WHEN l.loan_status_id=300 and lrs.duedate <= ? THEN COALESCE(lrs.principal_completed_derived,0) + COALESCE(lrs.interest_completed_derived,0) ELSE 0 END) as totaldue, " @@ -495,8 +495,8 @@ public class CenterReadPlatformServiceImpl implements CenterReadPlatformService String sql = centerCalendarMapper.schema(); Collection<CenterData> centerDataArray; if (staffId != null) { - sql += " and g.staff_id=? "; - sql += "and lrs.duedate<=? and l.loan_type_enum=3"; + sql += " and g.staff_id = ? "; + sql += " and lrs.duedate <= ? "; // and l.loan_type_enum = 3 "; sql += " group by c.id, ci.id, g.account_no, g.external_id, g.status_enum, g.activation_date, g.hierarchy"; centerDataArray = this.jdbcTemplate.query(sql, centerCalendarMapper, // NOSONAR meetingDate, meetingDate, meetingDate, meetingDate, meetingDate, meetingDate, officeId, staffId, meetingDate); diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java index b947278321..da2ec721ca 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/service/adjustment/LoanAdjustmentServiceImpl.java @@ -63,7 +63,6 @@ import org.apache.fineract.portfolio.loanaccount.repository.LoanBuyDownFeeBalanc import org.apache.fineract.portfolio.loanaccount.repository.LoanCapitalizedIncomeBalanceRepository; import org.apache.fineract.portfolio.loanaccount.serialization.LoanChargeValidator; import org.apache.fineract.portfolio.loanaccount.serialization.LoanTransactionValidator; -import org.apache.fineract.portfolio.loanaccount.service.BuyDownFeePlatformService; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService; import org.apache.fineract.portfolio.loanaccount.service.LoanBalanceService; import org.apache.fineract.portfolio.loanaccount.service.LoanDownPaymentHandlerService; @@ -100,7 +99,6 @@ public class LoanAdjustmentServiceImpl implements LoanAdjustmentService { private final LoanBalanceService loanBalanceService; private final ReprocessLoanTransactionsService reprocessLoanTransactionsService; private final LoanCapitalizedIncomeBalanceRepository loanCapitalizedIncomeBalanceRepository; - private final BuyDownFeePlatformService buyDownFeePlatformService; private final LoanBuyDownFeeBalanceRepository loanBuyDownFeeBalanceRepository; @Override diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java index 2bd83a92f1..62cdc035c6 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/AdvancedPaymentAllocationLoanRepaymentScheduleTest.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.integrationtests; +import static org.apache.fineract.integrationtests.BaseLoanIntegrationTest.InterestRateFrequencyType.MONTHS; import static org.apache.fineract.integrationtests.BaseLoanIntegrationTest.InterestRateFrequencyType.WHOLE_TERM; import static org.apache.fineract.integrationtests.BaseLoanIntegrationTest.InterestRateFrequencyType.YEARS; import static org.apache.fineract.integrationtests.BaseLoanIntegrationTest.RepaymentFrequencyType.DAYS; @@ -41,6 +42,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import org.apache.commons.lang3.tuple.Pair; import org.apache.fineract.client.models.AdvancedPaymentData; import org.apache.fineract.client.models.BusinessDateUpdateRequest; import org.apache.fineract.client.models.CreditAllocationData; @@ -62,6 +64,7 @@ import org.apache.fineract.client.models.PostLoanProductsResponse; import org.apache.fineract.client.models.PostLoansLoanIdChargesChargeIdRequest; import org.apache.fineract.client.models.PostLoansLoanIdRequest; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsRequest; +import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse; import org.apache.fineract.client.models.PostLoansLoanIdTransactionsTransactionIdRequest; import org.apache.fineract.client.models.PostLoansRequest; import org.apache.fineract.client.models.PostLoansResponse; @@ -78,9 +81,11 @@ import org.apache.fineract.integrationtests.common.accounting.Account; import org.apache.fineract.integrationtests.common.accounting.AccountHelper; import org.apache.fineract.integrationtests.common.accounting.PeriodicAccrualAccountingHelper; import org.apache.fineract.integrationtests.common.charges.ChargesHelper; +import org.apache.fineract.integrationtests.common.loans.CobHelper; import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder; import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper; import org.apache.fineract.integrationtests.common.organisation.StaffHelper; +import org.apache.fineract.integrationtests.common.products.DelinquencyBucketsHelper; import org.apache.fineract.integrationtests.common.system.CodeHelper; import org.apache.fineract.integrationtests.useradministration.roles.RolesHelper; import org.apache.fineract.integrationtests.useradministration.users.UserHelper; @@ -6000,6 +6005,147 @@ public class AdvancedPaymentAllocationLoanRepaymentScheduleTest extends BaseLoan }); } + // UC156: Loan Transaction reprocess + @Test + public void uc156() { + final String operationDate = "13 June 2025"; + AtomicLong createdLoanId = new AtomicLong(); + AtomicLong secondRepayment = new AtomicLong(); + AtomicLong createdLoanChargeId = new AtomicLong(); + final BigDecimal interestRatePerPeriod = BigDecimal.valueOf(11.32); + final BigDecimal principalAmount = BigDecimal.valueOf(135.94); + final Integer delinquencyBucketId = DelinquencyBucketsHelper.createDelinquencyBucket(requestSpec, responseSpec, List.of(// + Pair.of(1, 10), // + Pair.of(11, 30), // + Pair.of(31, 60), // + Pair.of(61, null)// + )); + + runAt(operationDate, () -> { + final ArrayList<String> interestRefundTypes = new ArrayList<String>(); + interestRefundTypes.add("PAYOUT_REFUND"); + interestRefundTypes.add("MERCHANT_ISSUED_REFUND"); + Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId(); + PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProductWithAdvancedPaymentAllocation() + .interestRatePerPeriod(interestRatePerPeriod.doubleValue()).interestRateFrequencyType(YEARS)// + .daysInMonthType(DaysInMonthType.DAYS_30)// + .daysInYearType(DaysInYearType.DAYS_360)// + .numberOfRepayments(6)// + .repaymentEvery(1)// + .repaymentFrequencyType(2L)// + .chargeOffBehaviour("ZERO_INTEREST")// + .repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue())// + .repaymentStartDateType(LoanProduct.RepaymentStartDateTypeEnum.SUBMITTED_ON_DATE.ordinal())// + .enableDownPayment(false)// + .enableAccrualActivityPosting(true)// + .allowPartialPeriodInterestCalcualtion(null)// + .enableAutoRepaymentForDownPayment(null)// + .isInterestRecalculationEnabled(true)// + .delinquencyBucketId(delinquencyBucketId.longValue())// + .enableInstallmentLevelDelinquency(true)// + .interestRecalculationCompoundingMethod(0)// + .interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)// + .installmentAmountInMultiplesOf(null)// + .supportedInterestRefundTypes(interestRefundTypes) // + .rescheduleStrategyMethod(LoanRescheduleStrategyMethod.ADJUST_LAST_UNPAID_PERIOD.getValue())// + .recalculationRestFrequencyType(2)// + .recalculationRestFrequencyInterval(1); + PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product); + PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductResponse.getResourceId(), operationDate, + principalAmount.doubleValue(), 6).interestCalculationPeriodType(DAYS)// + .transactionProcessingStrategyCode(LoanProductTestBuilder.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)// + .interestRatePerPeriod(interestRatePerPeriod)// + .repaymentEvery(1)// + .repaymentFrequencyType(MONTHS)// + .loanTermFrequency(6)// + .loanTermFrequencyType(MONTHS); + + PostLoansResponse loanResponse = loanTransactionHelper.applyLoan(applicationRequest); + createdLoanId.set(loanResponse.getLoanId()); + + loanTransactionHelper.approveLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().approvedLoanAmount(principalAmount) + .dateFormat(DATETIME_PATTERN).approvedOnDate(operationDate).locale("en")); + + loanTransactionHelper.disburseLoan(loanResponse.getLoanId(), new PostLoansLoanIdRequest().actualDisbursementDate(operationDate) + .dateFormat(DATETIME_PATTERN).locale("en").transactionAmount(principalAmount)); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get()); + assertTrue(loanDetails.getStatus().getActive()); + }); + + runAt("22 June 2025", () -> { + PostLoansLoanIdTransactionsResponse repayment = loanTransactionHelper.makeLoanRepayment(createdLoanId.get(), + new PostLoansLoanIdTransactionsRequest().transactionDate("22 June 2025").dateFormat("dd MMMM yyyy").locale("en") + .transactionAmount(25.0)); + }); + + runAt("13 July 2025", () -> { + PostLoansLoanIdTransactionsResponse repayment = loanTransactionHelper.makeLoanRepayment(createdLoanId.get(), + new PostLoansLoanIdTransactionsRequest().transactionDate("13 July 2025").dateFormat("dd MMMM yyyy").locale("en") + .transactionAmount(23.41)); + secondRepayment.set(repayment.getResourceId()); + }); + + runAt("16 July 2025", () -> { + loanTransactionHelper.makeMerchantIssuedRefund(createdLoanId.get(), new PostLoansLoanIdTransactionsRequest() + .dateFormat(DATETIME_PATTERN).transactionDate("16 July 2025").locale("en").transactionAmount(135.94)); + }); + + runAt("18 July 2025", () -> { + CobHelper.fastForwardLoansLastCOBDate(createdLoanId.get(), "18 July 2025"); + verifyLastClosedBusinessDate(createdLoanId.get(), "18 July 2025"); + loanTransactionHelper.reverseLoanTransaction(createdLoanId.get(), secondRepayment.get(), + new PostLoansLoanIdTransactionsTransactionIdRequest().dateFormat(DATETIME_PATTERN).transactionDate("18 July 2025") + .transactionAmount(0.0).locale("en")); + + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get()); + assertTrue(loanDetails.getStatus().getOverpaid()); + verifyTransactions(createdLoanId.get(), // + transaction(135.94, "Disbursement", "13 June 2025", 135.94, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), + transaction(25.0, "Repayment", "22 June 2025", 111.32, 24.62, 0.38, 0.0, 0.0, 0.0, 0.0), + transaction(23.41, "Repayment", "13 July 2025", 88.65, 22.67, 0.74, 0.0, 0.0, 0.0, 0.0, true), + transaction(0.38, "Accrual Activity", "13 July 2025", 0.0, 0.0, 0.38, 0.0, 0.0, 0.0, 0.0), + transaction(1.20, "Accrual", "16 July 2025", 0.0, 0.0, 1.20, 0.0, 0.0, 0.0, 0.0), + transaction(135.94, "Merchant Issued Refund", "16 July 2025", 0.0, 111.32, 0.84, 0.0, 0.0, 0.0, 23.78), + transaction(1.22, "Interest Refund", "16 July 2025", 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.22), + transaction(0.84, "Accrual Activity", "16 July 2025", 0.0, 0.0, 0.84, 0.0, 0.0, 0.0, 0.0), + transaction(0.02, "Accrual", "18 July 2025", 0.0, 0.0, 0.02, 0.0, 0.0, 0.0, 0.0)); + + createdLoanChargeId.set(addCharge(createdLoanId.get(), false, 2.8, "18 July 2025")); + LOG.info("------------------------------ REPROCESSING LOAN ---------------------------------------"); + + loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get()); + assertTrue(loanDetails.getStatus().getOverpaid()); + verifyTransactions(createdLoanId.get(), // + transaction(135.94, "Disbursement", "13 June 2025", 135.94, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), + transaction(25.0, "Repayment", "22 June 2025", 111.32, 24.62, 0.38, 0.0, 0.0, 0.0, 0.0), + transaction(23.41, "Repayment", "13 July 2025", 88.65, 22.67, 0.74, 0.0, 0.0, 0.0, 0.0, true), + transaction(0.38, "Accrual Activity", "13 July 2025", 0.0, 0.0, 0.38, 0.0, 0.0, 0.0, 0.0), + transaction(1.20, "Accrual", "16 July 2025", 0.0, 0.0, 1.20, 0.0, 0.0, 0.0, 0.0), + transaction(135.94, "Merchant Issued Refund", "16 July 2025", 0.0, 111.32, 0.84, 2.80, 0.0, 0.0, 20.98), + transaction(1.22, "Interest Refund", "16 July 2025", 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.22), + transaction(3.64, "Accrual Activity", "16 July 2025", 0.0, 0.0, 0.84, 2.80, 0.0, 0.0, 0.0), + transaction(0.02, "Accrual", "18 July 2025", 0.0, 0.0, 0.02, 0.0, 0.0, 0.0, 0.0), + transaction(2.80, "Accrual", "18 July 2025", 0.0, 0.0, 0.00, 2.80, 0.0, 0.0, 0.0)); + + CobHelper.reprocessLoan(createdLoanId.get()); + loanDetails = loanTransactionHelper.getLoanDetails(createdLoanId.get()); + assertTrue(loanDetails.getStatus().getOverpaid()); + + verifyTransactions(createdLoanId.get(), // + transaction(135.94, "Disbursement", "13 June 2025", 135.94, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), + transaction(25.0, "Repayment", "22 June 2025", 111.32, 24.62, 0.38, 0.0, 0.0, 0.0, 0.0), + transaction(23.41, "Repayment", "13 July 2025", 88.65, 22.67, 0.74, 0.0, 0.0, 0.0, 0.0, true), + transaction(0.38, "Accrual Activity", "13 July 2025", 0.0, 0.0, 0.38, 0.0, 0.0, 0.0, 0.0), + transaction(1.20, "Accrual", "16 July 2025", 0.0, 0.0, 1.20, 0.0, 0.0, 0.0, 0.0), + transaction(135.94, "Merchant Issued Refund", "16 July 2025", 0.0, 111.32, 0.84, 2.80, 0.0, 0.0, 20.98), + transaction(1.22, "Interest Refund", "16 July 2025", 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.22), + transaction(3.64, "Accrual Activity", "16 July 2025", 0.0, 0.0, 0.84, 2.80, 0.0, 0.0, 0.0), + transaction(0.02, "Accrual", "18 July 2025", 0.0, 0.0, 0.02, 0.0, 0.0, 0.0, 0.0), + transaction(2.80, "Accrual", "18 July 2025", 0.0, 0.0, 0.0, 2.80, 0.0, 0.0, 0.0)); + }); + } + private Long applyAndApproveLoanProgressiveAdvancedPaymentAllocationStrategyMonthlyRepayments(Long clientId, Long loanProductId, Integer numberOfRepayments, String loanDisbursementDate, double amount) { LOG.info("------------------------------APPLY AND APPROVE LOAN ---------------------------------------"); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java index e40e2a9391..9fff2beea6 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanTransactionAccrualActivityPostingTest.java @@ -992,7 +992,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), // transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // transaction(500.0, "Disbursement", disbursementDay2, 420.0, 0.0, 0.0, 0.0, 0.0, 0.0, 80.0, false), // - transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false) // + transaction(140.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 80.0, 60.0, 0.0, 0.0, false) // ); }); } @@ -1044,7 +1044,7 @@ public class LoanTransactionAccrualActivityPostingTest extends BaseLoanIntegrati verifyTransactions(loanId.get(), transaction(650.0, "Repayment", repaymentDate1, 0.0, 500.0, 0.0, 40.0, 30.0, 0.0, 80.0, false), // transaction(500.0, "Disbursement", disbursementDay2, 420.0, 0.0, 0.0, 0.0, 0.0, 0.0, 80.0, false), // transaction(70.0, "Accrual", repaymentDate1, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // - transaction(70.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 40.0, 30.0, 0.0, 0.0, false), // + transaction(140.0, "Accrual Activity", repaymentPeriod1DueDate, 0.0, 0.0, 0.0, 80.0, 60.0, 0.0, 0.0, false), // transaction(500.0, "Disbursement", disbursementDay, 500.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, false)); // }); diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/FineractClientHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/FineractClientHelper.java index 6d2abb75f1..f85a004eac 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/FineractClientHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/FineractClientHelper.java @@ -18,6 +18,7 @@ */ package org.apache.fineract.integrationtests.common; +import java.time.Duration; import java.util.function.Consumer; import java.util.function.Function; import okhttp3.logging.HttpLoggingInterceptor; @@ -43,7 +44,7 @@ public final class FineractClientHelper { String url = System.getProperty("fineract.it.url", buildURI()); // insecure(true) should *ONLY* ever be used for https://localhost:8443, NOT in real clients!! FineractClient.Builder builder = FineractClient.builder().insecure(true).baseURL(url).tenant(ConfigProperties.Backend.TENANT) - .basicAuth(username, password).logging(HttpLoggingInterceptor.Level.NONE); + .basicAuth(username, password).logging(HttpLoggingInterceptor.Level.NONE).readTimeout(Duration.ofSeconds(0)); customizer.accept(builder); return builder.build(); } diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/CobHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/CobHelper.java index fcc3adf37e..c0432dda4d 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/CobHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/loans/CobHelper.java @@ -23,6 +23,8 @@ import io.restassured.specification.ResponseSpecification; import java.util.List; import java.util.Map; import lombok.extern.slf4j.Slf4j; +import org.apache.fineract.client.util.Calls; +import org.apache.fineract.integrationtests.common.FineractClientHelper; import org.apache.fineract.integrationtests.common.Utils; @Slf4j @@ -41,15 +43,13 @@ public final class CobHelper { return Utils.performServerGet(requestSpec, responseSpec, url, jsonReturn); } - // TODO: Rewrite to use fineract-client instead! - // Example: org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long, - // org.apache.fineract.client.models.PostLoansLoanIdRequest) - @Deprecated(forRemoval = true) - public static void fastForwardLoansLastCOBDate(final RequestSpecification requestSpec, final ResponseSpecification responseSpec, - final Integer loanId, final String cobDate) { - final String url = "/fineract-provider/api/v1/internal/cob/fast-forward-cob-date-of-loan/" + loanId + "?" + Utils.TENANT_IDENTIFIER; - log.info("-------------------- -----------FAST FORWARD LAST COB DATE OF LOAN ----------------------------------------"); - Utils.performServerPost(requestSpec, responseSpec, url, "{\"lastClosedBusinessDate\":\"" + cobDate + "\"}"); + public static void fastForwardLoansLastCOBDate(final Long loanId, final String cobDate) { + Calls.ok(FineractClientHelper.getFineractClient().internalCob.updateLoanCobLastDate(loanId, + "{\"lastClosedBusinessDate\":\"" + cobDate + "\"}")); + } + + public static void reprocessLoan(final Long loanId) { + Calls.ok(FineractClientHelper.getFineractClient().internalCob.loanReprocess(loanId)); } }
