in fineract-provider/src/main/java/org/apache/fineract/portfolio/loanaccount/loanschedule/service/LoanScheduleAssembler.java [784:961]
public void assempleVariableScheduleFrom(final Loan loan, final String json) {
this.variableLoanScheduleFromApiJsonValidator.validateSchedule(json, loan);
List<LoanTermVariations> variations = loan.getLoanTermVariations();
List<LoanTermVariations> newVariations = new ArrayList<>();
extractLoanTermVariations(loan, json, newVariations);
final Map<LocalDate, LocalDate> adjustDueDateVariations = new HashMap<>();
if (!variations.isEmpty()) {
List<LoanTermVariations> retainVariations = adjustExistingVariations(variations, newVariations, adjustDueDateVariations);
newVariations = retainVariations;
}
variations.addAll(newVariations);
// Collections.sort(variations, new LoanTermVariationsComparator());
/*
* List<LoanTermVariationsData> loanTermVariationsDatas = new ArrayList<>();
* loanTermVariationsDatas.addAll(loanApplicationTerms. getLoanTermVariations ().getExceptionData());
* loanApplicationTerms = LoanApplicationTerms.assembleFrom(loanApplicationTerms, loanTermVariationsDatas);
*/
// date validations
List<LoanRepaymentScheduleInstallment> installments = loan.getRepaymentScheduleInstallments();
Set<LocalDate> dueDates = new TreeSet<>();
LocalDate graceApplicable = loan.getExpectedDisbursedOnLocalDate();
Integer graceOnPrincipal = loan.getLoanProductRelatedDetail().getGraceOnPrincipalPayment();
if (graceOnPrincipal == null) {
graceOnPrincipal = 0;
}
LocalDate lastDate = loan.getExpectedDisbursedOnLocalDate();
for (LoanRepaymentScheduleInstallment installment : installments) {
dueDates.add(installment.getDueDate());
if (DateUtils.isBefore(lastDate, installment.getDueDate())) {
lastDate = installment.getDueDate();
}
if (graceOnPrincipal.equals(installment.getInstallmentNumber())) {
graceApplicable = installment.getDueDate();
}
}
dueDates.addAll(adjustDueDateVariations.keySet());
for (Map.Entry<LocalDate, LocalDate> entry : adjustDueDateVariations.entrySet()) {
LocalDate removeDate = entry.getValue();
if (removeDate != null) {
dueDates.remove(removeDate);
}
}
Set<LocalDate> actualDueDates = new TreeSet<>(dueDates);
final List<ApiParameterError> dataValidationErrors = new ArrayList<>();
final DataValidatorBuilder baseDataValidator = new DataValidatorBuilder(dataValidationErrors).resource("loan");
List<LocalDate> overlappings = new ArrayList<>();
for (LoanTermVariations termVariations : variations) {
switch (termVariations.getTermType()) {
case INSERT_INSTALLMENT:
if (dueDates.contains(termVariations.fetchTermApplicaDate())) {
overlappings.add(termVariations.fetchTermApplicaDate());
} else {
dueDates.add(termVariations.fetchTermApplicaDate());
}
if (!DateUtils.isBefore(graceApplicable, termVariations.fetchTermApplicaDate())) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"variable.schedule.insert.not.allowed.before.grace.period", "Loan schedule insert request invalid");
}
if (DateUtils.isAfter(termVariations.fetchTermApplicaDate(), lastDate)) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"variable.schedule.insert.not.allowed.after.last.period.date", "Loan schedule insert request invalid");
} else if (DateUtils.isBefore(termVariations.fetchTermApplicaDate(), loan.getExpectedDisbursedOnLocalDate())) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"variable.schedule.insert.not.allowed.before.disbursement.date", "Loan schedule insert request invalid");
}
break;
case DELETE_INSTALLMENT:
if (dueDates.contains(termVariations.fetchTermApplicaDate())) {
dueDates.remove(termVariations.fetchTermApplicaDate());
} else {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("variable.schedule.remove.date.invalid",
"Loan schedule remove request invalid");
}
if (DateUtils.isEqual(lastDate, termVariations.fetchTermApplicaDate())) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"variable.schedule.delete.not.allowed.for.last.period.date", "Loan schedule remove request invalid");
}
break;
case DUE_DATE:
if (dueDates.contains(termVariations.fetchTermApplicaDate())) {
if (overlappings.contains(termVariations.fetchTermApplicaDate())) {
overlappings.remove(termVariations.fetchTermApplicaDate());
} else {
dueDates.remove(termVariations.fetchTermApplicaDate());
}
} else {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("variable.schedule.modify.date.invalid",
"Loan schedule modify due date request invalid");
}
if (dueDates.contains(termVariations.fetchDateValue())) {
overlappings.add(termVariations.fetchDateValue());
} else {
dueDates.add(termVariations.fetchDateValue());
}
if (DateUtils.isBefore(termVariations.fetchDateValue(), loan.getExpectedDisbursedOnLocalDate())) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"variable.schedule.insert.not.allowed.before.disbursement.date", "Loan schedule insert request invalid");
}
if (DateUtils.isEqual(lastDate, termVariations.fetchTermApplicaDate())) {
lastDate = termVariations.fetchDateValue();
}
break;
case PRINCIPAL_AMOUNT:
case EMI_AMOUNT:
if (!DateUtils.isBefore(graceApplicable, termVariations.fetchTermApplicaDate())) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"variable.schedule.amount.update.not.allowed.before.grace.period", "Loan schedule modify request invalid");
}
if (!dueDates.contains(termVariations.fetchTermApplicaDate())) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"variable.schedule.amount.update.from.date.invalid", "Loan schedule modify request invalid");
}
if (DateUtils.isEqual(termVariations.fetchTermApplicaDate(), lastDate)) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode(
"variable.schedule.amount.update.not.allowed.for.last.period", "Loan schedule modify request invalid");
}
break;
default:
break;
}
}
if (!overlappings.isEmpty()) {
baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode("variable.schedule.modify.date.can.not.be.due.date",
overlappings);
}
LoanProductVariableInstallmentConfig installmentConfig = loan.loanProduct().loanProductVariableInstallmentConfig();
final CalendarInstance loanCalendarInstance = calendarInstanceRepository.findCalendarInstanceByEntityId(loan.getId(),
CalendarEntityType.LOANS.getValue());
Calendar loanCalendar = null;
if (loanCalendarInstance != null) {
loanCalendar = loanCalendarInstance.getCalendar();
}
Boolean isSkipRepaymentOnFirstMonth = false;
Integer numberOfDays = 0;
boolean isSkipRepaymentOnFirstMonthEnabled = configurationDomainService.isSkippingMeetingOnFirstDayOfMonthEnabled();
if (isSkipRepaymentOnFirstMonthEnabled) {
isSkipRepaymentOnFirstMonth = this.loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), loanCalendar);
if (isSkipRepaymentOnFirstMonth) {
numberOfDays = configurationDomainService.retreivePeriodInNumberOfDaysForSkipMeetingDate().intValue();
}
}
final Integer minGap = installmentConfig.getMinimumGap();
final Integer maxGap = installmentConfig.getMaximumGap();
LocalDate previousDate = loan.getDisbursementDate();
for (LocalDate duedate : dueDates) {
int gap = DateUtils.getExactDifferenceInDays(previousDate, duedate);
previousDate = duedate;
if (gap < minGap || (maxGap != null && gap > maxGap)) {
baseDataValidator.reset().value(duedate).failWithCodeNoParameterAddedToErrorCode(
"variable.schedule.date.must.be.in.min.max.range", "Loan schedule date invalid");
} else if (loanCalendar != null && !actualDueDates.contains(duedate)
&& !loanCalendar.isValidRecurringDate(duedate, isSkipRepaymentOnFirstMonth, numberOfDays)) {
baseDataValidator.reset().value(duedate).failWithCodeNoParameterAddedToErrorCode("variable.schedule.date.not.meeting.date",
"Loan schedule date not in sync with meeting date");
}
}
if (!dataValidationErrors.isEmpty()) {
throw new PlatformApiDataValidationException(dataValidationErrors);
}
if (loan.getExpectedFirstRepaymentOnDate() == null) {
loan.setExpectedFirstRepaymentOnDate(loan.fetchRepaymentScheduleInstallment(1).getDueDate());
}
final LocalDate recalculateFrom = null;
ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom);
loanScheduleService.regenerateRepaymentSchedule(loan, scheduleGeneratorDTO);
loanAccrualsProcessingService.reprocessExistingAccruals(loan);
}