FINERACT-2326: Reprocessing loan internal api for testing purpose

This commit is contained in:
Jose Alberto Hernandez
2025-09-16 15:11:55 +02:00
committed by Aleksandar Vidakovic
parent 23080c5041
commit 000ebb4c1d
16 changed files with 414 additions and 24 deletions
@@ -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);
@@ -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);
}
}
@@ -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();
}
}
@@ -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
@@ -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 |
@@ -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;
}
@@ -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
@@ -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;
@@ -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));
}
}
@@ -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 ")
@@ -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);
@@ -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
@@ -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 ---------------------------------------");
@@ -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)); //
});
@@ -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();
}
@@ -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));
}
}