/* * 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.framework.components.batchjobs.helpers; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import org.hibernate.Query; import org.joda.time.Days; import org.joda.time.LocalDate; import org.mifos.accounts.business.AccountPenaltiesEntity; import org.mifos.accounts.loan.business.LoanBO; import org.mifos.accounts.loan.business.LoanPenaltyScheduleEntity; import org.mifos.accounts.loan.business.LoanScheduleEntity; import org.mifos.accounts.penalties.business.AmountPenaltyBO; import org.mifos.accounts.penalties.business.RatePenaltyBO; import org.mifos.accounts.penalties.util.helpers.PenaltyPeriod; import org.mifos.application.NamedQueryConstants; import org.mifos.framework.components.batchjobs.SchedulerConstants; import org.mifos.framework.components.batchjobs.TaskHelper; import org.mifos.framework.components.batchjobs.exceptions.BatchJobException; import org.mifos.framework.hibernate.helper.StaticHibernateUtil; import org.mifos.framework.util.helpers.Money; public class ApplyPenaltyToLoanAccountsHelper extends TaskHelper { private LocalDate currentLocalDate; private Date currentDate; @Override public void execute(final long timeInMillis) throws BatchJobException { setCurrentDates(timeInMillis); List<String> errorList = new ArrayList<String>(); List<LoanBO> loanAccounts; try { loanAccounts = getLoanAccounts(); } catch (Exception e) { throw new BatchJobException(e); } if (loanAccounts != null && !loanAccounts.isEmpty()) { Integer loanAccountId = null; try { for (LoanBO loanAccount : loanAccounts) { loanAccountId = loanAccount.getAccountId(); List<AccountPenaltiesEntity> penaltyEntities =new ArrayList<AccountPenaltiesEntity>(loanAccount.getAccountPenalties()); for (AccountPenaltiesEntity penaltyEntity : penaltyEntities) { List<LoanScheduleEntity> lateInstallments = loanAccount.getDetailsOfLateInstallmentsPeriod( new LocalDate(penaltyEntity.getCreatedDate()), currentLocalDate); for (LoanScheduleEntity entity : lateInstallments) { //check grace period for installment period type if(penaltyEntity.getPenalty().getPeriodType().getPenaltyPeriod() == PenaltyPeriod.INSTALLMENTS && penaltyEntity.hasPeriodType()) { if (lateInstallments.get(0).getInstallmentId().equals(entity.getInstallmentId()) && checkGracePeriodTypeInstallments(lateInstallments, penaltyEntity.getPenalty().getPeriodDuration())) { continue; } } //check grace period for daily period type else if (penaltyEntity.getPenalty().getPeriodType().getPenaltyPeriod() == PenaltyPeriod.DAYS && penaltyEntity.hasPeriodType()) { if (checkGracePeriodTypeDays(entity, penaltyEntity.getPenalty().getPeriodDuration())) { continue; } } LoanPenaltyScheduleEntity penaltySchedule = entity.getPenaltyScheduleEntity(penaltyEntity.getPenalty().getPenaltyId()); if (checkPeriod(penaltyEntity, new LocalDate(entity.getActionDate().getTime())) || (penaltySchedule != null && penaltySchedule.isOn(currentLocalDate))) { continue; } if (penaltyEntity.isAmountPenalty()) { addAmountPenalty(penaltyEntity, loanAccount, entity); } else { addRatePenalty(penaltyEntity, loanAccount, entity); } } } } } catch (Exception e) { if (loanAccountId != null) { getLogger().error(String.format("ApplyPenaltyToLoanAccountsTask execute failed with exception %s: %s at loan account %s", e.getClass().getName(), e.getMessage(), loanAccountId.toString()), e); errorList.add(loanAccountId.toString()); } StaticHibernateUtil.rollbackTransaction(); } finally { StaticHibernateUtil.closeSession(); } } if (!errorList.isEmpty()) { throw new BatchJobException(SchedulerConstants.FAILURE, errorList); } } private boolean checkPeriod(AccountPenaltiesEntity penaltyEntity, LocalDate installmentDate) { int days = Days.daysBetween(installmentDate, currentLocalDate).getDays(); boolean check = false; boolean oneTime = penaltyEntity.isOneTime(); if (oneTime && penaltyEntity.getLastAppliedDate() != null) { check = true; } else if (!oneTime && ((penaltyEntity.isMonthlyTime() && days % 31 != 1) || penaltyEntity.isWeeklyTime() && days % 7 != 1)) { check = true; } return check; } private boolean checkGracePeriodTypeInstallments(final List<LoanScheduleEntity> lateInstallments, final int duration) { boolean check = false; if(lateInstallments.size() - 1 < duration) { check = true; } else if (lateInstallments.size() -1 == duration) { check = lateInstallments.get(duration).isOn(currentLocalDate.minusDays(1)); } else { check = false; } return check; } private boolean checkGracePeriodTypeDays(final LoanScheduleEntity entity, final int duration) { boolean check = false; int days = Days.daysBetween(new LocalDate(entity.getActionDate().getTime()), currentLocalDate).getDays(); check = days <= duration; return check; } private void addAmountPenalty(final AccountPenaltiesEntity penaltyEntity, final LoanBO loanAccount, final LoanScheduleEntity loanScheduleEntity) { AmountPenaltyBO penalty = (AmountPenaltyBO) penaltyEntity.getPenalty(); Money accountPenaltyAmount = penaltyEntity.getAccountPenaltyAmount(); Money charge = verifyLimits(loanAccount.getTotalPenalty(accountPenaltyAmount.getCurrency(), penalty.getPenaltyId()), accountPenaltyAmount, penalty.getMinimumLimit()/loanAccount.calcFactorOfEntireLoan().doubleValue(), penalty.getMaximumLimit()/loanAccount.calcFactorOfEntireLoan().doubleValue()); if (charge != null && charge.isGreaterThanZero()) { loanAccount.applyPenalty(charge, loanScheduleEntity.getInstallmentId(), penaltyEntity, currentDate); } try { StaticHibernateUtil.startTransaction(); StaticHibernateUtil.getSessionTL().update(loanAccount); StaticHibernateUtil.commitTransaction(); } catch (Exception e) { getLogger().error(e.getMessage()); StaticHibernateUtil.rollbackTransaction(); } } private void addRatePenalty(final AccountPenaltiesEntity penaltyEntity, final LoanBO loanAccount, final LoanScheduleEntity loanScheduleEntity) { RatePenaltyBO penalty = (RatePenaltyBO) penaltyEntity.getPenalty(); Double radio = penaltyEntity.getAccountPenaltyAmount().getAmount().doubleValue() / 100.0d; Money charge = null; if (penalty.isOutstandingPrincipalAmount()) { charge = loanAccount.getLoanSummary().getOriginalPrincipal().multiply(radio); } else if (penalty.isOutstandingLoanAmount()) { charge = loanAccount.getLoanSummary().getOutstandingBalance().multiply(radio); } else if (penalty.isOverdueAmountDue()) { charge = loanScheduleEntity.getTotalDue().multiply(radio); } else if (penalty.isOverduePrincipal()) { charge = loanScheduleEntity.getPrincipalDue().multiply(radio); } else { charge = Money.zero(); } Money totalPenalty = loanAccount.getTotalPenalty(charge.getCurrency(), penalty.getPenaltyId()); charge = verifyLimits(totalPenalty, charge, penalty.getMinimumLimit()/loanAccount.calcFactorOfEntireLoan().doubleValue(), penalty.getMaximumLimit()/loanAccount.calcFactorOfEntireLoan().doubleValue()); if (charge.isGreaterThanZero()) { loanAccount.applyPenalty(charge, loanScheduleEntity.getInstallmentId(), penaltyEntity, currentDate); } try { StaticHibernateUtil.startTransaction(); StaticHibernateUtil.getSessionTL().update(loanAccount); StaticHibernateUtil.commitTransaction(); } catch (Exception e) { getLogger().error(e.getMessage()); StaticHibernateUtil.rollbackTransaction(); } } private Money verifyLimits(final Money total, final Money charge, final Double min, final double max) { Money cash = charge; boolean zero = total.isZero(); if (zero && charge.getAmount().doubleValue() < min) { cash = new Money(charge.getCurrency(), min); } else if (!zero && total.add(charge).getAmount().doubleValue() > max) { cash = new Money(charge.getCurrency(), max - total.getAmount().doubleValue()); } return cash; } private List<LoanBO> getLoanAccounts() { Query select = StaticHibernateUtil.getSessionTL().getNamedQuery(NamedQueryConstants.GET_ALL_LOAN_ACCOUNTS_WITH_PENALTIES); select.setDate("currentDate", currentDate); return castList(LoanBO.class, select.list()); } private <T> List<T> castList(final Class<? extends T> clazz, final Collection<?> collection) { List<T> results = new ArrayList<T>(collection.size()); for (Object item : collection) { results.add(clazz.cast(item)); } return results; } private void setCurrentDates(long time) { currentLocalDate = new LocalDate(time); currentDate = new Date(time); } }