/* * 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.customers.group.business; import static org.apache.commons.lang.math.NumberUtils.SHORT_ZERO; import static org.mifos.customers.group.business.GroupLoanCounter.TRANSFORM_GROUP_LOAN_COUNTER_TO_LOAN_CYCLE; import static org.mifos.framework.util.CollectionUtils.find; import static org.mifos.framework.util.CollectionUtils.select; import java.math.BigDecimal; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.commons.collections.CollectionUtils; import org.mifos.accounts.business.AccountBO; import org.mifos.accounts.business.service.AccountBusinessService; import org.mifos.accounts.exceptions.AccountException; import org.mifos.accounts.loan.business.LoanBO; import org.mifos.accounts.productdefinition.business.LoanOfferingBO; import org.mifos.accounts.productdefinition.business.PrdOfferingBO; import org.mifos.accounts.util.helpers.AccountTypes; import org.mifos.application.master.business.MifosCurrency; import org.mifos.application.util.helpers.YesNoFlag; import org.mifos.config.business.service.ConfigurationBusinessService; import org.mifos.customers.api.CustomerLevel; import org.mifos.customers.business.CustomerBO; import org.mifos.customers.business.CustomerPerformanceHistory; import org.mifos.customers.exceptions.CustomerException; import org.mifos.customers.group.business.GroupPerformanceHistoryUpdater.UpdateClientPerfHistoryForGroupLoanOnDisbursement; import org.mifos.customers.group.business.GroupPerformanceHistoryUpdater.UpdateClientPerfHistoryForGroupLoanOnRepayment; import org.mifos.customers.group.business.GroupPerformanceHistoryUpdater.UpdateClientPerfHistoryForGroupLoanOnReversal; import org.mifos.customers.group.business.GroupPerformanceHistoryUpdater.UpdateClientPerfHistoryForGroupLoanOnWriteOff; import org.mifos.customers.util.helpers.ChildrenStateType; import org.mifos.framework.exceptions.ServiceException; import org.mifos.framework.util.helpers.Money; import org.mifos.framework.util.helpers.Predicate; public class GroupPerformanceHistoryEntity extends CustomerPerformanceHistory { private Integer id; private Integer clientCount; private Money lastGroupLoanAmount; private Money avgLoanForMember; private Money totalOutstandingPortfolio; private Money totalSavings; private Money portfolioAtRisk; private GroupBO group; /** * stores the loan cycle information based on products */ private Set<GroupLoanCounter> loanCounters; private ConfigurationBusinessService configService; private AccountBusinessService accountBusinessService; public GroupPerformanceHistoryEntity(GroupBO group) { this(0, null, null, null, null, null); this.group = group; } private GroupPerformanceHistoryEntity(Integer clientCount, Money lastGroupLoanAmount, Money avgLoanForMember, Money totalOutstandingPortfolio, Money totalSavings, Money portfolioAtRisk, ConfigurationBusinessService configService, AccountBusinessService accountBusinessService) { if (null == portfolioAtRisk) { this.portfolioAtRisk = null; } else { this.portfolioAtRisk = portfolioAtRisk; } if (null == totalOutstandingPortfolio) { this.totalOutstandingPortfolio = null; } else { this.totalOutstandingPortfolio = totalOutstandingPortfolio; } if (null == totalSavings) { this.totalSavings = null; } else { this.totalSavings = totalSavings; } if (null == avgLoanForMember) { this.avgLoanForMember = null; } else { this.avgLoanForMember = avgLoanForMember; } if (null == lastGroupLoanAmount) { this.lastGroupLoanAmount = null; } else { this.lastGroupLoanAmount = lastGroupLoanAmount; } this.clientCount = clientCount; this.configService = configService; this.accountBusinessService = accountBusinessService; this.loanCounters = new HashSet<GroupLoanCounter>(); this.id = null; } GroupPerformanceHistoryEntity(ConfigurationBusinessService configService, AccountBusinessService accountBusinessService) { this(0, null, null, null, null, null, configService, accountBusinessService); } public GroupPerformanceHistoryEntity(Integer clientCount, Money lastGroupLoanAmount, Money avgLoanForMember, Money totalOutstandingPortfolio, Money totalSavings, Money portfolioAtRisk) { this(clientCount, lastGroupLoanAmount, avgLoanForMember, totalOutstandingPortfolio, totalSavings, portfolioAtRisk, new ConfigurationBusinessService(), new AccountBusinessService()); } protected GroupPerformanceHistoryEntity() { this(0, null, null, null, null, null); } public Integer getId() { return id; } void setId(Integer id) { this.id = id; } private Money getAvgLoanForMember() { return avgLoanForMember; } void setAvgLoanForMember(Money avgLoanForMember) { this.avgLoanForMember = avgLoanForMember; } public Integer getClientCount() { return clientCount; } void setClientCount(Integer clientCount) { this.clientCount = clientCount; } public Money getLastGroupLoanAmount() { return lastGroupLoanAmount; } public void setLastGroupLoanAmount(Money lastGroupLoanAmount) { this.lastGroupLoanAmount = lastGroupLoanAmount; } private Money getTotalOutstandingPortfolio() { return totalOutstandingPortfolio; } void setTotalOutstandingPortfolio(Money totalOutstandingPortfolio) { this.totalOutstandingPortfolio = totalOutstandingPortfolio; } public Money getPortfolioAtRisk() { return portfolioAtRisk; } void setPortfolioAtRisk(Money portfolioAtRisk) { this.portfolioAtRisk = portfolioAtRisk; } private Money getTotalSavings() { return totalSavings; } void setTotalSavings(Money totalSavings) { this.totalSavings = totalSavings; } public GroupBO getGroup() { return group; } void setGroup(GroupBO group) { this.group = group; } public Money getAvgLoanAmountForMember() throws CustomerException { Money amountForActiveAccount = new Money(getCurrency()); Integer countOfActiveLoans = 0; List<CustomerBO> clients = getChildren(); if (clients != null) { for (CustomerBO client : clients) { Money outstandingLoanAmount = client.getOutstandingLoanAmount(getCurrency()); if (amountForActiveAccount.getAmount().equals(BigDecimal.ZERO)) { amountForActiveAccount = outstandingLoanAmount; } else { amountForActiveAccount = amountForActiveAccount.add(outstandingLoanAmount); } countOfActiveLoans += client.getActiveLoanCounts(); } } if (countOfActiveLoans.intValue() > 0) { return amountForActiveAccount.divide(countOfActiveLoans); } return new Money(getCurrency()); } public Integer getActiveClientCount() throws CustomerException { List<CustomerBO> clients = getChildren(); if (clients != null) { return Integer.valueOf(clients.size()); } return Integer.valueOf(0); } public Money getTotalOutStandingLoanAmount() throws CustomerException { Money amount = group.getOutstandingLoanAmount(getCurrency()); List<CustomerBO> clients = getChildren(); if (clients != null) { for (CustomerBO client : clients) { Money outstandingLoanAmount = client.getOutstandingLoanAmount(getCurrency()); if (amount.getAmount().equals(BigDecimal.ZERO)) { amount = outstandingLoanAmount; } else { amount = amount.add(outstandingLoanAmount); } } } return amount; } public Money getTotalSavingsAmount() throws CustomerException { Money amount = group.getSavingsBalance(getCurrency()); List<CustomerBO> clients = getChildren(); if (clients != null) { for (CustomerBO client : clients) { Money savingsBalance = client.getSavingsBalance(getCurrency()); if (amount.getAmount().equals(BigDecimal.ZERO)) { amount = savingsBalance; } else { amount = amount.add(savingsBalance); } } } return amount; } public MifosCurrency getCurrency() { // TODO: performance history will only work if all accounts use the default (or same?) currency return Money.getDefaultCurrency(); } private Money getBalanceForAccountsAtRisk(CustomerBO customer) { Money amount = new Money(getCurrency()); for (AccountBO account : customer.getAccounts()) { if (account.getType() == AccountTypes.LOAN_ACCOUNT && ((LoanBO) account).isAccountActive()) { LoanBO loan = (LoanBO) account; if (loan.hasPortfolioAtRisk()) { Money remainingPrincipal = loan.getRemainingPrincipalAmount(); if (amount.getAmount().equals(BigDecimal.ZERO)) { amount = remainingPrincipal; } else { amount = amount.add(remainingPrincipal); } } } } return amount; } // this method calculates the PAR using the method // LoanBO.hasPortfolioAtRisk() to determine if a loan is in arrears public void generatePortfolioAtRisk() throws CustomerException { Money amount = getBalanceForAccountsAtRisk(group); List<CustomerBO> clients = getChildren(); if (clients != null) { for (CustomerBO client : clients) { Money balanceAtRisk = getBalanceForAccountsAtRisk(client); if (amount.getAmount().equals(BigDecimal.ZERO)) { amount = balanceAtRisk; } else { amount = amount.add(balanceAtRisk); } } } Money totalOutstandingLoanAmount = getTotalOutStandingLoanAmount(); if (!totalOutstandingLoanAmount.getAmount().equals(BigDecimal.ZERO)) { setPortfolioAtRisk(new Money(amount.getCurrency(), amount.divide(totalOutstandingLoanAmount))); } } private List<CustomerBO> getChildren() throws CustomerException { return group.getChildren(CustomerLevel.CLIENT, ChildrenStateType.ACTIVE_AND_ONHOLD); } public void updateLoanCounter(final LoanOfferingBO loanOffering, YesNoFlag yesNoFlag) { GroupLoanCounter loanCounter = null; try { loanCounter = findLoanCounterForProduct(loanOffering); } catch (Exception e) { } if (loanCounter == null) { loanCounter = new GroupLoanCounter(this, loanOffering, yesNoFlag); loanCounters.add(loanCounter); } else { loanCounter.updateLoanCounter(yesNoFlag); } } GroupLoanCounter findLoanCounterForProduct(final LoanOfferingBO loanOffering) throws Exception { return find(loanCounters, new Predicate<GroupLoanCounter>() { @Override public boolean evaluate(GroupLoanCounter loanCounter) { return loanOffering.isOfSameOffering(loanCounter.getLoanOffering()); } }); } public Short getMaxLoanCycleForProduct(final PrdOfferingBO prdOffering) { { Set<GroupLoanCounter> loanCounters = getLoanCounters(); try { Collection<Short> loanCyclesForProduct = select(loanCounters, new Predicate<GroupLoanCounter>() { @Override public boolean evaluate(GroupLoanCounter counter) { return counter.isOfSameProduct(prdOffering); } }, TRANSFORM_GROUP_LOAN_COUNTER_TO_LOAN_CYCLE); return loanCyclesForProduct.isEmpty() ? SHORT_ZERO : Collections.max(loanCyclesForProduct); } catch (Exception e) { return SHORT_ZERO; } } } public void updateOnDisbursement(LoanBO loan, Money disburseAmount) throws AccountException { LoanOfferingBO loanOffering = loan.getLoanOffering(); updateLoanCounter(loanOffering, YesNoFlag.YES); try { if ((configService.isNewGlimEnabled() || configService.isGlimEnabled()) && loan.getAccountId() != null) { CollectionUtils.forAllDo(accountBusinessService.getCoSigningClientsForGlim(loan.getAccountId()), new UpdateClientPerfHistoryForGroupLoanOnDisbursement(loan)); } } catch (ServiceException e) { throw new AccountException(e); } } public Set<GroupLoanCounter> getLoanCounters() { return loanCounters; } public void updateOnWriteOff(LoanBO loan) throws AccountException { updateLoanCounter(loan.getLoanOffering(), YesNoFlag.NO); try { if (configService.isGlimEnabled() || configService.isNewGlimEnabled()) { CollectionUtils.forAllDo(accountBusinessService.getCoSigningClientsForGlim(loan.getAccountId()), new UpdateClientPerfHistoryForGroupLoanOnWriteOff(loan)); } } catch (ServiceException e) { throw new AccountException(e); } } public void updateOnReversal(LoanBO loan, Money lastLoanAmount) throws AccountException { updateLoanCounter(loan.getLoanOffering(), YesNoFlag.NO); try { if (configService.isGlimEnabled() || configService.isNewGlimEnabled()) { CollectionUtils.forAllDo(accountBusinessService.getCoSigningClientsForGlim(loan.getAccountId()), new UpdateClientPerfHistoryForGroupLoanOnReversal(loan)); } } catch (ServiceException e) { throw new AccountException(e); } } public void updateOnFullRepayment(LoanBO loan) throws AccountException { setLastGroupLoanAmount(loan.getLoanAmount()); try { if ((configService.isNewGlimEnabled() || configService.isGlimEnabled()) && !loan.isGroupLoanAccount()) { CollectionUtils.forAllDo(accountBusinessService.getCoSigningClientsForGlim(loan.getAccountId()), new UpdateClientPerfHistoryForGroupLoanOnRepayment(loan)); } } catch (ServiceException e) { throw new AccountException(e); } } }