diff --git a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java index 20f14807e..d3cdbf02d 100644 --- a/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java +++ b/fineract-progressive-loan/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidator.java @@ -33,7 +33,6 @@ import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.Loan import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateSupportedParameters; import com.google.gson.JsonElement; -import jakarta.persistence.criteria.Predicate; import java.math.BigDecimal; import java.time.LocalDate; import java.util.ArrayList; @@ -44,17 +43,14 @@ import org.apache.commons.lang3.StringUtils; import org.apache.fineract.infrastructure.core.api.JsonCommand; import org.apache.fineract.infrastructure.core.data.ApiParameterError; import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; -import org.apache.fineract.infrastructure.core.exception.GeneralPlatformDomainRuleException; import org.apache.fineract.infrastructure.core.exception.PlatformApiDataValidationException; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.portfolio.loanaccount.domain.Loan; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.domain.LoanRescheduleRequestToTermVariationMapping; -import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus; import org.apache.fineract.portfolio.loanaccount.domain.LoanTermVariationType; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest; -import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository; import org.springframework.stereotype.Component; @Component("progressiveLoanRescheduleRequestDataValidatorImpl") @@ -62,7 +58,6 @@ import org.springframework.stereotype.Component; public class ProgressiveLoanRescheduleRequestDataValidator implements LoanRescheduleRequestDataValidator { private final FromJsonHelper fromJsonHelper; - private final LoanRescheduleRequestRepository loanRescheduleRequestRepository; @Override public void validateForCreateAction(JsonCommand jsonCommand, Loan loan) { @@ -115,9 +110,6 @@ public class ProgressiveLoanRescheduleRequestDataValidator implements LoanResche private void validateInterestRate(DataValidatorBuilder dataValidatorBuilder, Loan loan, LocalDate rescheduleFromDate) { validateLoanStatusIsActiveOrClosed(loan, dataValidatorBuilder); - if (rescheduleFromDate != null) { - validateInterestRateChangeRescheduleFromDate(loan, rescheduleFromDate); - } LoanRepaymentScheduleInstallment installment; installment = loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate); validateReschedulingInstallment(dataValidatorBuilder, installment); @@ -223,20 +215,6 @@ public class ProgressiveLoanRescheduleRequestDataValidator implements LoanResche throw new UnsupportedOperationException("Nothing to override here"); } - private void validateInterestRateChangeRescheduleFromDate(Loan loan, LocalDate rescheduleFromDate) { - boolean alreadyExistInterestRateChange = loanRescheduleRequestRepository.exists((root, query, criteriaBuilder) -> { - Predicate loanPredicate = criteriaBuilder.equal(root.get("loan"), loan); - Predicate statusPredicate = root.get("statusEnum") - .in(List.of(LoanStatus.SUBMITTED_AND_PENDING_APPROVAL.getValue(), LoanStatus.APPROVED.getValue())); - Predicate datePredicate = criteriaBuilder.equal(root.get("rescheduleFromDate"), rescheduleFromDate); - return criteriaBuilder.and(loanPredicate, statusPredicate, datePredicate); - }); - if (alreadyExistInterestRateChange) { - throw new GeneralPlatformDomainRuleException("loan.reschedule.interest.rate.change.already.exists", - "Interest rate change for the provided date is already exists.", rescheduleFromDate); - } - } - private BigDecimal validateInterestRateParam(final FromJsonHelper fromJsonHelper, final JsonElement jsonElement, DataValidatorBuilder dataValidatorBuilder, Loan loan) { final BigDecimal interestRate = fromJsonHelper diff --git a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidatorTest.java b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidatorTest.java index 1f5bc9181..e52d763b9 100644 --- a/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidatorTest.java +++ b/fineract-progressive-loan/src/test/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/data/ProgressiveLoanRescheduleRequestDataValidatorTest.java @@ -28,7 +28,6 @@ import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder; import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper; import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants; -import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -50,15 +49,11 @@ class ProgressiveLoanRescheduleRequestDataValidatorTest { @Mock private FromJsonHelper fromJsonHelper; - @Mock - private LoanRescheduleRequestRepository loanRescheduleRequestRepository; - private ProgressiveLoanRescheduleRequestDataValidator progressiveLoanRescheduleRequestDataValidator; @BeforeEach void setUp() { - progressiveLoanRescheduleRequestDataValidator = new ProgressiveLoanRescheduleRequestDataValidator(fromJsonHelper, - loanRescheduleRequestRepository); + progressiveLoanRescheduleRequestDataValidator = new ProgressiveLoanRescheduleRequestDataValidator(fromJsonHelper); when(dataValidatorBuilder.reset()).thenReturn(dataValidatorBuilder); when(dataValidatorBuilder.parameter(anyString())).thenReturn(dataValidatorBuilder); } diff --git a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java index bff1713d7..242cd26be 100644 --- a/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java +++ b/fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/rescheduleloan/service/LoanRescheduleRequestWritePlatformServiceImpl.java @@ -74,6 +74,7 @@ import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansA import org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidator; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository; +import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanTermVariationsRepository; import org.apache.fineract.portfolio.loanaccount.rescheduleloan.exception.LoanRescheduleRequestNotFoundException; import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService; import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler; @@ -117,6 +118,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche private final LoanScheduleComponent loanSchedule; private final LoanTransactionRepository loanTransactionRepository; private final LoanLifecycleStateMachine loanLifecycleStateMachine; + private final LoanTermVariationsRepository loanTermVariationsRepository; /** * create a new instance of the LoanRescheduleRequest object from the JsonCommand object and persist @@ -399,6 +401,17 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche hasInterestRateChange = true; } } + if (hasInterestRateChange && loan.isProgressiveSchedule()) { + final LocalDate termApplicableFromDate = rescheduleFromDate; + LoanTermVariations previousLoanTermVariations = activeLoanTermVariations.stream() + .filter(lt -> lt.getTermType().isInterestRateFromInstallment() // + && lt.getTermApplicableFrom().equals(termApplicableFromDate)) + .findFirst().orElse(null); + if (previousLoanTermVariations != null) { + previousLoanTermVariations.markAsInactive(); + loanTermVariationsRepository.save(previousLoanTermVariations); + } + } BigDecimal annualNominalInterestRate = null; List loanTermVariations = new ArrayList<>(); loanTermVariationsMapper.constructLoanTermVariations(scheduleGeneratorDTO.getFloatingRateDTO(), annualNominalInterestRate, diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java index 93da039e9..18fd2d7fc 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/LoanRescheduleRequestTest.java @@ -290,13 +290,9 @@ public class LoanRescheduleRequestTest extends BaseLoanIntegrationTest { .loanId(loanResponse.get().getLoanId()).dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("15 February 2023") .newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("16 February 2023"))); - exception = assertThrows(CallFailedRuntimeException.class, - () -> loanRescheduleRequestHelper - .createLoanRescheduleRequest(new PostCreateRescheduleLoansRequest().loanId(loanResponse.get().getLoanId()) - .dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("15 February 2023") - .newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("16 February 2023"))); - assertEquals(403, exception.getResponse().code()); - assertTrue(exception.getMessage().contains("loan.reschedule.interest.rate.change.already.exists")); + loanRescheduleRequestHelper.createLoanRescheduleRequest(new PostCreateRescheduleLoansRequest() + .loanId(loanResponse.get().getLoanId()).dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("15 February 2023") + .newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("16 February 2023")); }); // Do not allow approve an interest rate change if the reschedule from date is not in the future // Do not allow create interest rate change if a previous interest rate change got already approved for that @@ -310,14 +306,17 @@ public class LoanRescheduleRequestTest extends BaseLoanIntegrationTest { loanRescheduleRequestHelper.approveLoanRescheduleRequest(rescheduleLoansResponse.getResourceId(), new PostUpdateRescheduleLoansRequest().approvedOnDate("17 February 2024").locale("en").dateFormat(DATETIME_PATTERN)); - CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class, - () -> loanRescheduleRequestHelper - .createLoanRescheduleRequest(new PostCreateRescheduleLoansRequest().loanId(rescheduleLoansResponse.getLoanId()) - .dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("17 February 2023") - .newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("17 February 2023"))); - assertEquals(403, exception.getResponse().code()); - assertTrue(exception.getMessage().contains("loan.reschedule.interest.rate.change.already.exists")); + PostCreateRescheduleLoansResponse secondRescheduleLoansResponse = loanRescheduleRequestHelper + .createLoanRescheduleRequest(new PostCreateRescheduleLoansRequest().loanId(loanResponse.get().getLoanId()) + .dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("17 February 2023").newInterestRate(BigDecimal.TEN) + .rescheduleReasonId(1L).rescheduleFromDate("17 February 2023")); + GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.get().getLoanId()); + assertEquals(loanDetails.getSummary().getInterestCharged().stripTrailingZeros(), BigDecimal.valueOf(1.53).stripTrailingZeros()); + loanRescheduleRequestHelper.approveLoanRescheduleRequest(secondRescheduleLoansResponse.getResourceId(), + new PostUpdateRescheduleLoansRequest().approvedOnDate("17 February 2024").locale("en").dateFormat(DATETIME_PATTERN)); + loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.get().getLoanId()); + assertEquals(loanDetails.getSummary().getInterestCharged().stripTrailingZeros(), BigDecimal.valueOf(4.22).stripTrailingZeros()); }); // Allow new interest rate change if the previous got rejected diff --git a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/LoanRescheduleRequestHelper.java b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/LoanRescheduleRequestHelper.java index e7a2d1740..fa3a2b75d 100644 --- a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/LoanRescheduleRequestHelper.java +++ b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/LoanRescheduleRequestHelper.java @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import io.restassured.specification.RequestSpecification; import io.restassured.specification.ResponseSpecification; import java.util.HashMap; +import java.util.List; import org.apache.fineract.client.models.GetLoanRescheduleRequestResponse; import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest; import org.apache.fineract.client.models.PostCreateRescheduleLoansResponse; @@ -118,4 +119,9 @@ public class LoanRescheduleRequestHelper { return Calls .ok(FineractClientHelper.getFineractClient().rescheduleLoans.updateLoanRescheduleRequest(scheduleId, request, "reject")); } + + public List retrieveLoanRescheduleRequestsByLoan(final String command, final Long loanId) { + return Calls.ok(FineractClientHelper.getFineractClient().rescheduleLoans.retrieveAllRescheduleRequest(command, loanId)); + } + }