FINERACT-2114: Reschedule validations enhancement

This commit is contained in:
Adam Saghy
2024-08-23 14:25:48 +02:00
parent 1286b1e72f
commit 3456e1eb81
13 changed files with 781 additions and 452 deletions
@@ -0,0 +1,188 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.fineract.portfolio.loanaccount.rescheduleloan.data;
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.APPROVE_REQUEST_DATA_PARAMETERS;
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.CREATE_REQUEST_DATA_PARAMETERS;
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateAndRetrieveAdjustedDate;
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateAndRetrieveRescheduleFromDate;
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateApprovalDate;
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateForOverdueCharges;
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateInterestRate;
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateLoanIsActive;
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateRescheduleReasonComment;
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateRescheduleReasonId;
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateRescheduleRequestStatus;
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateReschedulingInstallment;
import static org.apache.fineract.portfolio.loanaccount.rescheduleloan.data.LoanRescheduleRequestDataValidatorImpl.validateSubmittedOnDate;
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;
import java.util.List;
import lombok.AllArgsConstructor;
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.infrastructure.core.service.DateUtils;
import org.apache.fineract.portfolio.loanaccount.domain.Loan;
import org.apache.fineract.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.apache.fineract.portfolio.loanaccount.domain.LoanStatus;
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")
@AllArgsConstructor
public class ProgressiveLoanRescheduleRequestDataValidator implements LoanRescheduleRequestDataValidator {
private final FromJsonHelper fromJsonHelper;
private final LoanRescheduleRequestRepository loanRescheduleRequestRepository;
@Override
public void validateForCreateAction(JsonCommand jsonCommand, Loan loan) {
validateSupportedParameters(jsonCommand, CREATE_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder dataValidatorBuilder = new DataValidatorBuilder(dataValidationErrors)
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
final JsonElement jsonElement = jsonCommand.parsedJson();
validateLoanIsActive(loan, dataValidatorBuilder);
validateSubmittedOnDate(fromJsonHelper, loan, jsonElement, dataValidatorBuilder);
final LocalDate rescheduleFromDate = validateAndRetrieveRescheduleFromDate(fromJsonHelper, jsonElement, dataValidatorBuilder);
validateRescheduleReasonId(fromJsonHelper, jsonElement, dataValidatorBuilder);
validateRescheduleReasonComment(fromJsonHelper, jsonElement, dataValidatorBuilder);
LocalDate adjustedDueDate = validateAndRetrieveAdjustedDate(fromJsonHelper, jsonElement, rescheduleFromDate, dataValidatorBuilder);
BigDecimal interestRate = validateInterestRate(fromJsonHelper, jsonElement, dataValidatorBuilder);
validateUnsupportedParams(jsonElement, dataValidatorBuilder);
boolean hasInterestRateChange = interestRate != null;
boolean hasAdjustDueDateChange = adjustedDueDate != null;
if (hasInterestRateChange && hasAdjustDueDateChange) {
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.adjustedDueDateParamName).failWithCode(
RescheduleLoansApiConstants.rescheduleMultipleOperationsNotSupportedErrorCode,
"Only one operation is supported at a time during Loan Rescheduling");
}
final LocalDate businessDate = DateUtils.getBusinessLocalDate();
if (rescheduleFromDate != null) {
if (hasInterestRateChange && !rescheduleFromDate.isAfter(businessDate)) {
dataValidatorBuilder.reset().parameter(RescheduleLoansApiConstants.rescheduleFromDateParamName).failWithCode(
"loan.reschedule.interest.rate.change.reschedule.from.date.should.be.in.future",
"Loan Reschedule From date should be in the future.");
}
if (hasInterestRateChange) {
validateInterestRateChangeRescheduleFromDate(loan, rescheduleFromDate);
}
}
LoanRepaymentScheduleInstallment installment;
if (hasInterestRateChange) {
installment = loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate);
} else {
installment = loan.getRepaymentScheduleInstallment(rescheduleFromDate);
}
validateReschedulingInstallment(dataValidatorBuilder, installment);
validateForOverdueCharges(dataValidatorBuilder, loan, installment);
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
private void validateUnsupportedParams(JsonElement jsonElement, DataValidatorBuilder dataValidatorBuilder) {
final var unsupportedFields = List.of(RescheduleLoansApiConstants.graceOnPrincipalParamName, //
RescheduleLoansApiConstants.graceOnInterestParamName, //
RescheduleLoansApiConstants.extraTermsParamName, //
RescheduleLoansApiConstants.emiParamName//
);
for (var unsupportedField : unsupportedFields) {
if (this.fromJsonHelper.parameterHasValue(unsupportedField, jsonElement)) {
dataValidatorBuilder.reset().parameter(unsupportedField).failWithCode(
RescheduleLoansApiConstants.rescheduleSelectedOperationNotSupportedErrorCode,
"Selected operation is not supported by Progressive Loan at a time during Loan Rescheduling");
}
}
}
@Override
public void validateForApproveAction(JsonCommand jsonCommand, LoanRescheduleRequest loanRescheduleRequest) {
validateSupportedParameters(jsonCommand, APPROVE_REQUEST_DATA_PARAMETERS);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder dataValidatorBuilder = new DataValidatorBuilder(dataValidationErrors)
.resource(StringUtils.lowerCase(RescheduleLoansApiConstants.ENTITY_NAME));
final JsonElement jsonElement = jsonCommand.parsedJson();
validateApprovalDate(fromJsonHelper, loanRescheduleRequest, jsonElement, dataValidatorBuilder);
validateRescheduleRequestStatus(loanRescheduleRequest, dataValidatorBuilder);
LocalDate rescheduleFromDate = loanRescheduleRequest.getRescheduleFromDate();
final Loan loan = loanRescheduleRequest.getLoan();
LoanRepaymentScheduleInstallment installment;
validateLoanIsActive(loan, dataValidatorBuilder);
if (loanRescheduleRequest.getInterestRateFromInstallmentTermVariationIfExists() != null) {
installment = loan.getRelatedRepaymentScheduleInstallment(rescheduleFromDate);
if (!rescheduleFromDate.isAfter(DateUtils.getBusinessLocalDate())) {
throw new GeneralPlatformDomainRuleException(
"loan.reschedule.interest.rate.change.reschedule.from.date.should.be.in.future",
"Loan Reschedule From date should be in the future.", rescheduleFromDate);
}
} else {
installment = loan.getRepaymentScheduleInstallment(rescheduleFromDate);
}
validateReschedulingInstallment(dataValidatorBuilder, installment);
validateForOverdueCharges(dataValidatorBuilder, loan, installment);
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
}
@Override
public void validateForRejectAction(JsonCommand jsonCommand, LoanRescheduleRequest loanRescheduleRequest) {
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);
}
}
}