Skip to content

Commit

Permalink
FINERACT-2081: fix loan creation validations
Browse files Browse the repository at this point in the history
  • Loading branch information
kjozsa committed Aug 6, 2024
1 parent ec1c55d commit daef727
Show file tree
Hide file tree
Showing 11 changed files with 403 additions and 367 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public static LocalDate getOffSetDateIfNonWorkingDay(final LocalDate date, final
}

public static boolean isWorkingDay(final WorkingDays workingDays, final LocalDate date) {
return CalendarUtils.isValidRedurringDate(workingDays.getRecurrence(), date, date);
return CalendarUtils.isValidRecurringDate(workingDays.getRecurrence(), date, date);
}

public static boolean isNonWorkingDay(final WorkingDays workingDays, final LocalDate date) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ public boolean isBetweenStartAndEndDate(final LocalDate compareDate) {

public boolean isValidRecurringDate(final LocalDate compareDate, final Boolean isSkipMeetingOnFirstDay, final Integer numberOfDays) {
if (isBetweenStartAndEndDate(compareDate)) {
return CalendarUtils.isValidRedurringDate(this.getRecurrence(), this.getStartDate(), compareDate, isSkipMeetingOnFirstDay,
return CalendarUtils.isValidRecurringDate(this.getRecurrence(), this.getStartDate(), compareDate, isSkipMeetingOnFirstDay,
numberOfDays);
}
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -558,14 +558,14 @@ private static String constructRecurrence(final CalendarFrequencyType frequencyT
public boolean isValidRecurringDate(final LocalDate compareDate, Boolean isSkipRepaymentOnFirstMonth, Integer numberOfDays) {

if (isBetweenStartAndEndDate(compareDate)) {
return CalendarUtils.isValidRedurringDate(getRecurrence(), getStartDateLocalDate(), compareDate, isSkipRepaymentOnFirstMonth,
return CalendarUtils.isValidRecurringDate(getRecurrence(), getStartDateLocalDate(), compareDate, isSkipRepaymentOnFirstMonth,
numberOfDays);
}

// validate with history details.
for (CalendarHistory history : history()) {
if (history.isBetweenStartAndEndDate(compareDate)) {
return CalendarUtils.isValidRedurringDate(history.getRecurrence(), history.getStartDate(), compareDate,
return CalendarUtils.isValidRecurringDate(history.getRecurrence(), history.getStartDate(), compareDate,
isSkipRepaymentOnFirstMonth, numberOfDays);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ public static String getRRuleReadable(final LocalDate startDate, final String re
return humanReadable;
}

public static boolean isValidRedurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date) {
public static boolean isValidRecurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date) {
final Recur recur = CalendarUtils.getICalRecur(recurringRule);
if (recur == null) {
return false;
Expand All @@ -365,7 +365,7 @@ public static boolean isValidRedurringDate(final String recurringRule, final Loc
return isValidRecurringDate(recur, seedDate, date, isSkipRepaymentonFirstDayOfMonth, numberOfDays);
}

public static boolean isValidRedurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date,
public static boolean isValidRecurringDate(final String recurringRule, final LocalDate seedDate, final LocalDate date,
boolean isSkipRepaymentonFirstDayOfMonth, final Integer numberOfDays) {

final Recur recur = CalendarUtils.getICalRecur(recurringRule);
Expand Down Expand Up @@ -529,7 +529,7 @@ public static LocalDate getFirstRepaymentMeetingDate(final Calendar calendar, fi
}
LocalDate startDate = disbursementDate;
final LocalDate seedDate = calendar.getStartDateLocalDate();
if (isValidRedurringDate(calendar.getRecurrence(), seedDate, startDate, isSkipRepaymentOnFirstDayOfMonth, numberOfDays)
if (isValidRecurringDate(calendar.getRecurrence(), seedDate, startDate, isSkipRepaymentOnFirstDayOfMonth, numberOfDays)
&& !frequency.equals(Recur.Frequency.DAILY.name())) {
startDate = startDate.plusDays(1);
}
Expand Down Expand Up @@ -728,10 +728,6 @@ public static LocalDate getNextScheduleDate(final Calendar calendar, final Local
return null;
}
final LocalDate seedDate = calendar.getStartDateLocalDate();
/**
* if (isValidRedurringDate(calendar.getRecurrence(), seedDate, date)) { date = date.plusDays(1); }
**/

return getNextRecurringDate(recur, seedDate, startDate);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,7 @@ public BigDecimal calculatePeriodsBetweenDates(final LocalDate startDate, final
this.repaymentPeriodFrequencyType);
} else {
LocalDate expectedStartDate = startDate;
if (!CalendarUtils.isValidRedurringDate(loanCalendar.getRecurrence(),
if (!CalendarUtils.isValidRecurringDate(loanCalendar.getRecurrence(),
loanCalendar.getStartDateLocalDate().minusMonths(getRepaymentEvery()), startDate)) {
expectedStartDate = CalendarUtils.getNewRepaymentMeetingDate(loanCalendar.getRecurrence(),
startDate.minusMonths(getRepaymentEvery()), startDate.minusMonths(getRepaymentEvery()), getRepaymentEvery(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ private List<DisbursementData> fetchDisbursementData(final JsonObject command) {

private void validateRepaymentsStartDateWithMeetingDates(final LocalDate repaymentsStartingFromDate, final Calendar calendar,
boolean isSkipRepaymentOnFirstDayOfMonth, final Integer numberOfDays) {
if (repaymentsStartingFromDate != null && !CalendarUtils.isValidRedurringDate(calendar.getRecurrence(),
if (repaymentsStartingFromDate != null && !CalendarUtils.isValidRecurringDate(calendar.getRecurrence(),
calendar.getStartDateLocalDate(), repaymentsStartingFromDate, isSkipRepaymentOnFirstDayOfMonth, numberOfDays)) {
final String errorMessage = "First repayment date '" + repaymentsStartingFromDate + "' do not fall on a meeting date";
throw new LoanApplicationDateException("first.repayment.date.do.not.match.meeting.date", errorMessage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -356,10 +356,6 @@ private void validateForCreate(final JsonElement element) {
.integerGreaterThanZero();
}

final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed(LoanApiConstants.principalParamName,
element);
baseDataValidator.reset().parameter(LoanApiConstants.principalParamName).value(principal).notNull().positiveAmount();

final Integer loanTermFrequency = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed(LoanApiConstants.loanTermFrequencyParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.loanTermFrequencyParameterName).value(loanTermFrequency).notNull()
Expand Down Expand Up @@ -422,7 +418,7 @@ private void validateForCreate(final JsonElement element) {
baseDataValidator.reset().parameter(LoanApiConstants.isFloatingInterestRate).trueOrFalseRequired(false);
}

if (interestType != null && interestType.equals(InterestMethod.FLAT.getValue())) {
if (InterestMethod.FLAT.getValue().equals(interestType)) {
baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName).failWithCode(
"should.be.0.for.selected.loan.product",
"interestType should be DECLINING_BALANCE for selected Loan Product as it is linked to floating rates.");
Expand Down Expand Up @@ -452,7 +448,7 @@ private void validateForCreate(final JsonElement element) {
.extractBigDecimalWithLocaleNamed(LoanApiConstants.interestRatePerPeriodParameterName, element);
baseDataValidator.reset().parameter(LoanApiConstants.interestRatePerPeriodParameterName).value(interestRatePerPeriod)
.notNull().zeroOrPositiveAmount();
isInterestBearing = interestRatePerPeriod.compareTo(BigDecimal.ZERO) > 0;
isInterestBearing = interestRatePerPeriod != null && interestRatePerPeriod.compareTo(BigDecimal.ZERO) > 0;
}

final Integer amortizationType = this.fromApiJsonHelper
Expand Down Expand Up @@ -622,6 +618,9 @@ private void validateForCreate(final JsonElement element) {
.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();
Expand Down Expand Up @@ -754,8 +753,11 @@ private void validateForCreate(final JsonElement element) {
validateDisbursementDateIsOnHoliday(expectedDisbursementDate, officeId);
final Integer recurringMoratoriumOnPrincipalPeriods = this.fromApiJsonHelper
.extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", element);
loanProductDataValidator.validateRepaymentPeriodWithGraceSettings(numberOfRepayments, graceOnPrincipalPayment,
graceOnInterestPayment, graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator);

if (numberOfRepayments != null) {
loanProductDataValidator.validateRepaymentPeriodWithGraceSettings(numberOfRepayments, graceOnPrincipalPayment,
graceOnInterestPayment, graceOnInterestCharged, recurringMoratoriumOnPrincipalPeriods, baseDataValidator);
}
});
}

Expand All @@ -777,7 +779,8 @@ private void validateBorrowerCycle(JsonElement element, LoanProduct loanProduct,
private void validateDisbursementDateIsOnNonWorkingDay(final LocalDate expectedDisbursementDate) {
final WorkingDays workingDays = this.workingDaysRepository.findOne();
final boolean allowTransactionsOnNonWorkingDay = this.configurationDomainService.allowTransactionsOnNonWorkingDayEnabled();
if (!allowTransactionsOnNonWorkingDay && !WorkingDaysUtil.isWorkingDay(workingDays, expectedDisbursementDate)) {
if (expectedDisbursementDate != null && !allowTransactionsOnNonWorkingDay
&& !WorkingDaysUtil.isWorkingDay(workingDays, expectedDisbursementDate)) {
final String errorMessage = "Expected disbursement date cannot be on a non working day";
throw new LoanApplicationDateException("disbursement.date.on.non.working.day", errorMessage, expectedDisbursementDate);
}
Expand Down Expand Up @@ -1032,7 +1035,7 @@ public void validateForModify(final JsonCommand command, final Loan loan) {
if (interestType == null) {
interestType = loan.getLoanProductRelatedDetail().getInterestMethod().getValue();
}
if (interestType != null && interestType.equals(InterestMethod.FLAT.getValue())) {
if (InterestMethod.FLAT.getValue().equals(interestType)) {
baseDataValidator.reset().parameter(LoanApiConstants.interestTypeParameterName).failWithCode(
"should.be.0.for.selected.loan.product",
"interestType should be DECLINING_BALANCE for selected Loan Product as it is linked to floating rates.");
Expand Down Expand Up @@ -1759,16 +1762,16 @@ private void validateTransactionProcessingStrategy(final String transactionProce
"Loan transaction processing strategy cannot be Advanced Payment Allocation Strategy if it's not configured on loan product");
} else {
// PROGRESSIVE: Repayment strategy MUST be only "advanced payment allocation"
if (loanProduct.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.PROGRESSIVE)) {
if (!transactionProcessingStrategy.equals(LoanProductConstants.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)) {
if (LoanScheduleType.PROGRESSIVE.equals(loanProduct.getLoanProductRelatedDetail().getLoanScheduleType())) {
if (!LoanProductConstants.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(transactionProcessingStrategy)) {
// TODO: GeneralPlatformDomainRuleException vs PlatformApiDataValidationException
throw new GeneralPlatformDomainRuleException(
"error.msg.loan.repayment.strategy.can.not.be.different.than.advanced.payment.allocation",
"Loan repayment strategy can not be different than Advanced Payment Allocation");
}
// CUMULATIVE: Repayment strategy CANNOT be "advanced payment allocation"
} else if (loanProduct.getLoanProductRelatedDetail().getLoanScheduleType().equals(LoanScheduleType.CUMULATIVE)) {
if (transactionProcessingStrategy.equals(LoanProductConstants.ADVANCED_PAYMENT_ALLOCATION_STRATEGY)) {
} else if (LoanScheduleType.CUMULATIVE.equals(loanProduct.getLoanProductRelatedDetail().getLoanScheduleType())) {
if (LoanProductConstants.ADVANCED_PAYMENT_ALLOCATION_STRATEGY.equals(transactionProcessingStrategy)) {
// TODO: GeneralPlatformDomainRuleException vs PlatformApiDataValidationException
throw new GeneralPlatformDomainRuleException(
"error.msg.loan.repayment.strategy.can.not.be.equal.to.advanced.payment.allocation",
Expand All @@ -1781,7 +1784,6 @@ private void validateTransactionProcessingStrategy(final String transactionProce
}

public void checkForProductMixRestrictions(final JsonElement element) {

final List<Long> activeLoansLoanProductIds;
final Long productId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.productIdParameterName, element);
final Long groupId = this.fromApiJsonHelper.extractLongNamed(LoanApiConstants.groupIdParameterName, element);
Expand Down Expand Up @@ -1863,17 +1865,17 @@ private void validateSubmittedOnDate(final JsonElement element, LocalDate origin
Group group = groupRepository.findOneWithNotFoundDetection(groupId);

if (group != null && group.isActivatedAfter(submittedOnDate)) {
final String errorMessage = "The date on which a loan is submitted cannot be earlier than groups's activation date.";
final String errorMessage = "The date on which a loan is submitted cannot be earlier than group's activation date.";
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.before.group.activation.date", errorMessage,
submittedOnDate, group.getActivationDate());
}
}

if (DateUtils.isAfter(submittedOnDate, expectedDisbursementDate)) {
final String errorMessage = "The date on which a loan is submitted cannot be after its expected disbursement date: "
+ expectedDisbursementDate;
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.after.expected.disbursement.date", errorMessage,
submittedOnDate, expectedDisbursementDate);
}
if (DateUtils.isAfter(submittedOnDate, expectedDisbursementDate)) {
final String errorMessage = "The date on which a loan is submitted cannot be after its expected disbursement date: "
+ expectedDisbursementDate;
throw new InvalidLoanStateTransitionException("submittal", "cannot.be.after.expected.disbursement.date", errorMessage,
submittedOnDate, expectedDisbursementDate);
}
}

Expand Down Expand Up @@ -1975,7 +1977,7 @@ public void validateApproval(JsonCommand command, Long loanId) {
expectedDisbursementDate = loan.getExpectedDisbursedOnLocalDate();
}

if (DateUtils.isBefore(approvedOnDate, loan.getSubmittedOnDate())) {
if (approvedOnDate != null && DateUtils.isBefore(approvedOnDate, loan.getSubmittedOnDate())) {
final String errorMessage = "Loan approval date " + approvedOnDate + " can not be before its submittal date: "
+ loan.getSubmittedOnDate();
throw new InvalidLoanStateTransitionException("approval", "cannot.be.before.submittal.date", errorMessage, approvedOnDate,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ public void validateRepaymentDateWithMeetingDate(final LocalDate repaymentDate,
final Calendar calendar = calendarInstance.getCalendar();
if (calendar != null && repaymentDate != null) {
// Disbursement date should fall on a meeting date
if (!CalendarUtils.isValidRedurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), repaymentDate)) {
if (!CalendarUtils.isValidRecurringDate(calendar.getRecurrence(), calendar.getStartDateLocalDate(), repaymentDate)) {
final String errorMessage = "Transaction date '" + repaymentDate.toString() + "' does not fall on a meeting date.";
throw new NotValidRecurringDateException("loan.transaction.date", errorMessage, repaymentDate.toString(),
calendar.getTitle());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1942,11 +1942,8 @@ private void validateChargeToIncomeAccountMappings(final DataValidatorBuilder ba

public void validateMinMaxConstraints(final JsonElement element, final DataValidatorBuilder baseDataValidator,
final LoanProduct loanProduct) {

validatePrincipalMinMaxConstraint(element, loanProduct, baseDataValidator);

validateNumberOfRepaymentsMinMaxConstraint(element, loanProduct, baseDataValidator);

validateNominalInterestRatePerPeriodMinMaxConstraint(element, loanProduct, baseDataValidator);
}

Expand Down Expand Up @@ -2029,7 +2026,6 @@ public void validateMinMaxConstraints(final JsonElement element, final DataValid

private void validatePrincipalMinMaxConstraint(final JsonElement element, final LoanProduct loanProduct,
final DataValidatorBuilder baseDataValidator) {

boolean principalUpdated = false;
boolean minPrincipalUpdated = false;
boolean maxPrincipalUpdated = false;
Expand Down Expand Up @@ -2555,11 +2551,7 @@ public void validateRepaymentPeriodWithGraceSettings(final Integer numberOfRepay
}
}

private Integer defaultToZeroIfNull(final Integer value) {
Integer result = value;
if (value == null) {
result = 0;
}
return result;
private Integer defaultToZeroIfNull(Integer value) {
return value != null ? value : 0;
}
}
Loading

0 comments on commit daef727

Please sign in to comment.