FINERACT-2326: Loan contract termination same disbursement date

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