/*
* Copyright (c) 2005-2011 Grameen Foundation USA
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*
* See also http://www.apache.org/licenses/LICENSE-2.0.html for an
* explanation of the license and how it is applied.
*/
package org.mifos.clientportfolio.newloan.domain;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.mifos.accounts.business.AccountFeesEntity;
import org.mifos.accounts.fees.persistence.FeeDao;
import org.mifos.accounts.fees.util.helpers.RateAmountFlag;
import org.mifos.accounts.loan.business.LoanFeeScheduleEntity;
import org.mifos.accounts.loan.business.LoanScheduleEntity;
import org.mifos.accounts.loan.util.helpers.InstallmentPrincipalAndInterest;
import org.mifos.accounts.productdefinition.business.LoanOfferingBO;
import org.mifos.accounts.productdefinition.util.helpers.GraceType;
import org.mifos.accounts.productdefinition.util.helpers.InterestType;
import org.mifos.accounts.util.helpers.AccountConstants;
import org.mifos.accounts.util.helpers.FeeInstallment;
import org.mifos.accounts.util.helpers.InstallmentDate;
import org.mifos.accounts.util.helpers.PaymentStatus;
import org.mifos.application.master.business.MifosCurrency;
import org.mifos.application.meeting.business.MeetingBO;
import org.mifos.customers.business.CustomerBO;
import org.mifos.framework.util.helpers.Money;
import org.mifos.framework.util.helpers.MoneyUtils;
import org.mifos.schedule.ScheduledEvent;
import org.mifos.service.BusinessRuleException;
public class IndividualLoanScheduleFactory implements LoanScheduleFactory {
private FeeDao feeDao;
public IndividualLoanScheduleFactory(FeeDao feeDao) {
this.feeDao = feeDao;
}
@Override
public LoanSchedule create(LocalDate disbursementDate, List<DateTime> loanScheduleDates, List<Number> totalInstallmentAmounts, LoanOfferingBO loanProduct,
CustomerBO customer, MeetingBO loanMeeting, Money loanAmount, Double interestRate, Integer interestDays, Integer gracePeriodDuration,
List<AccountFeesEntity> accountFees) {
GraceType graceType = loanProduct.getGraceType();
InterestType interestType = loanProduct.getInterestType();
boolean variableInstallmentLoanProduct = loanProduct.isVariableInstallmentsAllowed();
boolean roundingDifferenceInFirstPayment = loanProduct.isRoundingDifferenceInFirstPayment();
Integer numberOfInstallments = loanScheduleDates.size();
RecurringScheduledEventFactory scheduledEventFactory = new RecurringScheduledEventFactoryImpl();
ScheduledEvent meetingScheduledEvent = scheduledEventFactory.createScheduledEventFrom(loanMeeting);
Integer installmentNumber = 1;
List<InstallmentDate> dueInstallmentDates = new ArrayList<InstallmentDate>();
for (DateTime scheduledDate : loanScheduleDates) {
dueInstallmentDates.add(new InstallmentDate(installmentNumber.shortValue(), scheduledDate.toLocalDate()
.toDateMidnight().toDate()));
installmentNumber++;
}
if (loanProduct.isPrinDueLastInst()) {
// Principal due on last installment has been cut, so throw an exception if we reach this code.
throw new BusinessRuleException(AccountConstants.NOT_SUPPORTED_EMI_GENERATION);
}
// loan interest calculation for various interest calculation algorithms
LoanDecliningInterestAnnualPeriodCalculator decliningInterestAnnualPeriodCalculator = new LoanDecliningInterestAnnualPeriodCalculatorFactory().create(loanMeeting.getRecurrenceType());
Double decliningInterestAnnualPeriod = decliningInterestAnnualPeriodCalculator.calculate(loanMeeting.getRecurAfter().intValue(), interestDays);
Double interestFractionalRatePerInstallment = interestRate / decliningInterestAnnualPeriod / 100;
LoanDurationInAccountingYearsCalculator loanDurationInAccountingYearsCalculator = new LoanDurationInAccountingYearsCalculatorFactory().create(loanMeeting.getRecurrenceType());
Double durationInYears = loanDurationInAccountingYearsCalculator.calculate(loanMeeting.getRecurAfter().intValue(), numberOfInstallments, interestDays);
List<Money> totalInstallmentAmountsAsMoney = new ArrayList<Money>();
for (Number totalInstallmentAmount : totalInstallmentAmounts) {
Money totalAmount = new Money(loanAmount.getCurrency(), BigDecimal.valueOf(totalInstallmentAmount.doubleValue()));
totalInstallmentAmountsAsMoney.add(totalAmount);
}
LoanInterestCalculationDetails loanInterestCalculationDetails = new LoanInterestCalculationDetails(loanAmount, interestRate, graceType, gracePeriodDuration,
numberOfInstallments, durationInYears, interestFractionalRatePerInstallment, disbursementDate, loanScheduleDates);
loanInterestCalculationDetails.setTotalInstallmentAmounts(totalInstallmentAmountsAsMoney);
LoanInterestCalculatorFactory loanInterestCalculatorFactory = new LoanInterestCalculatorFactoryImpl();
LoanInterestCalculator loanInterestCalculator = loanInterestCalculatorFactory.create(interestType, variableInstallmentLoanProduct);
Money loanInterest = loanInterestCalculator.calculate(loanInterestCalculationDetails);
// end of loan Interest creation
EqualInstallmentGeneratorFactory equalInstallmentGeneratorFactory = new EqualInstallmentGeneratorFactoryImpl();
PrincipalWithInterestGenerator equalInstallmentGenerator = equalInstallmentGeneratorFactory.create(interestType, loanInterest, variableInstallmentLoanProduct);
List<InstallmentPrincipalAndInterest> EMIInstallments = equalInstallmentGenerator.generateEqualInstallments(loanInterestCalculationDetails);
List<LoanScheduleEntity> unroundedLoanSchedules = createUnroundedLoanSchedulesFromInstallments(dueInstallmentDates, loanInterest, loanAmount,
meetingScheduledEvent, EMIInstallments, accountFees, customer);
Money rawAmount = calculateTotalFeesAndInterestForLoanSchedules(unroundedLoanSchedules, loanAmount.getCurrency(), accountFees);
List<LoanScheduleEntity> allExistingLoanSchedules = new ArrayList<LoanScheduleEntity>();
List<LoanScheduleEntity> finalisedLoanSchedules = new ArrayList<LoanScheduleEntity>(unroundedLoanSchedules);
if (variableInstallmentLoanProduct && totalInstallmentAmounts.isEmpty()) {
// only round inital loan schedule of variable installments product.
LoanScheduleRounder loanScheduleInstallmentRounder = new VariableInstallmentLoanScheduleRounder();
finalisedLoanSchedules = loanScheduleInstallmentRounder.round(graceType, gracePeriodDuration.shortValue(), loanAmount,
interestType, unroundedLoanSchedules, allExistingLoanSchedules);
} else if (!variableInstallmentLoanProduct && roundingDifferenceInFirstPayment) {
LoanScheduleRounderHelper loanScheduleRounderHelper = new DefaultLoanScheduleRounderHelper();
LoanScheduleRounder loanScheduleInstallmentRounder = new FirstInstallmentRoudingDifferenceLoanScheduleRounder(loanScheduleRounderHelper);
finalisedLoanSchedules = loanScheduleInstallmentRounder.round(graceType, gracePeriodDuration.shortValue(), loanAmount,
interestType, unroundedLoanSchedules, allExistingLoanSchedules);
} else if (!variableInstallmentLoanProduct) {
LoanScheduleRounderHelper loanScheduleRounderHelper = new DefaultLoanScheduleRounderHelper();
LoanScheduleRounder loanScheduleInstallmentRounder = new DefaultLoanScheduleRounder(loanScheduleRounderHelper);
finalisedLoanSchedules = loanScheduleInstallmentRounder.round(graceType, gracePeriodDuration.shortValue(), loanAmount,
interestType, unroundedLoanSchedules, allExistingLoanSchedules);
}
return new LoanSchedule(finalisedLoanSchedules, rawAmount);
}
private List<LoanScheduleEntity> createUnroundedLoanSchedulesFromInstallments(List<InstallmentDate> installmentDates,
Money loanInterest, Money loanAmount, ScheduledEvent meetingScheduledEvent,
List<InstallmentPrincipalAndInterest> principalWithInterestInstallments, List<AccountFeesEntity> accountFees, CustomerBO customer) {
List<LoanScheduleEntity> unroundedLoanSchedules = new ArrayList<LoanScheduleEntity>();
List<AccountFeesEntity> accountFeesWithNoTimeOfDibursementFees = new ArrayList<AccountFeesEntity>();
List<FeeInstallment> feeInstallments = new ArrayList<FeeInstallment>();
if (!accountFees.isEmpty()) {
InstallmentFeeCalculatorFactory installmentFeeCalculatorFactory = new InstallmentFeeCalculatorFactoryImpl();
for (AccountFeesEntity accountFeesEntity : accountFees) {
RateAmountFlag feeType = accountFeesEntity.getFees().getFeeType();
InstallmentFeeCalculator installmentFeeCalculator = installmentFeeCalculatorFactory.create(this.feeDao, feeType);
Double feeAmountOrRate = accountFeesEntity.getFeeAmount();
Money accountFeeAmount = installmentFeeCalculator.calculate(feeAmountOrRate, loanAmount, loanInterest, accountFeesEntity.getFees());
accountFeesEntity.setAccountFeeAmount(accountFeeAmount);
if (!accountFeesEntity.isTimeOfDisbursement()) {
accountFeesWithNoTimeOfDibursementFees.add(accountFeesEntity);
}
}
feeInstallments = FeeInstallment.createMergedFeeInstallments(meetingScheduledEvent, accountFeesWithNoTimeOfDibursementFees, installmentDates.size());
}
int installmentIndex = 0;
for (InstallmentDate installmentDate1 : installmentDates) {
InstallmentPrincipalAndInterest em = principalWithInterestInstallments.get(installmentIndex);
LoanScheduleEntity loanScheduleEntity = new LoanScheduleEntity(null, customer, installmentDate1
.getInstallmentId(), new java.sql.Date(installmentDate1.getInstallmentDueDate().getTime()),
PaymentStatus.UNPAID, em.getPrincipal(), em.getInterest());
for (FeeInstallment feeInstallment : feeInstallments) {
if (feeInstallment.getInstallmentId().equals(installmentDate1.getInstallmentId())) {
LoanFeeScheduleEntity loanFeeScheduleEntity = new LoanFeeScheduleEntity(loanScheduleEntity,
feeInstallment.getAccountFeesEntity().getFees(), feeInstallment.getAccountFeesEntity(),
feeInstallment.getAccountFee());
loanScheduleEntity.addAccountFeesAction(loanFeeScheduleEntity);
}
}
unroundedLoanSchedules.add(loanScheduleEntity);
installmentIndex++;
}
return unroundedLoanSchedules;
}
private Money calculateTotalFeesAndInterestForLoanSchedules(List<LoanScheduleEntity> unroundedLoanSchedules, MifosCurrency currencyInUse, List<AccountFeesEntity> accountFees) {
Money zero = new Money(currencyInUse);
Money interest = zero;
Money fees = zero;
for (LoanScheduleEntity unroundedLoanSchedule : unroundedLoanSchedules) {
interest = interest.add(unroundedLoanSchedule.getInterest());
fees = fees.add(unroundedLoanSchedule.getTotalFeesDueWithMiscFee());
}
Money feeDisbursementAmount = zero;
for (AccountFeesEntity accountFeesEntity : accountFees) {
if (accountFeesEntity.getFees().isTimeOfDisbursement()) {
feeDisbursementAmount = fees.add(accountFeesEntity.getAccountFeeAmount());
}
}
fees = fees.add(feeDisbursementAmount);
fees = MoneyUtils.currencyRound(fees);
interest = MoneyUtils.currencyRound(interest);
Money rawAmount = interest.add(fees);
return rawAmount;
}
}