in fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java [879:1487]
public void validateForModify(final JsonCommand command, final Loan loan) {
String json = command.json();
validateRequestBody(json);
validateForSupportedParameters(json);
if (!loan.isSubmittedAndPendingApproval()) {
throw new LoanApplicationNotInSubmittedAndPendingApprovalStateCannotBeModified(loan.getId());
}
// If new loan product to be set
LoanProduct loanProduct;
final String productIdParamName = "productId";
final Long productId = command.longValueOfParameterNamed(productIdParamName);
if (productId == null || productId.equals(loan.getLoanProduct().getId())) {
loanProduct = loan.getLoanProduct();
} else {
loanProduct = this.loanProductRepository.findById(productId).orElseThrow(() -> new LoanProductNotFoundException(productId));
}
Validator.validateOrThrow("loan", baseDataValidator -> {
final JsonElement element = this.fromApiJsonHelper.parse(json);
boolean atLeastOneParameterPassedForUpdate = false;
Long clientId = loan.getClient() != null ? loan.getClient().getId() : null;
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.clientIdParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
clientId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.clientIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(clientId).notNull()
.integerGreaterThanZero();
}
Client client = null;
if (clientId != null) {
client = this.clientRepository.findOneWithNotFoundDetection(clientId);
}
Long groupId = loan.getGroup() != null ? loan.getGroup().getId() : null;
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.groupIdParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
groupId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.groupIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.groupIdParameterName).value(groupId).notNull()
.integerGreaterThanZero();
}
Group group = null;
if (groupId != null) {
group = this.groupRepository.findOneWithNotFoundDetection(groupId);
}
if (productId != null) {
atLeastOneParameterPassedForUpdate = true;
baseDataValidator.reset().parameter(LoanApiConstants.productIdParameterName).value(productId).notNull()
.integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.accountNoParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final String accountNo = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.accountNoParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.accountNoParameterName).value(accountNo).notBlank()
.notExceedingLengthOf(20);
}
boolean isEqualAmortization = loan.getLoanProductRelatedDetail().isEqualAmortization();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, element)) {
isEqualAmortization = this.fromApiJsonHelper.extractBooleanNamed(LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, element);
baseDataValidator.reset().parameter(LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM).value(isEqualAmortization)
.ignoreIfNull().validateForBooleanValue();
if (isEqualAmortization && loanProduct.isInterestRecalculationEnabled()) {
throw new EqualAmortizationUnsupportedFeatureException("interest.recalculation", "interest recalculation");
}
}
BigDecimal fixedPrincipalPercentagePerInstallment = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element);
baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName)
.value(fixedPrincipalPercentagePerInstallment).notLessThanMin(BigDecimal.ONE)
.notGreaterThanMax(BigDecimal.valueOf(100));
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.externalIdParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final String externalId = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.externalIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.externalIdParameterName).value(externalId).ignoreIfNull()
.notExceedingLengthOf(100);
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.fundIdParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Long fundId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.fundIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.fundIdParameterName).value(fundId).ignoreIfNull()
.integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.loanOfficerIdParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Long loanOfficerId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.loanOfficerIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanOfficerIdParameterName).value(loanOfficerId).ignoreIfNull()
.integerGreaterThanZero();
}
String transactionProcessingStrategy = loan.getTransactionProcessingStrategyCode();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.transactionProcessingStrategyCodeParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
transactionProcessingStrategy = this.fromApiJsonHelper
.extractStringNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.transactionProcessingStrategyCodeParameterName)
.value(transactionProcessingStrategy).notNull();
// Validating whether the processor is existing
validateTransactionProcessingStrategy(transactionProcessingStrategy, loanProduct);
}
if (!AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
.equals(loanProduct.getTransactionProcessingStrategyCode())
&& AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
.equals(transactionProcessingStrategy)) {
baseDataValidator.reset().parameter(LoanApiConstants.transactionProcessingStrategyCodeParameterName).failWithCode(
"strategy.cannot.be.advanced.payment.allocation.if.not.configured",
"Loan transaction processing strategy cannot be Advanced Payment Allocation Strategy if it's not configured on loan product");
}
BigDecimal principal = null;
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.principalParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.principalParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.principalParameterName).value(principal).notNull().positiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.inArrearsToleranceParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final BigDecimal inArrearsTolerance = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.inArrearsToleranceParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.inArrearsToleranceParameterName).value(inArrearsTolerance)
.ignoreIfNull().zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.loanTermFrequencyParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Integer loanTermFrequency = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.loanTermFrequencyParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanTermFrequencyParameterName).value(loanTermFrequency).notNull()
.integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.loanTermFrequencyTypeParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Integer loanTermFrequencyType = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.loanTermFrequencyTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanTermFrequencyTypeParameterName).value(loanTermFrequencyType)
.notNull().inMinMaxRange(0, 3);
}
Integer numberOfRepayments = loan.getNumberOfRepayments();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.numberOfRepaymentsParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
numberOfRepayments = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanApiConstants.numberOfRepaymentsParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.numberOfRepaymentsParameterName).value(numberOfRepayments).notNull()
.integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.repaymentEveryParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Integer repaymentEvery = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.repaymentEveryParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.repaymentEveryParameterName).value(repaymentEvery).notNull()
.integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.repaymentFrequencyTypeParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final Integer repaymentEveryType = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.repaymentFrequencyTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.repaymentFrequencyTypeParameterName).value(repaymentEveryType)
.notNull().inMinMaxRange(0, 3);
}
CalendarUtils.validateNthDayOfMonthFrequency(baseDataValidator, LoanApiConstants.repaymentFrequencyNthDayTypeParameterName,
LoanApiConstants.repaymentFrequencyDayOfWeekTypeParameterName, element, this.fromApiJsonHelper);
Integer interestType = null;
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestTypeParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
interestType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanApiConstants.interestTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName).value(interestType).notNull()
.inMinMaxRange(0, 1);
}
if (loanProduct.isLinkedToFloatingInterestRate()) {
if (isEqualAmortization) {
throw new EqualAmortizationUnsupportedFeatureException("floating.interest.rate", "floating interest rate");
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestRatePerPeriodParameterName, element)) {
baseDataValidator.reset().parameter(LoanApiConstants.interestRatePerPeriodParameterName).failWithCode(
"not.supported.loanproduct.linked.to.floating.rate",
"interestRatePerPeriod param is not supported, selected Loan Product is linked with floating interest rate.");
}
Boolean isFloatingInterestRate = loan.getIsFloatingInterestRate();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)) {
isFloatingInterestRate = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isFloatingInterestRate, element);
atLeastOneParameterPassedForUpdate = true;
}
if (isFloatingInterestRate != null) {
if (isFloatingInterestRate && !loanProduct.getFloatingRates().isFloatingInterestRateCalculationAllowed()) {
baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).failWithCode(
"true.not.supported.for.selected.loanproduct",
"isFloatingInterestRate value of true not supported for selected Loan Product.");
}
} else {
baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).trueOrFalseRequired(false);
}
if (interestType == null) {
interestType = loan.getLoanProductRelatedDetail().getInterestMethod().getValue();
}
if (InterestMethod.FLAT.getValue().equals(interestType)) {
baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName).failWithCode(
"should.be.0.for.selected.loan.product",
"interestType should be DECLINING_BALANCE for selected Loan Product as it is linked to floating rates.");
}
BigDecimal interestRateDifferential = loan.getInterestRateDifferential();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestRateDifferentialParameterName, element)) {
interestRateDifferential = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRateDifferentialParameterName, element);
atLeastOneParameterPassedForUpdate = true;
}
baseDataValidator.reset().parameter(LoanApiConstants.interestRateDifferentialParameterName).value(interestRateDifferential)
.notNull().zeroOrPositiveAmount()
.inMinAndMaxAmountRange(loanProduct.getFloatingRates().getMinDifferentialLendingRate(),
loanProduct.getFloatingRates().getMaxDifferentialLendingRate());
} else {
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)) {
baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).failWithCode(
"not.supported.loanproduct.not.linked.to.floating.rate",
"isFloatingInterestRate param is not supported, selected Loan Product is not linked with floating interest rate.");
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestRateDifferential, element)) {
baseDataValidator.reset().parameter(LoanApiConstants.interestRateDifferential).failWithCode(
"not.supported.loanproduct.not.linked.to.floating.rate",
"interestRateDifferential param is not supported, selected Loan Product is not linked with floating interest rate.");
}
BigDecimal interestRatePerPeriod = loan.getLoanProductRelatedDetail().getNominalInterestRatePerPeriod();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestRatePerPeriodParameterName, element)) {
this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRatePerPeriodParameterName, element);
atLeastOneParameterPassedForUpdate = true;
}
baseDataValidator.reset().parameter(LoanApiConstants.interestRatePerPeriodParameterName).value(interestRatePerPeriod)
.notNull().zeroOrPositiveAmount();
}
Integer interestCalculationPeriodType = loanProduct.getLoanProductRelatedDetail().getInterestCalculationPeriodMethod()
.getValue();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestCalculationPeriodTypeParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
interestCalculationPeriodType = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.interestCalculationPeriodTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.interestCalculationPeriodTypeParameterName)
.value(interestCalculationPeriodType).notNull().inMinMaxRange(0, 1);
}
Integer amortizationType = null;
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.amortizationTypeParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
amortizationType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanApiConstants.amortizationTypeParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.amortizationTypeParameterName).value(amortizationType).notNull()
.inMinMaxRange(0, 1);
}
if (!AmortizationMethod.EQUAL_PRINCIPAL.getValue().equals(amortizationType) && fixedPrincipalPercentagePerInstallment != null) {
baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName).failWithCode(
"not.supported.principal.fixing.not.allowed.with.equal.installments",
"Principal fixing cannot be done with equal installment amortization");
}
LocalDate expectedDisbursementDate = loan.getExpectedDisbursementDate();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.expectedDisbursementDateParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final String expectedDisbursementDateStr = this.fromApiJsonHelper
.extractStringNamed(LoanApiConstants.expectedDisbursementDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.expectedDisbursementDateParameterName)
.value(expectedDisbursementDateStr).notBlank();
expectedDisbursementDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.expectedDisbursementDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.expectedDisbursementDateParameterName).value(expectedDisbursementDate)
.notNull();
}
Integer graceOnPrincipalPayment = loan.getLoanProductRelatedDetail().getGraceOnPrincipalPayment();
// grace validation
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.graceOnPrincipalPaymentParameterName, element)) {
graceOnPrincipalPayment = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.graceOnPrincipalPaymentParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.graceOnPrincipalPaymentParameterName).value(graceOnPrincipalPayment)
.zeroOrPositiveAmount();
}
Integer graceOnInterestPayment = loan.getLoanProductRelatedDetail().getGraceOnInterestPayment();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.graceOnInterestPaymentParameterName, element)) {
graceOnInterestPayment = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.graceOnInterestPaymentParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.graceOnInterestPaymentParameterName).value(graceOnInterestPayment)
.zeroOrPositiveAmount();
}
Integer graceOnInterestCharged = loan.getLoanProductRelatedDetail().getGraceOnInterestCharged();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.graceOnInterestChargedParameterName, element)) {
graceOnInterestCharged = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.graceOnInterestChargedParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.graceOnInterestChargedParameterName).value(graceOnInterestCharged)
.zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME, element)) {
final Integer graceOnArrearsAgeing = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME, element);
baseDataValidator.reset().parameter(LoanProductConstants.GRACE_ON_ARREARS_AGEING_PARAMETER_NAME).value(graceOnArrearsAgeing)
.zeroOrPositiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestChargedFromDateParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final LocalDate interestChargedFromDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.interestChargedFromDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.interestChargedFromDateParameterName).value(interestChargedFromDate)
.ignoreIfNull();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.repaymentsStartingFromDateParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.repaymentsStartingFromDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.repaymentsStartingFromDateParameterName)
.value(repaymentsStartingFromDate).ignoreIfNull();
if (!loan.getLoanTermVariations().isEmpty()) {
baseDataValidator.reset()
.failWithCodeNoParameterAddedToErrorCode("cannot.modify.application.due.to.variable.installments");
}
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.submittedOnDateParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final LocalDate submittedOnDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.submittedOnDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.submittedOnDateParameterName).value(submittedOnDate).notNull();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.submittedOnNoteParameterName, element)) {
atLeastOneParameterPassedForUpdate = true;
final String submittedOnNote = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.submittedOnNoteParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.submittedOnNoteParameterName).value(submittedOnNote).ignoreIfNull()
.notExceedingLengthOf(500);
}
validateLinkedSavingsAccount(element, baseDataValidator);
// charges
loanChargeApiJsonValidator.validateLoanCharges(element, loanProduct, baseDataValidator);
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.loanTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanTypeParameterName).value(loanTypeStr).notNull();
if (!StringUtils.isBlank(loanTypeStr)) {
final AccountType loanType = AccountType.fromName(loanTypeStr);
if (loanType.isInvalid()) {
baseDataValidator.reset().parameter(LoanApiConstants.loanTypeParameterName).value(loanType.getValue())
.isOneOfEnumValues(AccountType.class);
}
if (!loanType.isInvalid() && loanType.isIndividualAccount()) {
// collateral
final String collateralParameterName = LoanApiConstants.collateralParameterName;
if (element.isJsonObject() && this.fromApiJsonHelper.parameterExists(collateralParameterName, element)) {
final JsonObject topLevelJsonElement = element.getAsJsonObject();
final Locale locale = this.fromApiJsonHelper.extractLocaleParameter(topLevelJsonElement);
if (topLevelJsonElement.get(LoanApiConstants.collateralParameterName).isJsonArray()) {
final Type collateralParameterTypeOfMap = new TypeToken<Map<String, Object>>() {
}.getType();
final Set<String> supportedParameters = new HashSet<>(Arrays.asList(LoanApiConstants.idParameterName,
LoanApiConstants.clientCollateralIdParameterName, LoanApiConstants.quantityParameterName));
final JsonArray array = topLevelJsonElement.get(LoanApiConstants.collateralParameterName).getAsJsonArray();
if (!array.isEmpty()) {
BigDecimal totalAmount = BigDecimal.ZERO;
for (int i = 1; i <= array.size(); i++) {
final JsonObject collateralItemElement = array.get(i - 1).getAsJsonObject();
final String collateralJson = this.fromApiJsonHelper.toJson(collateralItemElement);
this.fromApiJsonHelper.checkForUnsupportedParameters(collateralParameterTypeOfMap, collateralJson,
supportedParameters);
final Long id = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.idParameterName,
collateralItemElement);
baseDataValidator.reset().parameter(LoanApiConstants.collateralParameterName)
.parameterAtIndexArray(LoanApiConstants.idParameterName, i).value(id).ignoreIfNull();
final Long clientCollateralId = this.fromApiJsonHelper
.extractLongNamed(LoanApiConstants.clientCollateralIdParameterName, collateralItemElement);
baseDataValidator.reset().parameter(LoanApiConstants.collateralParameterName)
.parameterAtIndexArray(LoanApiConstants.clientCollateralIdParameterName, i)
.value(clientCollateralId).notNull().integerGreaterThanZero();
final BigDecimal quantity = this.fromApiJsonHelper
.extractBigDecimalNamed(LoanApiConstants.quantityParameterName, collateralItemElement, locale);
baseDataValidator.reset().parameter(LoanApiConstants.collateralParameterName)
.parameterAtIndexArray(LoanApiConstants.quantityParameterName, i).value(quantity).notNull()
.positiveAmount();
if (clientCollateralId != null || quantity != null) {
BigDecimal baseAmount = this.clientCollateralManagementRepositoryWrapper
.getCollateral(clientCollateralId).getCollaterals().getBasePrice();
BigDecimal pctToBase = this.clientCollateralManagementRepositoryWrapper
.getCollateral(clientCollateralId).getCollaterals().getPctToBase();
BigDecimal total = baseAmount.multiply(pctToBase).multiply(quantity);
totalAmount = totalAmount.add(total);
}
}
if (principal != null && principal.compareTo(totalAmount) > 0) {
throw new InvalidAmountOfCollaterals(totalAmount);
}
}
} else {
baseDataValidator.reset().parameter(collateralParameterName).expectedArrayButIsNot();
}
}
}
}
boolean meetingIdRequired = false;
// validate syncDisbursement
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.syncDisbursementWithMeetingParameterName, element)) {
final Boolean syncDisbursement = this.fromApiJsonHelper
.extractBooleanNamed(LoanApiConstants.syncDisbursementWithMeetingParameterName, element);
if (syncDisbursement == null) {
baseDataValidator.reset().parameter(LoanApiConstants.syncDisbursementWithMeetingParameterName).value(syncDisbursement)
.trueOrFalseRequired(false);
} else if (syncDisbursement.booleanValue()) {
meetingIdRequired = true;
}
}
// if disbursement is synced then must have a meeting (calendar)
if (meetingIdRequired || this.fromApiJsonHelper.parameterExists(LoanApiConstants.calendarIdParameterName, element)) {
final Long calendarId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.calendarIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.calendarIdParameterName).value(calendarId).notNull()
.integerGreaterThanZero();
}
if (!atLeastOneParameterPassedForUpdate) {
final Object forceError = null;
baseDataValidator.reset().anyOfNotNull(forceError);
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.fixedEmiAmountParameterName, element)) {
if (!(loanProduct.isCanDefineInstallmentAmount() || loanProduct.isMultiDisburseLoan())) {
List<String> unsupportedParameterList = new ArrayList<>();
unsupportedParameterList.add(LoanApiConstants.fixedEmiAmountParameterName);
throw new UnsupportedParameterException(unsupportedParameterList);
}
if (isEqualAmortization) {
throw new EqualAmortizationUnsupportedFeatureException("fixed.emi", "fixed emi");
}
final BigDecimal emiAnount = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedEmiAmountParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.fixedEmiAmountParameterName).value(emiAnount).ignoreIfNull()
.positiveAmount();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.maxOutstandingBalanceParameterName, element)) {
final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.maxOutstandingBalanceParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.maxOutstandingBalanceParameterName).value(maxOutstandingBalance)
.ignoreIfNull().positiveAmount();
}
if (loanProduct.isCanUseForTopup() && this.fromApiJsonHelper.parameterExists(LoanApiConstants.isTopup, element)) {
final Boolean isTopup = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isTopup, element);
baseDataValidator.reset().parameter(LoanApiConstants.isTopup).value(isTopup).ignoreIfNull().validateForBooleanValue();
if (isTopup != null && isTopup) {
final Long loanId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.loanIdToClose, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanIdToClose).value(loanId).notNull().longGreaterThanZero();
LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.submittedOnDateParameterName,
element);
if (submittedOnDate == null) {
submittedOnDate = loan.getSubmittedOnDate();
}
final Long loanIdToClose = command.longValueOfParameterNamed(LoanApiConstants.loanIdToClose);
final Loan loanToClose = this.loanRepositoryWrapper.findNonClosedLoanThatBelongsToClient(loanIdToClose, clientId);
if (loanToClose == null) {
throw new GeneralPlatformDomainRuleException(
"error.msg.loan.loanIdToClose.no.active.loan.associated.to.client.found",
"loanIdToClose is invalid, No Active Loan associated with the given Client ID found.");
}
if (loanToClose.isMultiDisburmentLoan()
&& !loanToClose.getLoanProductRelatedDetail().isInterestRecalculationEnabled()) {
throw new GeneralPlatformDomainRuleException(
"error.msg.loan.topup.on.multi.tranche.loan.without.interest.recalculation.not.supported",
"Topup on loan with multi-tranche disbursal and without interest recalculation is not supported.");
}
final LocalDate disbursalDateOfLoanToClose = loanToClose.getDisbursementDate();
if (!DateUtils.isAfter(submittedOnDate, disbursalDateOfLoanToClose)) {
throw new GeneralPlatformDomainRuleException(
"error.msg.loan.submitted.date.should.be.after.topup.loan.disbursal.date",
"Submitted date of this loan application " + submittedOnDate
+ " should be after the disbursed date of loan to be closed " + disbursalDateOfLoanToClose);
}
if (!loanToClose.getCurrencyCode().equals(loanProduct.getCurrency().getCode())) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.to.be.closed.has.different.currency",
"loanIdToClose is invalid, Currency code is different.");
}
final LocalDate lastUserTransactionOnLoanToClose = loanToClose.getLastUserTransactionDate();
if (DateUtils.isBefore(expectedDisbursementDate, lastUserTransactionOnLoanToClose)) {
throw new GeneralPlatformDomainRuleException(
"error.msg.loan.disbursal.date.should.be.after.last.transaction.date.of.loan.to.be.closed",
"Disbursal date of this loan application " + expectedDisbursementDate
+ " should be after last transaction date of loan to be closed "
+ lastUserTransactionOnLoanToClose);
}
BigDecimal loanOutstanding = this.loanReadPlatformService
.retrieveLoanPrePaymentTemplate(LoanTransactionType.REPAYMENT, loanIdToClose, expectedDisbursementDate)
.getAmount();
final BigDecimal firstDisbursalAmount = getFirstDisbursalAmount(loan);
if (loanOutstanding.compareTo(firstDisbursalAmount) > 0) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.amount.less.than.outstanding.of.loan.to.be.closed",
"Topup loan amount should be greater than outstanding amount of loan to be closed.");
}
}
}
validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, principal);
validatePartialPeriodSupport(interestCalculationPeriodType, baseDataValidator, element, loanProduct);
String loanScheduleProcessingType = loan.getLoanRepaymentScheduleDetail().getLoanScheduleProcessingType().name();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE, element)) {
loanScheduleProcessingType = this.fromApiJsonHelper.extractStringNamed(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE,
element);
baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE).value(loanScheduleProcessingType)
.ignoreIfNull().isOneOfEnumValues(LoanScheduleProcessingType.class);
}
if (LoanScheduleProcessingType.VERTICAL.equals(LoanScheduleProcessingType.valueOf(loanScheduleProcessingType))
&& !AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
.equals(transactionProcessingStrategy)) {
baseDataValidator.reset().parameter(LoanProductConstants.LOAN_SCHEDULE_PROCESSING_TYPE).failWithCode(
"supported.only.with.advanced.payment.allocation.strategy",
"Vertical repayment schedule processing is only available with `Advanced payment allocation` strategy");
}
List<LoanProductPaymentAllocationRule> allocationRules = loanProduct.getPaymentAllocationRules();
if (LoanScheduleProcessingType.HORIZONTAL.name().equals(loanScheduleProcessingType)
&& AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
.equals(transactionProcessingStrategy)) {
advancedPaymentAllocationsValidator.checkGroupingOfAllocationRules(allocationRules);
}
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, element)) {
final Boolean isEnableInstallmentLevelDelinquency = this.fromApiJsonHelper
.extractBooleanNamed(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY, element);
baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY)
.value(isEnableInstallmentLevelDelinquency).validateForBooleanValue();
if (loanProduct.getDelinquencyBucket() == null) {
if (isEnableInstallmentLevelDelinquency) {
baseDataValidator.reset().parameter(LoanProductConstants.ENABLE_INSTALLMENT_LEVEL_DELINQUENCY).failWithCode(
"can.be.enabled.for.loan.with.loan.product.having.valid.delinquency.bucket",
"Installment level delinquency cannot be enabled for a loan if Delinquency bucket is not configured for loan product");
}
}
}
loanScheduleValidator.validateDownPaymentAttribute(loanProduct.getLoanProductRelatedDetail().isEnableDownPayment(), element);
validateDisbursementDetails(loanProduct, element);
validateSubmittedOnDate(element, loan.getSubmittedOnDate(), loan.getExpectedDisbursementDate(), loanProduct);
validateClientOrGroup(client, group, productId);
// validate if disbursement date is a holiday or a non-working day
validateDisbursementDateIsOnNonWorkingDay(expectedDisbursementDate);
final Long officeId = resolveOfficeId(client, group);
validateDisbursementDateIsOnHoliday(expectedDisbursementDate, officeId);
Integer recurringMoratoriumOnPrincipalPeriods = loan.getLoanProductRelatedDetail().getRecurringMoratoriumOnPrincipalPeriods();
if (this.fromApiJsonHelper.parameterExists("recurringMoratoriumOnPrincipalPeriods", element)) {
recurringMoratoriumOnPrincipalPeriods = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", element);
}
validateBorrowerCycle(element, loanProduct, clientId, groupId, baseDataValidator);
loanProductDataValidator.validateRepaymentPeriodWithGraceSettings(numberOfRepayments, graceOnPrincipalPayment,
graceOnInterestPayment, graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator);
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE, element)) {
if (!LoanScheduleType.PROGRESSIVE.equals(loanProduct.getLoanProductRelatedDetail().getLoanScheduleType())) {
List<String> unsupportedParameterList = new ArrayList<>();
unsupportedParameterList.add(LoanApiConstants.INTEREST_RECOGNITION_ON_DISBURSEMENT_DATE);
throw new UnsupportedParameterException(unsupportedParameterList);
}
}
});
}