List of usage examples for org.joda.time LocalDate isBefore
public boolean isBefore(ReadablePartial partial)
From source file:com.gst.portfolio.loanaccount.loanschedule.domain.AbstractLoanScheduleGenerator.java
License:Apache License
private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, final LoanScheduleParams loanScheduleParams) { final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency(); // generate list of proposed schedule due dates LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms, holidayDetailDTO);//w w w. jav a 2 s . c o m LoanTermVariationsData lastDueDateVariation = loanApplicationTerms.getLoanTermVariations() .fetchLoanTermDueDateVariationsData(loanEndDate); if (lastDueDateVariation != null) { loanEndDate = lastDueDateVariation.getDateValue(); } loanApplicationTerms.updateLoanEndDate(loanEndDate); // determine the total charges due at time of disbursement final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges); // setup variables for tracking important facts required for loan // schedule generation. final MonetaryCurrency currency = loanApplicationTerms.getCurrency(); final int numberOfRepayments = loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions(); LoanScheduleParams scheduleParams = null; if (loanScheduleParams == null) { scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement), loanApplicationTerms.getExpectedDisbursementDate(), getPrincipalToBeScheduled(loanApplicationTerms)); } else if (!loanScheduleParams.isPartialUpdate()) { scheduleParams = LoanScheduleParams.createLoanScheduleParams(currency, Money.of(currency, chargesDueAtTimeOfDisbursement), loanApplicationTerms.getExpectedDisbursementDate(), getPrincipalToBeScheduled(loanApplicationTerms), loanScheduleParams); } else { scheduleParams = loanScheduleParams; } final Collection<RecalculationDetail> transactions = scheduleParams.getRecalculationDetails(); final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = scheduleParams .getLoanRepaymentScheduleTransactionProcessor(); Collection<LoanScheduleModelPeriod> periods = new ArrayList<>(); if (!scheduleParams.isPartialUpdate()) { periods = createNewLoanScheduleListWithDisbursementDetails(numberOfRepayments, loanApplicationTerms, chargesDueAtTimeOfDisbursement); } // Determine the total interest owed over the full loan for FLAT // interest method . if (!scheduleParams.isPartialUpdate()) { Money totalInterestChargedForFullLoanTerm = loanApplicationTerms .calculateTotalInterestCharged(this.paymentPeriodsInOneYearCalculator, mc); loanApplicationTerms.updateTotalInterestDue(totalInterestChargedForFullLoanTerm); } boolean isFirstRepayment = true; LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate( loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, isFirstRepayment, holidayDetailDTO); final LocalDate idealDisbursementDate = this.scheduledDateGenerator .idealDisbursementDateBasedOnFirstRepaymentDate( loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate, loanApplicationTerms.getLoanCalendar(), loanApplicationTerms.getHolidayDetailDTO(), loanApplicationTerms); if (!scheduleParams.isPartialUpdate()) { // Set Fixed Principal Amount updateAmortization(mc, loanApplicationTerms, scheduleParams.getPeriodNumber(), scheduleParams.getOutstandingBalance()); if (loanApplicationTerms.isMultiDisburseLoan()) { // fetches the first tranche amount and also updates other // tranche // details to map BigDecimal disburseAmt = getDisbursementAmount(loanApplicationTerms, scheduleParams.getPeriodStartDate(), periods, chargesDueAtTimeOfDisbursement, scheduleParams.getDisburseDetailMap(), scheduleParams.applyInterestRecalculation()); scheduleParams.setPrincipalToBeScheduled(Money.of(currency, disburseAmt)); loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().zero().plus(disburseAmt)); scheduleParams.setOutstandingBalance(Money.of(currency, disburseAmt)); scheduleParams.setOutstandingBalanceAsPerRest(Money.of(currency, disburseAmt)); } } // charges which depends on total loan interest will be added to this // set and handled separately after all installments generated final Set<LoanCharge> nonCompoundingCharges = seperateTotalCompoundingPercentageCharges(loanCharges); LocalDate currentDate = DateUtils.getLocalDateOfTenant(); LocalDate lastRestDate = currentDate; if (loanApplicationTerms.getRestCalendarInstance() != null) { lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); } boolean isNextRepaymentAvailable = true; Boolean extendTermForDailyRepayments = false; if (holidayDetailDTO.getWorkingDays().getExtendTermForDailyRepayments() == true && loanApplicationTerms.getRepaymentPeriodFrequencyType() == PeriodFrequencyType.DAYS && loanApplicationTerms.getRepaymentEvery() == 1) { holidayDetailDTO.getWorkingDays() .setRepaymentReschedulingType(RepaymentRescheduleType.MOVE_TO_NEXT_WORKING_DAY.getValue()); extendTermForDailyRepayments = true; } final Collection<LoanTermVariationsData> interestRates = loanApplicationTerms.getLoanTermVariations() .getInterestRateChanges(); final Collection<LoanTermVariationsData> interestRatesForInstallments = loanApplicationTerms .getLoanTermVariations().getInterestRateFromInstallment(); // this block is to start the schedule generation from specified date if (scheduleParams.isPartialUpdate()) { if (loanApplicationTerms.isMultiDisburseLoan()) { loanApplicationTerms.setPrincipal(scheduleParams.getPrincipalToBeScheduled()); } applyLoanVariationsForPartialScheduleGenerate(loanApplicationTerms, scheduleParams, interestRates, interestRatesForInstallments); isFirstRepayment = false; } while (!scheduleParams.getOutstandingBalance().isZero() || !scheduleParams.getDisburseDetailMap().isEmpty()) { LocalDate previousRepaymentDate = scheduleParams.getActualRepaymentDate(); scheduleParams.setActualRepaymentDate( this.scheduledDateGenerator.generateNextRepaymentDate(scheduleParams.getActualRepaymentDate(), loanApplicationTerms, isFirstRepayment, holidayDetailDTO)); isFirstRepayment = false; LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate( scheduleParams.getActualRepaymentDate(), loanApplicationTerms, holidayDetailDTO); // calculated interest start date for the period LocalDate periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod( loanApplicationTerms, scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentdate, loanApplicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled(), loanApplicationTerms.getExpectedDisbursementDate()); // Loan Schedule Exceptions that need to be applied for Loan Account LoanTermVariationParams termVariationParams = applyLoanTermVariations(loanApplicationTerms, scheduleParams, previousRepaymentDate, scheduledDueDate, interestRatesForInstallments, this.paymentPeriodsInOneYearCalculator, mc); scheduledDueDate = termVariationParams.getScheduledDueDate(); // Updates total days in term scheduleParams.addLoanTermInDays( Days.daysBetween(scheduleParams.getPeriodStartDate(), scheduledDueDate).getDays()); if (termVariationParams.isSkipPeriod()) { continue; } if (scheduleParams.getPeriodStartDate().isAfter(scheduledDueDate)) { throw new ScheduleDateException("Due date can't be before period start date", scheduledDueDate); } if (extendTermForDailyRepayments) { scheduleParams.setActualRepaymentDate(scheduledDueDate); } // this block is to generate the schedule till the specified // date(used for calculating preclosure) if (scheduleParams.getScheduleTillDate() != null && !scheduledDueDate.isBefore(scheduleParams.getScheduleTillDate())) { scheduledDueDate = scheduleParams.getScheduleTillDate(); isNextRepaymentAvailable = false; } if (loanApplicationTerms.isInterestRecalculationEnabled()) { populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, loanApplicationTerms, holidayDetailDTO, scheduleParams, loanCharges, currency); } // populates the collection with transactions till the due date of // the period for interest recalculation enabled loans Collection<RecalculationDetail> applicableTransactions = getApplicableTransactionsForPeriod( scheduleParams.applyInterestRecalculation(), scheduledDueDate, transactions); final double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest, scheduledDueDate, loanApplicationTerms.getInterestChargedFromLocalDate(), loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery()); ScheduleCurrentPeriodParams currentPeriodParams = new ScheduleCurrentPeriodParams(currency, interestCalculationGraceOnRepaymentPeriodFraction); if (loanApplicationTerms.isMultiDisburseLoan()) { updateBalanceBasedOnDisbursement(loanApplicationTerms, chargesDueAtTimeOfDisbursement, scheduleParams, periods, scheduledDueDate); } // process repayments to the schedule as per the repayment // transaction processor configuration // will add a new schedule with interest till the transaction date // for a loan repayment which falls between the // two periods for interest first repayment strategies handleRecalculationForNonDueDateTransactions(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, scheduleParams, periods, loanApplicationTerms.getTotalInterestDue(), idealDisbursementDate, firstRepaymentdate, lastRestDate, scheduledDueDate, periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams); if (currentPeriodParams.isSkipCurrentLoop()) { continue; } periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentdate, loanApplicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled(), loanApplicationTerms.getExpectedDisbursementDate()); // backup for pre-close transaction updateCompoundingDetails(scheduleParams, periodStartDateApplicableForInterest); // 5 determine principal,interest of repayment period PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), scheduleParams.getTotalCumulativePrincipal().minus(scheduleParams.getReducePrincipal()), scheduleParams.getTotalCumulativeInterest(), loanApplicationTerms.getTotalInterestDue(), scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(), scheduleParams.getOutstandingBalanceAsPerRest(), loanApplicationTerms, scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(scheduleParams), scheduleParams.getCompoundingMap(), periodStartDateApplicableForInterest, scheduledDueDate, interestRates); // will check for EMI amount greater than interest calculated if (loanApplicationTerms.getFixedEmiAmount() != null && loanApplicationTerms.getFixedEmiAmount() .compareTo(principalInterestForThisPeriod.interest().getAmount()) == -1) { String errorMsg = "EMI amount must be greater than : " + principalInterestForThisPeriod.interest().getAmount(); throw new MultiDisbursementEmiAmountException(errorMsg, principalInterestForThisPeriod.interest().getAmount(), loanApplicationTerms.getFixedEmiAmount()); } // update cumulative fields for principal & interest currentPeriodParams.setInterestForThisPeriod(principalInterestForThisPeriod.interest()); Money lastTotalOutstandingInterestPaymentDueToGrace = scheduleParams .getTotalOutstandingInterestPaymentDueToGrace(); scheduleParams.setTotalOutstandingInterestPaymentDueToGrace( principalInterestForThisPeriod.interestPaymentDueToGrace()); currentPeriodParams.setPrincipalForThisPeriod(principalInterestForThisPeriod.principal()); // applies early payments on principal portion updatePrincipalPortionBasedOnPreviousEarlyPayments(currency, scheduleParams, currentPeriodParams); // updates amounts with current earlyPaidAmount updateAmountsBasedOnCurrentEarlyPayments(mc, loanApplicationTerms, scheduleParams, currentPeriodParams); if (scheduleParams.getOutstandingBalance().isLessThanZero() || !isNextRepaymentAvailable) { currentPeriodParams.plusPrincipalForThisPeriod(scheduleParams.getOutstandingBalance()); scheduleParams.setOutstandingBalance(Money.zero(currency)); } if (!isNextRepaymentAvailable) { scheduleParams.getDisburseDetailMap().clear(); } // applies charges for the period applyChargesForCurrentPeriod(loanCharges, currency, scheduleParams, scheduledDueDate, currentPeriodParams); // sum up real totalInstallmentDue from components final Money totalInstallmentDue = currentPeriodParams.fetchTotalAmountForPeriod(); // if previous installment is last then add interest to same // installment if (currentPeriodParams.getLastInstallment() != null && currentPeriodParams.getPrincipalForThisPeriod().isZero()) { currentPeriodParams.getLastInstallment() .addInterestAmount(currentPeriodParams.getInterestForThisPeriod()); continue; } // create repayment period from parts LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment( scheduleParams.getInstalmentNumber(), scheduleParams.getPeriodStartDate(), scheduledDueDate, currentPeriodParams.getPrincipalForThisPeriod(), scheduleParams.getOutstandingBalance(), currentPeriodParams.getInterestForThisPeriod(), currentPeriodParams.getFeeChargesForInstallment(), currentPeriodParams.getPenaltyChargesForInstallment(), totalInstallmentDue, false); addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); // apply loan transactions on installments to identify early/late // payments for interest recalculation installment = handleRecalculationForTransactions(mc, loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, loanRepaymentScheduleTransactionProcessor, loanApplicationTerms.getTotalInterestDue(), lastRestDate, scheduledDueDate, periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams, lastTotalOutstandingInterestPaymentDueToGrace, installment, loanCharges); periods.add(installment); // Updates principal paid map with efective date for reducing // the amount from outstanding balance(interest calculation) updateAmountsWithEffectiveDate(loanApplicationTerms, holidayDetailDTO, scheduleParams, scheduledDueDate, currentPeriodParams, installment, lastRestDate); // handle cumulative fields scheduleParams.addTotalCumulativePrincipal(currentPeriodParams.getPrincipalForThisPeriod()); scheduleParams.addTotalRepaymentExpected(totalInstallmentDue); scheduleParams.addTotalCumulativeInterest(currentPeriodParams.getInterestForThisPeriod()); scheduleParams.setPeriodStartDate(scheduledDueDate); scheduleParams.incrementInstalmentNumber(); scheduleParams.incrementPeriodNumber(); if (termVariationParams.isRecalculateAmounts()) { loanApplicationTerms.setCurrentPeriodFixedEmiAmount(null); loanApplicationTerms.setCurrentPeriodFixedPrincipalAmount(null); adjustInstallmentOrPrincipalAmount(loanApplicationTerms, scheduleParams.getTotalCumulativePrincipal(), scheduleParams.getPeriodNumber(), mc); } } // this condition is to add the interest from grace period if not // already applied. if (scheduleParams.getTotalOutstandingInterestPaymentDueToGrace().isGreaterThanZero()) { LoanScheduleModelPeriod installment = ((List<LoanScheduleModelPeriod>) periods).get(periods.size() - 1); installment.addInterestAmount(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace()); scheduleParams.addTotalRepaymentExpected(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace()); scheduleParams .addTotalCumulativeInterest(scheduleParams.getTotalOutstandingInterestPaymentDueToGrace()); scheduleParams.setTotalOutstandingInterestPaymentDueToGrace(Money.zero(currency)); } // determine fees and penalties for charges which depends on total // loan interest updatePeriodsWithCharges(currency, scheduleParams, periods, nonCompoundingCharges); // this block is to add extra re-payment schedules with interest portion // if the loan not paid with in loan term if (scheduleParams.getScheduleTillDate() != null) { currentDate = scheduleParams.getScheduleTillDate(); } if (scheduleParams.applyInterestRecalculation() && scheduleParams.getLatePaymentMap().size() > 0 && currentDate.isAfter(scheduleParams.getPeriodStartDate())) { Money totalInterest = addInterestOnlyRepaymentScheduleForCurrentdate(mc, loanApplicationTerms, holidayDetailDTO, currency, periods, currentDate, loanRepaymentScheduleTransactionProcessor, transactions, loanCharges, scheduleParams); scheduleParams.addTotalCumulativeInterest(totalInterest); } loanApplicationTerms.resetFixedEmiAmount(); final BigDecimal totalPrincipalPaid = BigDecimal.ZERO; final BigDecimal totalOutstanding = BigDecimal.ZERO; updateCompoundingDetails(periods, scheduleParams, loanApplicationTerms); return LoanScheduleModel.from(periods, applicationCurrency, scheduleParams.getLoanTermInDays(), scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativePrincipal().getAmount(), totalPrincipalPaid, scheduleParams.getTotalCumulativeInterest().getAmount(), scheduleParams.getTotalFeeChargesCharged().getAmount(), scheduleParams.getTotalPenaltyChargesCharged().getAmount(), scheduleParams.getTotalRepaymentExpected().getAmount(), totalOutstanding); }
From source file:com.gst.portfolio.loanaccount.loanschedule.domain.AbstractLoanScheduleGenerator.java
License:Apache License
private void handleRecalculationForNonDueDateTransactions(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO, LoanScheduleParams scheduleParams, final Collection<LoanScheduleModelPeriod> periods, final Money totalInterestChargedForFullLoanTerm, final LocalDate idealDisbursementDate, LocalDate firstRepaymentdate, final LocalDate lastRestDate, final LocalDate scheduledDueDate, final LocalDate periodStartDateForInterest, final Collection<RecalculationDetail> applicableTransactions, final ScheduleCurrentPeriodParams currentPeriodParams) { if (scheduleParams.applyInterestRecalculation()) { final MonetaryCurrency currency = scheduleParams.getCurrency(); final Collection<LoanTermVariationsData> interestRates = loanApplicationTerms.getLoanTermVariations() .getInterestRateChanges(); boolean checkForOutstanding = true; List<RecalculationDetail> unprocessedTransactions = new ArrayList<>(); List<RecalculationDetail> processTransactions = new ArrayList<>(); LoanScheduleModelPeriod installment = null; LocalDate periodStartDateApplicableForInterest = periodStartDateForInterest; for (RecalculationDetail detail : applicableTransactions) { if (detail.isProcessed()) { continue; }//from w w w . j a v a2s . com boolean updateLatePaymentMap = false; final LocalDate transactionDate = detail.getTransactionDate(); if (transactionDate.isBefore(scheduledDueDate)) { if (scheduleParams.getLoanRepaymentScheduleTransactionProcessor() != null && scheduleParams.getLoanRepaymentScheduleTransactionProcessor() .isInterestFirstRepaymentScheduleTransactionProcessor()) { if (detail.getTransaction().isWaiver()) { processTransactions.add(detail); continue; } List<LoanTransaction> currentTransactions = new ArrayList<>(); for (RecalculationDetail processDetail : processTransactions) { currentTransactions.addAll(createCurrentTransactionList(processDetail)); } processTransactions.clear(); currentTransactions.addAll(createCurrentTransactionList(detail)); if (!transactionDate.isEqual(scheduleParams.getPeriodStartDate()) || scheduleParams.getInstalmentNumber() == 1) { int periodDays = Days.daysBetween(scheduleParams.getPeriodStartDate(), transactionDate) .getDays(); // calculates period start date for interest // calculation as per the configuration periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod( loanApplicationTerms, scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentdate, loanApplicationTerms.isInterestChargedFromDateSameAsDisbursalDateEnabled(), loanApplicationTerms.getExpectedDisbursementDate()); int daysInPeriodApplicable = Days .daysBetween(periodStartDateApplicableForInterest, transactionDate).getDays(); Money interestForThisinstallment = Money.zero(currency); if (daysInPeriodApplicable > 0) { // 5 determine interest till the transaction // date PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, currentPeriodParams.getInterestCalculationGraceOnRepaymentPeriodFraction(), scheduleParams.getTotalCumulativePrincipal() .minus(scheduleParams.getReducePrincipal()), scheduleParams.getTotalCumulativeInterest(), totalInterestChargedForFullLoanTerm, scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(), scheduleParams.getOutstandingBalanceAsPerRest(), loanApplicationTerms, scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(scheduleParams), scheduleParams.getCompoundingMap(), periodStartDateApplicableForInterest, transactionDate, interestRates); interestForThisinstallment = principalInterestForThisPeriod.interest(); scheduleParams.setTotalOutstandingInterestPaymentDueToGrace( principalInterestForThisPeriod.interestPaymentDueToGrace()); } Money principalForThisPeriod = Money.zero(currency); // applies all the applicable charges to the // newly // created installment PrincipalInterest principalInterest = new PrincipalInterest(principalForThisPeriod, interestForThisinstallment, null); Money feeChargesForInstallment = cumulativeFeeChargesDueWithin( scheduleParams.getPeriodStartDate(), transactionDate, loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(), false); Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin( scheduleParams.getPeriodStartDate(), transactionDate, loanCharges, currency, principalInterest, scheduleParams.getPrincipalToBeScheduled(), scheduleParams.getTotalCumulativeInterest(), false); // sum up real totalInstallmentDue from // components final Money totalInstallmentDue = principalForThisPeriod .plus(interestForThisinstallment).plus(feeChargesForInstallment) .plus(penaltyChargesForInstallment); // create repayment period from parts installment = LoanScheduleModelRepaymentPeriod.repayment( scheduleParams.getInstalmentNumber(), scheduleParams.getPeriodStartDate(), transactionDate, principalForThisPeriod, scheduleParams.getOutstandingBalance(), interestForThisinstallment, feeChargesForInstallment, penaltyChargesForInstallment, totalInstallmentDue, true); periods.add(installment); addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); updateCompoundingMap(loanApplicationTerms, holidayDetailDTO, scheduleParams, lastRestDate, scheduledDueDate); // update outstanding balance for interest // calculation as per the rest updateOutstandingBalanceAsPerRest(scheduleParams, transactionDate); // handle cumulative fields scheduleParams.addLoanTermInDays(periodDays); scheduleParams.addTotalRepaymentExpected(totalInstallmentDue); scheduleParams.addTotalCumulativeInterest(interestForThisinstallment); scheduleParams.addTotalFeeChargesCharged(feeChargesForInstallment); scheduleParams.addTotalPenaltyChargesCharged(penaltyChargesForInstallment); scheduleParams.setPeriodStartDate(transactionDate); periodStartDateApplicableForInterest = scheduleParams.getPeriodStartDate(); updateLatePaymentMap = true; scheduleParams.incrementInstalmentNumber(); populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, loanApplicationTerms, holidayDetailDTO, scheduleParams, loanCharges, currency); // creates and insert Loan repayment schedule // for // the period } else if (installment == null) { installment = ((List<LoanScheduleModelPeriod>) periods).get(periods.size() - 1); } // applies the transaction as per transaction // strategy // on scheduled installments to identify the // unprocessed(early payment ) amounts Money unprocessed = scheduleParams.getLoanRepaymentScheduleTransactionProcessor() .handleRepaymentSchedule(currentTransactions, currency, scheduleParams.getInstallments()); if (unprocessed.isGreaterThanZero()) { if (loanApplicationTerms.getPreClosureInterestCalculationStrategy() .calculateTillRestFrequencyEnabled()) { LocalDate applicableDate = getNextRestScheduleDate(transactionDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); checkForOutstanding = transactionDate.isEqual(applicableDate); } // reduces actual outstanding balance scheduleParams.reduceOutstandingBalance(unprocessed); // if outstanding balance becomes less than zero // then adjusts the princiapal Money addToPrinciapal = Money.zero(currency); if (!scheduleParams.getOutstandingBalance().isGreaterThanZero()) { addToPrinciapal = addToPrinciapal.plus(scheduleParams.getOutstandingBalance()); scheduleParams.setOutstandingBalance(Money.zero(currency)); currentPeriodParams.setLastInstallment(installment); } // updates principal portion map with the early // payment amounts and applicable date as per // rest updateAmountsBasedOnEarlyPayment(loanApplicationTerms, holidayDetailDTO, scheduleParams, installment, detail, unprocessed, addToPrinciapal); // method applies early payment strategy scheduleParams.addReducePrincipal(unprocessed); scheduleParams.setReducePrincipal(applyEarlyPaymentStrategy(loanApplicationTerms, scheduleParams.getReducePrincipal(), scheduleParams.getTotalCumulativePrincipal(), scheduleParams.getPeriodNumber(), mc)); } // identify late payments and add compounding // details to // map for interest calculation handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate, detail); if (updateLatePaymentMap) { updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams.getLatePaymentMap(), scheduledDueDate, scheduleParams.getInstallments(), true, lastRestDate); } } else if (scheduleParams.getLoanRepaymentScheduleTransactionProcessor() != null) { LocalDate applicableDate = getNextRestScheduleDate(transactionDate.minusDays(1), loanApplicationTerms, holidayDetailDTO); if (applicableDate.isBefore(scheduledDueDate)) { List<LoanTransaction> currentTransactions = createCurrentTransactionList(detail); Money unprocessed = scheduleParams.getLoanRepaymentScheduleTransactionProcessor() .handleRepaymentSchedule(currentTransactions, currency, scheduleParams.getInstallments()); Money arrears = fetchArrears(loanApplicationTerms, currency, detail.getTransaction()); if (unprocessed.isGreaterThanZero()) { updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), unprocessed, applicableDate); currentPeriodParams.plusEarlyPaidAmount(unprocessed); // this check is to identify pre-closure and // apply interest calculation as per // configuration for non due date payments if (!scheduleParams.getOutstandingBalance().isGreaterThan(unprocessed) && !loanApplicationTerms.getPreClosureInterestCalculationStrategy() .calculateTillRestFrequencyEnabled()) { scheduleParams.getCompoundingDateVariations().put( periodStartDateApplicableForInterest, new TreeMap<>(scheduleParams.getCompoundingMap())); LocalDate calculateTill = transactionDate; PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod( this.paymentPeriodsInOneYearCalculator, currentPeriodParams .getInterestCalculationGraceOnRepaymentPeriodFraction(), scheduleParams.getTotalCumulativePrincipal() .minus(scheduleParams.getReducePrincipal()), scheduleParams.getTotalCumulativeInterest(), totalInterestChargedForFullLoanTerm, scheduleParams.getTotalOutstandingInterestPaymentDueToGrace(), scheduleParams.getOutstandingBalanceAsPerRest(), loanApplicationTerms, scheduleParams.getPeriodNumber(), mc, mergeVariationsToMap(scheduleParams), scheduleParams.getCompoundingMap(), periodStartDateApplicableForInterest, calculateTill, interestRates); if (!principalInterestForThisPeriod.interest() .plus(principalInterestForThisPeriod.interestPaymentDueToGrace()) .plus(scheduleParams.getOutstandingBalance()) .isGreaterThan(unprocessed)) { currentPeriodParams.minusEarlyPaidAmount(unprocessed); updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), unprocessed.negated(), applicableDate); LoanTransaction loanTransaction = LoanTransaction.repayment(null, unprocessed, null, transactionDate, null, DateUtils.getLocalDateTimeOfTenant(), null); RecalculationDetail recalculationDetail = new RecalculationDetail( transactionDate, loanTransaction); unprocessedTransactions.add(recalculationDetail); break; } } LoanTransaction loanTransaction = LoanTransaction.repayment(null, unprocessed, null, scheduledDueDate, null, DateUtils.getLocalDateTimeOfTenant(), null); RecalculationDetail recalculationDetail = new RecalculationDetail(scheduledDueDate, loanTransaction); unprocessedTransactions.add(recalculationDetail); checkForOutstanding = false; scheduleParams.reduceOutstandingBalance(unprocessed); // if outstanding balance becomes less than // zero // then adjusts the princiapal Money addToPrinciapal = Money.zero(currency); if (scheduleParams.getOutstandingBalance().isLessThanZero()) { addToPrinciapal = addToPrinciapal.plus(scheduleParams.getOutstandingBalance()); scheduleParams.setOutstandingBalance(Money.zero(currency)); updateMapWithAmount(scheduleParams.getPrincipalPortionMap(), addToPrinciapal, applicableDate); currentPeriodParams.plusEarlyPaidAmount(addToPrinciapal); } } if (arrears.isGreaterThanZero() && applicableDate.isBefore(lastRestDate)) { handleLatePayments(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, lastRestDate, detail); } } } } } applicableTransactions.addAll(unprocessedTransactions); if (checkForOutstanding && scheduleParams.getOutstandingBalance().isZero() && scheduleParams.getDisburseDetailMap().isEmpty()) { currentPeriodParams.setSkipCurrentLoop(true); } } }
From source file:com.gst.portfolio.loanaccount.loanschedule.domain.AbstractLoanScheduleGenerator.java
License:Apache License
private void updateLatePaymentCompoundingAmount(final Map<LocalDate, Money> principalVariationMap, final Map<LocalDate, Money> latePaymentCompoundingMap, final MonetaryCurrency currency, final LocalDate lastRestDate, Money compoundedPortion, final LocalDate applicableDate) { Money appliedOnPrincipalVariationMap = Money.zero(currency); Map<LocalDate, Money> temp = new HashMap<>(); for (LocalDate date : latePaymentCompoundingMap.keySet()) { if (date.isBefore(lastRestDate)) { Money money = latePaymentCompoundingMap.get(date); appliedOnPrincipalVariationMap = appliedOnPrincipalVariationMap.plus(money); if (appliedOnPrincipalVariationMap.isLessThan(compoundedPortion)) { if (date.isBefore(applicableDate)) { updateMapWithAmount(principalVariationMap, money.negated(), date); updateMapWithAmount(principalVariationMap, money, applicableDate); }/*from w w w. j a v a 2s. co m*/ } else if (temp.isEmpty()) { Money diff = money.minus(appliedOnPrincipalVariationMap.minus(compoundedPortion)); updateMapWithAmount(principalVariationMap, diff.negated(), date); updateMapWithAmount(principalVariationMap, diff, applicableDate); updateMapWithAmount(temp, money.minus(diff), date); updateMapWithAmount(temp, money.minus(diff).negated(), lastRestDate); } else { updateMapWithAmount(temp, money, date); updateMapWithAmount(temp, money.negated(), lastRestDate); } } } latePaymentCompoundingMap.clear(); latePaymentCompoundingMap.putAll(temp); }
From source file:com.gst.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 ww w .j a v a 2 s . c om*/ */ 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) { latePaymentMap.clear(); LocalDate currentDate = DateUtils.getLocalDateOfTenant(); Money totalCompoundingAmount = Money.zero(currency); 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)); } } } if (totalCompoundingAmount.isGreaterThanZero()) { updateMapWithAmount(latePaymentMap, totalCompoundingAmount.negated(), lastRestDate); } }
From source file:com.gst.portfolio.loanaccount.loanschedule.domain.AbstractLoanScheduleGenerator.java
License:Apache License
private void populateCompoundingDatesInPeriod(final LocalDate startDate, final LocalDate endDate, final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO, final LoanScheduleParams scheduleParams, final Set<LoanCharge> charges, MonetaryCurrency currency) { if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) { final Map<LocalDate, Money> compoundingMap = scheduleParams.getCompoundingMap(); LocalDate lastCompoundingDate = startDate; LocalDate compoundingDate = startDate; boolean addUncompounded = true; while (compoundingDate.isBefore(endDate)) { if (loanApplicationTerms.allowCompoundingOnEod()) { compoundingDate = compoundingDate.minusDays(1); }// w w w . j av a 2 s . c o m compoundingDate = getNextCompoundScheduleDate(compoundingDate, loanApplicationTerms, holidayDetailDTO); if (compoundingDate.isBefore(endDate)) { Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(lastCompoundingDate, compoundingDate, charges, currency, null, loanApplicationTerms.getPrincipal(), null, false); Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(lastCompoundingDate, compoundingDate, charges, currency, null, loanApplicationTerms.getPrincipal(), null, false); Money compoundAmount = feeChargesForInstallment.plus(penaltyChargesForInstallment); if (addUncompounded) { compoundAmount = compoundAmount.plus(scheduleParams.getUnCompoundedAmount()); addUncompounded = false; } updateMapWithAmount(compoundingMap, compoundAmount, compoundingDate); } lastCompoundingDate = compoundingDate; } } }
From source file:com.gst.portfolio.loanaccount.loanschedule.domain.AbstractLoanScheduleGenerator.java
License:Apache License
/** * calculates Interest stating date as per the settings * //from www . ja va 2 s . com * @param firstRepaymentdate * TODO * @param boolean1 * @param localDate */ private LocalDate calculateInterestStartDateForPeriod(final LoanApplicationTerms loanApplicationTerms, LocalDate periodStartDate, final LocalDate idealDisbursementDate, final LocalDate firstRepaymentdate, final Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled, final LocalDate expectedDisbursementDate) { 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 (loanApplicationTerms.getInterestChargedFromLocalDate() == null && isInterestChargedFromDateSameAsDisbursalDateEnabled) { periodStartDateApplicableForInterest = expectedDisbursementDate; } else if (periodStartDate.isEqual(loanApplicationTerms.getExpectedDisbursementDate())) { periodStartDateApplicableForInterest = idealDisbursementDate; } } return periodStartDateApplicableForInterest; }
From source file:com.gst.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms.java
License:Apache License
private BigDecimal calculatePeriodsInLoanTerm() { BigDecimal periodsInLoanTerm = BigDecimal.valueOf(this.loanTermFrequency); switch (this.interestCalculationPeriodMethod) { case DAILY://from w ww. j av a2s. 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: if (this.allowPartialPeriodInterestCalcualtion) { LocalDate startDate = getExpectedDisbursementDate(); if (getInterestChargedFromDate() != null) { startDate = getInterestChargedFromLocalDate(); } periodsInLoanTerm = calculatePeriodsBetweenDates(startDate, this.loanEndDate); } break; } return periodsInLoanTerm; }
From source file:com.gst.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);// ww w.jav a 2 s .com 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); } else { if (repaymentPeriodFrequencyType == PeriodFrequencyType.MONTHS && nthDay != null && nthDay != NthDayType.INVALID.getValue()) { LocalDate calendarStartDate = repaymentsStartingFromDate; if (calendarStartDate == null) calendarStartDate = expectedDisbursementDate; calendar = createLoanCalendar(calendarStartDate, repaymentEvery, CalendarFrequencyType.MONTHLY, dayOfWeek, nthDay); } } /* * 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 */ final Long groupId = this.fromApiJsonHelper.extractLongNamed("groupId", element); Group group = null; if (groupId != null) { group = this.groupRepository.findOneWithNotFoundDetection(groupId); } Boolean isSkipMeetingOnFirstDay = false; Integer numberOfDays = 0; boolean isSkipRepaymentOnFirstMonthEnabled = configurationDomainService .isSkippingMeetingOnFirstDayOfMonthEnabled(); if (isSkipRepaymentOnFirstMonthEnabled) { isSkipMeetingOnFirstDay = this.loanUtilService.isLoanRepaymentsSyncWithMeeting(group, calendar); if (isSkipMeetingOnFirstDay) { numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate() .intValue(); } } if ((loanType.isJLGAccount() || loanType.isGroupAccount()) && calendar != null) { validateRepaymentsStartDateWithMeetingDates(calculatedRepaymentsStartingFromDate, calendar, isSkipMeetingOnFirstDay, numberOfDays); /* * If disbursement is synced on meeting, make sure disbursement date * is on a meeting date */ if (synchDisbursement != null && synchDisbursement.booleanValue()) { validateDisbursementDateWithMeetingDates(expectedDisbursementDate, calendar, isSkipMeetingOnFirstDay, numberOfDays); } } validateMinimumDaysBetweenDisbursalAndFirstRepayment(expectedDisbursementDate, calculatedRepaymentsStartingFromDate, loanProduct.getMinimumDaysBetweenDisbursalAndFirstRepayment()); // grace details final Integer graceOnPrincipalPayment = this.fromApiJsonHelper .extractIntegerWithLocaleNamed("graceOnPrincipalPayment", element); final Integer recurringMoratoriumOnPrincipalPeriods = this.fromApiJsonHelper .extractIntegerWithLocaleNamed("recurringMoratoriumOnPrincipalPeriods", 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 Boolean isInterestChargedFromDateSameAsDisbursalDateEnabled = this.configurationDomainService .isInterestChargedFromDateSameAsDisbursementDate(); 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; InterestRecalculationCompoundingMethod compoundingMethod = null; boolean allowCompoundingOnEod = false; if (isInterestRecalculationEnabled) { LoanProductInterestRecalculationDetails loanProductInterestRecalculationDetails = loanProduct .getProductInterestRecalculationDetails(); recalculationFrequencyType = loanProductInterestRecalculationDetails.getRestFrequencyType(); Integer repeatsOnDay = null; Integer recalculationFrequencyNthDay = loanProductInterestRecalculationDetails.getRestFrequencyOnDay(); if (recalculationFrequencyNthDay == null) { recalculationFrequencyNthDay = loanProductInterestRecalculationDetails.getRestFrequencyNthDay(); repeatsOnDay = loanProductInterestRecalculationDetails.getRestFrequencyWeekday(); } Integer frequency = loanProductInterestRecalculationDetails.getRestInterval(); if (recalculationFrequencyType.isSameAsRepayment()) { restCalendarInstance = createCalendarForSameAsRepayment(repaymentEvery, repaymentPeriodFrequencyType, expectedDisbursementDate); } else { LocalDate calendarStartDate = expectedDisbursementDate; restCalendarInstance = createInterestRecalculationCalendarInstance(calendarStartDate, recalculationFrequencyType, frequency, recalculationFrequencyNthDay, repeatsOnDay); } compoundingMethod = InterestRecalculationCompoundingMethod .fromInt(loanProductInterestRecalculationDetails.getInterestRecalculationCompoundingMethod()); if (compoundingMethod.isCompoundingEnabled()) { Integer compoundingRepeatsOnDay = null; Integer recalculationCompoundingFrequencyNthDay = loanProductInterestRecalculationDetails .getCompoundingFrequencyOnDay(); if (recalculationCompoundingFrequencyNthDay == null) { recalculationCompoundingFrequencyNthDay = loanProductInterestRecalculationDetails .getCompoundingFrequencyNthDay(); compoundingRepeatsOnDay = loanProductInterestRecalculationDetails .getCompoundingFrequencyWeekday(); } compoundingFrequencyType = loanProductInterestRecalculationDetails.getCompoundingFrequencyType(); if (compoundingFrequencyType.isSameAsRepayment()) { compoundingCalendarInstance = createCalendarForSameAsRepayment(repaymentEvery, repaymentPeriodFrequencyType, expectedDisbursementDate); } else { LocalDate calendarStartDate = expectedDisbursementDate; compoundingCalendarInstance = createInterestRecalculationCalendarInstance(calendarStartDate, compoundingFrequencyType, loanProductInterestRecalculationDetails.getCompoundingInterval(), recalculationCompoundingFrequencyNthDay, compoundingRepeatsOnDay); } allowCompoundingOnEod = loanProductInterestRecalculationDetails.allowCompoundingOnEod(); } } 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); } } final Long clientId = this.fromApiJsonHelper.extractLongNamed("clientId", element); Client client = null; Long officeId = null; if (clientId != null) { client = this.clientRepository.findOneWithNotFoundDetection(clientId); officeId = client.getOffice().getId(); } else if (groupId != null) { group = this.groupRepository.findOneWithNotFoundDetection(groupId); officeId = group.getOffice().getId(); } final boolean isHolidayEnabled = this.configurationDomainService.isRescheduleRepaymentsOnHolidaysEnabled(); final List<Holiday> holidays = this.holidayRepository.findByOfficeIdAndGreaterThanDate(officeId, expectedDisbursementDate.toDate(), HolidayStatusType.ACTIVE.getValue()); final WorkingDays workingDays = this.workingDaysRepository.findOne(); HolidayDetailDTO detailDTO = new HolidayDetailDTO(isHolidayEnabled, holidays, workingDays); return LoanApplicationTerms.assembleFrom(applicationCurrency, loanTermFrequency, loanTermPeriodFrequencyType, numberOfRepayments, repaymentEvery, repaymentPeriodFrequencyType, nthDay, weekDayType, amortizationMethod, interestMethod, interestRatePerPeriod, interestRatePeriodFrequencyType, annualNominalInterestRate, interestCalculationPeriodMethod, allowPartialPeriodInterestCalcualtion, principalMoney, expectedDisbursementDate, repaymentsStartingFromDate, calculatedRepaymentsStartingFromDate, graceOnPrincipalPayment, recurringMoratoriumOnPrincipalPeriods, graceOnInterestPayment, graceOnInterestCharged, interestChargedFromDate, inArrearsToleranceMoney, loanProduct.isMultiDisburseLoan(), emiAmount, disbursementDatas, maxOutstandingBalance, graceOnArrearsAgeing, daysInMonthType, daysInYearType, isInterestRecalculationEnabled, recalculationFrequencyType, restCalendarInstance, compoundingMethod, compoundingCalendarInstance, compoundingFrequencyType, principalThresholdForLastInstalment, installmentAmountInMultiplesOf, loanProduct.preCloseInterestCalculationStrategy(), calendar, BigDecimal.ZERO, loanTermVariations, isInterestChargedFromDateSameAsDisbursalDateEnabled, numberOfDays, isSkipMeetingOnFirstDay, detailDTO, allowCompoundingOnEod); }
From source file:com.gst.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; }/* www. ja v a2s .co m*/ 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().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(); } Boolean isSkipRepaymentOnFirstMonth = false; Integer numberOfDays = 0; boolean isSkipRepaymentOnFirstMonthEnabled = configurationDomainService .isSkippingMeetingOnFirstDayOfMonthEnabled(); if (isSkipRepaymentOnFirstMonthEnabled) { isSkipRepaymentOnFirstMonth = this.loanUtilService.isLoanRepaymentsSyncWithMeeting(loan.group(), loanCalendar); if (isSkipRepaymentOnFirstMonth) { numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate() .intValue(); } } 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, 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().toDate()); } final LocalDate recalculateFrom = null; ScheduleGeneratorDTO scheduleGeneratorDTO = this.loanUtilService.buildScheduleGeneratorDTO(loan, recalculateFrom); AppUser currentUser = this.context.getAuthenticatedUserIfPresent(); loan.regenerateRepaymentSchedule(scheduleGeneratorDTO, currentUser); }
From source file:com.gst.portfolio.loanaccount.loanschedule.service.LoanScheduleAssembler.java
License:Apache License
private LocalDate deriveFirstRepaymentDateForLoans(final Integer repaymentEvery, final LocalDate expectedDisbursementDate, final LocalDate refernceDateForCalculatingFirstRepaymentDate, final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer minimumDaysBetweenDisbursalAndFirstRepayment, final Calendar calendar) { boolean isMeetingSkipOnFirstDayOfMonth = configurationDomainService .isSkippingMeetingOnFirstDayOfMonthEnabled(); int numberOfDays = configurationDomainService.retreivePeroidInNumberOfDaysForSkipMeetingDate().intValue(); final String frequency = CalendarUtils .getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType); final LocalDate derivedFirstRepayment = CalendarUtils.getFirstRepaymentMeetingDate(calendar, refernceDateForCalculatingFirstRepaymentDate, repaymentEvery, frequency, isMeetingSkipOnFirstDayOfMonth, numberOfDays); final LocalDate minimumFirstRepaymentDate = expectedDisbursementDate .plusDays(minimumDaysBetweenDisbursalAndFirstRepayment); return minimumFirstRepaymentDate.isBefore(derivedFirstRepayment) ? derivedFirstRepayment : deriveFirstRepaymentDateForLoans(repaymentEvery, expectedDisbursementDate, derivedFirstRepayment, repaymentPeriodFrequencyType, minimumDaysBetweenDisbursalAndFirstRepayment, calendar); }