FINERACT-2090: refactor and cleanup Loan entity

This commit is contained in:
Kristof Jozsa
2024-06-12 16:29:50 +02:00
committed by Adam Saghy
parent 369b809700
commit c9f0799a06
5 changed files with 1514 additions and 2196 deletions
File diff suppressed because it is too large Load Diff
@@ -140,103 +140,39 @@ public class LoanSummary {
this.totalFeeChargesDueAtDisbursement = totalFeeChargesDueAtDisbursement; this.totalFeeChargesDueAtDisbursement = totalFeeChargesDueAtDisbursement;
} }
public void updateTotalFeeChargesDueAtDisbursement(final BigDecimal totalFeeChargesDueAtDisbursement) {
this.totalFeeChargesDueAtDisbursement = totalFeeChargesDueAtDisbursement;
}
public Money getTotalFeeChargesDueAtDisbursement(final MonetaryCurrency currency) {
return Money.of(currency, this.totalFeeChargesDueAtDisbursement);
}
public Money getTotalOutstanding(final MonetaryCurrency currency) {
return Money.of(currency, this.totalOutstanding);
}
public void updateFeeChargeOutstanding(final BigDecimal totalFeeChargesOutstanding) {
this.totalFeeChargesOutstanding = totalFeeChargesOutstanding;
}
public void updatePenaltyChargeOutstanding(final BigDecimal totalPenaltyChargesOutstanding) {
this.totalPenaltyChargesOutstanding = totalPenaltyChargesOutstanding;
}
public void updateFeeChargesWaived(final BigDecimal totalFeeChargesWaived) {
this.totalFeeChargesWaived = totalFeeChargesWaived;
}
public void updatePenaltyChargesWaived(final BigDecimal totalPenaltyChargesWaived) {
this.totalPenaltyChargesWaived = totalPenaltyChargesWaived;
}
public boolean isRepaidInFull(final MonetaryCurrency currency) {
return getTotalOutstanding(currency).isZero();
}
public BigDecimal getTotalInterestCharged() {
return this.totalInterestCharged;
}
public BigDecimal getTotalPrincipalOutstanding() {
return this.totalPrincipalOutstanding;
}
public BigDecimal getTotalInterestOutstanding() {
return this.totalInterestOutstanding;
}
public BigDecimal getTotalFeeChargesOutstanding() {
return this.totalFeeChargesOutstanding;
}
public BigDecimal getTotalPenaltyChargesOutstanding() {
return this.totalPenaltyChargesOutstanding;
}
public BigDecimal getTotalOutstanding() {
return this.totalOutstanding;
}
public void updateTotalOutstanding(final BigDecimal newTotalOutstanding) {
this.totalOutstanding = newTotalOutstanding;
}
public void updateTotalWaived(final BigDecimal totalWaived) {
this.totalWaived = totalWaived;
}
/** /**
* All fields but <code>totalFeeChargesDueAtDisbursement</code> should be reset. * All fields but <code>totalFeeChargesDueAtDisbursement</code> should be reset.
*/ */
public void zeroFields() { public void zeroFields() {
this.totalPrincipalDisbursed = BigDecimal.ZERO; this.totalCostOfLoan = BigDecimal.ZERO;
this.totalPrincipalAdjustments = BigDecimal.ZERO; this.totalExpectedCostOfLoan = BigDecimal.ZERO;
this.totalExpectedRepayment = BigDecimal.ZERO;
this.totalFeeAdjustments = BigDecimal.ZERO; this.totalFeeAdjustments = BigDecimal.ZERO;
this.totalPenaltyAdjustments = BigDecimal.ZERO;
this.totalPrincipalRepaid = BigDecimal.ZERO;
this.totalPrincipalWrittenOff = BigDecimal.ZERO;
this.totalPrincipalOutstanding = BigDecimal.ZERO;
this.totalInterestCharged = BigDecimal.ZERO;
this.totalInterestRepaid = BigDecimal.ZERO;
this.totalInterestWaived = BigDecimal.ZERO;
this.totalInterestWrittenOff = BigDecimal.ZERO;
this.totalInterestOutstanding = BigDecimal.ZERO;
this.totalFeeChargesCharged = BigDecimal.ZERO; this.totalFeeChargesCharged = BigDecimal.ZERO;
this.totalFeeChargesOutstanding = BigDecimal.ZERO;
this.totalFeeChargesRepaid = BigDecimal.ZERO; this.totalFeeChargesRepaid = BigDecimal.ZERO;
this.totalFeeChargesWaived = BigDecimal.ZERO; this.totalFeeChargesWaived = BigDecimal.ZERO;
this.totalFeeChargesWrittenOff = BigDecimal.ZERO; this.totalFeeChargesWrittenOff = BigDecimal.ZERO;
this.totalFeeChargesOutstanding = BigDecimal.ZERO; this.totalInterestCharged = BigDecimal.ZERO;
this.totalInterestOutstanding = BigDecimal.ZERO;
this.totalInterestRepaid = BigDecimal.ZERO;
this.totalInterestWaived = BigDecimal.ZERO;
this.totalInterestWrittenOff = BigDecimal.ZERO;
this.totalOutstanding = BigDecimal.ZERO;
this.totalPenaltyAdjustments = BigDecimal.ZERO;
this.totalPenaltyChargesCharged = BigDecimal.ZERO; this.totalPenaltyChargesCharged = BigDecimal.ZERO;
this.totalPenaltyChargesOutstanding = BigDecimal.ZERO;
this.totalPenaltyChargesRepaid = BigDecimal.ZERO; this.totalPenaltyChargesRepaid = BigDecimal.ZERO;
this.totalPenaltyChargesWaived = BigDecimal.ZERO; this.totalPenaltyChargesWaived = BigDecimal.ZERO;
this.totalPenaltyChargesWrittenOff = BigDecimal.ZERO; this.totalPenaltyChargesWrittenOff = BigDecimal.ZERO;
this.totalPenaltyChargesOutstanding = BigDecimal.ZERO; this.totalPrincipalAdjustments = BigDecimal.ZERO;
this.totalExpectedRepayment = BigDecimal.ZERO; this.totalPrincipalDisbursed = BigDecimal.ZERO;
this.totalPrincipalOutstanding = BigDecimal.ZERO;
this.totalPrincipalRepaid = BigDecimal.ZERO;
this.totalPrincipalWrittenOff = BigDecimal.ZERO;
this.totalRepayment = BigDecimal.ZERO; this.totalRepayment = BigDecimal.ZERO;
this.totalExpectedCostOfLoan = BigDecimal.ZERO;
this.totalCostOfLoan = BigDecimal.ZERO;
this.totalWaived = BigDecimal.ZERO; this.totalWaived = BigDecimal.ZERO;
this.totalWrittenOff = BigDecimal.ZERO; this.totalWrittenOff = BigDecimal.ZERO;
this.totalOutstanding = BigDecimal.ZERO;
} }
public void updateSummary(final MonetaryCurrency currency, final Money principal, public void updateSummary(final MonetaryCurrency currency, final Money principal,
@@ -327,58 +263,43 @@ public class LoanSummary {
this.totalOutstanding = totalOutstanding.getAmount(); this.totalOutstanding = totalOutstanding.getAmount();
} }
public BigDecimal getTotalPrincipalDisbursed() { public void updateTotalFeeChargesDueAtDisbursement(final BigDecimal totalFeeChargesDueAtDisbursement) {
return this.totalPrincipalDisbursed; this.totalFeeChargesDueAtDisbursement = totalFeeChargesDueAtDisbursement;
} }
public BigDecimal getTotalPrincipalRepaid() { public Money getTotalFeeChargesDueAtDisbursement(final MonetaryCurrency currency) {
return this.totalPrincipalRepaid; return Money.of(currency, this.totalFeeChargesDueAtDisbursement);
} }
public BigDecimal getTotalWrittenOff() { public Money getTotalOutstanding(final MonetaryCurrency currency) {
return this.totalWrittenOff; return Money.of(currency, this.totalOutstanding);
} }
/** public void updateFeeChargeOutstanding(final BigDecimal totalFeeChargesOutstanding) {
* @return total interest repaid this.totalFeeChargesOutstanding = totalFeeChargesOutstanding;
**/
public BigDecimal getTotalInterestRepaid() {
return this.totalInterestRepaid;
} }
public BigDecimal getTotalFeeChargesCharged() { public void updatePenaltyChargeOutstanding(final BigDecimal totalPenaltyChargesOutstanding) {
return this.totalFeeChargesCharged; this.totalPenaltyChargesOutstanding = totalPenaltyChargesOutstanding;
} }
public BigDecimal getTotalPenaltyChargesCharged() { public void updateFeeChargesWaived(final BigDecimal totalFeeChargesWaived) {
return this.totalPenaltyChargesCharged; this.totalFeeChargesWaived = totalFeeChargesWaived;
} }
public BigDecimal getTotalPrincipalWrittenOff() { public void updatePenaltyChargesWaived(final BigDecimal totalPenaltyChargesWaived) {
return this.totalPrincipalWrittenOff; this.totalPenaltyChargesWaived = totalPenaltyChargesWaived;
} }
public BigDecimal getTotalInterestWaived() { public boolean isRepaidInFull(final MonetaryCurrency currency) {
return this.totalInterestWaived; return getTotalOutstanding(currency).isZero();
} }
public BigDecimal getTotalFeeChargesRepaid() { public void updateTotalOutstanding(final BigDecimal newTotalOutstanding) {
return this.totalFeeChargesRepaid; this.totalOutstanding = newTotalOutstanding;
} }
public BigDecimal getTotalFeeChargesWaived() { public void updateTotalWaived(final BigDecimal totalWaived) {
return this.totalFeeChargesWaived; this.totalWaived = totalWaived;
}
public BigDecimal getTotalPenaltyChargesRepaid() {
return this.totalPenaltyChargesRepaid;
}
public BigDecimal getTotalPenaltyChargesWaived() {
return this.totalPenaltyChargesWaived;
}
public BigDecimal getTotalExpectedRepayment() {
return this.totalExpectedRepayment;
} }
} }
@@ -33,6 +33,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -235,19 +236,11 @@ public final class LoanApplicationValidator {
final Long clientId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.clientIdParameterName, element); final Long clientId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.clientIdParameterName, element);
final Long groupId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.groupIdParameterName, element); final Long groupId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.groupIdParameterName, element);
Client client = null; final Client client = clientId != null ? this.clientRepository.findOneWithNotFoundDetection(clientId) : null;
if (clientId != null) { final Group group = groupId != null ? this.groupRepository.findOneWithNotFoundDetection(groupId) : null;
client = this.clientRepository.findOneWithNotFoundDetection(clientId);
}
Group group = null;
if (groupId != null) {
group = this.groupRepository.findOneWithNotFoundDetection(groupId);
}
validateClientOrGroup(client, group, productId); validateClientOrGroup(client, group, productId);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>(); validateOrThrow("loan", baseDataValidator -> {
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.loanTypeParameterName, element); final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.loanTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanTypeParameterName).value(loanTypeStr).notNull(); baseDataValidator.reset().parameter(LoanApiConstants.loanTypeParameterName).value(loanTypeStr).notNull();
@@ -256,13 +249,15 @@ public final class LoanApplicationValidator {
baseDataValidator.reset().parameter(LoanApiConstants.loanTypeParameterName).value(loanType.getValue()).inMinMaxRange(1, 4); baseDataValidator.reset().parameter(LoanApiConstants.loanTypeParameterName).value(loanType.getValue()).inMinMaxRange(1, 4);
if (loanType.isIndividualAccount()) { if (loanType.isIndividualAccount()) {
baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(clientId).notNull().longGreaterThanZero(); baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(clientId).notNull()
.longGreaterThanZero();
baseDataValidator.reset().parameter(LoanApiConstants.groupIdParameterName).value(groupId) baseDataValidator.reset().parameter(LoanApiConstants.groupIdParameterName).value(groupId)
.mustBeBlankWhenParameterProvided(LoanApiConstants.clientIdParameterName, clientId); .mustBeBlankWhenParameterProvided(LoanApiConstants.clientIdParameterName, clientId);
} }
if (loanType.isGroupAccount()) { if (loanType.isGroupAccount()) {
baseDataValidator.reset().parameter(LoanApiConstants.groupIdParameterName).value(groupId).notNull().longGreaterThanZero(); baseDataValidator.reset().parameter(LoanApiConstants.groupIdParameterName).value(groupId).notNull()
.longGreaterThanZero();
baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(clientId) baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(clientId)
.mustBeBlankWhenParameterProvided(LoanApiConstants.groupIdParameterName, groupId); .mustBeBlankWhenParameterProvided(LoanApiConstants.groupIdParameterName, groupId);
} }
@@ -270,7 +265,8 @@ public final class LoanApplicationValidator {
if (loanType.isJLGAccount()) { if (loanType.isJLGAccount()) {
baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(clientId).notNull() baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(clientId).notNull()
.integerGreaterThanZero(); .integerGreaterThanZero();
baseDataValidator.reset().parameter(LoanApiConstants.groupIdParameterName).value(groupId).notNull().longGreaterThanZero(); baseDataValidator.reset().parameter(LoanApiConstants.groupIdParameterName).value(groupId).notNull()
.longGreaterThanZero();
// if it is JLG loan that must have meeting details // if it is JLG loan that must have meeting details
if (isMeetingMandatoryForJLGLoans) { if (isMeetingMandatoryForJLGLoans) {
@@ -291,7 +287,6 @@ public final class LoanApplicationValidator {
} }
} }
} }
boolean isEqualAmortization = false; boolean isEqualAmortization = false;
@@ -307,9 +302,11 @@ public final class LoanApplicationValidator {
BigDecimal fixedPrincipalPercentagePerInstallment = this.fromApiJsonHelper BigDecimal fixedPrincipalPercentagePerInstallment = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element); .extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element);
baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName) baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName)
.value(fixedPrincipalPercentagePerInstallment).notLessThanMin(BigDecimal.ONE).notGreaterThanMax(BigDecimal.valueOf(100)); .value(fixedPrincipalPercentagePerInstallment).notLessThanMin(BigDecimal.ONE)
.notGreaterThanMax(BigDecimal.valueOf(100));
baseDataValidator.reset().parameter(LoanApiConstants.productIdParameterName).value(productId).notNull().integerGreaterThanZero(); baseDataValidator.reset().parameter(LoanApiConstants.productIdParameterName).value(productId).notNull()
.integerGreaterThanZero();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.accountNoParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.accountNoParameterName, element)) {
final String accountNo = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.accountNoParameterName, element); final String accountNo = this.fromApiJsonHelper.extractStringNamed(LoanApiConstants.accountNoParameterName, element);
@@ -325,7 +322,8 @@ public final class LoanApplicationValidator {
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.fundIdParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.fundIdParameterName, element)) {
final Long fundId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.fundIdParameterName, element); final Long fundId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.fundIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.fundIdParameterName).value(fundId).ignoreIfNull().integerGreaterThanZero(); baseDataValidator.reset().parameter(LoanApiConstants.fundIdParameterName).value(fundId).ignoreIfNull()
.integerGreaterThanZero();
} }
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.loanOfficerIdParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.loanOfficerIdParameterName, element)) {
@@ -340,7 +338,8 @@ public final class LoanApplicationValidator {
.integerGreaterThanZero(); .integerGreaterThanZero();
} }
final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.principalParamName, element); final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.principalParamName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.principalParamName).value(principal).notNull().positiveAmount(); baseDataValidator.reset().parameter(LoanApiConstants.principalParamName).value(principal).notNull().positiveAmount();
final Integer loanTermFrequency = this.fromApiJsonHelper final Integer loanTermFrequency = this.fromApiJsonHelper
@@ -358,8 +357,8 @@ public final class LoanApplicationValidator {
baseDataValidator.reset().parameter(LoanApiConstants.numberOfRepaymentsParameterName).value(numberOfRepayments).notNull() baseDataValidator.reset().parameter(LoanApiConstants.numberOfRepaymentsParameterName).value(numberOfRepayments).notNull()
.integerGreaterThanZero(); .integerGreaterThanZero();
final Integer repaymentEvery = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanApiConstants.repaymentEveryParameterName, final Integer repaymentEvery = this.fromApiJsonHelper
element); .extractIntegerWithLocaleNamed(LoanApiConstants.repaymentEveryParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.repaymentEveryParameterName).value(repaymentEvery).notNull() baseDataValidator.reset().parameter(LoanApiConstants.repaymentEveryParameterName).value(repaymentEvery).notNull()
.integerGreaterThanZero(); .integerGreaterThanZero();
@@ -373,7 +372,8 @@ public final class LoanApplicationValidator {
final Integer interestType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(LoanApiConstants.interestTypeParameterName, final Integer interestType = this.fromApiJsonHelper.extractIntegerSansLocaleNamed(LoanApiConstants.interestTypeParameterName,
element); element);
baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName).value(interestType).notNull().inMinMaxRange(0, 1); baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName).value(interestType).notNull().inMinMaxRange(0,
1);
final Integer interestCalculationPeriodType = this.fromApiJsonHelper final Integer interestCalculationPeriodType = this.fromApiJsonHelper
.extractIntegerSansLocaleNamed(LoanApiConstants.interestCalculationPeriodTypeParameterName, element); .extractIntegerSansLocaleNamed(LoanApiConstants.interestCalculationPeriodTypeParameterName, element);
@@ -392,8 +392,8 @@ public final class LoanApplicationValidator {
} }
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.isFloatingInterestRate, element)) {
final Boolean isFloatingInterestRate = this.fromApiJsonHelper.extractBooleanNamed(LoanApiConstants.isFloatingInterestRate, final Boolean isFloatingInterestRate = this.fromApiJsonHelper
element); .extractBooleanNamed(LoanApiConstants.isFloatingInterestRate, element);
if (isFloatingInterestRate != null && isFloatingInterestRate if (isFloatingInterestRate != null && isFloatingInterestRate
&& !loanProduct.getFloatingRates().isFloatingInterestRateCalculationAllowed()) { && !loanProduct.getFloatingRates().isFloatingInterestRateCalculationAllowed()) {
baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).failWithCode( baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).failWithCode(
@@ -432,8 +432,8 @@ public final class LoanApplicationValidator {
final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRatePerPeriodParameterName, element); .extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRatePerPeriodParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.interestRatePerPeriodParameterName).value(interestRatePerPeriod).notNull() baseDataValidator.reset().parameter(LoanApiConstants.interestRatePerPeriodParameterName).value(interestRatePerPeriod)
.zeroOrPositiveAmount(); .notNull().zeroOrPositiveAmount();
isInterestBearing = interestRatePerPeriod.compareTo(BigDecimal.ZERO) > 0; isInterestBearing = interestRatePerPeriod.compareTo(BigDecimal.ZERO) > 0;
} }
@@ -484,15 +484,15 @@ public final class LoanApplicationValidator {
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.repaymentsStartingFromDateParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.repaymentsStartingFromDateParameterName, element)) {
final LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper final LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.repaymentsStartingFromDateParameterName, element); .extractLocalDateNamed(LoanApiConstants.repaymentsStartingFromDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.repaymentsStartingFromDateParameterName).value(repaymentsStartingFromDate) baseDataValidator.reset().parameter(LoanApiConstants.repaymentsStartingFromDateParameterName)
.ignoreIfNull().notNull(); .value(repaymentsStartingFromDate).ignoreIfNull().notNull();
} }
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.inArrearsToleranceParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.inArrearsToleranceParameterName, element)) {
final BigDecimal inArrearsTolerance = this.fromApiJsonHelper final BigDecimal inArrearsTolerance = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.inArrearsToleranceParameterName, element); .extractBigDecimalWithLocaleNamed(LoanApiConstants.inArrearsToleranceParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.inArrearsToleranceParameterName).value(inArrearsTolerance).ignoreIfNull() baseDataValidator.reset().parameter(LoanApiConstants.inArrearsToleranceParameterName).value(inArrearsTolerance)
.zeroOrPositiveAmount(); .ignoreIfNull().zeroOrPositiveAmount();
} }
final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.submittedOnDateParameterName, final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.submittedOnDateParameterName,
@@ -562,8 +562,8 @@ public final class LoanApplicationValidator {
.parameterAtIndexArray(LoanApiConstants.clientCollateralIdParameterName, i).value(clientCollateralId) .parameterAtIndexArray(LoanApiConstants.clientCollateralIdParameterName, i).value(clientCollateralId)
.notNull().integerGreaterThanZero(); .notNull().integerGreaterThanZero();
final BigDecimal quantity = this.fromApiJsonHelper.extractBigDecimalNamed(LoanApiConstants.quantityParameterName, final BigDecimal quantity = this.fromApiJsonHelper
collateralItemElement, locale); .extractBigDecimalNamed(LoanApiConstants.quantityParameterName, collateralItemElement, locale);
baseDataValidator.reset().parameter(LoanApiConstants.collateralParameterName) baseDataValidator.reset().parameter(LoanApiConstants.collateralParameterName)
.parameterAtIndexArray(LoanApiConstants.quantityParameterName, i).value(quantity).notNull() .parameterAtIndexArray(LoanApiConstants.quantityParameterName, i).value(quantity).notNull()
.positiveAmount(); .positiveAmount();
@@ -571,7 +571,8 @@ public final class LoanApplicationValidator {
final ClientCollateralManagement clientCollateralManagement = this.clientCollateralManagementRepositoryWrapper final ClientCollateralManagement clientCollateralManagement = this.clientCollateralManagementRepositoryWrapper
.getCollateral(clientCollateralId); .getCollateral(clientCollateralId);
if (clientCollateralId != null && BigDecimal.valueOf(0).compareTo(clientCollateralManagement.getQuantity()) >= 0) { if (clientCollateralId != null
&& BigDecimal.valueOf(0).compareTo(clientCollateralManagement.getQuantity()) >= 0) {
throw new InvalidAmountOfCollateralQuantity(clientCollateralManagement.getQuantity()); throw new InvalidAmountOfCollateralQuantity(clientCollateralManagement.getQuantity());
} }
@@ -659,8 +660,8 @@ public final class LoanApplicationValidator {
} }
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.daysInYearTypeParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.daysInYearTypeParameterName, element)) {
final Integer daysInYearType = this.fromApiJsonHelper.extractIntegerNamed(LoanApiConstants.daysInYearTypeParameterName, element, final Integer daysInYearType = this.fromApiJsonHelper.extractIntegerNamed(LoanApiConstants.daysInYearTypeParameterName,
Locale.getDefault()); element, Locale.getDefault());
baseDataValidator.reset().parameter(LoanApiConstants.daysInYearTypeParameterName).value(daysInYearType).notNull() baseDataValidator.reset().parameter(LoanApiConstants.daysInYearTypeParameterName).value(daysInYearType).notNull()
.isOneOfTheseValues(1, 360, 364, 365); .isOneOfTheseValues(1, 360, 364, 365);
} }
@@ -685,7 +686,8 @@ public final class LoanApplicationValidator {
List<LoanProductPaymentAllocationRule> allocationRules = loanProduct.getPaymentAllocationRules(); List<LoanProductPaymentAllocationRule> allocationRules = loanProduct.getPaymentAllocationRules();
if (LoanScheduleProcessingType.HORIZONTAL.name().equals(loanScheduleProcessingType) if (LoanScheduleProcessingType.HORIZONTAL.name().equals(loanScheduleProcessingType)
&& AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(transactionProcessingStrategy)) { && AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
.equals(transactionProcessingStrategy)) {
advancedPaymentAllocationsValidator.checkGroupingOfAllocationRules(allocationRules); advancedPaymentAllocationsValidator.checkGroupingOfAllocationRules(allocationRules);
} }
@@ -734,10 +736,7 @@ public final class LoanApplicationValidator {
.extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", element); .extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", element);
loanProductDataValidator.validateRepaymentPeriodWithGraceSettings(numberOfRepayments, graceOnPrincipalPayment, loanProductDataValidator.validateRepaymentPeriodWithGraceSettings(numberOfRepayments, graceOnPrincipalPayment,
graceOnInterestPayment, graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator); graceOnInterestPayment, graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator);
});
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
} }
private void validateBorrowerCycle(JsonElement element, LoanProduct loanProduct, Long clientId, Long groupId, private void validateBorrowerCycle(JsonElement element, LoanProduct loanProduct, Long clientId, Long groupId,
@@ -821,8 +820,7 @@ public final class LoanApplicationValidator {
loanProduct = this.loanProductRepository.findById(productId).orElseThrow(() -> new LoanProductNotFoundException(productId)); loanProduct = this.loanProductRepository.findById(productId).orElseThrow(() -> new LoanProductNotFoundException(productId));
} }
final List<ApiParameterError> dataValidationErrors = new ArrayList<>(); validateOrThrow("loan", baseDataValidator -> {
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
final JsonElement element = this.fromApiJsonHelper.parse(json); final JsonElement element = this.fromApiJsonHelper.parse(json);
boolean atLeastOneParameterPassedForUpdate = false; boolean atLeastOneParameterPassedForUpdate = false;
@@ -830,7 +828,8 @@ public final class LoanApplicationValidator {
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.clientIdParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.clientIdParameterName, element)) {
atLeastOneParameterPassedForUpdate = true; atLeastOneParameterPassedForUpdate = true;
clientId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.clientIdParameterName, element); clientId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.clientIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(clientId).notNull().integerGreaterThanZero(); baseDataValidator.reset().parameter(LoanApiConstants.clientIdParameterName).value(clientId).notNull()
.integerGreaterThanZero();
} }
Client client = null; Client client = null;
if (clientId != null) { if (clientId != null) {
@@ -840,7 +839,8 @@ public final class LoanApplicationValidator {
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.groupIdParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.groupIdParameterName, element)) {
atLeastOneParameterPassedForUpdate = true; atLeastOneParameterPassedForUpdate = true;
groupId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.groupIdParameterName, element); groupId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.groupIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.groupIdParameterName).value(groupId).notNull().integerGreaterThanZero(); baseDataValidator.reset().parameter(LoanApiConstants.groupIdParameterName).value(groupId).notNull()
.integerGreaterThanZero();
} }
Group group = null; Group group = null;
if (groupId != null) { if (groupId != null) {
@@ -863,8 +863,8 @@ public final class LoanApplicationValidator {
boolean isEqualAmortization = loan.getLoanProductRelatedDetail().isEqualAmortization(); boolean isEqualAmortization = loan.getLoanProductRelatedDetail().isEqualAmortization();
if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, element)) { if (this.fromApiJsonHelper.parameterExists(LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM, element)) {
isEqualAmortization = this.fromApiJsonHelper.extractBooleanNamed(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() baseDataValidator.reset().parameter(LoanProductConstants.IS_EQUAL_AMORTIZATION_PARAM).value(isEqualAmortization)
.validateForBooleanValue(); .ignoreIfNull().validateForBooleanValue();
if (isEqualAmortization && loanProduct.isInterestRecalculationEnabled()) { if (isEqualAmortization && loanProduct.isInterestRecalculationEnabled()) {
throw new EqualAmortizationUnsupportedFeatureException("interest.recalculation", "interest recalculation"); throw new EqualAmortizationUnsupportedFeatureException("interest.recalculation", "interest recalculation");
} }
@@ -873,7 +873,8 @@ public final class LoanApplicationValidator {
BigDecimal fixedPrincipalPercentagePerInstallment = this.fromApiJsonHelper BigDecimal fixedPrincipalPercentagePerInstallment = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element); .extractBigDecimalWithLocaleNamed(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName, element);
baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName) baseDataValidator.reset().parameter(LoanApiConstants.fixedPrincipalPercentagePerInstallmentParamName)
.value(fixedPrincipalPercentagePerInstallment).notLessThanMin(BigDecimal.ONE).notGreaterThanMax(BigDecimal.valueOf(100)); .value(fixedPrincipalPercentagePerInstallment).notLessThanMin(BigDecimal.ONE)
.notGreaterThanMax(BigDecimal.valueOf(100));
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.externalIdParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.externalIdParameterName, element)) {
atLeastOneParameterPassedForUpdate = true; atLeastOneParameterPassedForUpdate = true;
@@ -885,7 +886,8 @@ public final class LoanApplicationValidator {
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.fundIdParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.fundIdParameterName, element)) {
atLeastOneParameterPassedForUpdate = true; atLeastOneParameterPassedForUpdate = true;
final Long fundId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.fundIdParameterName, element); final Long fundId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.fundIdParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.fundIdParameterName).value(fundId).ignoreIfNull().integerGreaterThanZero(); baseDataValidator.reset().parameter(LoanApiConstants.fundIdParameterName).value(fundId).ignoreIfNull()
.integerGreaterThanZero();
} }
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.loanOfficerIdParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.loanOfficerIdParameterName, element)) {
@@ -908,7 +910,8 @@ public final class LoanApplicationValidator {
if (!AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY if (!AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
.equals(loanProduct.getTransactionProcessingStrategyCode()) .equals(loanProduct.getTransactionProcessingStrategyCode())
&& AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(transactionProcessingStrategy)) { && AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
.equals(transactionProcessingStrategy)) {
baseDataValidator.reset().parameter(LoanApiConstants.transactionProcessingStrategyCodeParameterName).failWithCode( baseDataValidator.reset().parameter(LoanApiConstants.transactionProcessingStrategyCodeParameterName).failWithCode(
"strategy.cannot.be.advanced.payment.allocation.if.not.configured", "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"); "Loan transaction processing strategy cannot be Advanced Payment Allocation Strategy if it's not configured on loan product");
@@ -925,8 +928,8 @@ public final class LoanApplicationValidator {
atLeastOneParameterPassedForUpdate = true; atLeastOneParameterPassedForUpdate = true;
final BigDecimal inArrearsTolerance = this.fromApiJsonHelper final BigDecimal inArrearsTolerance = this.fromApiJsonHelper
.extractBigDecimalWithLocaleNamed(LoanApiConstants.inArrearsToleranceParameterName, element); .extractBigDecimalWithLocaleNamed(LoanApiConstants.inArrearsToleranceParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.inArrearsToleranceParameterName).value(inArrearsTolerance).ignoreIfNull() baseDataValidator.reset().parameter(LoanApiConstants.inArrearsToleranceParameterName).value(inArrearsTolerance)
.zeroOrPositiveAmount(); .ignoreIfNull().zeroOrPositiveAmount();
} }
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.loanTermFrequencyParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.loanTermFrequencyParameterName, element)) {
@@ -941,8 +944,8 @@ public final class LoanApplicationValidator {
atLeastOneParameterPassedForUpdate = true; atLeastOneParameterPassedForUpdate = true;
final Integer loanTermFrequencyType = this.fromApiJsonHelper final Integer loanTermFrequencyType = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.loanTermFrequencyTypeParameterName, element); .extractIntegerWithLocaleNamed(LoanApiConstants.loanTermFrequencyTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanTermFrequencyTypeParameterName).value(loanTermFrequencyType).notNull() baseDataValidator.reset().parameter(LoanApiConstants.loanTermFrequencyTypeParameterName).value(loanTermFrequencyType)
.inMinMaxRange(0, 3); .notNull().inMinMaxRange(0, 3);
} }
Integer numberOfRepayments = loan.getNumberOfRepayments(); Integer numberOfRepayments = loan.getNumberOfRepayments();
@@ -966,8 +969,8 @@ public final class LoanApplicationValidator {
atLeastOneParameterPassedForUpdate = true; atLeastOneParameterPassedForUpdate = true;
final Integer repaymentEveryType = this.fromApiJsonHelper final Integer repaymentEveryType = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.repaymentFrequencyTypeParameterName, element); .extractIntegerWithLocaleNamed(LoanApiConstants.repaymentFrequencyTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.repaymentFrequencyTypeParameterName).value(repaymentEveryType).notNull() baseDataValidator.reset().parameter(LoanApiConstants.repaymentFrequencyTypeParameterName).value(repaymentEveryType)
.inMinMaxRange(0, 3); .notNull().inMinMaxRange(0, 3);
} }
CalendarUtils.validateNthDayOfMonthFrequency(baseDataValidator, LoanApiConstants.repaymentFrequencyNthDayTypeParameterName, CalendarUtils.validateNthDayOfMonthFrequency(baseDataValidator, LoanApiConstants.repaymentFrequencyNthDayTypeParameterName,
@@ -977,8 +980,8 @@ public final class LoanApplicationValidator {
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestTypeParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestTypeParameterName, element)) {
atLeastOneParameterPassedForUpdate = true; atLeastOneParameterPassedForUpdate = true;
interestType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanApiConstants.interestTypeParameterName, element); interestType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed(LoanApiConstants.interestTypeParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName).value(interestType).notNull().inMinMaxRange(0, baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName).value(interestType).notNull()
1); .inMinMaxRange(0, 1);
} }
if (loanProduct.isLinkedToFloatingInterestRate()) { if (loanProduct.isLinkedToFloatingInterestRate()) {
@@ -1022,7 +1025,8 @@ public final class LoanApplicationValidator {
atLeastOneParameterPassedForUpdate = true; atLeastOneParameterPassedForUpdate = true;
} }
baseDataValidator.reset().parameter(LoanApiConstants.interestRateDifferentialParameterName).value(interestRateDifferential) baseDataValidator.reset().parameter(LoanApiConstants.interestRateDifferentialParameterName).value(interestRateDifferential)
.notNull().zeroOrPositiveAmount().inMinAndMaxAmountRange(loanProduct.getFloatingRates().getMinDifferentialLendingRate(), .notNull().zeroOrPositiveAmount()
.inMinAndMaxAmountRange(loanProduct.getFloatingRates().getMinDifferentialLendingRate(),
loanProduct.getFloatingRates().getMaxDifferentialLendingRate()); loanProduct.getFloatingRates().getMaxDifferentialLendingRate());
} else { } else {
@@ -1043,12 +1047,13 @@ public final class LoanApplicationValidator {
this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRatePerPeriodParameterName, element); this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRatePerPeriodParameterName, element);
atLeastOneParameterPassedForUpdate = true; atLeastOneParameterPassedForUpdate = true;
} }
baseDataValidator.reset().parameter(LoanApiConstants.interestRatePerPeriodParameterName).value(interestRatePerPeriod).notNull() baseDataValidator.reset().parameter(LoanApiConstants.interestRatePerPeriodParameterName).value(interestRatePerPeriod)
.zeroOrPositiveAmount(); .notNull().zeroOrPositiveAmount();
} }
Integer interestCalculationPeriodType = loanProduct.getLoanProductRelatedDetail().getInterestCalculationPeriodMethod().getValue(); Integer interestCalculationPeriodType = loanProduct.getLoanProductRelatedDetail().getInterestCalculationPeriodMethod()
.getValue();
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestCalculationPeriodTypeParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.interestCalculationPeriodTypeParameterName, element)) {
atLeastOneParameterPassedForUpdate = true; atLeastOneParameterPassedForUpdate = true;
@@ -1079,11 +1084,11 @@ public final class LoanApplicationValidator {
final String expectedDisbursementDateStr = this.fromApiJsonHelper final String expectedDisbursementDateStr = this.fromApiJsonHelper
.extractStringNamed(LoanApiConstants.expectedDisbursementDateParameterName, element); .extractStringNamed(LoanApiConstants.expectedDisbursementDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.expectedDisbursementDateParameterName).value(expectedDisbursementDateStr) baseDataValidator.reset().parameter(LoanApiConstants.expectedDisbursementDateParameterName)
.notBlank(); .value(expectedDisbursementDateStr).notBlank();
expectedDisbursementDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.expectedDisbursementDateParameterName, expectedDisbursementDate = this.fromApiJsonHelper
element); .extractLocalDateNamed(LoanApiConstants.expectedDisbursementDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.expectedDisbursementDateParameterName).value(expectedDisbursementDate) baseDataValidator.reset().parameter(LoanApiConstants.expectedDisbursementDateParameterName).value(expectedDisbursementDate)
.notNull(); .notNull();
} }
@@ -1132,17 +1137,18 @@ public final class LoanApplicationValidator {
atLeastOneParameterPassedForUpdate = true; atLeastOneParameterPassedForUpdate = true;
final LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper final LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper
.extractLocalDateNamed(LoanApiConstants.repaymentsStartingFromDateParameterName, element); .extractLocalDateNamed(LoanApiConstants.repaymentsStartingFromDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.repaymentsStartingFromDateParameterName).value(repaymentsStartingFromDate) baseDataValidator.reset().parameter(LoanApiConstants.repaymentsStartingFromDateParameterName)
.ignoreIfNull(); .value(repaymentsStartingFromDate).ignoreIfNull();
if (!loan.getLoanTermVariations().isEmpty()) { if (!loan.getLoanTermVariations().isEmpty()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("cannot.modify.application.due.to.variable.installments"); baseDataValidator.reset()
.failWithCodeNoParameterAddedToErrorCode("cannot.modify.application.due.to.variable.installments");
} }
} }
if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.submittedOnDateParameterName, element)) { if (this.fromApiJsonHelper.parameterExists(LoanApiConstants.submittedOnDateParameterName, element)) {
atLeastOneParameterPassedForUpdate = true; atLeastOneParameterPassedForUpdate = true;
final LocalDate submittedOnDate = this.fromApiJsonHelper.extractLocalDateNamed(LoanApiConstants.submittedOnDateParameterName, final LocalDate submittedOnDate = this.fromApiJsonHelper
element); .extractLocalDateNamed(LoanApiConstants.submittedOnDateParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.submittedOnDateParameterName).value(submittedOnDate).notNull(); baseDataValidator.reset().parameter(LoanApiConstants.submittedOnDateParameterName).value(submittedOnDate).notNull();
} }
@@ -1293,7 +1299,8 @@ public final class LoanApplicationValidator {
final Long loanIdToClose = command.longValueOfParameterNamed(LoanApiConstants.loanIdToClose); final Long loanIdToClose = command.longValueOfParameterNamed(LoanApiConstants.loanIdToClose);
final Loan loanToClose = this.loanRepositoryWrapper.findNonClosedLoanThatBelongsToClient(loanIdToClose, clientId); final Loan loanToClose = this.loanRepositoryWrapper.findNonClosedLoanThatBelongsToClient(loanIdToClose, clientId);
if (loanToClose == null) { if (loanToClose == null) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.loanIdToClose.no.active.loan.associated.to.client.found", 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."); "loanIdToClose is invalid, No Active Loan associated with the given Client ID found.");
} }
if (loanToClose.isMultiDisburmentLoan() && !loanToClose.isInterestRecalculationEnabledForProduct()) { if (loanToClose.isMultiDisburmentLoan() && !loanToClose.isInterestRecalculationEnabledForProduct()) {
@@ -1303,7 +1310,8 @@ public final class LoanApplicationValidator {
} }
final LocalDate disbursalDateOfLoanToClose = loanToClose.getDisbursementDate(); final LocalDate disbursalDateOfLoanToClose = loanToClose.getDisbursementDate();
if (!DateUtils.isAfter(submittedOnDate, disbursalDateOfLoanToClose)) { if (!DateUtils.isAfter(submittedOnDate, disbursalDateOfLoanToClose)) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.submitted.date.should.be.after.topup.loan.disbursal.date", throw new GeneralPlatformDomainRuleException(
"error.msg.loan.submitted.date.should.be.after.topup.loan.disbursal.date",
"Submitted date of this loan application " + submittedOnDate "Submitted date of this loan application " + submittedOnDate
+ " should be after the disbursed date of loan to be closed " + disbursalDateOfLoanToClose); + " should be after the disbursed date of loan to be closed " + disbursalDateOfLoanToClose);
} }
@@ -1316,10 +1324,12 @@ public final class LoanApplicationValidator {
throw new GeneralPlatformDomainRuleException( throw new GeneralPlatformDomainRuleException(
"error.msg.loan.disbursal.date.should.be.after.last.transaction.date.of.loan.to.be.closed", "error.msg.loan.disbursal.date.should.be.after.last.transaction.date.of.loan.to.be.closed",
"Disbursal date of this loan application " + expectedDisbursementDate "Disbursal date of this loan application " + expectedDisbursementDate
+ " should be after last transaction date of loan to be closed " + lastUserTransactionOnLoanToClose); + " should be after last transaction date of loan to be closed "
+ lastUserTransactionOnLoanToClose);
} }
BigDecimal loanOutstanding = this.loanReadPlatformService BigDecimal loanOutstanding = this.loanReadPlatformService
.retrieveLoanPrePaymentTemplate(LoanTransactionType.REPAYMENT, loanIdToClose, expectedDisbursementDate).getAmount(); .retrieveLoanPrePaymentTemplate(LoanTransactionType.REPAYMENT, loanIdToClose, expectedDisbursementDate)
.getAmount();
final BigDecimal firstDisbursalAmount = loan.getFirstDisbursalAmount(); final BigDecimal firstDisbursalAmount = loan.getFirstDisbursalAmount();
if (loanOutstanding.compareTo(firstDisbursalAmount) > 0) { if (loanOutstanding.compareTo(firstDisbursalAmount) > 0) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.amount.less.than.outstanding.of.loan.to.be.closed", throw new GeneralPlatformDomainRuleException("error.msg.loan.amount.less.than.outstanding.of.loan.to.be.closed",
@@ -1350,7 +1360,8 @@ public final class LoanApplicationValidator {
List<LoanProductPaymentAllocationRule> allocationRules = loanProduct.getPaymentAllocationRules(); List<LoanProductPaymentAllocationRule> allocationRules = loanProduct.getPaymentAllocationRules();
if (LoanScheduleProcessingType.HORIZONTAL.name().equals(loanScheduleProcessingType) if (LoanScheduleProcessingType.HORIZONTAL.name().equals(loanScheduleProcessingType)
&& AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(transactionProcessingStrategy)) { && AdvancedPaymentScheduleTransactionProcessor.ADVANCED_PAYMENT_ALLOCATION_STRATEGY
.equals(transactionProcessingStrategy)) {
advancedPaymentAllocationsValidator.checkGroupingOfAllocationRules(allocationRules); advancedPaymentAllocationsValidator.checkGroupingOfAllocationRules(allocationRules);
} }
@@ -1387,11 +1398,7 @@ public final class LoanApplicationValidator {
loanProductDataValidator.validateRepaymentPeriodWithGraceSettings(numberOfRepayments, graceOnPrincipalPayment, loanProductDataValidator.validateRepaymentPeriodWithGraceSettings(numberOfRepayments, graceOnPrincipalPayment,
graceOnInterestPayment, graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator); graceOnInterestPayment, graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator);
});
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
} }
private void validateClientOrGroup(Client client, Group group, Long productId) { private void validateClientOrGroup(Client client, Group group, Long productId) {
@@ -1448,8 +1455,7 @@ public final class LoanApplicationValidator {
}.getType(); }.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, undoSupportedParameters); this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, undoSupportedParameters);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>(); validateOrThrow(LOANAPPLICATION_UNDO, baseDataValidator -> {
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource(LOANAPPLICATION_UNDO);
final JsonElement element = this.fromApiJsonHelper.parse(json); final JsonElement element = this.fromApiJsonHelper.parse(json);
final String note = "note"; final String note = "note";
@@ -1457,17 +1463,11 @@ public final class LoanApplicationValidator {
final String noteText = this.fromApiJsonHelper.extractStringNamed(note, element); final String noteText = this.fromApiJsonHelper.extractStringNamed(note, element);
baseDataValidator.reset().parameter(note).value(noteText).notExceedingLengthOf(1000); baseDataValidator.reset().parameter(note).value(noteText).notExceedingLengthOf(1000);
} }
});
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
} }
public void validateMinMaxConstraintValues(final JsonElement element, final LoanProduct loanProduct) { public void validateMinMaxConstraintValues(final JsonElement element, final LoanProduct loanProduct) {
validateOrThrow("loan", baseDataValidator -> {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
final BigDecimal minPrincipal = loanProduct.getMinPrincipalAmount().getAmount(); final BigDecimal minPrincipal = loanProduct.getMinPrincipalAmount().getAmount();
final BigDecimal maxPrincipal = loanProduct.getMaxPrincipalAmount().getAmount(); final BigDecimal maxPrincipal = loanProduct.getMaxPrincipalAmount().getAmount();
final String principalParameterName = LoanApiConstants.principalParameterName; final String principalParameterName = LoanApiConstants.principalParameterName;
@@ -1477,11 +1477,7 @@ public final class LoanApplicationValidator {
baseDataValidator.reset().parameter(principalParameterName).value(principal).notNull().positiveAmount() baseDataValidator.reset().parameter(principalParameterName).value(principal).notNull().positiveAmount()
.inMinAndMaxAmountRange(minPrincipal, maxPrincipal); .inMinAndMaxAmountRange(minPrincipal, maxPrincipal);
} }
});
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
} }
private void validateLoanTermAndRepaidEveryValues(final Integer loanTermFrequency, final Integer loanTermFrequencyType, private void validateLoanTermAndRepaidEveryValues(final Integer loanTermFrequency, final Integer loanTermFrequencyType,
@@ -1490,10 +1486,10 @@ public final class LoanApplicationValidator {
this.loanScheduleValidator.validateSelectedPeriodFrequencyTypeIsTheSame(dataValidationErrors, loanTermFrequency, this.loanScheduleValidator.validateSelectedPeriodFrequencyTypeIsTheSame(dataValidationErrors, loanTermFrequency,
loanTermFrequencyType, numberOfRepayments, repaymentEvery, repaymentEveryType); loanTermFrequencyType, numberOfRepayments, repaymentEvery, repaymentEveryType);
/** /*
* For multi-disbursal loans where schedules are auto-generated based on a fixed EMI, ensure the number of * For multi-disbursal loans where schedules are auto-generated based on a fixed EMI, ensure the number of
* repayments is within the permissible range defined by the loan product * repayments is within the permissible range defined by the loan product
**/ */
// TODO: is this condition necessary? // TODO: is this condition necessary?
if (loan.getFixedEmiAmount() != null) { if (loan.getFixedEmiAmount() != null) {
Integer minimumNoOfRepayments = loan.loanProduct().getMinNumberOfRepayments(); Integer minimumNoOfRepayments = loan.loanProduct().getMinNumberOfRepayments();
@@ -1583,6 +1579,12 @@ public final class LoanApplicationValidator {
} }
} }
public void validateLoanMultiDisbursementDate(final JsonElement element, LocalDate expectedDisbursementDate, BigDecimal principal) {
validateOrThrow("loan", baseDataValidator -> {
validateLoanMultiDisbursementDate(element, baseDataValidator, expectedDisbursementDate, principal);
});
}
public void validateLoanMultiDisbursementDate(final JsonElement element, final DataValidatorBuilder baseDataValidator, public void validateLoanMultiDisbursementDate(final JsonElement element, final DataValidatorBuilder baseDataValidator,
LocalDate expectedDisbursement, BigDecimal totalPrincipal) { LocalDate expectedDisbursement, BigDecimal totalPrincipal) {
this.validateDisbursementsAreDatewiseOrdered(element, baseDataValidator); this.validateDisbursementsAreDatewiseOrdered(element, baseDataValidator);
@@ -1648,23 +1650,16 @@ public final class LoanApplicationValidator {
.integerSameAsNumber(InterestMethod.DECLINING_BALANCE.getValue()); .integerSameAsNumber(InterestMethod.DECLINING_BALANCE.getValue());
} }
} }
} }
public void validateLoanForCollaterals(final Loan loan, final BigDecimal total) { public void validateLoanForCollaterals(final Loan loan, final BigDecimal total) {
String errorCode; validateOrThrow("loan", baseDataValidator -> {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
if (loan.getProposedPrincipal().compareTo(total) >= 0) { if (loan.getProposedPrincipal().compareTo(total) >= 0) {
errorCode = LoanApiConstants.LOAN_COLLATERAL_TOTAL_VALUE_SHOULD_BE_SUFFICIENT; String errorCode = LoanApiConstants.LOAN_COLLATERAL_TOTAL_VALUE_SHOULD_BE_SUFFICIENT;
baseDataValidator.reset().parameter(LoanApiConstants.collateralsParameterName).failWithCode(errorCode); baseDataValidator.reset().parameter(LoanApiConstants.collateralsParameterName).failWithCode(errorCode);
} }
});
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
} }
private void validatePartialPeriodSupport(final Integer interestCalculationPeriodType, final DataValidatorBuilder baseDataValidator, private void validatePartialPeriodSupport(final Integer interestCalculationPeriodType, final DataValidatorBuilder baseDataValidator,
@@ -1876,4 +1871,44 @@ public final class LoanApplicationValidator {
}.getType(); }.getType();
this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS); this.fromApiJsonHelper.checkForUnsupportedParameters(typeOfMap, json, SUPPORTED_PARAMETERS);
} }
public void validateTopupLoan(Loan loan, LocalDate expectedDisbursementDate) {
final Long loanIdToClose = loan.getTopupLoanDetails().getLoanIdToClose();
final Loan loanToClose = loanRepositoryWrapper.findNonClosedLoanThatBelongsToClient(loanIdToClose, loan.getClientId());
if (loanToClose == null) {
throw new GeneralPlatformDomainRuleException("error.msg.loan.to.be.closed.with.topup.is.not.active",
"Loan to be closed with this topup is not active.");
}
final LocalDate lastUserTransactionOnLoanToClose = loanToClose.getLastUserTransactionDate();
if (DateUtils.isBefore(loan.getDisbursementDate(), 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 " + loan.getDisbursementDate()
+ " should be after last transaction date of loan to be closed " + lastUserTransactionOnLoanToClose);
}
BigDecimal loanOutstanding = loanReadPlatformService
.retrieveLoanPrePaymentTemplate(LoanTransactionType.REPAYMENT, loanIdToClose, expectedDisbursementDate).getAmount();
final BigDecimal firstDisbursalAmount = loan.getFirstDisbursalAmount();
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.");
}
BigDecimal netDisbursalAmount = loan.getApprovedPrincipal().subtract(loanOutstanding);
loan.adjustNetDisbursalAmount(netDisbursalAmount);
}
public static void validateOrThrow(String resource, Consumer<DataValidatorBuilder> baseDataValidator) {
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder dataValidatorBuilder = new DataValidatorBuilder(dataValidationErrors).resource(resource);
baseDataValidator.accept(dataValidatorBuilder);
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException("validation.msg.validation.errors.exist", "Validation errors exist.",
dataValidationErrors);
}
}
} }
@@ -18,6 +18,8 @@
*/ */
package org.apache.fineract.portfolio.loanaccount.service; package org.apache.fineract.portfolio.loanaccount.service;
import static org.apache.fineract.portfolio.loanproduct.domain.RecalculationFrequencyType.SAME_AS_REPAYMENT_PERIOD;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
@@ -236,28 +238,21 @@ public class LoanApplicationWritePlatformServiceJpaRepositoryImpl implements Loa
private void createCalendar(final Loan loan, LocalDate calendarStartDate, Integer recalculationFrequencyNthDay, private void createCalendar(final Loan loan, LocalDate calendarStartDate, Integer recalculationFrequencyNthDay,
final Integer repeatsOnDay, final RecalculationFrequencyType recalculationFrequencyType, Integer frequency, final Integer repeatsOnDay, final RecalculationFrequencyType recalculationFrequencyType, Integer frequency,
CalendarEntityType calendarEntityType, final String title) { CalendarEntityType calendarEntityType, final String title) {
CalendarFrequencyType calendarFrequencyType = CalendarFrequencyType.INVALID;
Integer updatedRepeatsOnDay = repeatsOnDay; Integer updatedRepeatsOnDay = repeatsOnDay;
switch (recalculationFrequencyType) { final CalendarFrequencyType calendarFrequencyType = switch (recalculationFrequencyType) {
case DAILY: case DAILY -> CalendarFrequencyType.DAILY;
calendarFrequencyType = CalendarFrequencyType.DAILY; case WEEKLY -> CalendarFrequencyType.WEEKLY;
break; case MONTHLY -> CalendarFrequencyType.MONTHLY;
case MONTHLY: case SAME_AS_REPAYMENT_PERIOD -> CalendarFrequencyType.from(loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType());
calendarFrequencyType = CalendarFrequencyType.MONTHLY; case INVALID -> CalendarFrequencyType.INVALID;
break; };
case SAME_AS_REPAYMENT_PERIOD:
if (recalculationFrequencyType == SAME_AS_REPAYMENT_PERIOD) {
frequency = loan.repaymentScheduleDetail().getRepayEvery(); frequency = loan.repaymentScheduleDetail().getRepayEvery();
calendarFrequencyType = CalendarFrequencyType.from(loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType());
calendarStartDate = loan.getExpectedDisbursedOnLocalDate(); calendarStartDate = loan.getExpectedDisbursedOnLocalDate();
if (updatedRepeatsOnDay == null) { if (updatedRepeatsOnDay == null) {
updatedRepeatsOnDay = calendarStartDate.get(ChronoField.DAY_OF_WEEK); updatedRepeatsOnDay = calendarStartDate.get(ChronoField.DAY_OF_WEEK);
} }
break;
case WEEKLY:
calendarFrequencyType = CalendarFrequencyType.WEEKLY;
break;
default:
break;
} }
final Calendar calendar = Calendar.createRepeatingCalendar(title, calendarStartDate, CalendarType.COLLECTION.getValue(), final Calendar calendar = Calendar.createRepeatingCalendar(title, calendarStartDate, CalendarType.COLLECTION.getValue(),
@@ -971,11 +971,15 @@ public class LoanChargeWritePlatformServiceImpl implements LoanChargeWritePlatfo
final String defaultUserMessage = "Installment charge cannot be added to the loan."; final String defaultUserMessage = "Installment charge cannot be added to the loan.";
throw new LoanChargeCannotBeAddedException("loanCharge", "overdue.charge", defaultUserMessage, null, throw new LoanChargeCannotBeAddedException("loanCharge", "overdue.charge", defaultUserMessage, null,
chargeDefinition.getName()); chargeDefinition.getName());
} else if (loanCharge.getDueLocalDate() != null } else if (loanCharge.getDueLocalDate() != null) {
&& DateUtils.isBefore(loanCharge.getDueLocalDate(), loan.getLastUserTransactionForChargeCalc())) { // TODO: Review, error message seems not valid if interest recalculation is not enabled.
LocalDate validationDate = loan.repaymentScheduleDetail().isInterestRecalculationEnabled() ? loan.getLastUserTransactionDate()
: loan.getDisbursementDate();
if (DateUtils.isBefore(loanCharge.getDueLocalDate(), validationDate)) {
final String defaultUserMessage = "charge with date before last transaction date can not be added to loan."; final String defaultUserMessage = "charge with date before last transaction date can not be added to loan.";
throw new LoanChargeCannotBeAddedException("loanCharge", "date.is.before.last.transaction.date", defaultUserMessage, null, throw new LoanChargeCannotBeAddedException("loanCharge", "date.is.before.last.transaction.date", defaultUserMessage, null,
chargeDefinition.getName()); chargeDefinition.getName());
}
} else if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) { } else if (loan.repaymentScheduleDetail().isInterestRecalculationEnabled()) {
if (loanCharge.isInstalmentFee() && loan.getStatus().isActive()) { if (loanCharge.isInstalmentFee() && loan.getStatus().isActive()) {
final String defaultUserMessage = "installment charge addition not allowed after disbursement"; final String defaultUserMessage = "installment charge addition not allowed after disbursement";