org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AbstractLoanScheduleGenerator.java Source code

Java tutorial

Introduction

Here is the source code for org.mifosplatform.portfolio.loanaccount.loanschedule.domain.AbstractLoanScheduleGenerator.java

Source

/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package org.mifosplatform.portfolio.loanaccount.loanschedule.domain;

import java.math.BigDecimal;
import java.math.MathContext;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.joda.time.Days;
import org.joda.time.LocalDate;
import org.mifosplatform.infrastructure.core.service.DateUtils;
import org.mifosplatform.organisation.monetary.domain.ApplicationCurrency;
import org.mifosplatform.organisation.monetary.domain.MonetaryCurrency;
import org.mifosplatform.organisation.monetary.domain.Money;
import org.mifosplatform.organisation.workingdays.domain.RepaymentRescheduleType;
import org.mifosplatform.portfolio.calendar.domain.CalendarInstance;
import org.mifosplatform.portfolio.calendar.service.CalendarUtils;
import org.mifosplatform.portfolio.common.domain.PeriodFrequencyType;
import org.mifosplatform.portfolio.loanaccount.data.DisbursementData;
import org.mifosplatform.portfolio.loanaccount.data.HolidayDetailDTO;
import org.mifosplatform.portfolio.loanaccount.domain.Loan;
import org.mifosplatform.portfolio.loanaccount.domain.LoanCharge;
import org.mifosplatform.portfolio.loanaccount.domain.LoanRepaymentScheduleInstallment;
import org.mifosplatform.portfolio.loanaccount.domain.LoanSummary;
import org.mifosplatform.portfolio.loanaccount.domain.LoanTransaction;
import org.mifosplatform.portfolio.loanaccount.domain.transactionprocessor.LoanRepaymentScheduleTransactionProcessor;
import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementEmiAmountException;
import org.mifosplatform.portfolio.loanaccount.loanschedule.exception.MultiDisbursementOutstandingAmoutException;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModel;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleModelRepaymentPeriod;
import org.mifosplatform.portfolio.loanaccount.rescheduleloan.domain.LoanRescheduleRequest;
import org.mifosplatform.portfolio.loanproduct.domain.LoanProductMinimumRepaymentScheduleRelatedDetail;

/**
 *
 */
public abstract class AbstractLoanScheduleGenerator implements LoanScheduleGenerator {

    private final ScheduledDateGenerator scheduledDateGenerator = new DefaultScheduledDateGenerator();
    private final PaymentPeriodsInOneYearCalculator paymentPeriodsInOneYearCalculator = new DefaultPaymentPeriodsInOneYearCalculator();

    @Override
    public LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
            final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO) {
        final Collection<RecalculationDetail> diffAmt = null;
        final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor = null;
        final LocalDate scheduleTillDate = null;
        return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, diffAmt,
                loanRepaymentScheduleTransactionProcessor, scheduleTillDate);
    }

    private LoanScheduleModel generate(final MathContext mc, final LoanApplicationTerms loanApplicationTerms,
            final Set<LoanCharge> loanCharges, final HolidayDetailDTO holidayDetailDTO,
            final Collection<RecalculationDetail> transactions,
            final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
            final LocalDate scheduleTillDate) {

        final ApplicationCurrency applicationCurrency = loanApplicationTerms.getApplicationCurrency();
        // 1. generate list of proposed schedule due dates
        final LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms,
                holidayDetailDTO);
        loanApplicationTerms.updateLoanEndDate(loanEndDate);

        // 2. determine the total charges due at time of disbursement
        final BigDecimal chargesDueAtTimeOfDisbursement = deriveTotalChargesDueAtTimeOfDisbursement(loanCharges);

        // 3. setup variables for tracking important facts required for loan
        // schedule generation.
        Money principalDisbursed = loanApplicationTerms.getPrincipal();
        final MonetaryCurrency currency = principalDisbursed.getCurrency();
        final int numberOfRepayments = loanApplicationTerms.getNumberOfRepayments();

        // variables for cumulative totals
        int loanTermInDays = Integer.valueOf(0);
        final BigDecimal totalPrincipalPaid = BigDecimal.ZERO;
        BigDecimal totalFeeChargesCharged = chargesDueAtTimeOfDisbursement;
        BigDecimal totalPenaltyChargesCharged = BigDecimal.ZERO;
        BigDecimal totalRepaymentExpected = chargesDueAtTimeOfDisbursement;
        final BigDecimal totalOutstanding = BigDecimal.ZERO;
        Money totalCumulativePrincipal = principalDisbursed.zero();
        Money totalCumulativeInterest = principalDisbursed.zero();
        Money totalOutstandingInterestPaymentDueToGrace = principalDisbursed.zero();

        final Collection<LoanScheduleModelPeriod> periods = createNewLoanScheduleListWithDisbursementDetails(
                numberOfRepayments, loanApplicationTerms, chargesDueAtTimeOfDisbursement);

        // 4. Determine the total interest owed over the full loan for FLAT
        // interest method .
        Money totalInterestChargedForFullLoanTerm = loanApplicationTerms
                .calculateTotalInterestCharged(this.paymentPeriodsInOneYearCalculator, mc);

        LocalDate periodStartDate = loanApplicationTerms.getExpectedDisbursementDate();
        LocalDate actualRepaymentDate = periodStartDate;
        boolean isFirstRepayment = true;
        LocalDate firstRepaymentdate = this.scheduledDateGenerator.generateNextRepaymentDate(periodStartDate,
                loanApplicationTerms, isFirstRepayment);
        final LocalDate idealDisbursementDate = this.scheduledDateGenerator
                .idealDisbursementDateBasedOnFirstRepaymentDate(
                        loanApplicationTerms.getLoanTermPeriodFrequencyType(),
                        loanApplicationTerms.getRepaymentEvery(), firstRepaymentdate);

        LocalDate periodStartDateApplicableForInterest = periodStartDate;

        // Actual period Number as per the schedule
        int periodNumber = 1;
        // Actual period Number and interest only repayments
        int instalmentNumber = 1;

        Money outstandingBalance = principalDisbursed;
        // disbursement map for tranche details(will added to outstanding
        // balance as per the start date)
        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, periodStartDate, periods,
                    chargesDueAtTimeOfDisbursement, disburseDetailMap,
                    isInterestRecalculationRequired(loanApplicationTerms, transactions));
            principalDisbursed = principalDisbursed.zero().plus(disburseAmt);
            loanApplicationTerms.setPrincipal(loanApplicationTerms.getPrincipal().zero().plus(disburseAmt));
            outstandingBalance = outstandingBalance.zero().plus(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);

        // total outstanding balance as per rest for interest calculation.
        Money outstandingBalanceAsPerRest = outstandingBalance;
        // early payments will be added here and as per the selected strategy
        // action will be performed on this value
        Money reducePrincipal = totalCumulativePrincipal.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<>();
        final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
        LocalDate currentDate = DateUtils.getLocalDateOfTenant();
        LocalDate lastRestDate = currentDate;
        if (loanApplicationTerms.getRestCalendarInstance() != null) {
            lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms,
                    holidayDetailDTO);
        }
        // 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, TreeMap<LocalDate, Money>> compoundingDateVariations = new HashMap<>();
        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;
        }
        while (!outstandingBalance.isZero() || !disburseDetailMap.isEmpty()) {

            actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate,
                    loanApplicationTerms, isFirstRepayment);
            isFirstRepayment = false;
            LocalDate scheduledDueDate = this.scheduledDateGenerator.adjustRepaymentDate(actualRepaymentDate,
                    loanApplicationTerms, holidayDetailDTO);

            if (!latePaymentMap.isEmpty()) {
                populateCompoundingDatesInPeriod(periodStartDate, scheduledDueDate, currentDate,
                        loanApplicationTerms, holidayDetailDTO, compoundingMap, loanCharges, currency);
                compoundingDateVariations.put(periodStartDate, new TreeMap<>(compoundingMap));
            }

            if (extendTermForDailyRepayments) {
                actualRepaymentDate = scheduledDueDate;
            }

            // calculated interest start date for the period
            periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms,
                    periodStartDate, idealDisbursementDate, periodStartDateApplicableForInterest);
            int daysInPeriodApplicableForInterest = Days
                    .daysBetween(periodStartDateApplicableForInterest, scheduledDueDate).getDays();

            if (scheduleTillDate != null && !scheduledDueDate.isBefore(scheduleTillDate)) {
                scheduledDueDate = scheduleTillDate;
                isNextRepaymentAvailable = false;
            }

            // populates the collection with transactions till the due date of
            // the period for interest recalculation enabled loans
            Collection<RecalculationDetail> applicableTransactions = getApplicableTransactionsForPeriod(
                    loanApplicationTerms, scheduledDueDate, transactions);

            double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator
                    .calculatePortionOfRepaymentPeriodInterestChargingGrace(periodStartDateApplicableForInterest,
                            scheduledDueDate, loanApplicationTerms.getInterestChargedFromLocalDate(),
                            loanApplicationTerms.getLoanTermPeriodFrequencyType(),
                            loanApplicationTerms.getRepaymentEvery());
            if (loanApplicationTerms.isMultiDisburseLoan()) {
                // Updates fixed emi amount as the date if multiple amounts
                // provided
                loanApplicationTerms.setFixedEmiAmountForPeriod(scheduledDueDate);

                for (Map.Entry<LocalDate, Money> disburseDetail : disburseDetailMap.entrySet()) {
                    if (disburseDetail.getKey().isAfter(periodStartDate)
                            && !disburseDetail.getKey().isAfter(scheduledDueDate)) {
                        // validation check for amount not exceeds specified max
                        // amount as per the configuration
                        if (loanApplicationTerms.getMaxOutstandingBalance() != null
                                && outstandingBalance.plus(disburseDetail.getValue())
                                        .isGreaterThan(loanApplicationTerms.getMaxOutstandingBalance())) {
                            String errorMsg = "Outstanding balance must not exceed the amount: "
                                    + loanApplicationTerms.getMaxOutstandingBalance();
                            throw new MultiDisbursementOutstandingAmoutException(errorMsg,
                                    loanApplicationTerms.getMaxOutstandingBalance().getAmount(),
                                    disburseDetail.getValue());
                        }

                        // 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());
                        principalDisbursed = principalDisbursed.plus(disburseDetail.getValue());
                        loanApplicationTerms
                                .setPrincipal(loanApplicationTerms.getPrincipal().plus(disburseDetail.getValue()));
                    }
                }
            }

            // Adds new interest repayment to the schedule as per the repayment
            // transaction processor configuration
            // will be added only if there is a loan repayment between the
            // period for interest first repayment strategies
            Money earlyPaidAmount = Money.zero(currency);
            LoanScheduleModelPeriod lastInstallment = null;
            if (isInterestRecalculationRequired(loanApplicationTerms, transactions)) {
                boolean checkForOutstanding = true;
                List<RecalculationDetail> unprocessedTransactions = new ArrayList<>();
                LoanScheduleModelPeriod installment = null;
                for (RecalculationDetail detail : applicableTransactions) {
                    if (detail.isProcessed()) {
                        continue;
                    }
                    boolean updateLatePaymentMap = false;
                    if (detail.getTransactionDate().isBefore(scheduledDueDate)) {
                        if (loanRepaymentScheduleTransactionProcessor != null
                                && loanRepaymentScheduleTransactionProcessor
                                        .isInterestFirstRepaymentScheduleTransactionProcessor()) {
                            List<LoanTransaction> currentTransactions = createCurrentTransactionList(detail);
                            if (!detail.getTransactionDate().isEqual(periodStartDate)) {
                                int periodDays = Days.daysBetween(periodStartDate, detail.getTransactionDate())
                                        .getDays();
                                // calculates period start date for interest
                                // calculation as per the configuration
                                periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(
                                        loanApplicationTerms, periodStartDate, idealDisbursementDate,
                                        periodStartDateApplicableForInterest);

                                int daysInPeriodApplicable = Days.daysBetween(periodStartDateApplicableForInterest,
                                        detail.getTransactionDate()).getDays();
                                Money interestForThisinstallment = Money.zero(currency);
                                if (daysInPeriodApplicable > 0) {
                                    // 5 determine interest till the transaction
                                    // date
                                    if (!compoundingDateVariations
                                            .containsKey(periodStartDateApplicableForInterest)) {
                                        compoundingDateVariations.put(periodStartDateApplicableForInterest,
                                                new TreeMap<>(compoundingMap));
                                    }
                                    PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
                                            this.paymentPeriodsInOneYearCalculator,
                                            interestCalculationGraceOnRepaymentPeriodFraction,
                                            totalCumulativePrincipal.minus(reducePrincipal),
                                            totalCumulativeInterest, totalInterestChargedForFullLoanTerm,
                                            totalOutstandingInterestPaymentDueToGrace, outstandingBalanceAsPerRest,
                                            loanApplicationTerms, periodNumber, mc,
                                            mergeVariationsToMap(principalPortionMap, latePaymentMap,
                                                    disburseDetailMap, compoundingMap),
                                            compoundingMap, periodStartDateApplicableForInterest,
                                            detail.getTransactionDate(), daysInPeriodApplicableForInterest);
                                    interestForThisinstallment = principalInterestForThisPeriod.interest();

                                    totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod
                                            .interestPaymentDueToGrace();
                                }

                                Money principalForThisPeriod = principalDisbursed.zero();

                                // applies all the applicable charges to the
                                // newly
                                // created installment
                                PrincipalInterest principalInterest = new PrincipalInterest(principalForThisPeriod,
                                        interestForThisinstallment, null);
                                Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(periodStartDate,
                                        detail.getTransactionDate(), loanCharges, currency, principalInterest,
                                        principalDisbursed, totalCumulativeInterest, numberOfRepayments, true);
                                Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(
                                        periodStartDate, detail.getTransactionDate(), loanCharges, currency,
                                        principalInterest, principalDisbursed, totalCumulativeInterest,
                                        numberOfRepayments, true);

                                // 8. sum up real totalInstallmentDue from
                                // components
                                final Money totalInstallmentDue = principalForThisPeriod
                                        .plus(interestForThisinstallment).plus(feeChargesForInstallment)
                                        .plus(penaltyChargesForInstallment);
                                // 9. create repayment period from parts
                                installment = LoanScheduleModelRepaymentPeriod.repayment(instalmentNumber,
                                        periodStartDate, detail.getTransactionDate(), principalForThisPeriod,
                                        outstandingBalance, interestForThisinstallment, feeChargesForInstallment,
                                        penaltyChargesForInstallment, totalInstallmentDue, true);
                                periods.add(installment);

                                // update outstanding balance for interest
                                // calculation as per the rest
                                outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(
                                        principalPortionMap, detail.getTransactionDate(),
                                        outstandingBalanceAsPerRest, false);
                                outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(disburseDetailMap,
                                        detail.getTransactionDate(), outstandingBalanceAsPerRest, true);

                                // handle cumulative fields
                                loanTermInDays += periodDays;
                                totalRepaymentExpected = totalRepaymentExpected
                                        .add(totalInstallmentDue.getAmount());
                                totalCumulativeInterest = totalCumulativeInterest.plus(interestForThisinstallment);
                                totalFeeChargesCharged = totalFeeChargesCharged
                                        .add(feeChargesForInstallment.getAmount());
                                totalPenaltyChargesCharged = totalPenaltyChargesCharged
                                        .add(penaltyChargesForInstallment.getAmount());

                                periodStartDate = detail.getTransactionDate();
                                periodStartDateApplicableForInterest = periodStartDate;
                                updateLatePaymentMap = true;
                                instalmentNumber++;
                                // creates and insert Loan repayment schedule
                                // for
                                // the period
                                addLoanRepaymentScheduleInstallment(installments, 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 = loanRepaymentScheduleTransactionProcessor
                                    .handleRepaymentSchedule(currentTransactions, currency, installments);
                            if (unprocessed.isGreaterThanZero()) {

                                if (loanApplicationTerms.getPreClosureInterestCalculationStrategy()
                                        .calculateTillRestFrequencyEnabled()) {
                                    LocalDate applicableDate = getNextRestScheduleDate(
                                            detail.getTransactionDate().minusDays(1), loanApplicationTerms,
                                            holidayDetailDTO);
                                    checkForOutstanding = detail.getTransactionDate().isEqual(applicableDate);

                                }
                                // reduces actual outstanding balance
                                outstandingBalance = outstandingBalance.minus(unprocessed);
                                // if outstanding balance becomes less than zero
                                // then adjusts the princiapal
                                Money addToPrinciapal = Money.zero(currency);
                                if (!outstandingBalance.isGreaterThanZero()) {
                                    addToPrinciapal = addToPrinciapal.plus(outstandingBalance);
                                    outstandingBalance = outstandingBalance.zero();
                                    lastInstallment = installment;
                                }
                                // updates principal portion map with the early
                                // payment amounts and applicable date as per
                                // rest
                                updatePrincipalPaidPortionToMap(loanApplicationTerms, holidayDetailDTO,
                                        principalPortionMap, installment, detail, unprocessed.plus(addToPrinciapal),
                                        installments);
                                totalRepaymentExpected = totalRepaymentExpected
                                        .add(unprocessed.plus(addToPrinciapal).getAmount());
                                totalCumulativePrincipal = totalCumulativePrincipal
                                        .plus(unprocessed.plus(addToPrinciapal));

                                // method applies early payment strategy
                                reducePrincipal = reducePrincipal.plus(unprocessed);
                                reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal);
                            }
                            // identify late payments and add compounding
                            // details to
                            // map for interest calculation
                            updateLatePaidAmountsToPrincipalMap(principalPortionMap, detail.getTransaction(),
                                    latePaymentMap, compoundingMap, loanApplicationTerms, currency,
                                    holidayDetailDTO, lastRestDate);
                            compoundingDateVariations.put(periodStartDateApplicableForInterest,
                                    new TreeMap<>(compoundingMap));
                            if (updateLatePaymentMap) {
                                updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency,
                                        latePaymentMap, scheduledDueDate, installments, true, lastRestDate,
                                        compoundingMap);
                            }
                        } else if (loanRepaymentScheduleTransactionProcessor != null) {
                            LocalDate applicableDate = getNextRestScheduleDate(
                                    detail.getTransactionDate().minusDays(1), loanApplicationTerms,
                                    holidayDetailDTO);
                            if (applicableDate.isBefore(scheduledDueDate)) {
                                List<LoanTransaction> currentTransactions = createCurrentTransactionList(detail);
                                Money unprocessed = loanRepaymentScheduleTransactionProcessor
                                        .handleRepaymentSchedule(currentTransactions, currency, installments);
                                Money arrears = fetchCompoundedArrears(loanApplicationTerms, currency,
                                        detail.getTransaction());
                                if (unprocessed.isGreaterThanZero()) {
                                    arrears = getTotalAmount(latePaymentMap, currency);
                                    updateMapWithAmount(principalPortionMap, unprocessed, applicableDate);
                                    earlyPaidAmount = earlyPaidAmount.plus(unprocessed);

                                    // this check is to identify pre-closure and
                                    // apply interest calculation as per
                                    // configuration
                                    if (!outstandingBalance.isGreaterThan(unprocessed)
                                            && !loanApplicationTerms.getPreClosureInterestCalculationStrategy()
                                                    .calculateTillRestFrequencyEnabled()) {

                                        LocalDate calculateTill = detail.getTransactionDate();
                                        if (!compoundingDateVariations
                                                .containsKey(periodStartDateApplicableForInterest)) {
                                            compoundingDateVariations.put(periodStartDateApplicableForInterest,
                                                    new TreeMap<>(compoundingMap));
                                        }
                                        PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
                                                this.paymentPeriodsInOneYearCalculator,
                                                interestCalculationGraceOnRepaymentPeriodFraction,
                                                totalCumulativePrincipal.minus(reducePrincipal),
                                                totalCumulativeInterest, totalInterestChargedForFullLoanTerm,
                                                totalOutstandingInterestPaymentDueToGrace,
                                                outstandingBalanceAsPerRest, loanApplicationTerms, periodNumber, mc,
                                                mergeVariationsToMap(principalPortionMap, latePaymentMap,
                                                        disburseDetailMap, compoundingMap),
                                                compoundingMap, periodStartDateApplicableForInterest, calculateTill,
                                                daysInPeriodApplicableForInterest);
                                        if (!principalInterestForThisPeriod.interest()
                                                .plus(principalInterestForThisPeriod.interestPaymentDueToGrace())
                                                .plus(outstandingBalance).isGreaterThan(unprocessed)) {
                                            earlyPaidAmount = earlyPaidAmount.minus(unprocessed);
                                            updateMapWithAmount(principalPortionMap, unprocessed.negated(),
                                                    applicableDate);
                                            LoanTransaction loanTransaction = LoanTransaction.repayment(null,
                                                    unprocessed, null, detail.getTransactionDate(), null,
                                                    DateUtils.getLocalDateTimeOfTenant(), null);
                                            RecalculationDetail recalculationDetail = new RecalculationDetail(
                                                    detail.getTransactionDate(), 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;

                                    outstandingBalance = outstandingBalance.minus(unprocessed);
                                    // if outstanding balance becomes less than
                                    // zero
                                    // then adjusts the princiapal
                                    Money addToPrinciapal = Money.zero(currency);
                                    if (outstandingBalance.isLessThanZero()) {
                                        addToPrinciapal = addToPrinciapal.plus(outstandingBalance);
                                        outstandingBalance = outstandingBalance.zero();
                                        updateMapWithAmount(principalPortionMap, addToPrinciapal, applicableDate);
                                        earlyPaidAmount = earlyPaidAmount.plus(addToPrinciapal);
                                    }

                                }
                                LocalDate tillDate = getNextRestScheduleDate(currentDate.minusDays(1),
                                        loanApplicationTerms, holidayDetailDTO);
                                if (arrears.isGreaterThanZero() && applicableDate.isBefore(tillDate)) {
                                    updateLatePaidAmountsToPrincipalMap(principalPortionMap,
                                            detail.getTransaction(), latePaymentMap, compoundingMap,
                                            loanApplicationTerms, currency, holidayDetailDTO, lastRestDate);
                                    compoundingDateVariations.put(periodStartDateApplicableForInterest,
                                            new TreeMap<>(compoundingMap));
                                }
                            }

                        }
                    }

                }
                applicableTransactions.addAll(unprocessedTransactions);
                if (checkForOutstanding && outstandingBalance.isZero() && disburseDetailMap.isEmpty()) {
                    continue;
                }
            }

            int periodDays = Days.daysBetween(periodStartDate, scheduledDueDate).getDays();
            periodStartDateApplicableForInterest = calculateInterestStartDateForPeriod(loanApplicationTerms,
                    periodStartDate, idealDisbursementDate, periodStartDateApplicableForInterest);

            // backup for pre-close transaction
            if (compoundingDateVariations.containsKey(periodStartDateApplicableForInterest)) {
                compoundingMap.clear();
                compoundingMap.putAll(compoundingDateVariations.get(periodStartDateApplicableForInterest));
            } else {
                compoundingDateVariations.put(periodStartDateApplicableForInterest, new TreeMap<>(compoundingMap));
            }
            // 5 determine principal,interest of repayment period
            PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
                    this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction,
                    totalCumulativePrincipal.minus(reducePrincipal), totalCumulativeInterest,
                    totalInterestChargedForFullLoanTerm, totalOutstandingInterestPaymentDueToGrace,
                    outstandingBalanceAsPerRest, loanApplicationTerms, periodNumber, mc,
                    mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap, compoundingMap),
                    compoundingMap, periodStartDateApplicableForInterest, scheduledDueDate,
                    daysInPeriodApplicableForInterest);

            if (loanApplicationTerms.getFixedEmiAmount() != null && loanApplicationTerms.getFixedEmiAmount()
                    .compareTo(principalInterestForThisPeriod.interest().getAmount()) != 1) {
                String errorMsg = "EMI amount must be greter than : "
                        + principalInterestForThisPeriod.interest().getAmount();
                throw new MultiDisbursementEmiAmountException(errorMsg,
                        principalInterestForThisPeriod.interest().getAmount(),
                        loanApplicationTerms.getFixedEmiAmount());
            }

            // update cumulative fields for principal & interest
            Money interestForThisinstallment = principalInterestForThisPeriod.interest();
            Money lastTotalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace;
            totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod.interestPaymentDueToGrace();
            Money principalForThisPeriod = principalInterestForThisPeriod.principal();

            if (principalForThisPeriod.isZero()) {
                loanApplicationTerms.resetFixedEmiAmount();
            }

            // applies early payments on principal portion
            if (principalForThisPeriod.isGreaterThan(reducePrincipal)) {
                principalForThisPeriod = principalForThisPeriod.minus(reducePrincipal);
                reducePrincipal = reducePrincipal.zero();
            } else {
                reducePrincipal = reducePrincipal.minus(principalForThisPeriod);
                principalForThisPeriod = principalForThisPeriod.zero();
            }

            // earlyPaidAmount is already subtracted from balancereducePrincipal
            // reducePrincipal.plus(unprocessed);
            Money reducedBalance = earlyPaidAmount;
            earlyPaidAmount = earlyPaidAmount.minus(principalForThisPeriod);
            if (earlyPaidAmount.isGreaterThanZero()) {
                reducePrincipal = reducePrincipal.plus(earlyPaidAmount);
                reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal);
                principalForThisPeriod = principalForThisPeriod.plus(earlyPaidAmount);
            }

            // 6. update outstandingLoanBlance using current period
            // 'principalDue'
            outstandingBalance = outstandingBalance.minus(principalForThisPeriod.minus(reducedBalance));

            if (outstandingBalance.isLessThanZero() || !isNextRepaymentAvailable) {
                principalForThisPeriod = principalForThisPeriod.plus(outstandingBalance);
                outstandingBalance = outstandingBalance.zero();
            }

            // applies charges for the period
            PrincipalInterest principalInterest = new PrincipalInterest(principalForThisPeriod,
                    interestForThisinstallment, null);
            Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(periodStartDate, scheduledDueDate,
                    loanCharges, currency, principalInterest, principalDisbursed, totalCumulativeInterest,
                    numberOfRepayments, true);
            Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(periodStartDate,
                    scheduledDueDate, loanCharges, currency, principalInterest, principalDisbursed,
                    totalCumulativeInterest, numberOfRepayments, true);
            totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount());
            totalPenaltyChargesCharged = totalPenaltyChargesCharged.add(penaltyChargesForInstallment.getAmount());

            // 8. sum up real totalInstallmentDue from components
            final Money totalInstallmentDue = principalForThisPeriod.plus(interestForThisinstallment)
                    .plus(feeChargesForInstallment).plus(penaltyChargesForInstallment);

            // if previous installment is last then add interest to same
            // installment
            if (lastInstallment != null && principalForThisPeriod.isZero()) {
                lastInstallment.addInterestAmount(interestForThisinstallment);
                continue;
            }

            // 9. create repayment period from parts
            LoanScheduleModelPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(instalmentNumber,
                    periodStartDate, scheduledDueDate, principalForThisPeriod, outstandingBalance,
                    interestForThisinstallment, feeChargesForInstallment, penaltyChargesForInstallment,
                    totalInstallmentDue, false);

            // apply loan transactions on installments to identify early/late
            // payments for interest recalculation
            if (isInterestRecalculationRequired(loanApplicationTerms, transactions)
                    && loanRepaymentScheduleTransactionProcessor != null) {
                Money principalProcessed = Money.zero(currency);
                addLoanRepaymentScheduleInstallment(installments, installment);
                for (RecalculationDetail detail : applicableTransactions) {
                    if (!detail.isProcessed()) {
                        List<LoanTransaction> currentTransactions = new ArrayList<>(2);
                        currentTransactions.add(detail.getTransaction());
                        // applies the transaction as per transaction strategy
                        // on scheduled installments to identify the
                        // unprocessed(early payment ) amounts
                        Money unprocessed = loanRepaymentScheduleTransactionProcessor
                                .handleRepaymentSchedule(currentTransactions, currency, installments);

                        if (unprocessed.isGreaterThanZero()) {
                            outstandingBalance = outstandingBalance.minus(unprocessed);
                            // pre closure check and processing
                            if (outstandingBalance.isLessThan(interestForThisinstallment)
                                    && !scheduledDueDate.equals(detail.getTransactionDate())) {
                                LocalDate calculateTill = detail.getTransactionDate();
                                if (loanApplicationTerms.getPreClosureInterestCalculationStrategy()
                                        .calculateTillRestFrequencyEnabled()) {
                                    calculateTill = getNextRestScheduleDate(calculateTill.minusDays(1),
                                            loanApplicationTerms, holidayDetailDTO);
                                }
                                if (compoundingDateVariations.containsKey(periodStartDateApplicableForInterest)) {
                                    compoundingMap.clear();
                                    compoundingMap.putAll(
                                            compoundingDateVariations.get(periodStartDateApplicableForInterest));
                                }
                                PrincipalInterest interestTillDate = calculatePrincipalInterestComponentsForPeriod(
                                        this.paymentPeriodsInOneYearCalculator,
                                        interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal,
                                        totalCumulativeInterest, totalInterestChargedForFullLoanTerm,
                                        lastTotalOutstandingInterestPaymentDueToGrace, outstandingBalanceAsPerRest,
                                        loanApplicationTerms, periodNumber, mc,
                                        mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailMap,
                                                compoundingMap),
                                        compoundingMap, periodStartDateApplicableForInterest, calculateTill,
                                        daysInPeriodApplicableForInterest);
                                Money diff = interestForThisinstallment.minus(interestTillDate.interest());
                                if (!outstandingBalance.minus(diff).isGreaterThanZero()) {
                                    outstandingBalance = outstandingBalance.minus(diff);
                                    interestForThisinstallment = interestForThisinstallment.minus(diff);
                                    principalForThisPeriod = principalForThisPeriod.plus(diff);
                                    final Money totalDue = principalForThisPeriod//
                                            .plus(interestForThisinstallment);

                                    // 9. create and replaces repayment period
                                    // from parts
                                    installment = LoanScheduleModelRepaymentPeriod.repayment(instalmentNumber,
                                            periodStartDate, detail.getTransactionDate(), principalForThisPeriod,
                                            outstandingBalance, interestForThisinstallment,
                                            feeChargesForInstallment, penaltyChargesForInstallment, totalDue,
                                            false);
                                    totalOutstandingInterestPaymentDueToGrace = interestTillDate
                                            .interestPaymentDueToGrace();
                                }

                            }
                            Money addToPrinciapal = Money.zero(currency);
                            if (outstandingBalance.isLessThanZero()) {
                                addToPrinciapal = addToPrinciapal.plus(outstandingBalance);
                                outstandingBalance = outstandingBalance.zero();
                            }
                            // updates principal portion map with the early
                            // payment amounts and applicable date as per rest
                            updatePrincipalPaidPortionToMap(loanApplicationTerms, holidayDetailDTO,
                                    principalPortionMap, installment, detail, unprocessed.plus(addToPrinciapal),
                                    installments);
                            totalRepaymentExpected = totalRepaymentExpected
                                    .add(unprocessed.plus(addToPrinciapal).getAmount());
                            totalCumulativePrincipal = totalCumulativePrincipal
                                    .plus(unprocessed.plus(addToPrinciapal));

                            reducePrincipal = reducePrincipal.plus(unprocessed);
                            reducePrincipal = applyEarlyPaymentStrategy(loanApplicationTerms, reducePrincipal);
                            principalForThisPeriod = principalForThisPeriod.plus(unprocessed.plus(addToPrinciapal));
                            principalProcessed = principalProcessed.plus(unprocessed.plus(addToPrinciapal));
                        }
                    }
                }
                updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap,
                        scheduledDueDate, installments, true, lastRestDate, compoundingMap);
                principalForThisPeriod = principalForThisPeriod.minus(principalProcessed);
            }

            periods.add(installment);

            // Updates principal paid map with efective date for reducing
            // the amount from outstanding balance(interest calculation)
            LocalDate amountApplicableDate = installment.periodDueDate();
            if (loanApplicationTerms.isInterestRecalculationEnabled()) {
                amountApplicableDate = getNextRestScheduleDate(installment.periodDueDate().minusDays(1),
                        loanApplicationTerms, holidayDetailDTO);
            }
            updateMapWithAmount(principalPortionMap, principalForThisPeriod.minus(reducedBalance),
                    amountApplicableDate);

            // update outstanding balance for interest calculation
            outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(principalPortionMap, scheduledDueDate,
                    outstandingBalanceAsPerRest, false);
            outstandingBalanceAsPerRest = updateBalanceForInterestCalculation(disburseDetailMap, scheduledDueDate,
                    outstandingBalanceAsPerRest, true);

            // handle cumulative fields
            loanTermInDays += periodDays;
            totalCumulativePrincipal = totalCumulativePrincipal.plus(principalForThisPeriod);
            totalCumulativeInterest = totalCumulativeInterest.plus(interestForThisinstallment);
            totalRepaymentExpected = totalRepaymentExpected.add(totalInstallmentDue.getAmount());
            periodStartDate = scheduledDueDate;
            periodStartDateApplicableForInterest = periodStartDate;
            instalmentNumber++;
            periodNumber++;
            compoundingDateVariations.clear();
        }

        // this condition is to add the interest from grace period if not
        // already applied.
        if (totalOutstandingInterestPaymentDueToGrace.isGreaterThanZero()) {
            LoanScheduleModelPeriod installment = ((List<LoanScheduleModelPeriod>) periods).get(periods.size() - 1);
            installment.addInterestAmount(totalOutstandingInterestPaymentDueToGrace);
            totalRepaymentExpected = totalRepaymentExpected
                    .add(totalOutstandingInterestPaymentDueToGrace.getAmount());
            totalCumulativeInterest = totalCumulativeInterest.plus(totalOutstandingInterestPaymentDueToGrace);
            totalOutstandingInterestPaymentDueToGrace = totalOutstandingInterestPaymentDueToGrace.zero();
        }

        // 7. determine fees and penalties for charges which depends on total
        // loan interest
        for (LoanScheduleModelPeriod loanScheduleModelPeriod : periods) {
            if (loanScheduleModelPeriod.isRepaymentPeriod()) {
                PrincipalInterest principalInterest = new PrincipalInterest(
                        Money.of(currency, loanScheduleModelPeriod.principalDue()),
                        Money.of(currency, loanScheduleModelPeriod.interestDue()), null);
                Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(
                        loanScheduleModelPeriod.periodFromDate(), loanScheduleModelPeriod.periodDueDate(),
                        nonCompoundingCharges, currency, principalInterest, principalDisbursed,
                        totalCumulativeInterest, numberOfRepayments,
                        !loanScheduleModelPeriod.isRecalculatedInterestComponent());
                Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(
                        loanScheduleModelPeriod.periodFromDate(), loanScheduleModelPeriod.periodDueDate(),
                        nonCompoundingCharges, currency, principalInterest, principalDisbursed,
                        totalCumulativeInterest, numberOfRepayments,
                        !loanScheduleModelPeriod.isRecalculatedInterestComponent());
                totalFeeChargesCharged = totalFeeChargesCharged.add(feeChargesForInstallment.getAmount());
                totalPenaltyChargesCharged = totalPenaltyChargesCharged
                        .add(penaltyChargesForInstallment.getAmount());
                totalRepaymentExpected = totalRepaymentExpected.add(feeChargesForInstallment.getAmount())
                        .add(penaltyChargesForInstallment.getAmount());
                loanScheduleModelPeriod.addLoanCharges(feeChargesForInstallment.getAmount(),
                        penaltyChargesForInstallment.getAmount());
            }
        }

        // this block is to add extra re-payment schedules with interest portion
        // if the loan not paid with in loan term

        if (scheduleTillDate != null) {
            currentDate = scheduleTillDate;
        }
        if (isInterestRecalculationRequired(loanApplicationTerms, transactions) && latePaymentMap.size() > 0
                && currentDate.isAfter(periodStartDate)) {
            Money totalInterest = addInterestOnlyRepaymentScheduleForCurrentdate(mc, loanApplicationTerms,
                    holidayDetailDTO, currency, periods, periodStartDate, actualRepaymentDate, instalmentNumber,
                    latePaymentMap, currentDate, loanRepaymentScheduleTransactionProcessor, principalPortionMap,
                    compoundingMap, transactions, installments, loanCharges);
            totalCumulativeInterest = totalCumulativeInterest.plus(totalInterest);
        }

        loanApplicationTerms.resetFixedEmiAmount();

        return LoanScheduleModel.from(periods, applicationCurrency, loanTermInDays, principalDisbursed,
                totalCumulativePrincipal.getAmount(), totalPrincipalPaid, totalCumulativeInterest.getAmount(),
                totalFeeChargesCharged, totalPenaltyChargesCharged, totalRepaymentExpected, totalOutstanding);
    }

    private Money fetchCompoundedArrears(final LoanApplicationTerms loanApplicationTerms,
            final MonetaryCurrency currency, final LoanTransaction transaction) {
        Money arrears = transaction.getPrincipalPortion(currency);
        if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) {
            arrears = arrears.plus(transaction.getInterestPortion(currency));
        }

        if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isFeeCompoundingEnabled()) {
            arrears = arrears.plus(transaction.getFeeChargesPortion(currency))
                    .plus(transaction.getPenaltyChargesPortion(currency));
        }
        return arrears;
    }

    private boolean isInterestRecalculationRequired(final LoanApplicationTerms loanApplicationTerms,
            Collection<RecalculationDetail> transactions) {
        return loanApplicationTerms.isInterestRecalculationEnabled() && transactions != null;
    }

    /**
     * Method calculates interest on not paid outstanding principal and interest
     * (if compounding is enabled) till current date and adds new repayment
     * schedule detail
     * 
     * @param compoundingMap
     *            TODO
     * @param loanCharges
     *            TODO
     * @param principalPortioMap
     *            TODO
     * 
     */
    private Money addInterestOnlyRepaymentScheduleForCurrentdate(final MathContext mc,
            final LoanApplicationTerms loanApplicationTerms, final HolidayDetailDTO holidayDetailDTO,
            final MonetaryCurrency currency, final Collection<LoanScheduleModelPeriod> periods,
            LocalDate periodStartDate, LocalDate actualRepaymentDate, int instalmentNumber,
            Map<LocalDate, Money> latePaymentMap, final LocalDate currentDate,
            LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor,
            final Map<LocalDate, Money> principalPortionMap, TreeMap<LocalDate, Money> compoundingMap,
            final Collection<RecalculationDetail> transactions,
            final List<LoanRepaymentScheduleInstallment> installments, Set<LoanCharge> loanCharges) {
        boolean isFirstRepayment = false;
        LocalDate startDate = periodStartDate;
        Money outstanding = Money.zero(currency);
        Money totalInterest = Money.zero(currency);
        Money totalCumulativeInterest = Money.zero(currency);
        Map<LocalDate, Money> disburseDetailsMap = new HashMap<>();
        double interestCalculationGraceOnRepaymentPeriodFraction = Double.valueOf(0);
        int periodNumberTemp = 1;
        LocalDate lastRestDate = getNextRestScheduleDate(currentDate.minusDays(1), loanApplicationTerms,
                holidayDetailDTO);
        do {

            actualRepaymentDate = this.scheduledDateGenerator.generateNextRepaymentDate(actualRepaymentDate,
                    loanApplicationTerms, isFirstRepayment);
            int daysInPeriod = Days.daysBetween(periodStartDate, actualRepaymentDate).getDays();
            if (actualRepaymentDate.isAfter(currentDate)) {
                actualRepaymentDate = currentDate;
            }
            outstanding = updateOutstandingFromLatePayment(periodStartDate, latePaymentMap, outstanding);

            Collection<RecalculationDetail> applicableTransactions = getApplicableTransactionsForPeriod(
                    loanApplicationTerms, actualRepaymentDate, transactions);

            if (!latePaymentMap.isEmpty()) {
                populateCompoundingDatesInPeriod(periodStartDate, actualRepaymentDate, currentDate,
                        loanApplicationTerms, holidayDetailDTO, compoundingMap, loanCharges, currency);
            }

            for (RecalculationDetail detail : applicableTransactions) {
                if (detail.isProcessed()) {
                    continue;
                }
                List<LoanTransaction> currentTransactions = createCurrentTransactionList(detail);

                if (!periodStartDate.isEqual(detail.getTransactionDate())) {
                    PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
                            this.paymentPeriodsInOneYearCalculator,
                            interestCalculationGraceOnRepaymentPeriodFraction, totalInterest.zero(),
                            totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), outstanding,
                            loanApplicationTerms, periodNumberTemp, mc,
                            mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailsMap,
                                    compoundingMap),
                            compoundingMap, periodStartDate, detail.getTransactionDate(), daysInPeriod);

                    Money interest = principalInterestForThisPeriod.interest();
                    totalInterest = totalInterest.plus(interest);

                    LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(
                            instalmentNumber++, startDate, detail.getTransactionDate(), totalInterest.zero(),
                            totalInterest.zero(), totalInterest, totalInterest.zero(), totalInterest.zero(),
                            totalInterest, true);
                    periods.add(installment);
                    totalCumulativeInterest = totalCumulativeInterest.plus(totalInterest);
                    totalInterest = totalInterest.zero();
                    addLoanRepaymentScheduleInstallment(installments, installment);
                    periodStartDate = detail.getTransactionDate();
                    startDate = detail.getTransactionDate();
                }
                loanRepaymentScheduleTransactionProcessor.handleRepaymentSchedule(currentTransactions, currency,
                        installments);
                updateLatePaymentsToMap(loanApplicationTerms, holidayDetailDTO, currency, latePaymentMap,
                        currentDate, installments, false, lastRestDate, compoundingMap);
                outstanding = outstanding.zero();
                outstanding = updateOutstandingFromLatePayment(periodStartDate, latePaymentMap, outstanding);
                outstanding = updateBalanceForInterestCalculation(principalPortionMap, periodStartDate, outstanding,
                        false);
                if (latePaymentMap.isEmpty() && !outstanding.isGreaterThanZero()) {
                    break;
                }
            }

            if (outstanding.isGreaterThanZero()) {
                PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
                        this.paymentPeriodsInOneYearCalculator, interestCalculationGraceOnRepaymentPeriodFraction,
                        totalInterest.zero(), totalInterest.zero(), totalInterest.zero(), totalInterest.zero(),
                        outstanding, loanApplicationTerms, periodNumberTemp, mc,
                        mergeVariationsToMap(principalPortionMap, latePaymentMap, disburseDetailsMap,
                                compoundingMap),
                        compoundingMap, periodStartDate, actualRepaymentDate, daysInPeriod);
                Money interest = principalInterestForThisPeriod.interest();
                totalInterest = totalInterest.plus(interest);
                if (loanApplicationTerms.getInterestRecalculationCompoundingMethod()
                        .isInterestCompoundingEnabled()) {
                    LocalDate compoundingEffectiveDate = getNextCompoundScheduleDate(
                            actualRepaymentDate.minusDays(1), loanApplicationTerms, holidayDetailDTO);
                    latePaymentMap.put(compoundingEffectiveDate, interest);

                }
            }
            periodStartDate = actualRepaymentDate;
        } while (actualRepaymentDate.isBefore(currentDate) && outstanding.isGreaterThanZero());

        if (totalInterest.isGreaterThanZero()) {
            LoanScheduleModelRepaymentPeriod installment = LoanScheduleModelRepaymentPeriod.repayment(
                    instalmentNumber++, startDate, actualRepaymentDate, totalInterest.zero(), totalInterest.zero(),
                    totalInterest, totalInterest.zero(), totalInterest.zero(), totalInterest, true);
            periods.add(installment);
            totalCumulativeInterest = totalCumulativeInterest.plus(totalInterest);
        }
        return totalCumulativeInterest;
    }

    private Collection<RecalculationDetail> getApplicableTransactionsForPeriod(
            final LoanApplicationTerms loanApplicationTerms, LocalDate repaymentDate,
            final Collection<RecalculationDetail> transactions) {
        Collection<RecalculationDetail> applicableTransactions = new ArrayList<>();
        if (isInterestRecalculationRequired(loanApplicationTerms, transactions)) {
            for (RecalculationDetail detail : transactions) {
                if (!detail.getTransactionDate().isAfter(repaymentDate)) {
                    applicableTransactions.add(detail);
                }
            }
            transactions.removeAll(applicableTransactions);
        }
        return applicableTransactions;
    }

    private List<LoanTransaction> createCurrentTransactionList(RecalculationDetail detail) {
        List<LoanTransaction> currentTransactions = new ArrayList<>(2);
        currentTransactions.add(detail.getTransaction());
        detail.setProcessed(true);
        return currentTransactions;
    }

    private Money updateOutstandingFromLatePayment(LocalDate periodStartDate, Map<LocalDate, Money> latePaymentMap,
            Money outstanding) {
        Map<LocalDate, Money> retainEntries = new HashMap<>();
        for (Map.Entry<LocalDate, Money> mapEntry : latePaymentMap.entrySet()) {
            if (!mapEntry.getKey().isAfter(periodStartDate)) {
                outstanding = outstanding.plus(mapEntry.getValue());
            } else {
                retainEntries.put(mapEntry.getKey(), mapEntry.getValue());
            }
        }
        latePaymentMap.clear();
        latePaymentMap.putAll(retainEntries);
        retainEntries.clear();
        return outstanding;
    }

    /**
     * method applies early payment strategy as per the configurations provided
     */
    private Money applyEarlyPaymentStrategy(final LoanApplicationTerms loanApplicationTerms,
            Money reducePrincipal) {
        if (reducePrincipal.isGreaterThanZero()) {
            switch (loanApplicationTerms.getRescheduleStrategyMethod()) {
            case REDUCE_EMI_AMOUNT:
                // in this case emi amount will be reduced but number of
                // installments won't change
                if (!loanApplicationTerms.isMultiDisburseLoan()) {
                    loanApplicationTerms.setFixedEmiAmount(null);
                }
                reducePrincipal = reducePrincipal.zero();
                break;
            case REDUCE_NUMBER_OF_INSTALLMENTS:
                // number of installments will reduce but emi amount won't
                // get effected
                reducePrincipal = reducePrincipal.zero();
                break;
            case RESCHEDULE_NEXT_REPAYMENTS:
                // will reduce principal from the reduce Principal for each
                // installment(means installments will have less emi amount)
                // until this
                // amount becomes zero
                break;
            default:
                break;
            }
        }
        return reducePrincipal;
    }

    /**
     * Identifies all the past date principal changes and apply them on
     * outstanding balance for future calculations
     */
    private Money updateBalanceForInterestCalculation(final Map<LocalDate, Money> principalPortionMap,
            final LocalDate scheduledDueDate, final Money outstandingBalanceAsPerRest, boolean addMapDetails) {
        List<LocalDate> removeFromprincipalPortionMap = new ArrayList<>();
        Money outstandingBalance = outstandingBalanceAsPerRest;
        for (Map.Entry<LocalDate, Money> principal : principalPortionMap.entrySet()) {
            if (!principal.getKey().isAfter(scheduledDueDate)) {
                if (addMapDetails) {
                    outstandingBalance = outstandingBalance.plus(principal.getValue());
                } else {
                    outstandingBalance = outstandingBalance.minus(principal.getValue());
                }
                removeFromprincipalPortionMap.add(principal.getKey());
            }
        }
        for (LocalDate date : removeFromprincipalPortionMap) {
            principalPortionMap.remove(date);
        }
        return outstandingBalance;
    }

    // this is to make sure even paid late payments(principal and compounded
    // interest/fee) should be reduced as per rest date
    private void updateLatePaidAmountsToPrincipalMap(final Map<LocalDate, Money> principalVariationMap,
            final LoanTransaction loanTransaction, final Map<LocalDate, Money> latePaymentsMap,
            final Map<LocalDate, Money> compoundingMap, final LoanApplicationTerms applicationTerms,
            final MonetaryCurrency currency, final HolidayDetailDTO holidayDetailDTO,
            final LocalDate lastRestDate) {
        LocalDate applicableDate = getNextRestScheduleDate(loanTransaction.getTransactionDate().minusDays(1),
                applicationTerms, holidayDetailDTO);

        Money principalPortion = loanTransaction.getPrincipalPortion(currency);
        Money compoundedLatePayments = Money.zero(currency);
        if (applicationTerms.getInterestRecalculationCompoundingMethod().isInterestCompoundingEnabled()) {
            compoundedLatePayments = compoundedLatePayments.plus(loanTransaction.getInterestPortion(currency));
        }
        if (applicationTerms.getInterestRecalculationCompoundingMethod().isFeeCompoundingEnabled()) {
            compoundedLatePayments = compoundedLatePayments.plus(loanTransaction.getFeeChargesPortion(currency))
                    .plus(loanTransaction.getPenaltyChargesPortion(currency));
        }

        updateCompoundingAmount(principalVariationMap, latePaymentsMap, currency, lastRestDate, principalPortion,
                applicableDate);
        updateCompoundingAmount(principalVariationMap, compoundingMap, currency, lastRestDate,
                compoundedLatePayments, applicableDate);
    }

    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);
                    }
                } 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);
    }

    /**
     * this Method updates late/ not paid installment components to Map with
     * effective date as per REST(for principal portion ) and compounding
     * (interest or fee or interest and fee portions) frequency
     * 
     * @param lastRestDate
     *            TODO
     * @param compoundingMap
     *            TODO
     * 
     */
    private void updateLatePaymentsToMap(final LoanApplicationTerms loanApplicationTerms,
            final HolidayDetailDTO holidayDetailDTO, final MonetaryCurrency currency,
            final Map<LocalDate, Money> latePaymentMap, final LocalDate scheduledDueDate,
            List<LoanRepaymentScheduleInstallment> installments, boolean applyRestFrequencyForPrincipal,
            final LocalDate lastRestDate, final TreeMap<LocalDate, Money> compoundingMap) {
        latePaymentMap.clear();
        LocalDate currentDate = DateUtils.getLocalDateOfTenant();

        Money totalCompoundingAmount = Money.zero(currency);
        Money compoundedMoney = Money.zero(currency);
        if (!compoundingMap.isEmpty()) {
            compoundedMoney = compoundingMap.get(lastRestDate);
        }
        boolean clearCompoundingMap = true;
        for (LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment : installments) {
            if (loanRepaymentScheduleInstallment.isNotFullyPaidOff()
                    && !loanRepaymentScheduleInstallment.getDueDate().isAfter(scheduledDueDate)
                    && !loanRepaymentScheduleInstallment.isRecalculatedInterestComponent()) {
                LocalDate principalEffectiveDate = loanRepaymentScheduleInstallment.getDueDate();
                if (applyRestFrequencyForPrincipal) {
                    principalEffectiveDate = getNextRestScheduleDate(
                            loanRepaymentScheduleInstallment.getDueDate().minusDays(1), loanApplicationTerms,
                            holidayDetailDTO);
                }
                if (principalEffectiveDate.isBefore(currentDate)) {
                    updateMapWithAmount(latePaymentMap,
                            loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency),
                            principalEffectiveDate);
                    totalCompoundingAmount = totalCompoundingAmount
                            .plus(loanRepaymentScheduleInstallment.getPrincipalOutstanding(currency));
                }

                final Money changedCompoundedMoney = updateMapWithCompoundingDetails(loanApplicationTerms,
                        holidayDetailDTO, currency, compoundingMap, loanRepaymentScheduleInstallment, lastRestDate,
                        compoundedMoney, scheduledDueDate);
                if (compoundedMoney.isZero() || !compoundedMoney.isEqualTo(changedCompoundedMoney)) {
                    compoundedMoney = changedCompoundedMoney;
                    clearCompoundingMap = false;
                }
            }
        }
        if (totalCompoundingAmount.isGreaterThanZero()) {
            updateMapWithAmount(latePaymentMap, totalCompoundingAmount.negated(), lastRestDate);
        }
        if (clearCompoundingMap) {
            compoundingMap.clear();
        }
    }

    private Money updateMapWithCompoundingDetails(final LoanApplicationTerms loanApplicationTerms,
            final HolidayDetailDTO holidayDetailDTO, final MonetaryCurrency currency,
            final TreeMap<LocalDate, Money> compoundingMap,
            final LoanRepaymentScheduleInstallment loanRepaymentScheduleInstallment, final LocalDate lastRestDate,
            final Money compoundedMoney, final LocalDate scheduledDueDate) {
        Money ignoreMoney = compoundedMoney;
        if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) {
            LocalDate compoundingEffectiveDate = getNextCompoundScheduleDate(
                    loanRepaymentScheduleInstallment.getDueDate().minusDays(1), loanApplicationTerms,
                    holidayDetailDTO);

            if (compoundingEffectiveDate.isBefore(DateUtils.getLocalDateOfTenant())) {
                Money amount = Money.zero(currency);
                switch (loanApplicationTerms.getInterestRecalculationCompoundingMethod()) {
                case INTEREST:
                    amount = amount.plus(loanRepaymentScheduleInstallment.getInterestOutstanding(currency));
                    break;
                case FEE:
                    amount = amount.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency));
                    amount = amount.plus(loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency));
                    break;
                case INTEREST_AND_FEE:
                    amount = amount.plus(loanRepaymentScheduleInstallment.getInterestOutstanding(currency));
                    amount = amount.plus(loanRepaymentScheduleInstallment.getFeeChargesOutstanding(currency));
                    amount = amount.plus(loanRepaymentScheduleInstallment.getPenaltyChargesOutstanding(currency));
                    break;
                default:
                    break;
                }
                if (compoundingEffectiveDate.isBefore(scheduledDueDate)) {
                    ignoreMoney = ignoreMoney.plus(amount);
                    if (ignoreMoney.isGreaterThanZero()) {
                        updateMapWithAmount(compoundingMap, ignoreMoney, compoundingEffectiveDate);
                        updateMapWithAmount(compoundingMap, ignoreMoney.negated(), lastRestDate);
                        ignoreMoney = ignoreMoney.zero();
                    }
                } else {
                    if (ignoreMoney.isLessThanZero()) {
                        LocalDate firstKey = compoundingMap.firstKey();
                        updateMapWithAmount(compoundingMap, ignoreMoney, firstKey);
                        updateMapWithAmount(compoundingMap, ignoreMoney.negated(), lastRestDate);
                        ignoreMoney = ignoreMoney.zero();
                    }
                    updateMapWithAmount(compoundingMap, amount, compoundingEffectiveDate);
                    updateMapWithAmount(compoundingMap, amount.negated(), lastRestDate);
                }
            }
        }
        return ignoreMoney;
    }

    private void populateCompoundingDatesInPeriod(final LocalDate startDate, final LocalDate endDate,
            final LocalDate currentDate, final LoanApplicationTerms loanApplicationTerms,
            final HolidayDetailDTO holidayDetailDTO, final Map<LocalDate, Money> compoundingMap,
            final Set<LoanCharge> charges, MonetaryCurrency currency) {
        if (loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) {
            LocalDate lastCompoundingDate = startDate;
            LocalDate compoundingDate = startDate;
            while (compoundingDate.isBefore(endDate) && compoundingDate.isBefore(currentDate)) {
                compoundingDate = getNextCompoundScheduleDate(compoundingDate, loanApplicationTerms,
                        holidayDetailDTO);
                if (!compoundingDate.isBefore(currentDate)) {
                    break;
                } else if (compoundingDate.isAfter(endDate)) {
                    updateMapWithAmount(compoundingMap, Money.zero(currency), compoundingDate);
                } else {
                    Money feeChargesForInstallment = cumulativeFeeChargesDueWithin(lastCompoundingDate,
                            compoundingDate, charges, currency, null, loanApplicationTerms.getPrincipal(), null,
                            loanApplicationTerms.getNumberOfRepayments(), false);
                    Money penaltyChargesForInstallment = cumulativePenaltyChargesDueWithin(lastCompoundingDate,
                            compoundingDate, charges, currency, null, loanApplicationTerms.getPrincipal(), null,
                            loanApplicationTerms.getNumberOfRepayments(), false);
                    updateMapWithAmount(compoundingMap, feeChargesForInstallment.plus(penaltyChargesForInstallment),
                            compoundingDate);
                }
                lastCompoundingDate = compoundingDate;
            }
        }
    }

    protected void clearMapDetails(final LocalDate startDate, final Map<LocalDate, Money> compoundingMap) {
        Map<LocalDate, Money> temp = new HashMap<>();
        for (LocalDate date : compoundingMap.keySet()) {
            if (!date.isBefore(startDate)) {
                temp.put(date, compoundingMap.get(date));
            }
        }
        compoundingMap.clear();
        compoundingMap.putAll(temp);
    }

    /**
     * This Method updates principal paid component to map with effective date
     * as per the REST
     * 
     */
    private void updatePrincipalPaidPortionToMap(final LoanApplicationTerms loanApplicationTerms,
            final HolidayDetailDTO holidayDetailDTO, Map<LocalDate, Money> principalPortionMap,
            final LoanScheduleModelPeriod installment, final RecalculationDetail detail, final Money unprocessed,
            final List<LoanRepaymentScheduleInstallment> installments) {
        LocalDate applicableDate = getNextRestScheduleDate(detail.getTransactionDate().minusDays(1),
                loanApplicationTerms, holidayDetailDTO);
        updateMapWithAmount(principalPortionMap, unprocessed, applicableDate);
        installment.addPrincipalAmount(unprocessed);
        LoanRepaymentScheduleInstallment lastInstallment = installments.get(installments.size() - 1);
        lastInstallment.updatePrincipal(
                lastInstallment.getPrincipal(unprocessed.getCurrency()).plus(unprocessed).getAmount());
        lastInstallment.payPrincipalComponent(detail.getTransactionDate(), unprocessed);
    }

    /**
     * merges all the applicable amounts(compounding dates, disbursements, late
     * payment compounding and principal change as per rest) changes to single
     * map for interest calculation
     * 
     * @param compoundingDates
     *            TODO
     */
    private TreeMap<LocalDate, Money> mergeVariationsToMap(final Map<LocalDate, Money> princiaplPaidMap,
            final Map<LocalDate, Money> latePaymentMap, final Map<LocalDate, Money> disburseDetailsMap,
            final Map<LocalDate, Money> compoundingDates) {
        TreeMap<LocalDate, Money> map = new TreeMap<>();
        map.putAll(latePaymentMap);

        for (Map.Entry<LocalDate, Money> mapEntry : disburseDetailsMap.entrySet()) {
            Money value = mapEntry.getValue();
            if (map.containsKey(mapEntry.getKey())) {
                value = value.plus(map.get(mapEntry.getKey()));
            }
            map.put(mapEntry.getKey(), value);
        }

        for (Map.Entry<LocalDate, Money> mapEntry : princiaplPaidMap.entrySet()) {
            Money value = mapEntry.getValue().negated();
            if (map.containsKey(mapEntry.getKey())) {
                value = value.plus(map.get(mapEntry.getKey()));
            }
            map.put(mapEntry.getKey(), value);
        }

        for (Map.Entry<LocalDate, Money> mapEntry : compoundingDates.entrySet()) {
            Money value = mapEntry.getValue();
            if (!map.containsKey(mapEntry.getKey())) {
                map.put(mapEntry.getKey(), value.zero());
            }
        }

        return map;
    }

    /**
     * calculates Interest stating date as per the settings
     */
    private LocalDate calculateInterestStartDateForPeriod(final LoanApplicationTerms loanApplicationTerms,
            LocalDate periodStartDate, final LocalDate idealDisbursementDate,
            LocalDate periodStartDateApplicableForInterest) {
        if (periodStartDate.isBefore(idealDisbursementDate)) {
            if (loanApplicationTerms.getInterestChargedFromLocalDate() != null) {
                periodStartDateApplicableForInterest = loanApplicationTerms.getInterestChargedFromLocalDate();
            } else {
                periodStartDateApplicableForInterest = idealDisbursementDate;
            }
        }
        return periodStartDateApplicableForInterest;
    }

    private void updateMapWithAmount(final Map<LocalDate, Money> map, final Money amount,
            final LocalDate amountApplicableDate) {
        Money principalPaid = amount;
        if (map.containsKey(amountApplicableDate)) {
            principalPaid = map.get(amountApplicableDate).plus(principalPaid);
        }
        map.put(amountApplicableDate, principalPaid);
    }

    private Money getTotalAmount(final Map<LocalDate, Money> map, final MonetaryCurrency currency) {
        Money total = Money.zero(currency);
        for (Map.Entry<LocalDate, Money> mapEntry : map.entrySet()) {
            if (mapEntry.getKey().isBefore(DateUtils.getLocalDateOfTenant())) {
                total = total.plus(mapEntry.getValue());
            }
        }
        return total;
    }

    @Override
    public LoanRescheduleModel reschedule(final MathContext mathContext,
            final LoanRescheduleRequest loanRescheduleRequest, final ApplicationCurrency applicationCurrency,
            final HolidayDetailDTO holidayDetailDTO, final CalendarInstance restCalendarInstance,
            final CalendarInstance compoundingCalendarInstance) {

        final Loan loan = loanRescheduleRequest.getLoan();
        final LoanSummary loanSummary = loan.getSummary();
        final LoanProductMinimumRepaymentScheduleRelatedDetail loanProductRelatedDetail = loan
                .getLoanRepaymentScheduleDetail();
        final MonetaryCurrency currency = loanProductRelatedDetail.getCurrency();

        // create an archive of the current loan schedule installments
        Collection<LoanRepaymentScheduleHistory> loanRepaymentScheduleHistoryList = null;

        // get the initial list of repayment installments
        List<LoanRepaymentScheduleInstallment> repaymentScheduleInstallments = loan
                .getRepaymentScheduleInstallments();

        // sort list by installment number in ASC order
        Collections.sort(repaymentScheduleInstallments,
                LoanRepaymentScheduleInstallment.installmentNumberComparator);

        final Collection<LoanRescheduleModelRepaymentPeriod> periods = new ArrayList<>();

        Money outstandingLoanBalance = loan.getPrincpal();

        for (LoanRepaymentScheduleInstallment repaymentScheduleInstallment : repaymentScheduleInstallments) {

            Integer oldPeriodNumber = repaymentScheduleInstallment.getInstallmentNumber();
            LocalDate fromDate = repaymentScheduleInstallment.getFromDate();
            LocalDate dueDate = repaymentScheduleInstallment.getDueDate();
            Money principalDue = repaymentScheduleInstallment.getPrincipal(currency);
            Money interestDue = repaymentScheduleInstallment.getInterestCharged(currency);
            Money feeChargesDue = repaymentScheduleInstallment.getFeeChargesCharged(currency);
            Money penaltyChargesDue = repaymentScheduleInstallment.getPenaltyChargesCharged(currency);
            Money totalDue = principalDue.plus(interestDue).plus(feeChargesDue).plus(penaltyChargesDue);

            outstandingLoanBalance = outstandingLoanBalance.minus(principalDue);

            LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod.instance(oldPeriodNumber,
                    oldPeriodNumber, fromDate, dueDate, principalDue, outstandingLoanBalance, interestDue,
                    feeChargesDue, penaltyChargesDue, totalDue, false);

            periods.add(period);
        }

        Money outstandingBalance = loan.getPrincpal();
        Money totalCumulativePrincipal = Money.zero(currency);
        Money totalCumulativeInterest = Money.zero(currency);
        Money actualTotalCumulativeInterest = Money.zero(currency);
        Money totalOutstandingInterestPaymentDueToGrace = Money.zero(currency);
        Money totalPrincipalBeforeReschedulePeriod = Money.zero(currency);

        LocalDate installmentDueDate = null;
        LocalDate adjustedInstallmentDueDate = null;
        LocalDate installmentFromDate = null;
        Integer rescheduleFromInstallmentNo = defaultToZeroIfNull(
                loanRescheduleRequest.getRescheduleFromInstallment());
        Integer installmentNumber = rescheduleFromInstallmentNo;
        Integer graceOnPrincipal = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnPrincipal());
        Integer graceOnInterest = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnInterest());
        Integer extraTerms = defaultToZeroIfNull(loanRescheduleRequest.getExtraTerms());
        final boolean recalculateInterest = loanRescheduleRequest.getRecalculateInterest();
        Integer numberOfRepayments = repaymentScheduleInstallments.size();
        Integer rescheduleNumberOfRepayments = numberOfRepayments;
        final Money principal = loan.getPrincpal();
        final Money totalPrincipalOutstanding = Money.of(currency, loanSummary.getTotalPrincipalOutstanding());
        LocalDate adjustedDueDate = loanRescheduleRequest.getAdjustedDueDate();
        BigDecimal newInterestRate = loanRescheduleRequest.getInterestRate();
        int loanTermInDays = Integer.valueOf(0);

        if (rescheduleFromInstallmentNo > 0) {
            // this will hold the loan repayment installment that is before the
            // reschedule start installment
            // (rescheduleFrominstallment)
            LoanRepaymentScheduleInstallment previousInstallment = null;

            // get the install number of the previous installment
            int previousInstallmentNo = rescheduleFromInstallmentNo - 1;

            // only fetch the installment if the number is greater than 0
            if (previousInstallmentNo > 0) {
                previousInstallment = loan.fetchRepaymentScheduleInstallment(previousInstallmentNo);
            }

            LoanRepaymentScheduleInstallment firstInstallment = loan.fetchRepaymentScheduleInstallment(1);

            // the "installment from date" is equal to the due date of the
            // previous installment, if it exists
            if (previousInstallment != null) {
                installmentFromDate = previousInstallment.getDueDate();
            }

            else {
                installmentFromDate = firstInstallment.getFromDate();
            }

            installmentDueDate = installmentFromDate;
            LocalDate periodStartDateApplicableForInterest = installmentFromDate;
            Integer periodNumber = 1;
            outstandingLoanBalance = loan.getPrincpal();

            for (LoanRescheduleModelRepaymentPeriod period : periods) {

                if (period.periodDueDate().isBefore(loanRescheduleRequest.getRescheduleFromDate())) {

                    totalPrincipalBeforeReschedulePeriod = totalPrincipalBeforeReschedulePeriod
                            .plus(period.principalDue());
                    actualTotalCumulativeInterest = actualTotalCumulativeInterest.plus(period.interestDue());
                    rescheduleNumberOfRepayments--;
                    outstandingLoanBalance = outstandingLoanBalance.minus(period.principalDue());
                    outstandingBalance = outstandingBalance.minus(period.principalDue());
                }
            }

            while (graceOnPrincipal > 0 || graceOnInterest > 0) {

                LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod.instance(0, 0,
                        new LocalDate(), new LocalDate(), Money.zero(currency), Money.zero(currency),
                        Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency),
                        true);

                periods.add(period);

                if (graceOnPrincipal > 0) {
                    graceOnPrincipal--;
                }

                if (graceOnInterest > 0) {
                    graceOnInterest--;
                }

                rescheduleNumberOfRepayments++;
                numberOfRepayments++;
            }

            while (extraTerms > 0) {

                LoanRescheduleModelRepaymentPeriod period = LoanRescheduleModelRepaymentPeriod.instance(0, 0,
                        new LocalDate(), new LocalDate(), Money.zero(currency), Money.zero(currency),
                        Money.zero(currency), Money.zero(currency), Money.zero(currency), Money.zero(currency),
                        true);

                periods.add(period);

                extraTerms--;
                rescheduleNumberOfRepayments++;
                numberOfRepayments++;
            }

            // get the loan application terms from the Loan object
            final LoanApplicationTerms loanApplicationTerms = loan.getLoanApplicationTerms(applicationCurrency,
                    restCalendarInstance, compoundingCalendarInstance);

            // update the number of repayments
            loanApplicationTerms.updateNumberOfRepayments(numberOfRepayments);

            LocalDate loanEndDate = this.scheduledDateGenerator.getLastRepaymentDate(loanApplicationTerms,
                    holidayDetailDTO);
            loanApplicationTerms.updateLoanEndDate(loanEndDate);

            if (newInterestRate != null) {
                loanApplicationTerms.updateAnnualNominalInterestRate(newInterestRate);
                loanApplicationTerms.updateInterestRatePerPeriod(newInterestRate);
            }

            graceOnPrincipal = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnPrincipal());
            graceOnInterest = defaultToZeroIfNull(loanRescheduleRequest.getGraceOnInterest());

            loanApplicationTerms.updateInterestPaymentGrace(graceOnInterest);
            loanApplicationTerms.updatePrincipalGrace(graceOnPrincipal);

            loanApplicationTerms.setPrincipal(totalPrincipalOutstanding);
            loanApplicationTerms.updateNumberOfRepayments(rescheduleNumberOfRepayments);
            loanApplicationTerms.updateLoanTermFrequency(rescheduleNumberOfRepayments);
            loanApplicationTerms.updateInterestChargedFromDate(periodStartDateApplicableForInterest);

            Money totalInterestChargedForFullLoanTerm = loanApplicationTerms
                    .calculateTotalInterestCharged(this.paymentPeriodsInOneYearCalculator, mathContext);

            if (!recalculateInterest && newInterestRate == null) {
                totalInterestChargedForFullLoanTerm = Money.of(currency, loanSummary.getTotalInterestCharged());
                totalInterestChargedForFullLoanTerm = totalInterestChargedForFullLoanTerm
                        .minus(actualTotalCumulativeInterest);

                loanApplicationTerms.updateTotalInterestDue(totalInterestChargedForFullLoanTerm);
            }

            for (LoanRescheduleModelRepaymentPeriod period : periods) {

                if (period.periodDueDate().isEqual(loanRescheduleRequest.getRescheduleFromDate())
                        || period.periodDueDate().isAfter(loanRescheduleRequest.getRescheduleFromDate())
                        || period.isNew()) {

                    installmentDueDate = this.scheduledDateGenerator.generateNextRepaymentDate(installmentDueDate,
                            loanApplicationTerms, false);

                    if (adjustedDueDate != null && periodNumber == 1) {
                        installmentDueDate = adjustedDueDate;
                    }

                    adjustedInstallmentDueDate = this.scheduledDateGenerator.adjustRepaymentDate(installmentDueDate,
                            loanApplicationTerms, holidayDetailDTO);

                    final int daysInInstallment = Days.daysBetween(installmentFromDate, adjustedInstallmentDueDate)
                            .getDays();

                    period.updatePeriodNumber(installmentNumber);
                    period.updatePeriodFromDate(installmentFromDate);
                    period.updatePeriodDueDate(adjustedInstallmentDueDate);

                    double interestCalculationGraceOnRepaymentPeriodFraction = this.paymentPeriodsInOneYearCalculator
                            .calculatePortionOfRepaymentPeriodInterestChargingGrace(
                                    periodStartDateApplicableForInterest, adjustedInstallmentDueDate,
                                    periodStartDateApplicableForInterest,
                                    loanApplicationTerms.getLoanTermPeriodFrequencyType(),
                                    loanApplicationTerms.getRepaymentEvery());

                    // ========================= Calculate the interest due
                    // ========================================

                    // change the principal to => Principal Disbursed - Total
                    // Principal Paid
                    // interest calculation is always based on the total
                    // principal outstanding
                    loanApplicationTerms.setPrincipal(totalPrincipalOutstanding);

                    // determine the interest & principal for the period
                    PrincipalInterest principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
                            this.paymentPeriodsInOneYearCalculator,
                            interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal,
                            totalCumulativeInterest, totalInterestChargedForFullLoanTerm,
                            totalOutstandingInterestPaymentDueToGrace, outstandingBalance, loanApplicationTerms,
                            periodNumber, mathContext, null, null, installmentFromDate, adjustedInstallmentDueDate,
                            daysInInstallment);

                    // update the interest due for the period
                    period.updateInterestDue(principalInterestForThisPeriod.interest());

                    // =============================================================================================

                    // ========================== Calculate the principal due
                    // ======================================

                    // change the principal to => Principal Disbursed - Total
                    // cumulative Principal Amount before the reschedule
                    // installment
                    loanApplicationTerms.setPrincipal(principal.minus(totalPrincipalBeforeReschedulePeriod));

                    principalInterestForThisPeriod = calculatePrincipalInterestComponentsForPeriod(
                            this.paymentPeriodsInOneYearCalculator,
                            interestCalculationGraceOnRepaymentPeriodFraction, totalCumulativePrincipal,
                            totalCumulativeInterest, totalInterestChargedForFullLoanTerm,
                            totalOutstandingInterestPaymentDueToGrace, outstandingBalance, loanApplicationTerms,
                            periodNumber, mathContext, null, null, installmentFromDate, adjustedInstallmentDueDate,
                            daysInInstallment);

                    period.updatePrincipalDue(principalInterestForThisPeriod.principal());

                    // ==============================================================================================

                    outstandingLoanBalance = outstandingLoanBalance.minus(period.principalDue());
                    period.updateOutstandingLoanBalance(outstandingLoanBalance);

                    Money principalDue = Money.of(currency, period.principalDue());
                    Money interestDue = Money.of(currency, period.interestDue());

                    if (principalDue.isZero() && interestDue.isZero()) {
                        period.updateFeeChargesDue(Money.zero(currency));
                        period.updatePenaltyChargesDue(Money.zero(currency));
                    }

                    Money feeChargesDue = Money.of(currency, period.feeChargesDue());
                    Money penaltyChargesDue = Money.of(currency, period.penaltyChargesDue());

                    Money totalDue = principalDue.plus(interestDue).plus(feeChargesDue).plus(penaltyChargesDue);

                    period.updateTotalDue(totalDue);

                    // update cumulative fields for principal & interest
                    totalCumulativePrincipal = totalCumulativePrincipal.plus(period.principalDue());
                    totalCumulativeInterest = totalCumulativeInterest.plus(period.interestDue());
                    actualTotalCumulativeInterest = actualTotalCumulativeInterest.plus(period.interestDue());
                    totalOutstandingInterestPaymentDueToGrace = principalInterestForThisPeriod
                            .interestPaymentDueToGrace();

                    installmentFromDate = adjustedInstallmentDueDate;
                    installmentNumber++;
                    periodNumber++;
                    loanTermInDays += daysInInstallment;

                    outstandingBalance = outstandingBalance.minus(period.principalDue());
                }
            }
        }

        final Money totalRepaymentExpected = principal // get the loan Principal
                // amount
                .plus(actualTotalCumulativeInterest) // add the actual total
                // cumulative interest
                .plus(loanSummary.getTotalFeeChargesCharged()) // add the total
                // fees charged
                .plus(loanSummary.getTotalPenaltyChargesCharged()); // finally
                                                                    // add the
                                                                    // total
                                                                    // penalty
                                                                    // charged

        return LoanRescheduleModel.instance(periods, loanRepaymentScheduleHistoryList, applicationCurrency,
                loanTermInDays, loan.getPrincpal(), loan.getPrincpal().getAmount(),
                loanSummary.getTotalPrincipalRepaid(), actualTotalCumulativeInterest.getAmount(),
                loanSummary.getTotalFeeChargesCharged(), loanSummary.getTotalPenaltyChargesCharged(),
                totalRepaymentExpected.getAmount(), loanSummary.getTotalOutstanding());
    }

    protected double calculateInterestForDays(int daysInPeriodApplicableForInterest, BigDecimal interest,
            int days) {
        if (interest.doubleValue() == 0 || days == 0) {
            return 0;
        }
        return ((interest.doubleValue()) / daysInPeriodApplicableForInterest) * days;
    }

    public abstract PrincipalInterest calculatePrincipalInterestComponentsForPeriod(
            PaymentPeriodsInOneYearCalculator calculator, double interestCalculationGraceOnRepaymentPeriodFraction,
            Money totalCumulativePrincipal, Money totalCumulativeInterest, Money totalInterestDueForLoan,
            Money cumulatingInterestPaymentDueToGrace, Money outstandingBalance,
            LoanApplicationTerms loanApplicationTerms, int periodNumber, MathContext mc,
            TreeMap<LocalDate, Money> principalVariation, Map<LocalDate, Money> compoundingMap,
            LocalDate periodStartDate, LocalDate periodEndDate, int daysForInterestInFullPeriod);

    protected final boolean isLastRepaymentPeriod(final int numberOfRepayments, final int periodNumber) {
        return periodNumber == numberOfRepayments;
    }

    private BigDecimal deriveTotalChargesDueAtTimeOfDisbursement(final Set<LoanCharge> loanCharges) {
        BigDecimal chargesDueAtTimeOfDisbursement = BigDecimal.ZERO;
        for (final LoanCharge loanCharge : loanCharges) {
            if (loanCharge.isDueAtDisbursement()) {
                chargesDueAtTimeOfDisbursement = chargesDueAtTimeOfDisbursement.add(loanCharge.amount());
            }
        }
        return chargesDueAtTimeOfDisbursement;
    }

    private BigDecimal getDisbursementAmount(final LoanApplicationTerms loanApplicationTerms,
            LocalDate disbursementDate, final Collection<LoanScheduleModelPeriod> periods,
            final BigDecimal chargesDueAtTimeOfDisbursement, final Map<LocalDate, Money> disurseDetail,
            final boolean excludePastUndisbursed) {
        BigDecimal principal = BigDecimal.ZERO;
        MonetaryCurrency currency = loanApplicationTerms.getPrincipal().getCurrency();
        for (DisbursementData disbursementData : loanApplicationTerms.getDisbursementDatas()) {
            if (disbursementData.disbursementDate().equals(disbursementDate)) {
                final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
                        .disbursement(disbursementData.disbursementDate(),
                                Money.of(currency, disbursementData.amount()), chargesDueAtTimeOfDisbursement);
                periods.add(disbursementPeriod);
                principal = principal.add(disbursementData.amount());
            } else if (!excludePastUndisbursed || disbursementData.isDisbursed()
                    || !disbursementData.disbursementDate().isBefore(DateUtils.getLocalDateOfTenant())) {
                disurseDetail.put(disbursementData.disbursementDate(),
                        Money.of(currency, disbursementData.amount()));
            }
        }
        return principal;
    }

    private Collection<LoanScheduleModelPeriod> createNewLoanScheduleListWithDisbursementDetails(
            final int numberOfRepayments, final LoanApplicationTerms loanApplicationTerms,
            final BigDecimal chargesDueAtTimeOfDisbursement) {

        Collection<LoanScheduleModelPeriod> periods = null;
        if (loanApplicationTerms.isMultiDisburseLoan()) {
            periods = new ArrayList<>(numberOfRepayments + loanApplicationTerms.getDisbursementDatas().size());
        } else {
            periods = new ArrayList<>(numberOfRepayments + 1);
            final LoanScheduleModelDisbursementPeriod disbursementPeriod = LoanScheduleModelDisbursementPeriod
                    .disbursement(loanApplicationTerms, chargesDueAtTimeOfDisbursement);
            periods.add(disbursementPeriod);
        }

        return periods;
    }

    private Set<LoanCharge> seperateTotalCompoundingPercentageCharges(final Set<LoanCharge> loanCharges) {
        Set<LoanCharge> interestCharges = new HashSet<>();
        for (final LoanCharge loanCharge : loanCharges) {
            if (loanCharge.isSpecifiedDueDate() && (loanCharge.getChargeCalculation().isPercentageOfInterest()
                    || loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest())) {
                interestCharges.add(loanCharge);
            }
        }
        loanCharges.removeAll(interestCharges);
        return interestCharges;
    }

    private Money cumulativeFeeChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
            final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency,
            final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed,
            final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments,
            boolean isInstallmentChargeApplicable) {

        Money cumulative = Money.zero(monetaryCurrency);

        for (final LoanCharge loanCharge : loanCharges) {
            if (loanCharge.isFeeCharge()) {
                if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
                    cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, numberOfRepayments,
                            cumulative, loanCharge);
                } else if (loanCharge.isOverdueInstallmentCharge()
                        && loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
                        && loanCharge.getChargeCalculation().isPercentageBased()) {
                    cumulative = cumulative.plus(loanCharge.chargeAmount());
                } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
                        && loanCharge.getChargeCalculation().isPercentageBased()) {
                    cumulative = calculateSpecificDueDateChargeWithPercentage(principalDisbursed,
                            totalInterestChargedForFullLoanTerm, cumulative, loanCharge);
                } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
                    cumulative = cumulative.plus(loanCharge.amount());
                }
            }
        }

        return cumulative;
    }

    private Money calculateSpecificDueDateChargeWithPercentage(final Money principalDisbursed,
            final Money totalInterestChargedForFullLoanTerm, Money cumulative, final LoanCharge loanCharge) {
        BigDecimal amount = BigDecimal.ZERO;
        if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
            amount = amount.add(principalDisbursed.getAmount())
                    .add(totalInterestChargedForFullLoanTerm.getAmount());
        } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
            amount = amount.add(totalInterestChargedForFullLoanTerm.getAmount());
        } else {
            amount = amount.add(principalDisbursed.getAmount());
        }
        BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
        cumulative = cumulative.plus(loanChargeAmt);
        return cumulative;
    }

    private Money calculateInstallmentCharge(final PrincipalInterest principalInterestForThisPeriod,
            int numberOfRepayments, Money cumulative, final LoanCharge loanCharge) {
        if (loanCharge.getChargeCalculation().isPercentageBased()) {
            BigDecimal amount = BigDecimal.ZERO;
            if (loanCharge.getChargeCalculation().isPercentageOfAmountAndInterest()) {
                amount = amount.add(principalInterestForThisPeriod.principal().getAmount())
                        .add(principalInterestForThisPeriod.interest().getAmount());
            } else if (loanCharge.getChargeCalculation().isPercentageOfInterest()) {
                amount = amount.add(principalInterestForThisPeriod.interest().getAmount());
            } else {
                amount = amount.add(principalInterestForThisPeriod.principal().getAmount());
            }
            BigDecimal loanChargeAmt = amount.multiply(loanCharge.getPercentage()).divide(BigDecimal.valueOf(100));
            cumulative = cumulative.plus(loanChargeAmt);
        } else {
            cumulative = cumulative.plus(loanCharge.amount().divide(BigDecimal.valueOf(numberOfRepayments)));
        }
        return cumulative;
    }

    private Money cumulativePenaltyChargesDueWithin(final LocalDate periodStart, final LocalDate periodEnd,
            final Set<LoanCharge> loanCharges, final MonetaryCurrency monetaryCurrency,
            final PrincipalInterest principalInterestForThisPeriod, final Money principalDisbursed,
            final Money totalInterestChargedForFullLoanTerm, int numberOfRepayments,
            boolean isInstallmentChargeApplicable) {

        Money cumulative = Money.zero(monetaryCurrency);

        for (final LoanCharge loanCharge : loanCharges) {
            if (loanCharge.isPenaltyCharge()) {
                if (loanCharge.isInstalmentFee() && isInstallmentChargeApplicable) {
                    cumulative = calculateInstallmentCharge(principalInterestForThisPeriod, numberOfRepayments,
                            cumulative, loanCharge);
                } else if (loanCharge.isOverdueInstallmentCharge()
                        && loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
                        && loanCharge.getChargeCalculation().isPercentageBased()) {
                    cumulative = cumulative.plus(loanCharge.chargeAmount());
                } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)
                        && loanCharge.getChargeCalculation().isPercentageBased()) {
                    cumulative = calculateSpecificDueDateChargeWithPercentage(principalDisbursed,
                            totalInterestChargedForFullLoanTerm, cumulative, loanCharge);
                } else if (loanCharge.isDueForCollectionFromAndUpToAndIncluding(periodStart, periodEnd)) {
                    cumulative = cumulative.plus(loanCharge.amount());
                }
            }
        }

        return cumulative;
    }

    /**
     * Method calls schedule regeneration by passing transactions one after
     * another(this is done mainly to handle the scenario where interest or fee
     * of over due installment should be collected before collecting principal )
     */
    @Override
    public LoanScheduleModel rescheduleNextInstallments(final MathContext mc,
            final LoanApplicationTerms loanApplicationTerms, final Set<LoanCharge> loanCharges,
            final HolidayDetailDTO holidayDetailDTO, final List<LoanTransaction> transactions,
            final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {

        Collection<RecalculationDetail> recalculationDetails = new ArrayList<>();
        for (LoanTransaction loanTransaction : transactions) {
            recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(),
                    LoanTransaction.copyTransactionProperties(loanTransaction)));
        }
        final LocalDate scheduleTillDate = null;
        return generate(mc, loanApplicationTerms, loanCharges, holidayDetailDTO, recalculationDetails,
                loanRepaymentScheduleTransactionProcessor, scheduleTillDate);
    }

    private List<LoanRepaymentScheduleInstallment> fetchInstallmentsFromScheduleModel(
            final LoanScheduleModel loanScheduleModel) {
        final List<LoanRepaymentScheduleInstallment> installments = new ArrayList<>();
        for (LoanScheduleModelPeriod loanScheduleModelPeriod : loanScheduleModel.getPeriods()) {
            addLoanRepaymentScheduleInstallment(installments, loanScheduleModelPeriod);
        }
        return installments;
    }

    private void addLoanRepaymentScheduleInstallment(final List<LoanRepaymentScheduleInstallment> installments,
            final LoanScheduleModelPeriod scheduledLoanInstallment) {
        if (scheduledLoanInstallment.isRepaymentPeriod()) {
            final LoanRepaymentScheduleInstallment installment = new LoanRepaymentScheduleInstallment(null,
                    scheduledLoanInstallment.periodNumber(), scheduledLoanInstallment.periodFromDate(),
                    scheduledLoanInstallment.periodDueDate(), scheduledLoanInstallment.principalDue(),
                    scheduledLoanInstallment.interestDue(), scheduledLoanInstallment.feeChargesDue(),
                    scheduledLoanInstallment.penaltyChargesDue(),
                    scheduledLoanInstallment.isRecalculatedInterestComponent());
            installments.add(installment);
        }
    }

    private LocalDate getNextRestScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms,
            final HolidayDetailDTO holidayDetailDTO) {
        LocalDate nextScheduleDate = null;
        if (loanApplicationTerms.getRecalculationFrequencyType().isSameAsRepayment()) {
            nextScheduleDate = this.scheduledDateGenerator.generateNextScheduleDateStartingFromDisburseDate(
                    startDate, loanApplicationTerms, holidayDetailDTO);
        } else {
            CalendarInstance calendarInstance = loanApplicationTerms.getRestCalendarInstance();
            nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate);
        }

        return nextScheduleDate;
    }

    private LocalDate getNextCompoundScheduleDate(LocalDate startDate, LoanApplicationTerms loanApplicationTerms,
            final HolidayDetailDTO holidayDetailDTO) {
        LocalDate nextScheduleDate = null;
        if (!loanApplicationTerms.getInterestRecalculationCompoundingMethod().isCompoundingEnabled()) {
            return null;
        }
        if (loanApplicationTerms.getCompoundingFrequencyType().isSameAsRepayment()) {
            nextScheduleDate = this.scheduledDateGenerator.generateNextScheduleDateStartingFromDisburseDate(
                    startDate, loanApplicationTerms, holidayDetailDTO);
        } else {
            CalendarInstance calendarInstance = loanApplicationTerms.getCompoundingCalendarInstance();
            nextScheduleDate = CalendarUtils.getNextScheduleDate(calendarInstance.getCalendar(), startDate);
        }

        return nextScheduleDate;
    }

    /**
     * Method returns the amount payable to close the loan account as of today.
     */
    @Override
    public LoanRepaymentScheduleInstallment calculatePrepaymentAmount(
            List<LoanRepaymentScheduleInstallment> installments, MonetaryCurrency currency, final LocalDate onDate,
            LoanApplicationTerms loanApplicationTerms, MathContext mc, Set<LoanCharge> charges,
            final HolidayDetailDTO holidayDetailDTO, final List<LoanTransaction> loanTransactions,
            final LoanRepaymentScheduleTransactionProcessor loanRepaymentScheduleTransactionProcessor) {

        LocalDate calculateTill = onDate;
        if (loanApplicationTerms.getPreClosureInterestCalculationStrategy().calculateTillRestFrequencyEnabled()) {
            calculateTill = getNextRestScheduleDate(onDate.minusDays(1), loanApplicationTerms, holidayDetailDTO);
        }
        Collection<RecalculationDetail> recalculationDetails = new ArrayList<>();
        for (LoanTransaction loanTransaction : loanTransactions) {
            recalculationDetails.add(new RecalculationDetail(loanTransaction.getTransactionDate(),
                    LoanTransaction.copyTransactionProperties(loanTransaction)));
        }
        LoanScheduleModel loanScheduleModel = generate(mc, loanApplicationTerms, charges, holidayDetailDTO,
                recalculationDetails, loanRepaymentScheduleTransactionProcessor, calculateTill);
        installments = fetchInstallmentsFromScheduleModel(loanScheduleModel);
        loanRepaymentScheduleTransactionProcessor.handleTransaction(
                loanApplicationTerms.getExpectedDisbursementDate(), loanTransactions, currency, installments,
                charges);
        Money feeCharges = Money.zero(currency);
        Money penaltyCharges = Money.zero(currency);
        Money totalPrincipal = Money.zero(currency);
        Money totalInterest = Money.zero(currency);
        for (final LoanRepaymentScheduleInstallment currentInstallment : installments) {
            if (currentInstallment.isNotFullyPaidOff()) {
                totalPrincipal = totalPrincipal.plus(currentInstallment.getPrincipalOutstanding(currency));
                totalInterest = totalInterest.plus(currentInstallment.getInterestOutstanding(currency));
                feeCharges = feeCharges.plus(currentInstallment.getFeeChargesOutstanding(currency));
                penaltyCharges = penaltyCharges.plus(currentInstallment.getPenaltyChargesOutstanding(currency));
            }
        }

        return new LoanRepaymentScheduleInstallment(null, 0, onDate, onDate, totalPrincipal.getAmount(),
                totalInterest.getAmount(), feeCharges.getAmount(), penaltyCharges.getAmount(), false);
    }

    /**
     * set the value to zero if the provided value is null
     * 
     * @return integer value equal/greater than 0
     **/
    private Integer defaultToZeroIfNull(Integer value) {

        return (value != null) ? value : 0;
    }
}