FINERACT-2220: Use stored ProgressiveLoanInterestScheduleModel for calculation

This commit is contained in:
Soma Sörös
2025-04-29 13:12:51 +02:00
committed by Adam Saghy
parent d7f321bc65
commit 2d17fcd478
14 changed files with 408 additions and 162 deletions
@@ -23,17 +23,33 @@ import java.util.List;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.PostLoanProductsResponse;
import org.apache.fineract.client.models.PostLoansResponse;
import org.apache.fineract.integrationtests.common.BusinessStepHelper;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
public class LoanSummaryTest extends BaseLoanIntegrationTest {
private static BusinessStepHelper.BusinessStepsSnapshot originalConfig;
private static final BusinessStepHelper businessStepHelper = new BusinessStepHelper();
Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
Long loanId;
@BeforeAll
static void setup() {
originalConfig = businessStepHelper.getConfigurationSnapshot("LOAN_CLOSE_OF_BUSINESS");
}
@AfterAll
public static void teardown() {
originalConfig.restore();
}
@Test
public void testUnpaidPayableNotDueInterestForProgressiveLoanInCaseOfEarlyRepayment() {
businessStepHelper.updateSteps("LOAN_CLOSE_OF_BUSINESS", "ADD_PERIODIC_ACCRUAL_ENTRIES", "LOAN_INTEREST_RECALCULATION");
runAt("1 January 2024", () -> {
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(create4IProgressive());
PostLoansResponse postLoansResponse = loanTransactionHelper.applyLoan(applyLP2ProgressiveLoanRequest(clientId,
@@ -98,6 +114,7 @@ public class LoanSummaryTest extends BaseLoanIntegrationTest {
@Test
public void testUnpaidPayableNotDueInterestForProgressiveLoanInCaseOfEarlyRepaymentAlmostFullyPaid2ndPeriod() {
businessStepHelper.updateSteps("LOAN_CLOSE_OF_BUSINESS", "LOAN_INTEREST_RECALCULATION");
runAt("15 March 2025", () -> {
final PostLoanProductsResponse loanProductsResponse = loanProductHelper.createLoanProduct(
create4IProgressive().interestRatePerPeriod(35.99).numberOfRepayments(12).isInterestRecalculationEnabled(true));
@@ -18,10 +18,16 @@
*/
package org.apache.fineract.integrationtests;
import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicLong;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.GetLoansLoanIdResponse;
import org.apache.fineract.client.models.ProgressiveLoanInterestScheduleModel;
import org.apache.fineract.integrationtests.common.ClientHelper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@Slf4j
public class ProgressiveLoanModelIntegrationTest extends BaseLoanIntegrationTest {
private final Long clientId = clientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getClientId();
@@ -29,26 +35,56 @@ public class ProgressiveLoanModelIntegrationTest extends BaseLoanIntegrationTest
.getResourceId();
@Test
public void testModelReturnsNullAndThenSaveThenNotNull() {
public void testModelReturnsSavedModelAfterDisbursement() {
AtomicLong loanIdA = new AtomicLong();
runAt("1 January 2024", () -> {
Long loanId = applyAndApproveProgressiveLoan(clientId, loanProductId, "1 January 2024", 1000.0, 96.32, 6, null);
loanIdA.set(loanId);
log.info("Testing on loanId: {}", loanId);
loanTransactionHelper.disburseLoan(loanId, "1 January 2024", 1000.0);
// Model saved automatically, fetching it. It should return non null
ProgressiveLoanInterestScheduleModel model = assertNotNullAndChanged(null, loanId);
loanTransactionHelper.makeLoanRepayment(loanId, "Repayment", "1 January 2024", 12.78);
model = assertNotNullAndChanged(model, loanId);
assertNotNullAndSame(model, loanId);
});
runAt("28 February 2024", () -> {
Long loanId = loanIdA.get();
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanId);
BigDecimal totalUnpaidPayableNotDueInterest = loanDetails.getSummary().getTotalUnpaidPayableNotDueInterest();
BigDecimal totalUnpaidDueInterest = loanDetails.getSummary().getTotalUnpaidPayableDueInterest();
// Model not saved, fetching it. It should return null
ProgressiveLoanInterestScheduleModel progressiveLoanInterestScheduleModelResponse1 = ok(
fineractClient().progressiveLoanApi.fetchModel(loanId));
assertThat(progressiveLoanInterestScheduleModelResponse1).isNull();
// Forcing Model recalculation and save to database. It should return the actual model.
ProgressiveLoanInterestScheduleModel ok = ok(fineractClient().progressiveLoanApi.updateModel(loanId));
assertThat(ok).isNotNull();
// Model saved in previous step. API should return the previous model.
ProgressiveLoanInterestScheduleModel progressiveLoanInterestScheduleModelResponse2 = ok(
fineractClient().progressiveLoanApi.fetchModel(loanId));
assertThat(progressiveLoanInterestScheduleModelResponse2).isNotNull();
Assertions.assertEquals(73.78, totalUnpaidPayableNotDueInterest.doubleValue(), 0.001);
Assertions.assertEquals(79.24, totalUnpaidDueInterest.doubleValue(), 0.001);
});
}
private ProgressiveLoanInterestScheduleModel assertNotNullAndChanged(ProgressiveLoanInterestScheduleModel prev, Long loanId) {
return assertNotNullAndChanged(prev, ok(fineractClient().progressiveLoanApi.fetchModel(loanId)));
}
private ProgressiveLoanInterestScheduleModel assertNotNullAndSame(ProgressiveLoanInterestScheduleModel prev, Long loanId) {
return assertNotNullAndSame(prev, ok(fineractClient().progressiveLoanApi.fetchModel(loanId)));
}
private ProgressiveLoanInterestScheduleModel assertNotNullAndSame(ProgressiveLoanInterestScheduleModel prev,
ProgressiveLoanInterestScheduleModel actual) {
if (actual == null) {
Assertions.fail("Model is null");
}
Assertions.assertEquals(prev.toString(), actual.toString());
return actual;
}
private ProgressiveLoanInterestScheduleModel assertNotNullAndChanged(ProgressiveLoanInterestScheduleModel prev,
ProgressiveLoanInterestScheduleModel actual) {
if (actual == null) {
Assertions.fail("Model is null");
}
if (prev != null) {
Assertions.assertNotEquals(prev.toString(), actual.toString());
}
return actual;
}
}
@@ -20,6 +20,8 @@ package org.apache.fineract.integrationtests.common;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.fineract.client.models.BusinessStep;
import org.apache.fineract.client.models.BusinessStepRequest;
import org.apache.fineract.client.util.Calls;
@@ -28,6 +30,13 @@ public class BusinessStepHelper {
public BusinessStepHelper() {}
public BusinessStepsSnapshot getConfigurationSnapshot(String jobName) {
List<BusinessStep> businessSteps = Calls
.ok(FineractClientHelper.getFineractClient().businessStepConfiguration.retrieveAllConfiguredBusinessStep(jobName))
.getBusinessSteps();
return new BusinessStepsSnapshot(jobName, businessSteps);
}
public void updateSteps(String jobName, String... steps) {
long order = 0;
List<BusinessStep> stepList = new ArrayList<>();
@@ -41,4 +50,17 @@ public class BusinessStepHelper {
Calls.ok(FineractClientHelper.getFineractClient().businessStepConfiguration.updateJobBusinessStepConfig(jobName,
new BusinessStepRequest().businessSteps(stepList)));
}
@Getter
@AllArgsConstructor
public static class BusinessStepsSnapshot {
private String jobName;
private List<BusinessStep> businessSteps;
public void restore() {
Calls.ok(FineractClientHelper.getFineractClient().businessStepConfiguration.updateJobBusinessStepConfig(jobName,
new BusinessStepRequest().businessSteps(businessSteps)));
}
}
}