List of usage examples for org.joda.time Days daysBetween
public static Days daysBetween(ReadablePartial start, ReadablePartial end)
Days
representing the number of whole days between the two specified partial datetimes. 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 .ja va 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 av a 2 s . c om*/ 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 LoanScheduleDTO rescheduleNextInstallments(final MathContext mc, final LoanApplicationTerms loanApplicationTerms, Loan loan, final HolidayDetailDTO holidayDetailDTO, final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor, final LocalDate rescheduleFrom, final LocalDate scheduleTillDate) { // Loan transactions to process and find the variation on payments Collection<RecalculationDetail> recalculationDetails = new ArrayList<>(); List<LoanTransaction> transactions = loan.getLoanTransactions(); for (LoanTransaction loanTransaction : transactions) { if (loanTransaction.isPaymentTransaction()) { recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(), LoanTransaction.copyTransactionProperties(loanTransaction))); }//from w ww . j a va 2 s . co m } 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( loan.charges()); periods = createNewLoanScheduleListWithDisbursementDetails( loanApplicationTerms.fetchNumberOfRepaymentsAfterExceptions(), loanApplicationTerms, chargesDueAtTimeOfDisbursement); 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(); Money uncompoundedAmount = 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<>(); final Map<LocalDate, Map<LocalDate, Money>> compoundingDateVariations = new HashMap<>(); LocalDate currentDate = DateUtils.getLocalDateOfTenant(); LocalDate lastRestDate = currentDate; if (loanApplicationTerms.isInterestRecalculationEnabled()) { 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); // count periods without interest grace to exclude for flat loan calculations 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; List<LoanTermVariationsData> exceptionDataList = loanApplicationTerms.getLoanTermVariations() .getExceptionData(); final ListIterator<LoanTermVariationsData> exceptionDataListIterator = exceptionDataList.listIterator(); LoanTermVariationParams loanTermVariationParams = null; // identify retain installments final List<LoanRepaymentScheduleInstallment> processInstallmentsInstallments = fetchRetainedInstallments( loan.getRepaymentScheduleInstallments(), rescheduleFrom, currency); final List<LoanRepaymentScheduleInstallment> newRepaymentScheduleInstallments = new ArrayList<>(); // 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 : processInstallmentsInstallments) { // 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)) { if (totalCumulativePrincipal .isGreaterThanOrEqualTo(loanApplicationTerms.getTotalDisbursedAmount())) { break; } ArrayList<LoanTermVariationsData> dueDateVariationsDataList = new ArrayList<>(); // check for date changes do { actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate( actualRepaymentDate, loanApplicationTerms, isFirstRepayment, holidayDetailDTO); isFirstRepayment = false; lastInstallmentDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate, loanApplicationTerms, holidayDetailDTO); while (loanApplicationTerms.getLoanTermVariations() .hasDueDateVariation(lastInstallmentDate)) { LoanTermVariationsData variation = loanApplicationTerms.getLoanTermVariations() .nextDueDateVariation(); if (!variation.isSpecificToInstallment()) { actualRepaymentDate = variation.getDateValue(); /*if (!isDueDateChangeApplied) { previousRepaymentDate = actualRepaymentDate; isDueDateChangeApplied = true; }*/ lastInstallmentDate = actualRepaymentDate; } dueDateVariationsDataList.add(variation); } loanTermVariationParams = applyExceptionLoanTermVariations(loanApplicationTerms, lastInstallmentDate, exceptionDataListIterator, instalmentNumber, totalCumulativePrincipal, totalCumulativeInterest, mc); } while (loanTermVariationParams != null && loanTermVariationParams.isSkipPeriod()); /*if (!lastInstallmentDate.isBefore(rescheduleFrom)) { actualRepaymentDate = previousRepaymentDate; if(isDueDateChangeApplied){ int numberOfDateChangesApplied = dueDateVariationsDataList.size(); while(numberOfDateChangesApplied >0 ){ loanApplicationTerms.getLoanTermVariations().previousDueDateVariation(); numberOfDateChangesApplied--; } } break; }*/ periodNumber++; for (LoanTermVariationsData dueDateVariation : dueDateVariationsDataList) { dueDateVariation.setProcessed(true); } if (loanTermVariationParams != null && loanTermVariationParams.isSkipPeriod()) { ArrayList<LoanTermVariationsData> variationsDataList = loanTermVariationParams .getVariationsDataList(); for (LoanTermVariationsData variationsData : variationsDataList) { variationsData.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(); if (loanApplicationTerms.isInterestRecalculationEnabled()) { // 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 = null; if (loanApplicationTerms.getRestCalendarInstance() != null) { 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); uncompoundedAmount = updateCompoundingDetailsForPartialScheduleGeneration(installment, loanApplicationTerms, principalPortionMap, compoundingDateVariations, uncompoundedAmount, applicableTransactions, lastRestDate, holidayDetailDTO); // update outstanding balance for interest calculation outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(principalPortionMap, installment.getDueDate(), outstandingBalanceAsPerRest, false); outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(disburseDetailMap, installment.getDueDate(), outstandingBalanceAsPerRest, true); // updates the map with over due amounts updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap, lastInstallmentDate, newRepaymentScheduleInstallments, true, lastRestDate); } else { outstandingBalanceAsPerRest = outstandingBalance; } } totalRepaymentExpected = totalCumulativePrincipal.plus(totalCumulativeInterest) .plus(totalFeeChargesCharged).plus(totalPenaltyChargesCharged); // 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, uncompoundedAmount, disburseDetailMap, principalToBeScheduled, outstandingBalance, outstandingBalanceAsPerRest, newRepaymentScheduleInstallments, recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, currency, applyInterestRecalculation); retainedInstallments.addAll(newRepaymentScheduleInstallments); loanScheduleParams.getCompoundingDateVariations().putAll(compoundingDateVariations); loanApplicationTerms.updateTotalInterestDue( Money.of(currency, loan.getLoanSummary().getTotalInterestCharged())); } else { loanApplicationTerms.getLoanTermVariations().resetVariations(); } } // for complete schedule generation if (loanScheduleParams == null) { loanScheduleParams = LoanScheduleParams.createLoanScheduleParamsForCompleteUpdate(recalculationDetails, loanRepaymentScheduleTransactionProcessor, scheduleTillDate, applyInterestRecalculation); periods.clear(); } LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, loan.charges(), 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:com.gst.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 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; }/* ww w.j av a 2 s . co m*/ interestRates.put(fromDate, loanTermVariation.getDecimalValue()); if (!principalVariation.containsKey(fromDate)) { principalVariation.put(fromDate, balanceForInterestCalculation.zero()); } } } if (principalVariation != null) { 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()); compoundingMap.put(principal.getKey(), interestToBeCompounded.plus(compoundFee)); } balanceForInterestCalculation = balanceForInterestCalculation.plus(principal.getValue()) .plus(compoundFee); if (interestRates.containsKey(principal.getKey())) { loanApplicationTerms.updateAnnualNominalInterestRate(interestRates.get(principal.getKey())); } } } } 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:com.gst.portfolio.loanaccount.loanschedule.domain.DefaultPaymentPeriodsInOneYearCalculator.java
License:Apache License
@Override public double calculatePortionOfRepaymentPeriodInterestChargingGrace(final LocalDate repaymentPeriodStartDate, final LocalDate scheduledDueDate, final LocalDate interestChargedFromLocalDate, final PeriodFrequencyType repaymentPeriodFrequencyType, final Integer repaidEvery) { Double periodFraction = Double.valueOf("0.0"); final LocalDateInterval repaymentPeriod = new LocalDateInterval(repaymentPeriodStartDate, scheduledDueDate); if (interestChargedFromLocalDate != null && repaymentPeriod.fallsBefore(interestChargedFromLocalDate.plusDays(1))) { periodFraction = Double.valueOf("1.0"); } else if (interestChargedFromLocalDate != null && repaymentPeriod.contains(interestChargedFromLocalDate)) { final int numberOfDaysInterestCalculationGraceInPeriod = Days .daysBetween(repaymentPeriodStartDate, interestChargedFromLocalDate).getDays(); periodFraction = calculateRepaymentPeriodFraction(repaymentPeriodFrequencyType, repaidEvery, numberOfDaysInterestCalculationGraceInPeriod); }// www. j a v a 2 s .c o m return periodFraction; }
From source file:com.gst.portfolio.loanaccount.loanschedule.domain.DefaultScheduledDateGenerator.java
License:Apache License
@Override public Boolean isDateFallsInSchedule(final PeriodFrequencyType frequency, final int repaidEvery, final LocalDate startDate, final LocalDate date) { boolean isScheduledDate = false; switch (frequency) { case DAYS:/*from ww w . j a va 2 s.c o m*/ int diff = Days.daysBetween(startDate, date).getDays(); isScheduledDate = (diff % repaidEvery) == 0; break; case WEEKS: int weekDiff = Weeks.weeksBetween(startDate, date).getWeeks(); isScheduledDate = (weekDiff % repaidEvery) == 0; if (isScheduledDate) { LocalDate modifiedDate = startDate.plusWeeks(weekDiff); isScheduledDate = modifiedDate.isEqual(date); } break; case MONTHS: int monthDiff = Months.monthsBetween(startDate, date).getMonths(); isScheduledDate = (monthDiff % repaidEvery) == 0; if (isScheduledDate) { LocalDate modifiedDate = startDate.plusMonths(monthDiff); isScheduledDate = modifiedDate.isEqual(date); } break; case YEARS: int yearDiff = Years.yearsBetween(startDate, date).getYears(); isScheduledDate = (yearDiff % repaidEvery) == 0; if (isScheduledDate) { LocalDate modifiedDate = startDate.plusYears(yearDiff); isScheduledDate = modifiedDate.isEqual(date); } break; case INVALID: break; } return isScheduledDate; }
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 www. j av a2s . c o 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.domain.LoanApplicationTerms.java
License:Apache License
public BigDecimal calculatePeriodsBetweenDates(final LocalDate startDate, final LocalDate endDate) { BigDecimal numberOfPeriods = BigDecimal.ZERO; switch (this.repaymentPeriodFrequencyType) { case DAYS://from ww w . j a va 2 s .c om int numOfDays = Days.daysBetween(startDate, endDate).getDays(); numberOfPeriods = BigDecimal.valueOf((double) numOfDays); break; case WEEKS: int numberOfWeeks = Weeks.weeksBetween(startDate, endDate).getWeeks(); int daysLeftAfterWeeks = Days.daysBetween(startDate.plusWeeks(numberOfWeeks), endDate).getDays(); numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfWeeks)) .add(BigDecimal.valueOf((double) daysLeftAfterWeeks / 7)); break; case MONTHS: int numberOfMonths = Months.monthsBetween(startDate, endDate).getMonths(); LocalDate startDateAfterConsideringMonths = null; LocalDate endDateAfterConsideringMonths = null; int diffDays = 0; if (this.loanCalendar == null) { startDateAfterConsideringMonths = startDate.plusMonths(numberOfMonths); startDateAfterConsideringMonths = CalendarUtils.adjustDate(startDateAfterConsideringMonths, getSeedDate(), this.repaymentPeriodFrequencyType); endDateAfterConsideringMonths = startDate.plusMonths(numberOfMonths + 1); endDateAfterConsideringMonths = CalendarUtils.adjustDate(endDateAfterConsideringMonths, getSeedDate(), this.repaymentPeriodFrequencyType); } else { LocalDate expectedStartDate = startDate; if (!CalendarUtils.isValidRedurringDate(loanCalendar.getRecurrence(), loanCalendar.getStartDateLocalDate().minusMonths(getRepaymentEvery()), startDate)) { expectedStartDate = CalendarUtils.getNewRepaymentMeetingDate(loanCalendar.getRecurrence(), startDate.minusMonths(getRepaymentEvery()), startDate.minusMonths(getRepaymentEvery()), getRepaymentEvery(), CalendarUtils .getMeetingFrequencyFromPeriodFrequencyType(getLoanTermPeriodFrequencyType()), this.holidayDetailDTO.getWorkingDays(), isSkipRepaymentOnFirstDayOfMonth, numberOfDays); } if (!expectedStartDate.isEqual(startDate)) { diffDays = Days.daysBetween(startDate, expectedStartDate).getDays(); } if (numberOfMonths == 0) { startDateAfterConsideringMonths = expectedStartDate; } else { startDateAfterConsideringMonths = CalendarUtils.getNewRepaymentMeetingDate( loanCalendar.getRecurrence(), expectedStartDate, expectedStartDate.plusMonths(numberOfMonths), getRepaymentEvery(), CalendarUtils .getMeetingFrequencyFromPeriodFrequencyType(getLoanTermPeriodFrequencyType()), this.holidayDetailDTO.getWorkingDays(), isSkipRepaymentOnFirstDayOfMonth, numberOfDays); } endDateAfterConsideringMonths = CalendarUtils.getNewRepaymentMeetingDate( loanCalendar.getRecurrence(), startDateAfterConsideringMonths, startDateAfterConsideringMonths.plusDays(1), getRepaymentEvery(), CalendarUtils.getMeetingFrequencyFromPeriodFrequencyType(getLoanTermPeriodFrequencyType()), this.holidayDetailDTO.getWorkingDays(), isSkipRepaymentOnFirstDayOfMonth, numberOfDays); } int daysLeftAfterMonths = Days.daysBetween(startDateAfterConsideringMonths, endDate).getDays() + diffDays; int daysInPeriodAfterMonths = Days .daysBetween(startDateAfterConsideringMonths, endDateAfterConsideringMonths).getDays(); numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfMonths)) .add(BigDecimal.valueOf((double) daysLeftAfterMonths / daysInPeriodAfterMonths)); break; case YEARS: int numberOfYears = Years.yearsBetween(startDate, endDate).getYears(); LocalDate startDateAfterConsideringYears = startDate.plusYears(numberOfYears); LocalDate endDateAfterConsideringYears = startDate.plusYears(numberOfYears + 1); int daysLeftAfterYears = Days.daysBetween(startDateAfterConsideringYears, endDate).getDays(); int daysInPeriodAfterYears = Days .daysBetween(startDateAfterConsideringYears, endDateAfterConsideringYears).getDays(); numberOfPeriods = numberOfPeriods.add(BigDecimal.valueOf(numberOfYears)) .add(BigDecimal.valueOf((double) daysLeftAfterYears / daysInPeriodAfterYears)); break; default: break; } return numberOfPeriods; }
From source file:com.gst.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms.java
License:Apache License
private BigDecimal periodicInterestRate(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc, final DaysInMonthType daysInMonthType, final DaysInYearType daysInYearType, LocalDate periodStartDate, LocalDate periodEndDate) { final long loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0")); final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear); BigDecimal periodicInterestRate = BigDecimal.ZERO; BigDecimal loanTermFrequencyBigDecimal = calculateLoanTermFrequency(periodStartDate, periodEndDate); switch (this.interestCalculationPeriodMethod) { case INVALID: break;//from ww w. jav a 2 s . c o m case DAILY: // For daily work out number of days in the period BigDecimal numberOfDaysInPeriod = BigDecimal .valueOf(Days.daysBetween(periodStartDate, periodEndDate).getDays()); final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate .divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc); switch (this.repaymentPeriodFrequencyType) { case INVALID: break; case DAYS: periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc); break; case WEEKS: periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc); break; case MONTHS: if (daysInMonthType.isDaysInMonth_30()) { numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(30), mc); } periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc); break; case YEARS: switch (daysInYearType) { case DAYS_360: numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(360), mc); break; case DAYS_364: numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(364), mc); break; case DAYS_365: numberOfDaysInPeriod = loanTermFrequencyBigDecimal.multiply(BigDecimal.valueOf(365), mc); break; default: break; } periodicInterestRate = oneDayOfYearInterestRate.multiply(numberOfDaysInPeriod, mc); break; } break; case SAME_AS_REPAYMENT_PERIOD: periodicInterestRate = this.annualNominalInterestRate.divide(loanTermPeriodsInYearBigDecimal, mc) .divide(divisor, mc).multiply(loanTermFrequencyBigDecimal); break; } return periodicInterestRate; }
From source file:com.gst.portfolio.loanaccount.loanschedule.domain.LoanApplicationTerms.java
License:Apache License
public BigDecimal interestRateFor(final PaymentPeriodsInOneYearCalculator calculator, final MathContext mc, final Money outstandingBalance, final LocalDate fromDate, final LocalDate toDate) { long loanTermPeriodsInOneYear = calculator.calculate(PeriodFrequencyType.DAYS).longValue(); int repaymentEvery = Days.daysBetween(fromDate, toDate).getDays(); if (isFallingInRepaymentPeriod(fromDate, toDate)) { loanTermPeriodsInOneYear = calculatePeriodsInOneYear(calculator); repaymentEvery = getPeriodsBetween(fromDate, toDate); }// w ww .j a va 2 s. c om final BigDecimal divisor = BigDecimal.valueOf(Double.valueOf("100.0")); final BigDecimal loanTermPeriodsInYearBigDecimal = BigDecimal.valueOf(loanTermPeriodsInOneYear); final BigDecimal oneDayOfYearInterestRate = this.annualNominalInterestRate .divide(loanTermPeriodsInYearBigDecimal, mc).divide(divisor, mc); BigDecimal interestRate = oneDayOfYearInterestRate.multiply(BigDecimal.valueOf(repaymentEvery), mc); return outstandingBalance.getAmount().multiply(interestRate, mc); }