List of usage examples for org.joda.time LocalDate isBefore
public boolean isBefore(ReadablePartial partial)
From source file:org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AbstractLoanScheduleGenerator.java
License:Apache License
/** * this Method updates late/ not paid installment components to Map with * effective date as per REST(for principal portion ) and compounding * (interest or fee or interest and fee portions) frequency * //from w w w. j a v a 2 s . c o m */ private void updateLatePaymentsToMap(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, final MonetaryCurrency currency, final Map<LocalDate, Money> latePaymentMap, final LocalDate scheduledDueDate, List<LoanRepaymentScheduleInstallment> installments, boolean applyRestFrequencyForPrincipal, final LocalDate lastRestDate, final TreeMap<LocalDate, Money> compoundingMap) { latePaymentMap.clear(); LocalDate currentDate = DateUtils.getLocalDateOfTenant(); Money totalCompoundingAmount = Money.zero(currency); Money compoundedMoney = Money.zero(currency); if (!compoundingMap.isEmpty()) { compoundedMoney = compoundingMap.get(lastRestDate); } boolean clearCompoundingMap = true; for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) { if (loanRepaymentScheduleInstallment.isNotFullyPaidOff() && !loanRepaymentScheduleInstallment.getDueDate().isAfter(scheduledDueDate) && !loanRepaymentScheduleInstallment.isRecalculatedInterestComponent()) { LocalDate principalEffectiveDate = loanRepaymentScheduleInstallment.getDueDate(); if (applyRestFrequencyForPrincipal) { principalEffectiveDate = getNextRestScheduleDate( loanRepaymentScheduleInstallment.getDueDate().minusDays(1), loanApplicationTerms, holidayDetailDTO); } if (principalEffectiveDate.isBefore(currentDate)) { updateMapWithAmount(latePaymentMap, loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency), principalEffectiveDate); totalCompoundingAmount = totalCompoundingAmount .plus(loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency)); } final Money changedCompoundedMoney = updateMapWithCompoundingDetails(loanApplicationTerms, holidayDetailDTO, currency, compoundingMap, loanRepaymentScheduleInstallment, lastRestDate, compoundedMoney, scheduledDueDate); if (compoundedMoney.isZero() || !compoundedMoney.isEqualTo(changedCompoundedMoney)) { compoundedMoney = changedCompoundedMoney; clearCompoundingMap = false; } } } if (totalCompoundingAmount.isGreaterThanZero()) { updateMapWithAmount(latePaymentMap, totalCompoundingAmount.negated(), lastRestDate); } if (clearCompoundingMap) { compoundingMap.clear(); } }
From source file:org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AbstractLoanScheduleGenerator.java
License:Apache License
private Money updateMapWithCompoundingDetails(final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, final MonetaryCurrency currency, final TreeMap<LocalDate, Money> compoundingMap, final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment, final LocalDate lastRestDate, final Money compoundedMoney, final LocalDate scheduledDueDate) { Money ignoreMoney = compoundedMoney; if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { LocalDate compoundingEffectiveDate = getNextCompoundScheduleDate( loanRepaymentScheduleInstallment.getDueDate().minusDays(1), loanApplicationTerms, holidayDetailDTO);// w ww. j a v a 2s .c om if (compoundingEffectiveDate.isBefore(DateUtils.getLocalDateOfTenant())) { Money amount = Money.zero(currency); switch (loanApplicationTerms.getInterestRecalculationCompoundingMethod()) { case INTEREST: amount = amount.plus(loanRepaymentScheduleInstallment.getInterestOutstanding(currency)); break; case FEE: amount = amount.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)); amount = amount.plus(loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency)); break; case INTEREST_AND_FEE: amount = amount.plus(loanRepaymentScheduleInstallment.getInterestOutstanding(currency)); amount = amount.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency)); amount = amount.plus(loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency)); break; default: break; } if (compoundingEffectiveDate.isBefore(scheduledDueDate)) { ignoreMoney = ignoreMoney.plus(amount); if (ignoreMoney.isGreaterThanZero()) { updateMapWithAmount(compoundingMap, ignoreMoney, compoundingEffectiveDate); updateMapWithAmount(compoundingMap, ignoreMoney.negated(), lastRestDate); ignoreMoney = ignoreMoney.zero(); } } else { if (ignoreMoney.isLessThanZero()) { LocalDate firstKey = compoundingMap.firstKey(); updateMapWithAmount(compoundingMap, ignoreMoney, firstKey); updateMapWithAmount(compoundingMap, ignoreMoney.negated(), lastRestDate); ignoreMoney = ignoreMoney.zero(); } updateMapWithAmount(compoundingMap, amount, compoundingEffectiveDate); updateMapWithAmount(compoundingMap, amount.negated(), lastRestDate); } } } return ignoreMoney; }
From source file:org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AbstractLoanScheduleGenerator.java
License:Apache License
private void populateCompoundingDatesInPeriod(final LocalDate startDate, final LocalDate endDate, final LocalDate currentDate, final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, final Map<LocalDate, Money> compoundingMap, final Set<LoanCharge> charges, MonetaryCurrency currency) { if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { LocalDate lastCompoundingDate = startDate; LocalDate compoundingDate = startDate; while (compoundingDate.isBefore(endDate) && compoundingDate.isBefore(currentDate)) { compoundingDate = getNextCompoundScheduleDate(compoundingDate, loanApplicationTerms, holidayDetailDTO);/*from www. j a v a 2 s.com*/ if (!compoundingDate.isBefore(currentDate)) { break; } else if (compoundingDate.isAfter(endDate)) { updateMapWithAmount(compoundingMap, Money.zero(currency), compoundingDate); } else { Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(lastCompoundingDate, compoundingDate, charges, currency, null, loanApplicationTerms.getPrincipal(), null, false); Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(lastCompoundingDate, compoundingDate, charges, currency, null, loanApplicationTerms.getPrincipal(), null, false); updateMapWithAmount(compoundingMap, feeChargesForInstallment.plus(penaltyChargesForInstallment), compoundingDate); } lastCompoundingDate = compoundingDate; } } }
From source file:org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AbstractLoanScheduleGenerator.java
License:Apache License
protected void clearMapDetails(final LocalDate startDate, final Map<LocalDate, Money> compoundingMap) { Map<LocalDate, Money> temp = new HashMap<>(); for (LocalDate date : compoundingMap.keySet()) { if (!date.isBefore(startDate)) { temp.put(date, compoundingMap.get(date)); }//from w ww . ja v a2 s.com } compoundingMap.clear(); compoundingMap.putAll(temp); }
From source file:org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AbstractLoanScheduleGenerator.java
License:Apache License
/** * calculates Interest stating date as per the settings * /*from w w w .java 2 s .c o m*/ * @param firstRepaymentdate * TODO */ private LocalDate calculateInterestStartDateForPeriod(final LoanApplicationTerms loanApplicationTerms, LocalDate periodStartDate, final LocalDate idealDisbursementDate, final LocalDate firstRepaymentdate) { LocalDate periodStartDateApplicableForInterest = periodStartDate; if (periodStartDate.isBefore(idealDisbursementDate) || firstRepaymentdate.isAfter(periodStartDate)) { if (loanApplicationTerms.getInterestChargedFromLocalDate() != null) { if (periodStartDate.isEqual(loanApplicationTerms.getExpectedDisbursementDate()) || loanApplicationTerms.getInterestChargedFromLocalDate().isAfter(periodStartDate)) { periodStartDateApplicableForInterest = loanApplicationTerms.getInterestChargedFromLocalDate(); } } else if (periodStartDate.isEqual(loanApplicationTerms.getExpectedDisbursementDate())) { periodStartDateApplicableForInterest = idealDisbursementDate; } } return periodStartDateApplicableForInterest; }
From source file:org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AbstractLoanScheduleGenerator.java
License:Apache License
private LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, final List<LoanTransaction> transactions, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments, final LocalDate rescheduleFrom, final LocalDate scheduleTillDate) { // Loan transactions to process and find the variation on payments Collection<RecalculationDetail> recalculationDetails = new ArrayList<>(); for (LoanTransaction loanTransaction : transactions) { recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(), LoanTransaction.copyTransactionProperties(loanTransaction))); }/*w w w. j a v a 2s . c om*/ final boolean applyInterestRecalculation = loanApplicationTerms.isInterestRecalculationEnabled(); LoanScheduleParams loanScheduleParams = null; Collection<LoanScheduleModelPeriod> periods = new ArrayList<>(); final List<LoanRepaymentScheduleInstallment> retainedInstallments = new ArrayList<>(); // this block is to retain the schedule installments prior to the // provided date and creates late and early payment details for further // calculations if (rescheduleFrom != null) { Money principalToBeScheduled = getPrincipalToBeScheduled(loanApplicationTerms); // actual outstanding balance for interest calculation Money outstandingBalance = principalToBeScheduled; // total outstanding balance as per rest for interest calculation. Money outstandingBalanceAsPerRest = outstandingBalance; // this is required to update total fee amounts in the // LoanScheduleModel final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement( loanCharges); periods = createNewLoanScheduleListWithDisbursementDetails( loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions(), loanApplicationTerms, chargesDueAtTimeOfDisbursement); final List<LoanRepaymentScheduleInstallment> newRepaymentScheduleInstallments = new ArrayList<>(); MonetaryCurrency currency = outstandingBalance.getCurrency(); // early payments will be added here and as per the selected // strategy // action will be performed on this value Money reducePrincipal = outstandingBalanceAsPerRest.zero(); // principal changes will be added along with date(after applying // rest) // from when these amounts will effect the outstanding balance for // interest calculation final Map<LocalDate, Money> principalPortionMap = new HashMap<>(); // compounding(principal) amounts will be added along with // date(after applying compounding frequency) // from when these amounts will effect the outstanding balance for // interest calculation final Map<LocalDate, Money> latePaymentMap = new HashMap<>(); // compounding(interest/Fee) amounts will be added along with // date(after applying compounding frequency) // from when these amounts will effect the outstanding balance for // interest calculation final TreeMap<LocalDate, Money> compoundingMap = new TreeMap<>(); LocalDate currentDate = DateUtils.getLocalDateOfTenant(); LocalDate lastRestDate = currentDate; if (loanApplicationTerms.getRestCalendarInstance() != null) { lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); } LocalDate actualRepaymentDate = loanApplicationTerms.getExpectedDisbursementDate(); boolean isFirstRepayment = true; // cumulative fields Money totalCumulativePrincipal = principalToBeScheduled.zero(); Money totalCumulativeInterest = principalToBeScheduled.zero(); Money totalFeeChargesCharged = principalToBeScheduled.zero().plus(chargesDueAtTimeOfDisbursement); Money totalPenaltyChargesCharged = principalToBeScheduled.zero(); Money totalRepaymentExpected = principalToBeScheduled.zero(); // Actual period Number as per the schedule int periodNumber = 1; // Actual period Number plus interest only repayments int instalmentNumber = 1; LocalDate lastInstallmentDate = actualRepaymentDate; LocalDate periodStartDate = loanApplicationTerms.getExpectedDisbursementDate(); // Set fixed Amortization Amounts(either EMI or Principal ) updateAmortization(mc, loanApplicationTerms, periodNumber, outstandingBalance); final Map<LocalDate, Money> disburseDetailMap = new HashMap<>(); if (loanApplicationTerms.isMultiDisburseLoan()) { // fetches the first tranche amount and also updates other // tranche // details to map BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, loanApplicationTerms.getExpectedDisbursementDate(), periods, chargesDueAtTimeOfDisbursement, disburseDetailMap, true); outstandingBalance = outstandingBalance.zero().plus(disburseAmt); outstandingBalanceAsPerRest = outstandingBalance; principalToBeScheduled = principalToBeScheduled.zero().plus(disburseAmt); } int loanTermInDays = 0; // Block process the installment and creates the period if it falls // before reschedule from date // This will create the recalculation details by applying the // transactions for (LoanRepaymentScheduleInstallment installment : repaymentScheduleInstallments) { // this will generate the next schedule due date and allows to // process the installment only if recalculate from date is // greater than due date if (installment.getDueDate().isAfter(lastInstallmentDate)) { LocalDate previousRepaymentDate = actualRepaymentDate; actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO); isFirstRepayment = false; lastInstallmentDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, holidayDetailDTO); if (!lastInstallmentDate.isBefore(rescheduleFrom)) { actualRepaymentDate = previousRepaymentDate; break; } periodNumber++; // check for date changes while (loanApplicationTerms.getLoanTermVariations().hasDueDateVariation(lastInstallmentDate)) { LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations() .nextDueDateVariation(); if (!variation.isSpecificToInstallment()) { actualRepaymentDate = variation.getDateValue(); } variation.setProcessed(true); } } for (Map.Entry<LocalDate, Money> disburseDetail : disburseDetailMap.entrySet()) { if (disburseDetail.getKey().isAfter(installment.getFromDate()) && !disburseDetail.getKey().isAfter(installment.getDueDate())) { // creates and add disbursement detail to the repayments // period final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod .disbursement(disburseDetail.getKey(), disburseDetail.getValue(), chargesDueAtTimeOfDisbursement); periods.add(disbursementPeriod); // updates actual outstanding balance with new // disbursement detail outstandingBalance = outstandingBalance.plus(disburseDetail.getValue()); principalToBeScheduled = principalToBeScheduled.plus(disburseDetail.getValue()); } } // calculation of basic fields to start the schedule generation // from the middle periodStartDate = installment.getDueDate(); installment.resetDerivedComponents(); newRepaymentScheduleInstallments.add(installment); outstandingBalance = outstandingBalance.minus(installment.getPrincipal(currency)); final LoanScheduleModelPeriod loanScheduleModelPeriod = createLoanScheduleModelPeriod(installment, outstandingBalance); periods.add(loanScheduleModelPeriod); totalCumulativePrincipal = totalCumulativePrincipal.plus(installment.getPrincipal(currency)); totalCumulativeInterest = totalCumulativeInterest.plus(installment.getInterestCharged(currency)); totalFeeChargesCharged = totalFeeChargesCharged.plus(installment.getFeeChargesCharged(currency)); totalPenaltyChargesCharged = totalPenaltyChargesCharged .plus(installment.getPenaltyChargesCharged(currency)); instalmentNumber++; loanTermInDays = Days.daysBetween(installment.getFromDate(), installment.getDueDate()).getDays(); // populates the collection with transactions till the due date // of // the period for interest recalculation enabled loans Collection<RecalculationDetail> applicableTransactions = getApplicableTransactionsForPeriod( applyInterestRecalculation, installment.getDueDate(), recalculationDetails); // calculates the expected principal value for this repayment // schedule Money principalPortionCalculated = principalToBeScheduled.zero(); if (!installment.isRecalculatedInterestComponent()) { principalPortionCalculated = calculateExpectedPrincipalPortion( installment.getInterestCharged(currency), loanApplicationTerms); } // expected principal considering the previously paid excess // amount Money actualPrincipalPortion = principalPortionCalculated.minus(reducePrincipal); if (actualPrincipalPortion.isLessThanZero()) { actualPrincipalPortion = principalPortionCalculated.zero(); } Money unprocessed = updateEarlyPaidAmountsToMap(loanApplicationTerms, holidayDetailDTO, loanRepaymentScheduleTransactionProcessor, newRepaymentScheduleInstallments, currency, principalPortionMap, installment, applicableTransactions, actualPrincipalPortion); // this block is to adjust the period number based on the actual // schedule due date and installment due date // recalculatedInterestComponent installment shouldn't be // considered while calculating fixed EMI amounts int period = periodNumber; if (!lastInstallmentDate.isEqual(installment.getDueDate())) { period--; } reducePrincipal = fetchEarlyPaidAmount(installment.getPrincipal(currency), principalPortionCalculated, reducePrincipal, loanApplicationTerms, totalCumulativePrincipal, period, mc); // Updates principal paid map with efective date for reducing // the amount from outstanding balance(interest calculation) LocalDate amountApplicableDate = getNextRestScheduleDate(installment.getDueDate().minusDays(1), loanApplicationTerms, holidayDetailDTO); // updates map with the installment principal amount excluding // unprocessed amount since this amount is already accounted. updateMapWithAmount(principalPortionMap, installment.getPrincipal(currency).minus(unprocessed), amountApplicableDate); // update outstanding balance for interest calculation outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(principalPortionMap, installment.getDueDate(), outstandingBalanceAsPerRest, false); outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(disburseDetailMap, installment.getDueDate(), outstandingBalanceAsPerRest, true); } totalRepaymentExpected = totalCumulativePrincipal.plus(totalCumulativeInterest) .plus(totalFeeChargesCharged).plus(totalPenaltyChargesCharged); // updates the map with over due amounts updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, lastInstallmentDate, newRepaymentScheduleInstallments, true, lastRestDate, compoundingMap); // for partial schedule generation if (!newRepaymentScheduleInstallments.isEmpty() && totalCumulativeInterest.isGreaterThanZero()) { Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency); loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForPartialUpdate(periodNumber, instalmentNumber, loanTermInDays, periodStartDate, actualRepaymentDate, totalCumulativePrincipal, totalCumulativeInterest, totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstandingInterestPaymentDueToGrace, reducePrincipal, principalPortionMap, latePaymentMap, compoundingMap, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, newRepaymentScheduleInstallments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, currency, applyInterestRecalculation); retainedInstallments.addAll(newRepaymentScheduleInstallments); } } // for complete schedule generation if (loanScheduleParams == null) { loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForCompleteUpdate(recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation); } LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, loanScheduleParams); for (LoanScheduleModelPeriod loanScheduleModelPeriod : loanScheduleModel.getPeriods()) { if (loanScheduleModelPeriod.isRepaymentPeriod()) { // adding newly created repayment periods to installments addLoanRepaymentScheduleInstallment(retainedInstallments, loanScheduleModelPeriod); } } periods.addAll(loanScheduleModel.getPeriods()); LoanScheduleModel loanScheduleModelwithPeriodChanges = LoanScheduleModel .withLoanScheduleModelPeriods(periods, loanScheduleModel); return LoanScheduleDTO.from(retainedInstallments, loanScheduleModelwithPeriodChanges); }
From source file:org.apache.fineract.portfolio.loanaccount.loanschedule.domain.DecliningBalanceInterestLoanScheduleGenerator.java
License:Apache License
@Override public PrincipalInterest calculatePrincipalInterestComponentsForPeriod( final PaymentPeriodsInOneYearCalculator calculator, final double interestCalculationGraceOnRepaymentPeriodFraction, final Money totalCumulativePrincipal, @SuppressWarnings("unused") final Money totalCumulativeInterest, @SuppressWarnings("unused") final Money totalInterestDueForLoan, final Money cumulatingInterestPaymentDueToGrace, final Money outstandingBalance, final LoanApplicationTerms loanApplicationTerms, final int periodNumber, final MathContext mc, final TreeMap<LocalDate, Money> principalVariation, final Map<LocalDate, Money> compoundingMap, final LocalDate periodStartDate, final LocalDate periodEndDate, final Collection<LoanTermVariationsData> termVariations) { LocalDate interestStartDate = periodStartDate; Money interestForThisInstallment = totalCumulativePrincipal.zero(); Money compoundedMoney = totalCumulativePrincipal.zero(); Money compoundedInterest = totalCumulativePrincipal.zero(); Money balanceForInterestCalculation = outstandingBalance; Money cumulatingInterestDueToGrace = cumulatingInterestPaymentDueToGrace; Map<LocalDate, BigDecimal> interestRates = new HashMap<>(termVariations.size()); for (LoanTermVariationsData loanTermVariation : termVariations) { if (loanTermVariation.getTermVariationType().isInterestRateVariation() && loanTermVariation.isApplicable(periodStartDate, periodEndDate)) { LocalDate fromDate = loanTermVariation.getTermApplicableFrom(); if (fromDate == null) { fromDate = periodStartDate; }//from www . j a va 2 s. com interestRates.put(fromDate, loanTermVariation.getDecimalValue()); if (!principalVariation.containsKey(fromDate)) { principalVariation.put(fromDate, balanceForInterestCalculation.zero()); } } } if (principalVariation != null) { // identifies rest date after current date for reducing all // compounding // values LocalDate compoundingEndDate = principalVariation.ceilingKey(DateUtils.getLocalDateOfTenant()); if (compoundingEndDate == null) { compoundingEndDate = DateUtils.getLocalDateOfTenant(); } for (Map.Entry<LocalDate, Money> principal : principalVariation.entrySet()) { if (!principal.getKey().isAfter(periodEndDate)) { int interestForDays = Days.daysBetween(interestStartDate, principal.getKey()).getDays(); if (interestForDays > 0) { final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod( calculator, interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestDueToGrace, balanceForInterestCalculation, interestStartDate, principal.getKey()); interestForThisInstallment = interestForThisInstallment.plus(result.interest()); cumulatingInterestDueToGrace = result.interestPaymentDueToGrace(); interestStartDate = principal.getKey(); } Money compoundFee = totalCumulativePrincipal.zero(); if (compoundingMap.containsKey(principal.getKey())) { Money interestToBeCompounded = totalCumulativePrincipal.zero(); // for interest compounding if (loanApplicationTerms.getInterestRecalculationCompoundingMethod() .isInterestCompoundingEnabled()) { interestToBeCompounded = interestForThisInstallment.minus(compoundedInterest); balanceForInterestCalculation = balanceForInterestCalculation .plus(interestToBeCompounded); compoundedInterest = interestForThisInstallment; } // fee compounding will be done after calculation compoundFee = compoundingMap.get(principal.getKey()); compoundedMoney = compoundedMoney.plus(interestToBeCompounded).plus(compoundFee); } balanceForInterestCalculation = balanceForInterestCalculation.plus(principal.getValue()) .plus(compoundFee); if (interestRates.containsKey(principal.getKey())) { loanApplicationTerms.updateAnnualNominalInterestRate(interestRates.get(principal.getKey())); } } } if (!periodEndDate.isBefore(compoundingEndDate)) { balanceForInterestCalculation = balanceForInterestCalculation.minus(compoundedMoney); compoundingMap.clear(); } else if (compoundedMoney.isGreaterThanZero()) { compoundingMap.put(periodEndDate, compoundedMoney); compoundingMap.put(compoundingEndDate, compoundedMoney.negated()); clearMapDetails(periodEndDate, compoundingMap); } } final PrincipalInterest result = loanApplicationTerms.calculateTotalInterestForPeriod(calculator, interestCalculationGraceOnRepaymentPeriodFraction, periodNumber, mc, cumulatingInterestDueToGrace, balanceForInterestCalculation, interestStartDate, periodEndDate); interestForThisInstallment = interestForThisInstallment.plus(result.interest()); cumulatingInterestDueToGrace = result.interestPaymentDueToGrace(); Money interestForPeriod = interestForThisInstallment; if (interestForPeriod.isGreaterThanZero()) { interestForPeriod = interestForPeriod.minus(cumulatingInterestPaymentDueToGrace); } else { interestForPeriod = cumulatingInterestDueToGrace.minus(cumulatingInterestPaymentDueToGrace); } Money principalForThisInstallment = loanApplicationTerms.calculateTotalPrincipalForPeriod(calculator, outstandingBalance, periodNumber, mc, interestForPeriod); // update cumulative fields for principal & interest final Money interestBroughtFowardDueToGrace = cumulatingInterestDueToGrace; final Money totalCumulativePrincipalToDate = totalCumulativePrincipal.plus(principalForThisInstallment); // adjust if needed principalForThisInstallment = loanApplicationTerms.adjustPrincipalIfLastRepaymentPeriod( principalForThisInstallment, totalCumulativePrincipalToDate, periodNumber); return new PrincipalInterest(principalForThisInstallment, interestForThisInstallment, interestBroughtFowardDueToGrace); }
From source file:org.apache.fineract.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms.java
License:Apache License
private BigDecimal calculatePeriodsInLoanTerm() { BigDecimal periodsInLoanTerm = BigDecimal.valueOf(this.loanTermFrequency); switch (this.interestCalculationPeriodMethod) { case DAILY://from www . ja va 2 s. co m // number of days from 'ideal disbursement' to final date LocalDate loanStartDate = getExpectedDisbursementDate(); if (getInterestChargedFromDate() != null && loanStartDate.isBefore(getInterestChargedFromLocalDate())) { loanStartDate = getInterestChargedFromLocalDate(); } final int periodsInLoanTermInteger = Days.daysBetween(loanStartDate, this.loanEndDate).getDays(); periodsInLoanTerm = BigDecimal.valueOf(periodsInLoanTermInteger); break; case INVALID: break; case SAME_AS_REPAYMENT_PERIOD: LocalDate startDate = getExpectedDisbursementDate(); periodsInLoanTerm = calculatePeriodsBetweenDates(startDate, this.loanEndDate); break; } return periodsInLoanTerm; }
From source file:org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler.java
License:Apache License
private LoanApplicationTerms assembleLoanApplicationTermsFrom(final JsonElement element, final LoanProduct loanProduct) { final MonetaryCurrency currency = loanProduct.getCurrency(); final ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository .findOneWithNotFoundDetection(currency); // loan terms final Integer loanTermFrequency = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("loanTermFrequency", element);/*from www .jav a2s. c o m*/ final Integer loanTermFrequencyType = this.fromApiJsonHelper .extractIntegerWithLocaleNamed("loanTermFrequencyType", element); final PeriodFrequencyType loanTermPeriodFrequencyType = PeriodFrequencyType.fromInt(loanTermFrequencyType); final Integer numberOfRepayments = this.fromApiJsonHelper .extractIntegerWithLocaleNamed("numberOfRepayments", element); final Integer repaymentEvery = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentEvery", element); final Integer repaymentFrequencyType = this.fromApiJsonHelper .extractIntegerWithLocaleNamed("repaymentFrequencyType", element); final PeriodFrequencyType repaymentPeriodFrequencyType = PeriodFrequencyType .fromInt(repaymentFrequencyType); final Integer nthDay = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("repaymentFrequencyNthDayType", element); final Integer dayOfWeek = this.fromApiJsonHelper .extractIntegerWithLocaleNamed("repaymentFrequencyDayOfWeekType", element); final DayOfWeekType weekDayType = DayOfWeekType.fromInt(dayOfWeek); final Integer amortizationType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("amortizationType", element); final AmortizationMethod amortizationMethod = AmortizationMethod.fromInt(amortizationType); // interest terms final Integer interestType = this.fromApiJsonHelper.extractIntegerWithLocaleNamed("interestType", element); final InterestMethod interestMethod = InterestMethod.fromInt(interestType); final Integer interestCalculationPeriodType = this.fromApiJsonHelper .extractIntegerWithLocaleNamed("interestCalculationPeriodType", element); final InterestCalculationPeriodMethod interestCalculationPeriodMethod = InterestCalculationPeriodMethod .fromInt(interestCalculationPeriodType); Boolean allowPartialPeriodInterestCalcualtion = this.fromApiJsonHelper .extractBooleanNamed(LoanProductConstants.allowPartialPeriodInterestCalcualtionParamName, element); if (allowPartialPeriodInterestCalcualtion == null) { allowPartialPeriodInterestCalcualtion = loanProduct.getLoanProductRelatedDetail() .isAllowPartialPeriodInterestCalcualtion(); } final BigDecimal interestRatePerPeriod = this.fromApiJsonHelper .extractBigDecimalWithLocaleNamed("interestRatePerPeriod", element); final PeriodFrequencyType interestRatePeriodFrequencyType = loanProduct.getInterestPeriodFrequencyType(); BigDecimal annualNominalInterestRate = BigDecimal.ZERO; if (interestRatePerPeriod != null) { annualNominalInterestRate = this.aprCalculator.calculateFrom(interestRatePeriodFrequencyType, interestRatePerPeriod); } // disbursement details final BigDecimal principal = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed("principal", element); final Money principalMoney = Money.of(currency, principal); final LocalDate expectedDisbursementDate = this.fromApiJsonHelper .extractLocalDateNamed("expectedDisbursementDate", element); final LocalDate repaymentsStartingFromDate = this.fromApiJsonHelper .extractLocalDateNamed("repaymentsStartingFromDate", element); LocalDate calculatedRepaymentsStartingFromDate = repaymentsStartingFromDate; final Boolean synchDisbursement = this.fromApiJsonHelper.extractBooleanNamed("syncDisbursementWithMeeting", element); final Long calendarId = this.fromApiJsonHelper.extractLongNamed("calendarId", element); Calendar calendar = null; final String loanTypeParameterName = "loanType"; final String loanTypeStr = this.fromApiJsonHelper.extractStringNamed(loanTypeParameterName, element); final AccountType loanType = AccountType.fromName(loanTypeStr); /* * If it is JLG loan/Group Loan then make sure loan frequency is same as * Group/Center meeting frequency or multiple of it. TODO: Check should * be either same frequency or loan freq is multiple of center/group * meeting freq multiples */ if ((loanType.isJLGAccount() || loanType.isGroupAccount()) && calendarId != null) { calendar = this.calendarRepository.findOne(calendarId); if (calendar == null) { throw new CalendarNotFoundException(calendarId); } final PeriodFrequencyType meetingPeriodFrequency = CalendarUtils .getMeetingPeriodFrequencyType(calendar.getRecurrence()); validateRepaymentFrequencyIsSameAsMeetingFrequency(meetingPeriodFrequency.getValue(), repaymentFrequencyType, CalendarUtils.getInterval(calendar.getRecurrence()), repaymentEvery); } /* * If user has not passed the first repayments date then then derive the * same based on loan type. */ if (calculatedRepaymentsStartingFromDate == null) { calculatedRepaymentsStartingFromDate = deriveFirstRepaymentDate(loanType, repaymentEvery, expectedDisbursementDate, repaymentPeriodFrequencyType, loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment(), calendar); } /* * If it is JLG loan/Group Loan synched with a meeting, then make sure * first repayment falls on meeting date */ if ((loanType.isJLGAccount() || loanType.isGroupAccount()) && calendar != null) { validateRepaymentsStartDateWithMeetingDates(calculatedRepaymentsStartingFromDate, calendar); /* * If disbursement is synced on meeting, make sure disbursement date * is on a meeting date */ if (synchDisbursement != null && synchDisbursement.booleanValue()) { validateDisbursementDateWithMeetingDates(expectedDisbursementDate, calendar); } } validateMinimumDaysBetweenDisbursalAndFirstRepayment(expectedDisbursementDate, calculatedRepaymentsStartingFromDate, loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment()); // grace details final Integer graceOnPrincipalPayment = this.fromApiJsonHelper .extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element); final Integer graceOnInterestPayment = this.fromApiJsonHelper .extractIntegerWithLocaleNamed("graceOnInterestPayment", element); final Integer graceOnInterestCharged = this.fromApiJsonHelper .extractIntegerWithLocaleNamed("graceOnInterestCharged", element); final LocalDate interestChargedFromDate = this.fromApiJsonHelper .extractLocalDateNamed("interestChargedFromDate", element); final Integer graceOnArrearsAgeing = this.fromApiJsonHelper .extractIntegerWithLocaleNamed(LoanProductConstants.graceOnArrearsAgeingParameterName, element); // other final BigDecimal inArrearsTolerance = this.fromApiJsonHelper .extractBigDecimalWithLocaleNamed("inArrearsTolerance", element); final Money inArrearsToleranceMoney = Money.of(currency, inArrearsTolerance); final BigDecimal emiAmount = this.fromApiJsonHelper .extractBigDecimalWithLocaleNamed(LoanApiConstants.emiAmountParameterName, element); final BigDecimal maxOutstandingBalance = this.fromApiJsonHelper .extractBigDecimalWithLocaleNamed(LoanApiConstants.maxOutstandingBalanceParameterName, element); final List<DisbursementData> disbursementDatas = fetchDisbursementData(element.getAsJsonObject()); /** * Interest recalculation settings copy from product definition */ final DaysInMonthType daysInMonthType = loanProduct.fetchDaysInMonthType(); final DaysInYearType daysInYearType = loanProduct.fetchDaysInYearType(); final boolean isInterestRecalculationEnabled = loanProduct.isInterestRecalculationEnabled(); RecalculationFrequencyType recalculationFrequencyType = null; CalendarInstance restCalendarInstance = null; RecalculationFrequencyType compoundingFrequencyType = null; CalendarInstance compoundingCalendarInstance = null; if (isInterestRecalculationEnabled) { LoanProductInterestRecalculationDetails loanProductInterestRecalculationDetails = loanProduct .getProductInterestRecalculationDetails(); recalculationFrequencyType = loanProductInterestRecalculationDetails.getRestFrequencyType(); if (recalculationFrequencyType.isSameAsRepayment()) { restCalendarInstance = createCalendarForSameAsRepayment(repaymentEvery, repaymentPeriodFrequencyType, expectedDisbursementDate); } else { LocalDate calendarStartDate = this.fromApiJsonHelper.extractLocalDateNamed( LoanProductConstants.recalculationRestFrequencyDateParamName, element); if (calendarStartDate == null) { calendarStartDate = expectedDisbursementDate; } restCalendarInstance = createInterestRecalculationCalendarInstance(calendarStartDate, recalculationFrequencyType, loanProductInterestRecalculationDetails.getRestInterval()); } InterestRecalculationCompoundingMethod compoundingMethod = InterestRecalculationCompoundingMethod .fromInt(loanProductInterestRecalculationDetails.getInterestRecalculationCompoundingMethod()); if (compoundingMethod.isCompoundingEnabled()) { compoundingFrequencyType = loanProductInterestRecalculationDetails.getCompoundingFrequencyType(); if (compoundingFrequencyType.isSameAsRepayment()) { compoundingCalendarInstance = createCalendarForSameAsRepayment(repaymentEvery, repaymentPeriodFrequencyType, expectedDisbursementDate); } else { LocalDate calendarStartDate = this.fromApiJsonHelper.extractLocalDateNamed( LoanProductConstants.recalculationCompoundingFrequencyDateParamName, element); if (calendarStartDate == null) { calendarStartDate = expectedDisbursementDate; } compoundingCalendarInstance = createInterestRecalculationCalendarInstance(calendarStartDate, compoundingFrequencyType, loanProductInterestRecalculationDetails.getCompoundingInterval()); } } } final BigDecimal principalThresholdForLastInstalment = loanProduct .getPrincipalThresholdForLastInstallment(); final Integer installmentAmountInMultiplesOf = loanProduct.getInstallmentAmountInMultiplesOf(); List<LoanTermVariationsData> loanTermVariations = new ArrayList<>(); if (loanProduct.isLinkedToFloatingInterestRate()) { final BigDecimal interestRateDiff = this.fromApiJsonHelper.extractBigDecimalWithLocaleNamed( LoanApiConstants.interestRateDifferentialParameterName, element); final Boolean isFloatingInterestRate = this.fromApiJsonHelper .extractBooleanNamed(LoanApiConstants.isFloatingInterestRateParameterName, element); List<FloatingRatePeriodData> baseLendingRatePeriods = null; try { baseLendingRatePeriods = this.floatingRatesReadPlatformService.retrieveBaseLendingRate() .getRatePeriods(); } catch (final FloatingRateNotFoundException ex) { // Do not do anything } FloatingRateDTO floatingRateDTO = new FloatingRateDTO(isFloatingInterestRate, expectedDisbursementDate, interestRateDiff, baseLendingRatePeriods); Collection<FloatingRatePeriodData> applicableRates = loanProduct.fetchInterestRates(floatingRateDTO); LocalDate interestRateStartDate = DateUtils.getLocalDateOfTenant(); final LocalDate dateValue = null; final boolean isSpecificToInstallment = false; for (FloatingRatePeriodData periodData : applicableRates) { LoanTermVariationsData loanTermVariation = new LoanTermVariationsData( LoanEnumerations.loanvariationType(LoanTermVariationType.INTEREST_RATE), periodData.getFromDateAsLocalDate(), periodData.getInterestRate(), dateValue, isSpecificToInstallment); if (!interestRateStartDate.isBefore(periodData.getFromDateAsLocalDate())) { interestRateStartDate = periodData.getFromDateAsLocalDate(); annualNominalInterestRate = periodData.getInterestRate(); } loanTermVariations.add(loanTermVariation); } } return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, recalculationFrequencyType, restCalendarInstance, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(), calendar, BigDecimal.ZERO, loanTermVariations); }
From source file:org.apache.fineract.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler.java
License:Apache License
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; }/*from w w w. j a va 2 s . com*/ 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.fetchRepaymentScheduleInstallments(); Set<LocalDate> dueDates = new TreeSet<>(); LocalDate graceApplicable = loan.getExpectedDisbursedOnLocalDate(); Integer graceOnPrincipal = loan.getLoanProductRelatedDetail().graceOnPrincipalPayment(); if (graceOnPrincipal == null) { graceOnPrincipal = 0; } LocalDate lastDate = loan.getExpectedDisbursedOnLocalDate(); for (LoanRepaymentScheduleInstallment installment : installments) { dueDates.add(installment.getDueDate()); if (lastDate.isBefore(installment.getDueDate())) { lastDate = installment.getDueDate(); } if (graceOnPrincipal.equals(installment.getInstallmentNumber())) { graceApplicable = installment.getDueDate(); } } Collection<LocalDate> keySet = adjustDueDateVariations.keySet(); dueDates.addAll(keySet); for (final LocalDate date : keySet) { LocalDate removeDate = adjustDueDateVariations.get(date); 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 (!graceApplicable.isBefore(termVariations.fetchTermApplicaDate())) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( "variable.schedule.insert.not.allowed.before.grace.period", "Loan schedule insert request invalid"); } if (termVariations.fetchTermApplicaDate().isAfter(lastDate)) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( "variable.schedule.insert.not.allowed.after.last.period.date", "Loan schedule insert request invalid"); } else if (termVariations.fetchTermApplicaDate().isBefore(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 (termVariations.fetchTermApplicaDate().isEqual(lastDate)) { 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 (termVariations.fetchDateValue().isBefore(loan.getExpectedDisbursedOnLocalDate())) { baseDataValidator.reset().failWithCodeNoParameterAddedToErrorCode( "variable.schedule.insert.not.allowed.before.disbursement.date", "Loan schedule insert request invalid"); } if (termVariations.fetchTermApplicaDate().isEqual(lastDate)) { lastDate = termVariations.fetchDateValue(); } break; case PRINCIPAL_AMOUNT: case EMI_AMOUNT: if (!graceApplicable.isBefore(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 (termVariations.fetchTermApplicaDate().isEqual(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 .findCalendarInstaneByEntityId(loan.getId(), CalendarEntityType.LOANS.getValue()); Calendar loanCalendar = null; if (loanCalendarInstance != null) { loanCalendar = loanCalendarInstance.getCalendar(); } final Integer minGap = installmentConfig.getMinimumGap(); final Integer maxGap = installmentConfig.getMaximumGap(); LocalDate previousDate = loan.getDisbursementDate(); for (LocalDate duedate : dueDates) { int gap = Days.daysBetween(previousDate, duedate).getDays(); 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)) { 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().toDate()); } final LocalDate recalculateFrom = null; ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); AppUser currentUser = this.context.getAuthenticatedUserIfPresent(); loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); }