FINERACT-2435: fix as of data api
This commit is contained in:
committed by
Arnold Gálovics
parent
2b4de8e655
commit
bf879064af
+592
@@ -19,10 +19,12 @@
|
||||
package org.apache.fineract.integrationtests.loan.pointintime;
|
||||
|
||||
import static org.apache.fineract.integrationtests.BaseLoanIntegrationTest.TransactionProcessingStrategyCode.ADVANCED_PAYMENT_ALLOCATION_STRATEGY;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
|
||||
import org.apache.fineract.client.models.LoanPointInTimeData;
|
||||
import org.apache.fineract.client.models.LoanProductChargeData;
|
||||
import org.apache.fineract.client.models.PostLoanProductsRequest;
|
||||
@@ -33,6 +35,7 @@ import org.apache.fineract.client.models.PostLoansRequestChargeData;
|
||||
import org.apache.fineract.client.models.PostLoansResponse;
|
||||
import org.apache.fineract.integrationtests.BaseLoanIntegrationTest;
|
||||
import org.apache.fineract.integrationtests.common.ClientHelper;
|
||||
import org.apache.fineract.integrationtests.common.charges.ChargesHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class LoanPointInTimeTest extends BaseLoanIntegrationTest {
|
||||
@@ -858,4 +861,593 @@ public class LoanPointInTimeTest extends BaseLoanIntegrationTest {
|
||||
assertThat(pointInTimeData.getArrears().getTotalOverdue()).isZero();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_LoanPointInTimeData_InstallmentFeeAllocation() {
|
||||
AtomicReference<Long> aLoanId = new AtomicReference<>();
|
||||
double installmentFeeAmount = 100.0;
|
||||
|
||||
runAt("01 October 2025", () -> {
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
|
||||
int numberOfRepayments = 6;
|
||||
int repaymentEvery = 1;
|
||||
|
||||
Long installmentFeeChargeId = createInstallmentFeeCharge(installmentFeeAmount);
|
||||
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct()
|
||||
.numberOfRepayments(numberOfRepayments).repaymentEvery(repaymentEvery).installmentAmountInMultiplesOf(null)
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()).interestType(InterestType.DECLINING_BALANCE)
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)
|
||||
.interestRecalculationCompoundingMethod(InterestRecalculationCompoundingMethod.NONE)
|
||||
.isInterestRecalculationEnabled(true).recalculationRestFrequencyInterval(1)
|
||||
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY)
|
||||
.rescheduleStrategyMethod(RescheduleStrategyMethod.REDUCE_EMI_AMOUNT).allowPartialPeriodInterestCalculation(false)
|
||||
.disallowExpectedDisbursements(false).allowApprovedDisbursedAmountsOverApplied(false).overAppliedNumber(null)
|
||||
.overAppliedCalculationType(null).multiDisburseLoan(null)
|
||||
.charges(List.of(new LoanProductChargeData().id(installmentFeeChargeId)));
|
||||
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||
Long loanProductId = loanProductResponse.getResourceId();
|
||||
|
||||
double amount = 6000.0;
|
||||
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 October 2025", amount, numberOfRepayments)
|
||||
.repaymentEvery(repaymentEvery).loanTermFrequency(numberOfRepayments)
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS).loanTermFrequencyType(RepaymentFrequencyType.MONTHS)
|
||||
.interestType(InterestType.DECLINING_BALANCE).interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)
|
||||
.charges(List.of(new PostLoansRequestChargeData().chargeId(installmentFeeChargeId)
|
||||
.amount(BigDecimal.valueOf(installmentFeeAmount))));
|
||||
|
||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
|
||||
PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
|
||||
approveLoanRequest(amount, "01 October 2025"));
|
||||
|
||||
aLoanId.getAndSet(approvedLoanResult.getLoanId());
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
disburseLoan(loanId, BigDecimal.valueOf(amount), "01 October 2025");
|
||||
|
||||
verifyTransactions(loanId, transaction(6000.0, "Disbursement", "01 October 2025"));
|
||||
});
|
||||
|
||||
runAt("01 November 2025", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
addRepaymentForLoan(loanId, 1100.0, "01 November 2025");
|
||||
|
||||
verifyTransactions(loanId, transaction(6000.0, "Disbursement", "01 October 2025"),
|
||||
transaction(1100.0, "Repayment", "01 November 2025"));
|
||||
});
|
||||
|
||||
runAt("01 December 2025", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
addRepaymentForLoan(loanId, 1100.0, "01 December 2025");
|
||||
|
||||
verifyTransactions(loanId, transaction(6000.0, "Disbursement", "01 October 2025"),
|
||||
transaction(1100.0, "Repayment", "01 November 2025"), transaction(1100.0, "Repayment", "01 December 2025"));
|
||||
});
|
||||
|
||||
runAt("01 January 2026", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
addRepaymentForLoan(loanId, 1100.0, "01 January 2026");
|
||||
|
||||
verifyTransactions(loanId, transaction(6000.0, "Disbursement", "01 October 2025"),
|
||||
transaction(1100.0, "Repayment", "01 November 2025"), transaction(1100.0, "Repayment", "01 December 2025"),
|
||||
transaction(1100.0, "Repayment", "01 January 2026"));
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
BigDecimal regularApiFeeChargesPaid = loanDetails.getSummary().getFeeChargesPaid();
|
||||
BigDecimal regularApiFeeChargesCharged = loanDetails.getSummary().getFeeChargesCharged();
|
||||
|
||||
assertThat(regularApiFeeChargesCharged).isEqualByComparingTo(BigDecimal.valueOf(600.0));
|
||||
assertThat(regularApiFeeChargesPaid).isEqualByComparingTo(BigDecimal.valueOf(300.0));
|
||||
});
|
||||
|
||||
runAt("08 January 2026", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
LoanPointInTimeData pointInTimeData = getPointInTimeData(loanId, "08 January 2026");
|
||||
|
||||
assertThat(pointInTimeData.getFee().getFeeChargesCharged())
|
||||
.as("Point-in-time feeChargesCharged should only include fees for installments due by the requested date")
|
||||
.isEqualByComparingTo(BigDecimal.valueOf(300.0));
|
||||
|
||||
assertThat(pointInTimeData.getFee().getFeeChargesPaid())
|
||||
.as("Point-in-time feeChargesPaid should reflect paid installment fees up to the requested date")
|
||||
.isEqualByComparingTo(BigDecimal.valueOf(300.0));
|
||||
|
||||
assertThat(pointInTimeData.getFee().getFeeChargesOutstanding())
|
||||
.as("Point-in-time feeChargesOutstanding should be 0 since all due fees are paid")
|
||||
.isEqualByComparingTo(BigDecimal.ZERO);
|
||||
|
||||
verifyOutstanding(pointInTimeData, outstanding(3000.0, 0.0, 0.0, 0.0, 3000.0));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_LoanPointInTimeData_ClosedLoanWithInstallmentFees() {
|
||||
AtomicReference<Long> aLoanId = new AtomicReference<>();
|
||||
double installmentFeeAmount = 25.0;
|
||||
|
||||
runAt("01 October 2023", () -> {
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
|
||||
int numberOfRepayments = 2;
|
||||
int repaymentEvery = 1;
|
||||
|
||||
Long installmentFeeChargeId = createInstallmentFeeCharge(installmentFeeAmount);
|
||||
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct()
|
||||
.numberOfRepayments(numberOfRepayments).repaymentEvery(repaymentEvery).installmentAmountInMultiplesOf(null)
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()).interestType(InterestType.FLAT)
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD)
|
||||
.isInterestRecalculationEnabled(false).disallowExpectedDisbursements(false)
|
||||
.allowApprovedDisbursedAmountsOverApplied(false).overAppliedNumber(null).overAppliedCalculationType(null)
|
||||
.multiDisburseLoan(null).charges(List.of(new LoanProductChargeData().id(installmentFeeChargeId)));
|
||||
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||
Long loanProductId = loanProductResponse.getResourceId();
|
||||
|
||||
double amount = 2000.0;
|
||||
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 October 2023", amount, numberOfRepayments)
|
||||
.repaymentEvery(repaymentEvery).loanTermFrequency(numberOfRepayments)
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS).loanTermFrequencyType(RepaymentFrequencyType.MONTHS)
|
||||
.interestType(InterestType.FLAT).interestCalculationPeriodType(InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD)
|
||||
.charges(List.of(new PostLoansRequestChargeData().chargeId(installmentFeeChargeId)
|
||||
.amount(BigDecimal.valueOf(installmentFeeAmount))));
|
||||
|
||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
|
||||
PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
|
||||
approveLoanRequest(amount, "01 October 2023"));
|
||||
|
||||
aLoanId.getAndSet(approvedLoanResult.getLoanId());
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
disburseLoan(loanId, BigDecimal.valueOf(amount), "01 October 2023");
|
||||
|
||||
verifyTransactions(loanId, transaction(2000.0, "Disbursement", "01 October 2023"));
|
||||
});
|
||||
|
||||
runAt("01 November 2023", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
// First repayment: 1000 principal + 25 fee = 1025
|
||||
addRepaymentForLoan(loanId, 1025.0, "01 November 2023");
|
||||
|
||||
verifyTransactions(loanId, transaction(2000.0, "Disbursement", "01 October 2023"),
|
||||
transaction(1025.0, "Repayment", "01 November 2023"));
|
||||
});
|
||||
|
||||
runAt("01 December 2023", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
// Second repayment: 1000 principal + 25 fee = 1025 (loan should close)
|
||||
addRepaymentForLoan(loanId, 1025.0, "01 December 2023");
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
|
||||
// Verify loan is closed
|
||||
assertThat(loanDetails.getStatus().getCode()).isEqualTo("loanStatusType.closed.obligations.met");
|
||||
|
||||
// Verify fee charges
|
||||
BigDecimal feeChargesCharged = loanDetails.getSummary().getFeeChargesCharged();
|
||||
BigDecimal feeChargesPaid = loanDetails.getSummary().getFeeChargesPaid();
|
||||
|
||||
assertThat(feeChargesCharged).as("Total fee charges charged should be 50 (2 installments * 25)")
|
||||
.isEqualByComparingTo(BigDecimal.valueOf(50.0));
|
||||
assertThat(feeChargesPaid).as("Total fee charges paid should be 50").isEqualByComparingTo(BigDecimal.valueOf(50.0));
|
||||
|
||||
// With periodic accrual accounting, an accrual transaction is created for the installment fees
|
||||
verifyTransactions(loanId, transaction(2000.0, "Disbursement", "01 October 2023"),
|
||||
transaction(1025.0, "Repayment", "01 November 2023"), transaction(50.0, "Accrual", "01 December 2023"),
|
||||
transaction(1025.0, "Repayment", "01 December 2023"));
|
||||
});
|
||||
|
||||
runAt("15 December 2023", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
// Query point-in-time data for a date AFTER the loan was closed
|
||||
LoanPointInTimeData pointInTimeData = getPointInTimeData(loanId, "15 December 2023");
|
||||
|
||||
// For a closed loan, all installment fees should be included (50 total)
|
||||
assertThat(pointInTimeData.getFee().getFeeChargesCharged())
|
||||
.as("Point-in-time feeChargesCharged for closed loan should be 50.0 (2 installments * 25)")
|
||||
.isEqualByComparingTo(BigDecimal.valueOf(50.0));
|
||||
|
||||
assertThat(pointInTimeData.getFee().getFeeChargesPaid()).as("Point-in-time feeChargesPaid for closed loan should be 50.0")
|
||||
.isEqualByComparingTo(BigDecimal.valueOf(50.0));
|
||||
|
||||
assertThat(pointInTimeData.getFee().getFeeChargesOutstanding())
|
||||
.as("Point-in-time feeChargesOutstanding for closed loan should be 0").isEqualByComparingTo(BigDecimal.ZERO);
|
||||
|
||||
// Total outstanding should be 0 for a closed loan
|
||||
verifyOutstanding(pointInTimeData, outstanding(0.0, 0.0, 0.0, 0.0, 0.0));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_LoanPointInTimeData_TotalExpectedAmountsConsistency() {
|
||||
AtomicReference<Long> aLoanId = new AtomicReference<>();
|
||||
double installmentFeeAmount = 100.0;
|
||||
|
||||
runAt("01 October 2025", () -> {
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
|
||||
int numberOfRepayments = 6;
|
||||
int repaymentEvery = 1;
|
||||
|
||||
Long installmentFeeChargeId = createInstallmentFeeCharge(installmentFeeAmount);
|
||||
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct()
|
||||
.numberOfRepayments(numberOfRepayments).repaymentEvery(repaymentEvery).installmentAmountInMultiplesOf(null)
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()).interestType(InterestType.DECLINING_BALANCE)
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)
|
||||
.interestRecalculationCompoundingMethod(InterestRecalculationCompoundingMethod.NONE)
|
||||
.isInterestRecalculationEnabled(true).recalculationRestFrequencyInterval(1)
|
||||
.recalculationRestFrequencyType(RecalculationRestFrequencyType.DAILY)
|
||||
.rescheduleStrategyMethod(RescheduleStrategyMethod.REDUCE_EMI_AMOUNT).allowPartialPeriodInterestCalculation(false)
|
||||
.disallowExpectedDisbursements(false).allowApprovedDisbursedAmountsOverApplied(false).overAppliedNumber(null)
|
||||
.overAppliedCalculationType(null).multiDisburseLoan(null)
|
||||
.charges(List.of(new LoanProductChargeData().id(installmentFeeChargeId)));
|
||||
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||
Long loanProductId = loanProductResponse.getResourceId();
|
||||
|
||||
double amount = 6000.0;
|
||||
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 October 2025", amount, numberOfRepayments)
|
||||
.repaymentEvery(repaymentEvery).loanTermFrequency(numberOfRepayments)
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS).loanTermFrequencyType(RepaymentFrequencyType.MONTHS)
|
||||
.interestType(InterestType.DECLINING_BALANCE).interestCalculationPeriodType(InterestCalculationPeriodType.DAILY)
|
||||
.charges(List.of(new PostLoansRequestChargeData().chargeId(installmentFeeChargeId)
|
||||
.amount(BigDecimal.valueOf(installmentFeeAmount))));
|
||||
|
||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
|
||||
PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
|
||||
approveLoanRequest(amount, "01 October 2025"));
|
||||
|
||||
aLoanId.getAndSet(approvedLoanResult.getLoanId());
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
disburseLoan(loanId, BigDecimal.valueOf(amount), "01 October 2025");
|
||||
});
|
||||
|
||||
runAt("01 November 2025", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
addRepaymentForLoan(loanId, 1100.0, "01 November 2025");
|
||||
});
|
||||
|
||||
runAt("01 December 2025", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
addRepaymentForLoan(loanId, 1100.0, "01 December 2025");
|
||||
});
|
||||
|
||||
runAt("01 January 2026", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
addRepaymentForLoan(loanId, 1100.0, "01 January 2026");
|
||||
|
||||
// Verify regular API values (full schedule)
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
BigDecimal regularApiFeeChargesCharged = loanDetails.getSummary().getFeeChargesCharged();
|
||||
assertThat(regularApiFeeChargesCharged).as("Regular API should show full fees (600)")
|
||||
.isEqualByComparingTo(BigDecimal.valueOf(600.0));
|
||||
});
|
||||
|
||||
runAt("08 January 2026", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
LoanPointInTimeData pointInTimeData = getPointInTimeData(loanId, "08 January 2026");
|
||||
|
||||
// Fee charges should only include fees for installments due by the requested date (3 installments * 100 =
|
||||
// 300)
|
||||
BigDecimal expectedFeeCharged = BigDecimal.valueOf(300.0);
|
||||
assertThat(pointInTimeData.getFee().getFeeChargesCharged())
|
||||
.as("Point-in-time feeChargesCharged should be 300 (3 installments * 100)").isEqualByComparingTo(expectedFeeCharged);
|
||||
|
||||
// Verify that totalExpectedRepayment is consistent with adjusted fees
|
||||
// totalExpectedRepayment = principal + interest + fees + penalties
|
||||
// For this loan: 6000 (principal) + 0 (interest) + 300 (adjusted fees) + 0 (penalties) = 6300
|
||||
BigDecimal expectedTotalExpectedRepayment = BigDecimal.valueOf(6300.0);
|
||||
assertThat(pointInTimeData.getTotal().getTotalExpectedRepayment())
|
||||
.as("Point-in-time totalExpectedRepayment should be consistent with adjusted feeChargesCharged (6000 + 300 = 6300)")
|
||||
.isEqualByComparingTo(expectedTotalExpectedRepayment);
|
||||
|
||||
// Verify that totalExpectedCostOfLoan is consistent with adjusted fees
|
||||
// totalExpectedCostOfLoan = interest + fees + penalties
|
||||
// For this loan: 0 (interest) + 300 (adjusted fees) + 0 (penalties) = 300
|
||||
BigDecimal expectedTotalExpectedCostOfLoan = BigDecimal.valueOf(300.0);
|
||||
assertThat(pointInTimeData.getTotal().getTotalExpectedCostOfLoan())
|
||||
.as("Point-in-time totalExpectedCostOfLoan should be consistent with adjusted feeChargesCharged (0 + 300 = 300)")
|
||||
.isEqualByComparingTo(expectedTotalExpectedCostOfLoan);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_LoanPointInTimeData_InterestFieldsRemainCoherent() {
|
||||
AtomicReference<Long> aLoanId = new AtomicReference<>();
|
||||
double installmentFeeAmount = 50.0;
|
||||
double interestRatePerPeriod = 12.0;
|
||||
|
||||
runAt("01 October 2025", () -> {
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
|
||||
int numberOfRepayments = 4;
|
||||
int repaymentEvery = 1;
|
||||
|
||||
Long installmentFeeChargeId = createInstallmentFeeCharge(installmentFeeAmount);
|
||||
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysPeriodicAccrualProduct(interestRatePerPeriod)
|
||||
.numberOfRepayments(numberOfRepayments).repaymentEvery(repaymentEvery).installmentAmountInMultiplesOf(null)
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()).interestType(InterestType.FLAT)
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD)
|
||||
.isInterestRecalculationEnabled(false).disallowExpectedDisbursements(false)
|
||||
.allowApprovedDisbursedAmountsOverApplied(false).overAppliedNumber(null).overAppliedCalculationType(null)
|
||||
.multiDisburseLoan(null).charges(List.of(new LoanProductChargeData().id(installmentFeeChargeId)));
|
||||
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||
Long loanProductId = loanProductResponse.getResourceId();
|
||||
|
||||
double amount = 4000.0;
|
||||
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 October 2025", amount, numberOfRepayments)
|
||||
.repaymentEvery(repaymentEvery).loanTermFrequency(numberOfRepayments)
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS).loanTermFrequencyType(RepaymentFrequencyType.MONTHS)
|
||||
.interestType(InterestType.FLAT).interestCalculationPeriodType(InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD)
|
||||
.interestRatePerPeriod(BigDecimal.valueOf(interestRatePerPeriod)).charges(List.of(new PostLoansRequestChargeData()
|
||||
.chargeId(installmentFeeChargeId).amount(BigDecimal.valueOf(installmentFeeAmount))));
|
||||
|
||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
|
||||
PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
|
||||
approveLoanRequest(amount, "01 October 2025"));
|
||||
|
||||
aLoanId.getAndSet(approvedLoanResult.getLoanId());
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
disburseLoan(loanId, BigDecimal.valueOf(amount), "01 October 2025");
|
||||
});
|
||||
|
||||
runAt("01 November 2025", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
addRepaymentForLoan(loanId, 1170.0, "01 November 2025");
|
||||
});
|
||||
|
||||
runAt("01 December 2025", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
addRepaymentForLoan(loanId, 1170.0, "01 December 2025");
|
||||
});
|
||||
|
||||
runAt("08 December 2025", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
BigDecimal regularApiInterestCharged = loanDetails.getSummary().getInterestCharged();
|
||||
BigDecimal regularApiInterestPaid = loanDetails.getSummary().getInterestPaid();
|
||||
BigDecimal regularApiInterestOutstanding = loanDetails.getSummary().getInterestOutstanding();
|
||||
|
||||
LoanPointInTimeData pointInTimeData = getPointInTimeData(loanId, "08 December 2025");
|
||||
|
||||
assertThat(pointInTimeData.getInterest().getInterestCharged())
|
||||
.as("Point-in-time interestCharged should remain unchanged from regular API")
|
||||
.isEqualByComparingTo(regularApiInterestCharged);
|
||||
|
||||
assertThat(pointInTimeData.getInterest().getInterestPaid())
|
||||
.as("Point-in-time interestPaid should remain unchanged from regular API").isEqualByComparingTo(regularApiInterestPaid);
|
||||
|
||||
assertThat(pointInTimeData.getInterest().getInterestOutstanding())
|
||||
.as("Point-in-time interestOutstanding should remain unchanged from regular API")
|
||||
.isEqualByComparingTo(regularApiInterestOutstanding);
|
||||
|
||||
assertThat(pointInTimeData.getFee().getFeeChargesCharged())
|
||||
.as("Point-in-time feeChargesCharged should only include fees for 2 installments due by Dec 8")
|
||||
.isEqualByComparingTo(BigDecimal.valueOf(100.0));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_LoanPointInTimeData_PenaltyFieldsRemainCoherent() {
|
||||
AtomicReference<Long> aLoanId = new AtomicReference<>();
|
||||
double installmentFeeAmount = 50.0;
|
||||
double installmentPenaltyAmount = 25.0;
|
||||
|
||||
runAt("01 October 2025", () -> {
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
|
||||
int numberOfRepayments = 4;
|
||||
int repaymentEvery = 1;
|
||||
|
||||
Long installmentFeeChargeId = createInstallmentFeeCharge(installmentFeeAmount);
|
||||
Long installmentPenaltyChargeId = createInstallmentPenaltyCharge(installmentPenaltyAmount);
|
||||
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct()
|
||||
.numberOfRepayments(numberOfRepayments).repaymentEvery(repaymentEvery).installmentAmountInMultiplesOf(null)
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()).interestType(InterestType.FLAT)
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD)
|
||||
.isInterestRecalculationEnabled(false).disallowExpectedDisbursements(false)
|
||||
.allowApprovedDisbursedAmountsOverApplied(false).overAppliedNumber(null).overAppliedCalculationType(null)
|
||||
.multiDisburseLoan(null).charges(List.of(new LoanProductChargeData().id(installmentFeeChargeId),
|
||||
new LoanProductChargeData().id(installmentPenaltyChargeId)));
|
||||
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||
Long loanProductId = loanProductResponse.getResourceId();
|
||||
|
||||
double amount = 4000.0;
|
||||
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 October 2025", amount, numberOfRepayments)
|
||||
.repaymentEvery(repaymentEvery).loanTermFrequency(numberOfRepayments)
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS).loanTermFrequencyType(RepaymentFrequencyType.MONTHS)
|
||||
.interestType(InterestType.FLAT).interestCalculationPeriodType(InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD)
|
||||
.charges(List.of(
|
||||
new PostLoansRequestChargeData().chargeId(installmentFeeChargeId)
|
||||
.amount(BigDecimal.valueOf(installmentFeeAmount)),
|
||||
new PostLoansRequestChargeData().chargeId(installmentPenaltyChargeId)
|
||||
.amount(BigDecimal.valueOf(installmentPenaltyAmount))));
|
||||
|
||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
|
||||
PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
|
||||
approveLoanRequest(amount, "01 October 2025"));
|
||||
|
||||
aLoanId.getAndSet(approvedLoanResult.getLoanId());
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
disburseLoan(loanId, BigDecimal.valueOf(amount), "01 October 2025");
|
||||
});
|
||||
|
||||
runAt("01 November 2025", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
addRepaymentForLoan(loanId, 1075.0, "01 November 2025");
|
||||
});
|
||||
|
||||
runAt("01 December 2025", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
addRepaymentForLoan(loanId, 1075.0, "01 December 2025");
|
||||
});
|
||||
|
||||
runAt("08 December 2025", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
BigDecimal regularApiPenaltyCharged = loanDetails.getSummary().getPenaltyChargesCharged();
|
||||
BigDecimal regularApiPenaltyPaid = loanDetails.getSummary().getPenaltyChargesPaid();
|
||||
BigDecimal regularApiPenaltyOutstanding = loanDetails.getSummary().getPenaltyChargesOutstanding();
|
||||
|
||||
LoanPointInTimeData pointInTimeData = getPointInTimeData(loanId, "08 December 2025");
|
||||
|
||||
assertThat(pointInTimeData.getPenalty().getPenaltyChargesCharged())
|
||||
.as("Point-in-time penaltyChargesCharged should remain unchanged from regular API")
|
||||
.isEqualByComparingTo(regularApiPenaltyCharged);
|
||||
|
||||
assertThat(pointInTimeData.getPenalty().getPenaltyChargesPaid())
|
||||
.as("Point-in-time penaltyChargesPaid should remain unchanged from regular API")
|
||||
.isEqualByComparingTo(regularApiPenaltyPaid);
|
||||
|
||||
assertThat(pointInTimeData.getPenalty().getPenaltyChargesOutstanding())
|
||||
.as("Point-in-time penaltyChargesOutstanding should remain unchanged from regular API")
|
||||
.isEqualByComparingTo(regularApiPenaltyOutstanding);
|
||||
|
||||
assertThat(pointInTimeData.getFee().getFeeChargesCharged())
|
||||
.as("Point-in-time feeChargesCharged should only include fees for 2 installments due by Dec 8")
|
||||
.isEqualByComparingTo(BigDecimal.valueOf(100.0));
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_LoanPointInTimeData_MatchesRegularApiWhenNoFiltering() {
|
||||
AtomicReference<Long> aLoanId = new AtomicReference<>();
|
||||
double installmentFeeAmount = 100.0;
|
||||
|
||||
runAt("01 October 2025", () -> {
|
||||
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
|
||||
|
||||
int numberOfRepayments = 3;
|
||||
int repaymentEvery = 1;
|
||||
|
||||
Long installmentFeeChargeId = createInstallmentFeeCharge(installmentFeeAmount);
|
||||
|
||||
PostLoanProductsRequest product = createOnePeriod30DaysLongNoInterestPeriodicAccrualProduct()
|
||||
.numberOfRepayments(numberOfRepayments).repaymentEvery(repaymentEvery).installmentAmountInMultiplesOf(null)
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS.longValue()).interestType(InterestType.FLAT)
|
||||
.interestCalculationPeriodType(InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD)
|
||||
.isInterestRecalculationEnabled(false).disallowExpectedDisbursements(false)
|
||||
.allowApprovedDisbursedAmountsOverApplied(false).overAppliedNumber(null).overAppliedCalculationType(null)
|
||||
.multiDisburseLoan(null).charges(List.of(new LoanProductChargeData().id(installmentFeeChargeId)));
|
||||
|
||||
PostLoanProductsResponse loanProductResponse = loanProductHelper.createLoanProduct(product);
|
||||
Long loanProductId = loanProductResponse.getResourceId();
|
||||
|
||||
double amount = 3000.0;
|
||||
|
||||
PostLoansRequest applicationRequest = applyLoanRequest(clientId, loanProductId, "01 October 2025", amount, numberOfRepayments)
|
||||
.repaymentEvery(repaymentEvery).loanTermFrequency(numberOfRepayments)
|
||||
.repaymentFrequencyType(RepaymentFrequencyType.MONTHS).loanTermFrequencyType(RepaymentFrequencyType.MONTHS)
|
||||
.interestType(InterestType.FLAT).interestCalculationPeriodType(InterestCalculationPeriodType.SAME_AS_REPAYMENT_PERIOD)
|
||||
.charges(List.of(new PostLoansRequestChargeData().chargeId(installmentFeeChargeId)
|
||||
.amount(BigDecimal.valueOf(installmentFeeAmount))));
|
||||
|
||||
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applicationRequest);
|
||||
|
||||
PostLoansLoanIdResponse approvedLoanResult = loanTransactionHelper.approveLoan(postLoansResponse.getResourceId(),
|
||||
approveLoanRequest(amount, "01 October 2025"));
|
||||
|
||||
aLoanId.getAndSet(approvedLoanResult.getLoanId());
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
disburseLoan(loanId, BigDecimal.valueOf(amount), "01 October 2025");
|
||||
});
|
||||
|
||||
runAt("01 November 2025", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
addRepaymentForLoan(loanId, 1100.0, "01 November 2025");
|
||||
});
|
||||
|
||||
runAt("01 December 2025", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
addRepaymentForLoan(loanId, 1100.0, "01 December 2025");
|
||||
});
|
||||
|
||||
runAt("01 January 2026", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
addRepaymentForLoan(loanId, 1100.0, "01 January 2026");
|
||||
|
||||
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
|
||||
|
||||
assertThat(loanDetails.getStatus().getCode()).isEqualTo("loanStatusType.closed.obligations.met");
|
||||
});
|
||||
|
||||
runAt("15 January 2026", () -> {
|
||||
Long loanId = aLoanId.get();
|
||||
|
||||
GetLoansLoanIdResponse regularApi = loanTransactionHelper.getLoanDetails(loanId);
|
||||
LoanPointInTimeData pointInTimeApi = getPointInTimeData(loanId, "15 January 2026");
|
||||
|
||||
assertThat(pointInTimeApi.getFee().getFeeChargesCharged())
|
||||
.as("Closed loan: point-in-time feeChargesCharged should match regular API")
|
||||
.isEqualByComparingTo(regularApi.getSummary().getFeeChargesCharged());
|
||||
|
||||
assertThat(pointInTimeApi.getFee().getFeeChargesPaid()).as("Closed loan: point-in-time feeChargesPaid should match regular API")
|
||||
.isEqualByComparingTo(regularApi.getSummary().getFeeChargesPaid());
|
||||
|
||||
assertThat(pointInTimeApi.getFee().getFeeChargesOutstanding())
|
||||
.as("Closed loan: point-in-time feeChargesOutstanding should match regular API")
|
||||
.isEqualByComparingTo(regularApi.getSummary().getFeeChargesOutstanding());
|
||||
|
||||
assertThat(pointInTimeApi.getTotal().getTotalExpectedRepayment())
|
||||
.as("Closed loan: point-in-time totalExpectedRepayment should match regular API")
|
||||
.isEqualByComparingTo(regularApi.getSummary().getTotalExpectedRepayment());
|
||||
|
||||
assertThat(pointInTimeApi.getTotal().getTotalExpectedCostOfLoan())
|
||||
.as("Closed loan: point-in-time totalExpectedCostOfLoan should match regular API")
|
||||
.isEqualByComparingTo(regularApi.getSummary().getTotalExpectedCostOfLoan());
|
||||
|
||||
assertThat(pointInTimeApi.getPrincipal().getPrincipalOutstanding())
|
||||
.as("Closed loan: point-in-time principalOutstanding should match regular API")
|
||||
.isEqualByComparingTo(regularApi.getSummary().getPrincipalOutstanding());
|
||||
|
||||
assertThat(pointInTimeApi.getTotal().getTotalOutstanding())
|
||||
.as("Closed loan: point-in-time totalOutstanding should match regular API")
|
||||
.isEqualByComparingTo(regularApi.getSummary().getTotalOutstanding());
|
||||
});
|
||||
}
|
||||
|
||||
private Long createInstallmentFeeCharge(double amount) {
|
||||
Integer chargeId = ChargesHelper.createCharges(requestSpec, responseSpec,
|
||||
ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(amount), false));
|
||||
assertNotNull(chargeId);
|
||||
return chargeId.longValue();
|
||||
}
|
||||
|
||||
private Long createInstallmentPenaltyCharge(double amount) {
|
||||
Integer chargeId = ChargesHelper.createCharges(requestSpec, responseSpec,
|
||||
ChargesHelper.getLoanInstallmentJSON(ChargesHelper.CHARGE_CALCULATION_TYPE_FLAT, String.valueOf(amount), true));
|
||||
assertNotNull(chargeId);
|
||||
return chargeId.longValue();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user