FINERACT-2280: Interest pause breaks repayment schedule while loan is added with interest rate zero

This commit is contained in:
Soma Sörös
2025-05-16 14:25:06 +02:00
committed by Adam Saghy
parent cd03dc020e
commit 9ab08ec428
3 changed files with 87 additions and 17 deletions
@@ -184,6 +184,11 @@ public class InterestPauseWritePlatformServiceImpl implements InterestPauseWrite
"Interest pause is only supported for progressive loans.");
}
if (!loan.isInterestBearing()) {
throw new GeneralPlatformDomainRuleException("loan.must.be.interest.bearing",
"Interest pause is only supported for interest bearing loans.");
}
if (!loan.getLoanRepaymentScheduleDetail().isInterestRecalculationEnabled()) {
throw new GeneralPlatformDomainRuleException("loan.must.have.recalculate.interest.enabled",
"Interest pause is only supported for loans with recalculate interest enabled.");
@@ -27,6 +27,7 @@ import java.math.BigDecimal;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.AdvancedPaymentData;
import org.apache.fineract.client.models.CommandProcessingResult;
import org.apache.fineract.client.models.PostLoansLoanIdRequest;
import org.apache.fineract.client.models.PostLoansLoanIdTransactionsResponse;
import org.apache.fineract.integrationtests.common.ClientHelper;
@@ -35,23 +36,17 @@ import org.apache.fineract.integrationtests.common.accounting.Account;
import org.apache.fineract.integrationtests.common.accounting.AccountHelper;
import org.apache.fineract.integrationtests.common.loans.LoanApplicationTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanProductTestBuilder;
import org.apache.fineract.integrationtests.common.loans.LoanTestLifecycleExtension;
import org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleProcessingType;
import org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanScheduleType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import retrofit2.Response;
@Slf4j
@ExtendWith(LoanTestLifecycleExtension.class)
public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
private static final Logger LOG = LoggerFactory.getLogger(LoanInterestPauseApiTest.class);
private static RequestSpecification REQUEST_SPEC;
private static ResponseSpecification RESPONSE_SPEC;
private static ResponseSpecification RESPONSE_SPEC_403;
@@ -65,7 +60,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
private static final Integer nonExistLoanId = 99999;
private static String externalId;
private static final String nonExistExternalId = "7c4fb86f-a778-4d02-b7a8-ec3ec98941fa";
private Integer clientId;
private Long clientId;
private Integer loanProductId;
private Integer loanId;
private final String loanPrincipalAmount = "10000.00";
@@ -614,20 +609,81 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
}
}
Long loan;
@Test
public void testInterestPauseOnZeroInterestRate() {
runAt("1 September 2019", () -> {
Long loanProductId = loanProductHelper.createLoanProduct(create4IProgressive()).getResourceId();
loan = applyAndApproveProgressiveLoan(clientId, loanProductId, "1 September 2019", 1200.0, 0.0, 4, null);
disburseLoan(loan, BigDecimal.valueOf(1200.0), "1 September 2019");
});
runAt("1 October 2019", () -> {
loanTransactionHelper.makeLoanRepayment(loan, "Repayment", "1 October 2019", 300.0);
});
runAt("1 November 2019", () -> {
loanTransactionHelper.makeLoanRepayment(loan, "Repayment", "1 November 2019", 300.0);
});
runAt("1 December 2019", () -> {
loanTransactionHelper.makeLoanRepayment(loan, "Repayment", "1 December 2019", 300.0);
verifyTransactions(loan, //
transaction(1200.0, "Disbursement", "01 September 2019", 1200.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0), //
transaction(300.0, "Repayment", "01 October 2019", 900.0, 300.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
transaction(300.0, "Repayment", "01 November 2019", 600.0, 300.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
transaction(300.0, "Repayment", "01 December 2019", 300.0, 300.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
);
Response<CommandProcessingResult> response = loanTransactionHelper.createInterestPause(loan, "1 October 2019",
"15 December 2019");
Assertions.assertEquals(403, response.code());
verifyTransactions(loan, //
transaction(1200.0, "Disbursement", "01 September 2019", 1200.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0), //
transaction(300.0, "Repayment", "01 October 2019", 900.0, 300.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
transaction(300.0, "Repayment", "01 November 2019", 600.0, 300.0, 0.0, 0.0, 0.0, 0.0, 0.0), //
transaction(300.0, "Repayment", "01 December 2019", 300.0, 300.0, 0.0, 0.0, 0.0, 0.0, 0.0) //
);
});
}
@Test
public void testInterestPauseOnZeroInterestRateRightAfterDisbursement() {
runAt("1 September 2019", () -> {
Long loanProductId = loanProductHelper.createLoanProduct(create4IProgressive()).getResourceId();
loan = applyAndApproveProgressiveLoan(clientId, loanProductId, "1 September 2019", 1200.0, 0.0, 4, null);
disburseLoan(loan, BigDecimal.valueOf(1200.0), "1 September 2019");
verifyRepaymentSchedule(loan, //
installment(1200.0, null, "01 September 2019"), //
installment(300.0, 0.0, 300.0, false, "01 October 2019"), //
installment(300.0, 0.0, 300.0, false, "01 November 2019"), //
installment(300.0, 0.0, 300.0, false, "01 December 2019"), //
installment(300.0, 0.0, 300.0, false, "01 January 2020") //
);
Response<CommandProcessingResult> response = loanTransactionHelper.createInterestPause(loan, "1 October 2019",
"15 December 2019");
Assertions.assertEquals(403, response.code());
verifyRepaymentSchedule(loan, //
installment(1200.0, null, "01 September 2019"), //
installment(300.0, 0.0, 300.0, false, "01 October 2019"), //
installment(300.0, 0.0, 300.0, false, "01 November 2019"), //
installment(300.0, 0.0, 300.0, false, "01 December 2019"), //
installment(300.0, 0.0, 300.0, false, "01 January 2020") //
);
});
}
/**
* create a new client
**/
private void createClientEntity() {
this.clientId = ClientHelper.createClient(REQUEST_SPEC, RESPONSE_SPEC);
ClientHelper.verifyClientCreatedOnServer(REQUEST_SPEC, RESPONSE_SPEC, clientId);
this.clientId = ClientHelper.createClient(ClientHelper.defaultClientCreationRequest()).getResourceId();
Assertions.assertNotNull(clientId);
ClientHelper.verifyClientCreatedOnServer(REQUEST_SPEC, RESPONSE_SPEC, clientId.intValue());
}
/**
* create a new loan product
**/
private void createLoanProductEntity() {
LOG.info("---------------------------------CREATING LOAN PRODUCT------------------------------------------");
log.info("---------------------------------CREATING LOAN PRODUCT------------------------------------------");
final String interestRecalculationCompoundingMethod = LoanProductTestBuilder.RECALCULATION_COMPOUNDING_METHOD_NONE;
final String rescheduleStrategyMethod = LoanProductTestBuilder.RECALCULATION_STRATEGY_ADJUST_LAST_UNPAID_PERIOD;
@@ -651,14 +707,14 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
.build();
loanProductId = LOAN_TRANSACTION_HELPER.getLoanProductId(loanProductJSON);
LOG.info("Successfully created loan product (ID:{}) ", loanProductId);
log.info("Successfully created loan product (ID:{}) ", loanProductId);
}
/**
* submit a new loan application, approve and disburse the loan
**/
private void createLoanEntity() {
LOG.info("---------------------------------NEW LOAN APPLICATION------------------------------------------");
log.info("---------------------------------NEW LOAN APPLICATION------------------------------------------");
String loanApplicationJSON = new LoanApplicationTestBuilder().withPrincipal(loanPrincipalAmount)
.withLoanTermFrequency(numberOfRepayments).withLoanTermFrequencyAsDays().withNumberOfRepayments(numberOfRepayments)
@@ -670,7 +726,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
loanId = LOAN_TRANSACTION_HELPER.getLoanId(loanApplicationJSON);
LOG.info("Sucessfully created loan (ID: {} )", loanId);
log.info("Sucessfully created loan (ID: {} )", loanId);
approveLoanApplication();
disburseLoan();
@@ -683,7 +739,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
if (loanId != null) {
LOAN_TRANSACTION_HELPER.approveLoan(dateString, loanId);
LOG.info("Successfully approved loan (ID: {} )", loanId);
log.info("Successfully approved loan (ID: {} )", loanId);
}
}
@@ -695,7 +751,7 @@ public class LoanInterestPauseApiTest extends BaseLoanIntegrationTest {
if (loanId != null) {
LOAN_TRANSACTION_HELPER.disburseLoan(externalId, new PostLoansLoanIdRequest().actualDisbursementDate(dateString)
.transactionAmount(new BigDecimal(loanPrincipalAmount)).locale("en").dateFormat("dd MMMM yyyy"));
LOG.info("Successfully disbursed loan (ID: {} )", loanId);
log.info("Successfully disbursed loan (ID: {} )", loanId);
}
}
}
@@ -45,6 +45,7 @@ import java.util.Locale;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.fineract.client.models.AdvancedPaymentData;
import org.apache.fineract.client.models.CommandProcessingResult;
import org.apache.fineract.client.models.DeleteLoansLoanIdChargesChargeIdResponse;
import org.apache.fineract.client.models.DeleteLoansLoanIdResponse;
import org.apache.fineract.client.models.DisbursementDetail;
@@ -66,6 +67,7 @@ import org.apache.fineract.client.models.GetLoansLoanIdTransactionsResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTemplateResponse;
import org.apache.fineract.client.models.GetLoansLoanIdTransactionsTransactionIdResponse;
import org.apache.fineract.client.models.GetLoansResponse;
import org.apache.fineract.client.models.InterestPauseRequestDto;
import org.apache.fineract.client.models.PaymentTypeData;
import org.apache.fineract.client.models.PostAddAndDeleteDisbursementDetailRequest;
import org.apache.fineract.client.models.PostLoanProductsRequest;
@@ -104,6 +106,7 @@ import org.apache.fineract.integrationtests.common.accounting.Account;
import org.apache.fineract.portfolio.delinquency.domain.DelinquencyAction;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;
import retrofit2.Response;
@Slf4j
@SuppressWarnings({ "rawtypes", "unchecked" })
@@ -919,6 +922,12 @@ public class LoanTransactionHelper {
.transactionDate(transactionDate).dateFormat("dd MMMM yyyy").locale("en"));
}
public Response<CommandProcessingResult> createInterestPause(Long loanId, String startDate, String endDate) {
log.info("Creating interest pause for Loan {} from {} to {}", loanId, startDate, endDate);
return Calls.executeU(FineractClientHelper.getFineractClient().loanInterestPauseApi.createInterestPause(loanId,
new InterestPauseRequestDto().startDate(startDate).endDate(endDate).dateFormat(DATE_FORMAT).locale("en")));
}
// 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)