in fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/serialization/LoanApplicationValidator.java [252:798]
private void validateForCreate(final JsonElement element) {
boolean isMeetingMandatoryForJLGLoans = configurationDomainService.isMeetingMandatoryForJLGLoans();
final Long productId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.productIdParameterName, element);
if (productId == null) {
throwMandatoryParameterError(LoanApiConstants.productIdParameterName);
}
final LoanProduct loanProduct = this.loanProductRepository.findById(productId)
.orElseThrow(() -> new LoanProductNotFoundException(productId));
final Long clientId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.clientIdParameterName, element);
final Long groupId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.groupIdParameterName, element);
final Client client = clientId != null ? this.clientRepository.findOneWithNotFoundDetection(clientId) : null;
final Group group = groupId != null ? this.groupRepository.findOneWithNotFoundDetection(groupId) : null;
validateClientOrGroup(client, group, productId);
Validator.validateOrThrow("loan", 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);
baseDataValidator.reset().parameter(LoanApiConstants.loanTypeParameterName).value(loanType.getValue()).inMinMaxRange(1, 4);
if (loanType.isIndividualAccount()) {
baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(clientId).notNull()
.longGreaterThanZero();
baseDataValidator.reset().parameter(LoanApiConstants.groupIdParameterName).value(groupId)
.mustBeBlankWhenParameterProvided(LoanApiConstants.clientIdParameterName, clientId);
}
if (loanType.isGroupAccount()) {
baseDataValidator.reset().parameter(LoanApiConstants.groupIdParameterName).value(groupId).notNull()
.longGreaterThanZero();
baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(clientId)
.mustBeBlankWhenParameterProvided(LoanApiConstants.groupIdParameterName, groupId);
}
if (loanType.isJLGAccount()) {
baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(clientId).notNull()
.integerGreaterThanZero();
baseDataValidator.reset().parameter(LoanApiConstants.groupIdParameterName).value(groupId).notNull()
.longGreaterThanZero();
// if it is JLG loan that must have meeting details
if (isMeetingMandatoryForJLGLoans) {
final Long calendarId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.calendarIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.calendarIdParameterName).value(calendarId).notNull()
.integerGreaterThanZero();
// if it is JLG loan then must have a value for
// syncDisbursement passed in
final Boolean syncDisbursement = this.fromApiJsonHelper
.extractBooleanNamed(LoanApiConstants.syncDisbursementWithMeetingParameterName, element);
if (syncDisbursement == null) {
baseDataValidator.reset().parameter(LoanApiConstants.syncDisbursementWithMeetingParameterName)
.value(syncDisbursement).trueOrFalseRequired(false);
}
}
}
}
boolean isEqualAmortization = false;
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isEqualAmortizationParam, element)) {
isEqualAmortization = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isEqualAmortizationParam, element);
baseDataValidator.reset().parameter(LoanApiConstants.isEqualAmortizationParam).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));
baseDataValidator.reset().parameter(LoanApiConstants.productIdParameterName).value(productId).notNull()
.integerGreaterThanZero();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.accountNoParameterName, element)) {
final String accountNo = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.accountNoParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.accountNoParameterName).value(accountNo).ignoreIfNull()
.notExceedingLengthOf(20);
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.externalIdParameterName, element)) {
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)) {
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)) {
final Long loanOfficerId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.loanOfficerIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanOfficerIdParameterName).value(loanOfficerId).ignoreIfNull()
.integerGreaterThanZero();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.loanPurposeIdParameterName, element)) {
final Long loanPurposeId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.loanPurposeIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanPurposeIdParameterName).value(loanPurposeId).ignoreIfNull()
.integerGreaterThanZero();
}
final Integer loanTermFrequency = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.loanTermFrequencyParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanTermFrequencyParameterName).value(loanTermFrequency).notNull()
.integerGreaterThanZero();
final Integer loanTermFrequencyType = this.fromApiJsonHelper
.extractIntegerSansLocaleNamed(LoanApiConstants.loanTermFrequencyTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanTermFrequencyTypeParameterName).value(loanTermFrequencyType).notNull()
.inMinMaxRange(0, 3);
final Integer numberOfRepayments = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.numberOfRepaymentsParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.numberOfRepaymentsParameterName).value(numberOfRepayments).notNull()
.integerGreaterThanZero();
final Integer repaymentEvery = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.repaymentEveryParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.repaymentEveryParameterName).value(repaymentEvery).notNull()
.integerGreaterThanZero();
final Integer repaymentEveryType = this.fromApiJsonHelper
.extractIntegerSansLocaleNamed(LoanApiConstants.repaymentFrequencyTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.repaymentFrequencyTypeParameterName).value(repaymentEveryType).notNull()
.inMinMaxRange(0, 3);
CalendarUtils.validateNthDayOfMonthFrequency(baseDataValidator, LoanApiConstants.repaymentFrequencyNthDayTypeParameterName,
LoanApiConstants.repaymentFrequencyDayOfWeekTypeParameterName, element, this.fromApiJsonHelper);
final Integer interestType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(LoanApiConstants.interestTypeParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName).value(interestType).notNull().inMinMaxRange(0,
1);
final Integer interestCalculationPeriodType = this.fromApiJsonHelper
.extractIntegerSansLocaleNamed(LoanApiConstants.interestCalculationPeriodTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.interestCalculationPeriodTypeParameterName)
.value(interestCalculationPeriodType).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.");
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)) {
final Boolean isFloatingInterestRate = this.fromApiJsonHelper
.extractBooleanNamed(LoanApiConstants.isFloatingInterestRate, element);
if (isFloatingInterestRate != null && 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 (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.");
}
final String interestRateDifferentialParameterName = LoanApiConstants.interestRateDifferential;
final BigDecimal interestRateDifferential = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(interestRateDifferentialParameterName, element);
baseDataValidator.reset().parameter(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.");
}
final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRatePerPeriodParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.interestRatePerPeriodParameterName).value(interestRatePerPeriod)
.notNull().zeroOrPositiveAmount();
}
final Integer amortizationType = this.fromApiJsonHelper
.extractIntegerSansLocaleNamed(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");
}
final LocalDate expectedDisbursementDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.expectedDisbursementDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.expectedDisbursementDateParameterName).value(expectedDisbursementDate)
.notNull();
// grace validation
final Integer graceOnPrincipalPayment = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.graceOnPrincipalPaymentParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.graceOnPrincipalPaymentParameterName).value(graceOnPrincipalPayment)
.zeroOrPositiveAmount();
final Integer graceOnInterestPayment = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.graceOnInterestPaymentParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.graceOnInterestPaymentParameterName).value(graceOnInterestPayment)
.zeroOrPositiveAmount();
final Integer graceOnInterestCharged = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.graceOnInterestChargedParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.graceOnInterestChargedParameterName).value(graceOnInterestCharged)
.zeroOrPositiveAmount();
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)) {
final LocalDate interestChargedFromDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.interestChargedFromDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.interestChargedFromDateParameterName).value(interestChargedFromDate)
.ignoreIfNull().notNull();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.repaymentsStartingFromDateParameterName, element)) {
final LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.repaymentsStartingFromDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.repaymentsStartingFromDateParameterName)
.value(repaymentsStartingFromDate).ignoreIfNull().notNull();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.inArrearsToleranceParameterName, element)) {
final BigDecimal inArrearsTolerance = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.inArrearsToleranceParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.inArrearsToleranceParameterName).value(inArrearsTolerance)
.ignoreIfNull().zeroOrPositiveAmount();
}
final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.submittedOnDateParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.submittedOnDateParameterName).value(submittedOnDate).notNull();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.submittedOnNoteParameterName, element)) {
final String submittedOnNote = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.submittedOnNoteParameterName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.submittedOnNoteParameterName).value(submittedOnNote).ignoreIfNull()
.notExceedingLengthOf(500);
}
final String transactionProcessingStrategy = this.fromApiJsonHelper
.extractStringNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.transactionProcessingStrategyCodeParameterName)
.value(transactionProcessingStrategy).notNull();
validateLinkedSavingsAccount(element, baseDataValidator);
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.createStandingInstructionAtDisbursementParameterName, element)) {
final Boolean createStandingInstructionAtDisbursement = this.fromApiJsonHelper
.extractBooleanNamed(LoanApiConstants.createStandingInstructionAtDisbursementParameterName, element);
final Long linkAccountId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.linkAccountIdParameterName, element);
if (createStandingInstructionAtDisbursement) {
baseDataValidator.reset().parameter(LoanApiConstants.linkAccountIdParameterName).value(linkAccountId).notNull()
.longGreaterThanZero();
}
}
// charges
loanChargeApiJsonValidator.validateLoanCharges(element, loanProduct, baseDataValidator);
/*
* TODO: Add collaterals for other loan accounts if needed. For now it's only applicable for individual
* accounts. (loanType.isJLG() || loanType.isGLIM())
*/
if (!StringUtils.isBlank(loanTypeStr)) {
final AccountType loanType = AccountType.fromName(loanTypeStr);
// collateral
if (loanType.isIndividualAccount() && element.isJsonObject()
&& this.fromApiJsonHelper.parameterExists(LoanApiConstants.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.clientCollateralIdParameterName, LoanApiConstants.quantityParameterName));
final JsonArray array = topLevelJsonElement.get(LoanApiConstants.collateralParameterName).getAsJsonArray();
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 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();
final ClientCollateralManagement clientCollateralManagement = this.clientCollateralManagementRepositoryWrapper
.getCollateral(clientCollateralId);
if (clientCollateralId != null
&& BigDecimal.valueOf(0).compareTo(clientCollateralManagement.getQuantity()) >= 0) {
throw new InvalidAmountOfCollateralQuantity(clientCollateralManagement.getQuantity());
}
}
} else {
baseDataValidator.reset().parameter(LoanApiConstants.collateralParameterName).expectedArrayButIsNot();
}
}
}
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 emiAmount = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedEmiAmountParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.fixedEmiAmountParameterName).value(emiAmount).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();
}
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.principalParamName,
element);
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).validateForBooleanValue();
if (isTopup != null && isTopup) {
final Long loanId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.loanIdToClose, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanIdToClose).value(loanId).notNull().longGreaterThanZero();
if (clientId != null) {
final Long loanIdToClose = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.loanIdToClose, element);
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();
if (loanOutstanding.compareTo(principal) > 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.");
}
}
}
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.datatables, element)) {
final JsonArray datatables = this.fromApiJsonHelper.extractJsonArrayNamed(LoanApiConstants.datatables, element);
baseDataValidator.reset().parameter(LoanApiConstants.datatables).value(datatables).notNull().jsonArrayNotEmpty();
}
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.daysInYearTypeParameterName, element)) {
final Integer daysInYearType = this.fromApiJsonHelper.extractIntegerNamed(LoanApiConstants.daysInYearTypeParameterName,
element, Locale.getDefault());
baseDataValidator.reset().parameter(LoanApiConstants.daysInYearTypeParameterName).value(daysInYearType).notNull()
.isOneOfTheseValues(1, 360, 364, 365);
}
validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, principal);
String loanScheduleProcessingType = loanProduct.getLoanProductRelatedDetail().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)
.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);
}
validatePartialPeriodSupport(interestCalculationPeriodType, baseDataValidator, element, loanProduct);
// validate enable installment level delinquency
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");
}
}
}
validateBorrowerCycle(element, loanProduct, clientId, groupId, baseDataValidator);
// Validate If the externalId is already registered
final String externalIdStr = this.fromApiJsonHelper.extractStringNamed("externalId", element);
ExternalId externalId = ExternalIdFactory.produce(externalIdStr);
if (!externalId.isEmpty()) {
final boolean existByExternalId = this.loanRepositoryWrapper.existLoanByExternalId(externalId);
if (existByExternalId) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.with.externalId.already.used",
"Loan with externalId is already registered.");
}
}
loanScheduleValidator.validateDownPaymentAttribute(loanProduct.getLoanProductRelatedDetail().isEnableDownPayment(), element);
checkForProductMixRestrictions(element);
validateDisbursementDetails(loanProduct, element);
validateCollateral(element);
// validate if disbursement date is a holiday or a non-working day
validateDisbursementDateIsOnNonWorkingDay(expectedDisbursementDate);
Long officeId = resolveOfficeId(client, group);
validateDisbursementDateIsOnHoliday(expectedDisbursementDate, officeId);
final Integer recurringMoratoriumOnPrincipalPeriods = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", element);
if (numberOfRepayments != null) {
loanProductDataValidator.validateRepaymentPeriodWithGraceSettings(numberOfRepayments, graceOnPrincipalPayment,
graceOnInterestPayment, graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator);
}
if (fromApiJsonHelper.parameterExists(LoanApiConstants.daysInYearCustomStrategyParameterName, element)) {
DaysInYearCustomStrategyType daysInYearCustomStrategy = fromApiJsonHelper
.enumValueOfParameterNamed("daysInYearCustomStrategyParameterName", element, DaysInYearCustomStrategyType.class);
if (daysInYearCustomStrategy != null) {
if (!LoanScheduleType.PROGRESSIVE.equals(loanProduct.getLoanProductRelatedDetail().getLoanScheduleType())) {
baseDataValidator.reset().parameter(LoanApiConstants.daysInYearCustomStrategyParameterName).failWithCode(
"days.in.year.custom.strategy.is.only.supported.for.progressive.loan.schedule.type",
"daysInYearCustomStrategy is only supported for progressive loan schedule type");
}
if (!DaysInYearType.ACTUAL.getValue().equals(loanProduct.getLoanProductRelatedDetail().getDaysInYearType())) {
baseDataValidator.reset().parameter(LoanApiConstants.daysInYearCustomStrategyParameterName).failWithCode(
"days.in.year.custom.strategy.is.only.applicable.for.actual.days.in.year.type",
"daysInYearCustomStrategy is only applicable for ACTUAL days in year type");
}
}
}
});
validateSubmittedOnDate(element, null, null, loanProduct);
final String transactionProcessingStrategy = this.fromApiJsonHelper
.extractStringNamed(LoanApiConstants.transactionProcessingStrategyCodeParameterName, element);
validateTransactionProcessingStrategy(transactionProcessingStrategy, loanProduct);
fixedLengthValidations(element);
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);
}
}
}