List of usage examples for org.joda.time LocalDate isBefore
public boolean isBefore(ReadablePartial partial)
From source file:org.apache.fineract.portfolio.loanaccount.domain.Loan.java
License:Apache License
public void updateLoanRepaymentScheduleDates(final LocalDate meetingStartDate, final String recuringRule, final boolean isHolidayEnabled, final List<Holiday> holidays, final WorkingDays workingDays) { // first repayment's from date is same as disbursement date. LocalDate tmpFromDate = getDisbursementDate(); final PeriodFrequencyType repaymentPeriodFrequencyType = this.loanRepaymentScheduleDetail .getRepaymentPeriodFrequencyType(); final Integer loanRepaymentInterval = this.loanRepaymentScheduleDetail.getRepayEvery(); final String frequency = CalendarUtils .getMeetingFrequencyFromPeriodFrequencyType(repaymentPeriodFrequencyType); LocalDate newRepaymentDate = null; LocalDate seedDate = meetingStartDate; LocalDate latestRepaymentDate = null; for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) { LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate(); // FIXME: AA this won't update repayment dates before current date. if (oldDueDate.isAfter(seedDate) && oldDueDate.isAfter(DateUtils.getLocalDateOfTenant())) { newRepaymentDate = CalendarUtils.getNewRepaymentMeetingDate(recuringRule, seedDate, oldDueDate, loanRepaymentInterval, frequency, workingDays); final LocalDate maxDateLimitForNewRepayment = getMaxDateLimitForNewRepayment( repaymentPeriodFrequencyType, loanRepaymentInterval, tmpFromDate); if (newRepaymentDate.isAfter(maxDateLimitForNewRepayment)) { newRepaymentDate = CalendarUtils.getNextRepaymentMeetingDate(recuringRule, seedDate, tmpFromDate, loanRepaymentInterval, frequency, workingDays); }/*from ww w .j a v a 2 s .c o m*/ if (isHolidayEnabled) { newRepaymentDate = HolidayUtil.getRepaymentRescheduleDateToIfHoliday(newRepaymentDate, holidays); } if (latestRepaymentDate == null || latestRepaymentDate.isBefore(newRepaymentDate)) { latestRepaymentDate = newRepaymentDate; } loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate); // reset from date to get actual daysInPeriod loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate); tmpFromDate = newRepaymentDate;// update with new repayment // date } else { tmpFromDate = oldDueDate; } } if (latestRepaymentDate != null) { this.expectedMaturityDate = latestRepaymentDate.toDate(); } }
From source file:org.apache.fineract.portfolio.loanaccount.domain.Loan.java
License:Apache License
public void applyHolidayToRepaymentScheduleDates(final Holiday holiday) { // first repayment's from date is same as disbursement date. LocalDate tmpFromDate = getDisbursementDate(); // Loop through all loanRepayments for (final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : this.repaymentScheduleInstallments) { final LocalDate oldDueDate = loanRepaymentScheduleInstallment.getDueDate(); // update from date if it's not same as previous installament's due // date.//www . ja v a 2 s. c o m if (!loanRepaymentScheduleInstallment.getFromDate().isEqual(tmpFromDate)) { loanRepaymentScheduleInstallment.updateFromDate(tmpFromDate); } if (oldDueDate.isAfter(holiday.getToDateLocalDate())) { break; } if (oldDueDate.equals(holiday.getFromDateLocalDate()) || oldDueDate.equals(holiday.getToDateLocalDate()) || oldDueDate.isAfter(holiday.getFromDateLocalDate()) && oldDueDate.isBefore(holiday.getToDateLocalDate())) { // FIXME: AA do we need to apply non-working days. // Assuming holiday's repayment reschedule to date cannot be // created on a non-working day. final LocalDate newRepaymentDate = holiday.getRepaymentsRescheduledToLocalDate(); loanRepaymentScheduleInstallment.updateDueDate(newRepaymentDate); } tmpFromDate = loanRepaymentScheduleInstallment.getDueDate(); } }
From source file:org.apache.fineract.portfolio.loanaccount.domain.Loan.java
License:Apache License
private LocalDate getLastUserTransactionDate() { LocalDate currentTransactionDate = getDisbursementDate(); for (final LoanTransaction previousTransaction : this.loanTransactions) { if (!(previousTransaction.isReversed() || previousTransaction.isAccrual())) { if (currentTransactionDate.isBefore(previousTransaction.getTransactionDate())) { currentTransactionDate = previousTransaction.getTransactionDate(); }/* ww w . ja v a 2 s. c o m*/ } } return currentTransactionDate; }
From source file:org.apache.fineract.portfolio.loanaccount.domain.Loan.java
License:Apache License
public void regenerateRepaymentScheduleWithInterestRecalculation(final ScheduleGeneratorDTO generatorDTO, final AppUser currentUser) { LocalDate lastTransactionDate = getLastUserTransactionDate(); final LoanScheduleDTO loanSchedule = getRecalculatedSchedule(generatorDTO); if (loanSchedule == null) { return;/*from w ww.j av a 2 s. c om*/ } updateLoanSchedule(loanSchedule.getInstallments(), currentUser); this.interestRecalculatedOn = DateUtils.getDateOfTenant(); LocalDate lastRepaymentDate = this.getLastRepaymentPeriodDueDate(); Set<LoanCharge> charges = this.charges(); for (LoanCharge loanCharge : charges) { if (!loanCharge.isDueAtDisbursement()) { updateOverdueScheduleInstallment(loanCharge); if (loanCharge.getDueLocalDate() == null || (!lastRepaymentDate.isBefore(loanCharge.getDueLocalDate()))) { if (!loanCharge.isWaived() && (loanCharge.getDueLocalDate() == null || !lastTransactionDate.isAfter(loanCharge.getDueLocalDate()))) { recalculateLoanCharge(loanCharge, generatorDTO.getPenaltyWaitPeriod()); loanCharge.updateWaivedAmount(getCurrency()); } } else { loanCharge.setActive(false); } } } processPostDisbursementTransactions(); }
From source file:org.apache.fineract.portfolio.loanaccount.domain.Loan.java
License:Apache License
private ChangedTransactionDetail handleRefundTransaction(final LoanTransaction loanTransaction, final LoanLifecycleStateMachine loanLifecycleStateMachine, final LoanTransaction adjustedTransaction) { ChangedTransactionDetail changedTransactionDetail = null; final LoanStatus statusEnum = loanLifecycleStateMachine.transition(LoanEvent.LOAN_REFUND, LoanStatus.fromInt(this.loanStatus)); this.loanStatus = statusEnum.getValue(); loanTransaction.updateLoan(this); // final boolean isTransactionChronologicallyLatest = // isChronologicallyLatestRefund(loanTransaction, // this.loanTransactions); if (status().isOverpaid() || status().isClosed()) { final String errorMessage = "This refund option is only for active loans "; throw new InvalidLoanStateTransitionException("transaction", "is.exceeding.overpaid.amount", errorMessage, this.totalOverpaid, loanTransaction.getAmount(getCurrency()).getAmount()); } else if (this.getTotalPaidInRepayments().isZero()) { final String errorMessage = "Cannot refund when no payment has been made"; throw new InvalidLoanStateTransitionException("transaction", "no.payment.yet.made.for.loan", errorMessage);/*from w ww. j ava 2 s . c om*/ } if (loanTransaction.isNotZero(loanCurrency())) { this.loanTransactions.add(loanTransaction); } if (loanTransaction.isNotRefundForActiveLoan()) { final String errorMessage = "A transaction of type refund was expected but not received."; throw new InvalidLoanTransactionTypeException("transaction", "is.not.a.refund.transaction", errorMessage); } final LocalDate loanTransactionDate = loanTransaction.getTransactionDate(); if (loanTransactionDate.isBefore(getDisbursementDate())) { final String errorMessage = "The transaction date cannot be before the loan disbursement date: " + getApprovedOnDate().toString(); throw new InvalidLoanStateTransitionException("transaction", "cannot.be.before.disbursement.date", errorMessage, loanTransactionDate, getDisbursementDate()); } if (loanTransactionDate.isAfter(DateUtils.getLocalDateOfTenant())) { final String errorMessage = "The transaction date cannot be in the future."; throw new InvalidLoanStateTransitionException("transaction", "cannot.be.a.future.date", errorMessage, loanTransactionDate); } if (this.loanProduct.isMultiDisburseLoan() && adjustedTransaction == null) { BigDecimal totalDisbursed = getDisbursedAmount(); if (totalDisbursed.compareTo(this.summary.getTotalPrincipalRepaid()) < 0) { final String errorMessage = "The transaction cannot be done before the loan disbursement: " + getApprovedOnDate().toString(); throw new InvalidLoanStateTransitionException("transaction", "cannot.be.done.before.disbursement", errorMessage); } } final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = this.transactionProcessorFactory .determineProcessor(this.transactionProcessingStrategy); // If is a refund if (adjustedTransaction == null) { loanRepaymentScheduleTransactionProcessor.handleRefund(loanTransaction, getCurrency(), this.repaymentScheduleInstallments, charges()); } else { final List<LoanTransaction> allNonContraTransactionsPostDisbursement = retreiveListOfTransactionsPostDisbursement(); changedTransactionDetail = loanRepaymentScheduleTransactionProcessor.handleTransaction( getDisbursementDate(), allNonContraTransactionsPostDisbursement, getCurrency(), this.repaymentScheduleInstallments, charges()); for (final Map.Entry<Long, LoanTransaction> mapEntry : changedTransactionDetail .getNewTransactionMappings().entrySet()) { mapEntry.getValue().updateLoan(this); } } updateLoanSummaryDerivedFields(); doPostLoanTransactionChecks(loanTransaction.getTransactionDate(), loanLifecycleStateMachine); return changedTransactionDetail; }
From source file:org.apache.fineract.portfolio.loanaccount.domain.Loan.java
License:Apache License
public Map<String, Object> undoLastDisbursal(ScheduleGeneratorDTO scheduleGeneratorDTO, List<Long> existingTransactionIds, List<Long> existingReversedTransactionIds, AppUser currentUser, Loan loan) {//from w w w .j a v a2s . co m validateAccountStatus(LoanEvent.LOAN_DISBURSAL_UNDO_LAST); existingTransactionIds.addAll(findExistingTransactionIds()); existingReversedTransactionIds.addAll(findExistingReversedTransactionIds()); final Map<String, Object> actualChanges = new LinkedHashMap<>(); validateActivityNotBeforeClientOrGroupTransferDate(LoanEvent.LOAN_DISBURSAL_UNDO_LAST, getDisbursementDate()); LocalDate actualDisbursementDate = null; LocalDate lastTransactionDate = getDisbursementDate(); List<LoanTransaction> loanTransactions = retreiveListOfTransactionsExcludeAccruals(); Collections.reverse(loanTransactions); for (final LoanTransaction previousTransaction : loanTransactions) { if (lastTransactionDate.isBefore(previousTransaction.getTransactionDate())) { if (previousTransaction.isRepayment() || previousTransaction.isWaiver() || previousTransaction.isChargePayment()) { throw new UndoLastTrancheDisbursementException(previousTransaction.getId()); } } if (previousTransaction.isDisbursement()) { lastTransactionDate = previousTransaction.getTransactionDate(); break; } } actualDisbursementDate = lastTransactionDate; updateLoanToLastDisbursalState(actualDisbursementDate); for (Iterator<LoanTermVariations> iterator = this.loanTermVariations.iterator(); iterator.hasNext();) { LoanTermVariations loanTermVariations = iterator.next(); if (loanTermVariations.fetchDateValue().isAfter(actualDisbursementDate)) { iterator.remove(); } } reverseExistingTransactionsTillLastDisbursal(actualDisbursementDate); loan.recalculateScheduleFromLastTransaction(scheduleGeneratorDTO, existingTransactionIds, existingReversedTransactionIds, currentUser); actualChanges.put("undolastdisbursal", "true"); actualChanges.put("disbursedAmount", this.getDisbursedAmount()); updateLoanSummaryDerivedFields(); return actualChanges; }
From source file:org.apache.fineract.portfolio.loanaccount.domain.LoanAccountDomainServiceJpa.java
License:Apache License
@Override public void recalculateAccruals(Loan loan) { LocalDate accruedTill = loan.getAccruedTill(); if (!loan.isPeriodicAccrualAccountingEnabledOnLoanProduct() || !loan.repaymentScheduleDetail().isInterestRecalculationEnabled() || accruedTill == null || loan.isNpa() || !loan.status().isActive()) { return;/*from www .j a v a 2s . c om*/ } Collection<LoanScheduleAccrualData> loanScheduleAccrualDatas = new ArrayList<>(); List<LoanRepaymentScheduleInstallment> installments = loan.fetchRepaymentScheduleInstallments(); Long loanId = loan.getId(); Long officeId = loan.getOfficeId(); LocalDate accrualStartDate = null; PeriodFrequencyType repaymentFrequency = loan.repaymentScheduleDetail().getRepaymentPeriodFrequencyType(); Integer repayEvery = loan.repaymentScheduleDetail().getRepayEvery(); LocalDate interestCalculatedFrom = loan.getInterestChargedFromDate(); Long loanProductId = loan.productId(); MonetaryCurrency currency = loan.getCurrency(); ApplicationCurrency applicationCurrency = this.applicationCurrencyRepository .findOneWithNotFoundDetection(currency); CurrencyData currencyData = applicationCurrency.toData(); Set<LoanCharge> loanCharges = loan.charges(); for (LoanRepaymentScheduleInstallment installment : installments) { if (!accruedTill.isBefore(installment.getDueDate()) || (accruedTill.isAfter(installment.getFromDate()) && !accruedTill.isAfter(installment.getDueDate()))) { BigDecimal dueDateFeeIncome = BigDecimal.ZERO; BigDecimal dueDatePenaltyIncome = BigDecimal.ZERO; LocalDate chargesTillDate = installment.getDueDate(); if (!accruedTill.isAfter(installment.getDueDate())) { chargesTillDate = accruedTill; } for (final LoanCharge loanCharge : loanCharges) { if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(installment.getFromDate(), chargesTillDate)) { if (loanCharge.isFeeCharge()) { dueDateFeeIncome = dueDateFeeIncome.add(loanCharge.amount()); } else if (loanCharge.isPenaltyCharge()) { dueDatePenaltyIncome = dueDatePenaltyIncome.add(loanCharge.amount()); } } } LoanScheduleAccrualData accrualData = new LoanScheduleAccrualData(loanId, officeId, installment.getInstallmentNumber(), accrualStartDate, repaymentFrequency, repayEvery, installment.getDueDate(), installment.getFromDate(), installment.getId(), loanProductId, installment.getInterestCharged(currency).getAmount(), installment.getFeeChargesCharged(currency).getAmount(), installment.getPenaltyChargesCharged(currency).getAmount(), installment.getInterestAccrued(currency).getAmount(), installment.getFeeAccrued(currency).getAmount(), installment.getPenaltyAccrued(currency).getAmount(), currencyData, interestCalculatedFrom, installment.getInterestWaived(currency).getAmount()); loanScheduleAccrualDatas.add(accrualData); } } if (!loanScheduleAccrualDatas.isEmpty()) { String error = this.loanAccrualPlatformService.addPeriodicAccruals(accruedTill, loanScheduleAccrualDatas); if (error.length() > 0) { String globalisationMessageCode = "error.msg.accrual.exception"; throw new GeneralPlatformDomainRuleException(globalisationMessageCode, error, error); } } }
From source file:org.apache.fineract.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);/*from w w w . j a v a2s . 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(); final Collection<LoanScheduleModelPeriod> periods = createNewLoanScheduleListWithDisbursementDetails( numberOfRepayments, loanApplicationTerms, chargesDueAtTimeOfDisbursement); // Determine the total interest owed over the full loan for FLAT // interest method . final Money totalInterestChargedForFullLoanTerm = loanApplicationTerms .calculateTotalInterestCharged(this.paymentPeriodsInOneYearCalculator, mc); boolean isFirstRepayment = true; LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate( loanApplicationTerms.getExpectedDisbursementDate(), loanApplicationTerms, isFirstRepayment, holidayDetailDTO); final LocalDate idealDisbursementDate = this.scheduledDateGenerator .idealDisbursementDateBasedOnFirstRepaymentDate( loanApplicationTerms.getLoanTermPeriodFrequencyType(), loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate); 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(); // 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); 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); // Loan Schedule Exceptions that need to be applied for Loan Account LoanTermVariationParams termVariationParams = applyLoanTermVariations(loanApplicationTerms, scheduleParams, previousRepaymentDate, scheduledDueDate); 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 (!scheduleParams.getLatePaymentMap().isEmpty()) { populateCompoundingDatesInPeriod(scheduleParams.getPeriodStartDate(), scheduledDueDate, currentDate, loanApplicationTerms, holidayDetailDTO, scheduleParams.getCompoundingMap(), loanCharges, currency); scheduleParams.getCompoundingDateVariations().put(scheduleParams.getPeriodStartDate(), new TreeMap<>(scheduleParams.getCompoundingMap())); } 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; } // 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, totalInterestChargedForFullLoanTerm, idealDisbursementDate, firstRepaymentdate, lastRestDate, scheduledDueDate, periodStartDateApplicableForInterest, applicableTransactions, currentPeriodParams); if (currentPeriodParams.isSkipCurrentLoop()) { continue; } periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms, scheduleParams.getPeriodStartDate(), idealDisbursementDate, firstRepaymentdate); // 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(), totalInterestChargedForFullLoanTerm, 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); // apply loan transactions on installments to identify early/late // payments for interest recalculation installment = handleRecalculationForTransactions(mc, loanApplicationTerms, holidayDetailDTO, currency, scheduleParams, loanRepaymentScheduleTransactionProcessor, totalInterestChargedForFullLoanTerm, 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); // handle cumulative fields scheduleParams.addTotalCumulativePrincipal(currentPeriodParams.getPrincipalForThisPeriod()); scheduleParams.addTotalRepaymentExpected(totalInstallmentDue); scheduleParams.addTotalCumulativeInterest(currentPeriodParams.getInterestForThisPeriod()); scheduleParams.setPeriodStartDate(scheduledDueDate); scheduleParams.incrementInstalmentNumber(); scheduleParams.incrementPeriodNumber(); scheduleParams.getCompoundingDateVariations().clear(); 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; 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:org.apache.fineract.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<>(); LoanScheduleModelPeriod installment = null; LocalDate periodStartDateApplicableForInterest = periodStartDateForInterest; for (RecalculationDetail detail : applicableTransactions) { if (detail.isProcessed()) { continue; }//from w w w. j a va 2 s. c om boolean updateLatePaymentMap = false; final LocalDate transactionDate = detail.getTransactionDate(); if (transactionDate.isBefore(scheduledDueDate)) { if (scheduleParams.getLoanRepaymentScheduleTransactionProcessor() != null && scheduleParams.getLoanRepaymentScheduleTransactionProcessor() .isInterestFirstRepaymentScheduleTransactionProcessor()) { List<LoanTransaction> currentTransactions = 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); int daysInPeriodApplicable = Days .daysBetween(periodStartDateApplicableForInterest, transactionDate).getDays(); Money interestForThisinstallment = Money.zero(currency); if (daysInPeriodApplicable > 0) { // 5 determine interest till the transaction // date if (!scheduleParams.getCompoundingDateVariations() .containsKey(periodStartDateApplicableForInterest)) { scheduleParams.getCompoundingDateVariations().put( periodStartDateApplicableForInterest, new TreeMap<>(scheduleParams.getCompoundingMap())); } 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); // 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(); // creates and insert Loan repayment schedule // for // the period addLoanRepaymentScheduleInstallment(scheduleParams.getInstallments(), installment); } 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, periodStartDateApplicableForInterest, detail); if (updateLatePaymentMap) { updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, scheduleParams.getLatePaymentMap(), scheduledDueDate, scheduleParams.getInstallments(), true, lastRestDate, scheduleParams.getCompoundingMap()); } } 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 = fetchCompoundedArrears(loanApplicationTerms, currency, detail.getTransaction()); if (unprocessed.isGreaterThanZero()) { arrears = getTotalAmount(scheduleParams.getLatePaymentMap(), currency); 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()) { LocalDate calculateTill = transactionDate; if (!scheduleParams.getCompoundingDateVariations() .containsKey(periodStartDateApplicableForInterest)) { scheduleParams.getCompoundingDateVariations().put( periodStartDateApplicableForInterest, new TreeMap<>(scheduleParams.getCompoundingMap())); } 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, periodStartDateApplicableForInterest, detail); } } } } } applicableTransactions.addAll(unprocessedTransactions); if (checkForOutstanding && scheduleParams.getOutstandingBalance().isZero() && scheduleParams.getDisburseDetailMap().isEmpty()) { currentPeriodParams.setSkipCurrentLoop(true); } } }
From source file:org.apache.fineract.portfolio.loanaccount.loanschedule.domain.AbstractLoanScheduleGenerator.java
License:Apache License
private void updateCompoundingAmount(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 ww .ja va 2 s .c o 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); }