FINERACT-2421: Allow multiple interest rate schedule adjustments same date
This commit is contained in:
committed by
Adam Saghy
parent
f40ce6d9f6
commit
532190a649
-22
@@ -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 static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateSupportedParameters;
|
||||||
|
|
||||||
import com.google.gson.JsonElement;
|
import com.google.gson.JsonElement;
|
||||||
import jakarta.persistence.criteria.Predicate;
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.util.ArrayList;
|
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.api.JsonCommand;
|
||||||
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
import org.apache.fineract.infrastructure.core.data.ApiParameterError;
|
||||||
import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
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.exception.PlatformApiDataValidationException;
|
||||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRescheduleRequestToTermVariationMapping;
|
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.domain.LoanTermVariationType;
|
||||||
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
|
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.LoanRescheduleRequest;
|
||||||
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequestRepository;
|
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
@Component("progressiveLoanRescheduleRequestDataValidatorImpl")
|
@Component("progressiveLoanRescheduleRequestDataValidatorImpl")
|
||||||
@@ -62,7 +58,6 @@ import org.springframework.stereotype.Component;
|
|||||||
public class ProgressiveLoanRescheduleRequestDataValidator implements LoanRescheduleRequestDataValidator {
|
public class ProgressiveLoanRescheduleRequestDataValidator implements LoanRescheduleRequestDataValidator {
|
||||||
|
|
||||||
private final FromJsonHelper fromJsonHelper;
|
private final FromJsonHelper fromJsonHelper;
|
||||||
private final LoanRescheduleRequestRepository loanRescheduleRequestRepository;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void validateForCreateAction(JsonCommand jsonCommand, Loan loan) {
|
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) {
|
private void validateInterestRate(DataValidatorBuilder dataValidatorBuilder, Loan loan, LocalDate rescheduleFromDate) {
|
||||||
validateLoanStatusIsActiveOrClosed(loan, dataValidatorBuilder);
|
validateLoanStatusIsActiveOrClosed(loan, dataValidatorBuilder);
|
||||||
if (rescheduleFromDate != null) {
|
|
||||||
validateInterestRateChangeRescheduleFromDate(loan, rescheduleFromDate);
|
|
||||||
}
|
|
||||||
LoanRepaymentScheduleInstallment installment;
|
LoanRepaymentScheduleInstallment installment;
|
||||||
installment = loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate);
|
installment = loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate);
|
||||||
validateReschedulingInstallment(dataValidatorBuilder, installment);
|
validateReschedulingInstallment(dataValidatorBuilder, installment);
|
||||||
@@ -223,20 +215,6 @@ public class ProgressiveLoanRescheduleRequestDataValidator implements LoanResche
|
|||||||
throw new UnsupportedOperationException("Nothing to override here");
|
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,
|
private BigDecimal validateInterestRateParam(final FromJsonHelper fromJsonHelper, final JsonElement jsonElement,
|
||||||
DataValidatorBuilder dataValidatorBuilder, Loan loan) {
|
DataValidatorBuilder dataValidatorBuilder, Loan loan) {
|
||||||
final BigDecimal interestRate = fromJsonHelper
|
final BigDecimal interestRate = fromJsonHelper
|
||||||
|
|||||||
+1
-6
@@ -28,7 +28,6 @@ import org.apache.fineract.infrastructure.core.data.DataValidatorBuilder;
|
|||||||
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
import org.apache.fineract.infrastructure.core.serialization.FromJsonHelper;
|
||||||
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
|
||||||
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.RescheduleLoansApiConstants;
|
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.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
@@ -50,15 +49,11 @@ class ProgressiveLoanRescheduleRequestDataValidatorTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private FromJsonHelper fromJsonHelper;
|
private FromJsonHelper fromJsonHelper;
|
||||||
|
|
||||||
@Mock
|
|
||||||
private LoanRescheduleRequestRepository loanRescheduleRequestRepository;
|
|
||||||
|
|
||||||
private ProgressiveLoanRescheduleRequestDataValidator progressiveLoanRescheduleRequestDataValidator;
|
private ProgressiveLoanRescheduleRequestDataValidator progressiveLoanRescheduleRequestDataValidator;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
progressiveLoanRescheduleRequestDataValidator = new ProgressiveLoanRescheduleRequestDataValidator(fromJsonHelper,
|
progressiveLoanRescheduleRequestDataValidator = new ProgressiveLoanRescheduleRequestDataValidator(fromJsonHelper);
|
||||||
loanRescheduleRequestRepository);
|
|
||||||
when(dataValidatorBuilder.reset()).thenReturn(dataValidatorBuilder);
|
when(dataValidatorBuilder.reset()).thenReturn(dataValidatorBuilder);
|
||||||
when(dataValidatorBuilder.parameter(anyString())).thenReturn(dataValidatorBuilder);
|
when(dataValidatorBuilder.parameter(anyString())).thenReturn(dataValidatorBuilder);
|
||||||
}
|
}
|
||||||
|
|||||||
+13
@@ -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.data.LoanRescheduleRequestDataValidator;
|
||||||
import org.apache.fineract.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
|
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.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.rescheduleloan.exception.LoanRescheduleRequestNotFoundException;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAccrualsProcessingService;
|
||||||
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
import org.apache.fineract.portfolio.loanaccount.service.LoanAssembler;
|
||||||
@@ -117,6 +118,7 @@ public class LoanRescheduleRequestWritePlatformServiceImpl implements LoanResche
|
|||||||
private final LoanScheduleComponent loanSchedule;
|
private final LoanScheduleComponent loanSchedule;
|
||||||
private final LoanTransactionRepository loanTransactionRepository;
|
private final LoanTransactionRepository loanTransactionRepository;
|
||||||
private final LoanLifecycleStateMachine loanLifecycleStateMachine;
|
private final LoanLifecycleStateMachine loanLifecycleStateMachine;
|
||||||
|
private final LoanTermVariationsRepository loanTermVariationsRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* create a new instance of the LoanRescheduleRequest object from the JsonCommand object and persist
|
* 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;
|
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;
|
BigDecimal annualNominalInterestRate = null;
|
||||||
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
|
List<LoanTermVariationsData> loanTermVariations = new ArrayList<>();
|
||||||
loanTermVariationsMapper.constructLoanTermVariations(scheduleGeneratorDTO.getFloatingRateDTO(), annualNominalInterestRate,
|
loanTermVariationsMapper.constructLoanTermVariations(scheduleGeneratorDTO.getFloatingRateDTO(), annualNominalInterestRate,
|
||||||
|
|||||||
+13
-14
@@ -290,13 +290,9 @@ public class LoanRescheduleRequestTest extends BaseLoanIntegrationTest {
|
|||||||
.loanId(loanResponse.get().getLoanId()).dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("15 February 2023")
|
.loanId(loanResponse.get().getLoanId()).dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("15 February 2023")
|
||||||
.newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("16 February 2023")));
|
.newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("16 February 2023")));
|
||||||
|
|
||||||
exception = assertThrows(CallFailedRuntimeException.class,
|
loanRescheduleRequestHelper.createLoanRescheduleRequest(new PostCreateRescheduleLoansRequest()
|
||||||
() -> loanRescheduleRequestHelper
|
.loanId(loanResponse.get().getLoanId()).dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("15 February 2023")
|
||||||
.createLoanRescheduleRequest(new PostCreateRescheduleLoansRequest().loanId(loanResponse.get().getLoanId())
|
.newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("16 February 2023"));
|
||||||
.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"));
|
|
||||||
});
|
});
|
||||||
// Do not allow approve an interest rate change if the reschedule from date is not in the future
|
// 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
|
// 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(),
|
loanRescheduleRequestHelper.approveLoanRescheduleRequest(rescheduleLoansResponse.getResourceId(),
|
||||||
new PostUpdateRescheduleLoansRequest().approvedOnDate("17 February 2024").locale("en").dateFormat(DATETIME_PATTERN));
|
new PostUpdateRescheduleLoansRequest().approvedOnDate("17 February 2024").locale("en").dateFormat(DATETIME_PATTERN));
|
||||||
|
|
||||||
CallFailedRuntimeException exception = assertThrows(CallFailedRuntimeException.class,
|
PostCreateRescheduleLoansResponse secondRescheduleLoansResponse = loanRescheduleRequestHelper
|
||||||
() -> loanRescheduleRequestHelper
|
.createLoanRescheduleRequest(new PostCreateRescheduleLoansRequest().loanId(loanResponse.get().getLoanId())
|
||||||
.createLoanRescheduleRequest(new PostCreateRescheduleLoansRequest().loanId(rescheduleLoansResponse.getLoanId())
|
.dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("17 February 2023").newInterestRate(BigDecimal.TEN)
|
||||||
.dateFormat(DATETIME_PATTERN).locale("en").submittedOnDate("17 February 2023")
|
.rescheduleReasonId(1L).rescheduleFromDate("17 February 2023"));
|
||||||
.newInterestRate(BigDecimal.ONE).rescheduleReasonId(1L).rescheduleFromDate("17 February 2023")));
|
GetLoansLoanIdResponse loanDetails = loanTransactionHelper.getLoanDetails(loanResponse.get().getLoanId());
|
||||||
assertEquals(403, exception.getResponse().code());
|
assertEquals(loanDetails.getSummary().getInterestCharged().stripTrailingZeros(), BigDecimal.valueOf(1.53).stripTrailingZeros());
|
||||||
assertTrue(exception.getMessage().contains("loan.reschedule.interest.rate.change.already.exists"));
|
|
||||||
|
|
||||||
|
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
|
// Allow new interest rate change if the previous got rejected
|
||||||
|
|||||||
+6
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
import io.restassured.specification.RequestSpecification;
|
import io.restassured.specification.RequestSpecification;
|
||||||
import io.restassured.specification.ResponseSpecification;
|
import io.restassured.specification.ResponseSpecification;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import org.apache.fineract.client.models.GetLoanRescheduleRequestResponse;
|
import org.apache.fineract.client.models.GetLoanRescheduleRequestResponse;
|
||||||
import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest;
|
import org.apache.fineract.client.models.PostCreateRescheduleLoansRequest;
|
||||||
import org.apache.fineract.client.models.PostCreateRescheduleLoansResponse;
|
import org.apache.fineract.client.models.PostCreateRescheduleLoansResponse;
|
||||||
@@ -118,4 +119,9 @@ public class LoanRescheduleRequestHelper {
|
|||||||
return Calls
|
return Calls
|
||||||
.ok(FineractClientHelper.getFineractClient().rescheduleLoans.updateLoanRescheduleRequest(scheduleId, request, "reject"));
|
.ok(FineractClientHelper.getFineractClient().rescheduleLoans.updateLoanRescheduleRequest(scheduleId, request, "reject"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<GetLoanRescheduleRequestResponse> retrieveLoanRescheduleRequestsByLoan(final String command, final Long loanId) {
|
||||||
|
return Calls.ok(FineractClientHelper.getFineractClient().rescheduleLoans.retrieveAllRescheduleRequest(command, loanId));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user