/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.coa.service.impl;
import java.sql.Date;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections.IteratorUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.kuali.kfs.coa.businessobject.Account;
import org.kuali.kfs.coa.businessobject.AccountDelegate;
import org.kuali.kfs.coa.dataaccess.AccountDao;
import org.kuali.kfs.coa.service.AccountService;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSConstants.SystemGroupParameterNames;
import org.kuali.kfs.sys.KFSParameterKeyConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.AccountingLine;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.service.NonTransactional;
import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.kew.api.doctype.DocumentTypeService;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.util.KimCommonUtils;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.util.ObjectUtils;
import org.springframework.cache.annotation.Cacheable;
/**
* This class is the service implementation for the Account structure. This is the default, Kuali provided implementation.
*/
@NonTransactional
public class AccountServiceImpl implements AccountService {
private static final Logger LOG = Logger.getLogger(AccountServiceImpl.class);
protected ParameterService parameterService;
protected AccountDao accountDao;
protected DateTimeService dateTimeService;
protected DocumentTypeService documentTypeService;
protected BusinessObjectService businessObjectService;
/**
* Retrieves an Account object based on primary key.
*
* @param chartOfAccountsCode - Chart of Accounts Code
* @param accountNumber - Account Number
* @return Account
* @see AccountService
*/
@Override
@Cacheable(value=Account.CACHE_NAME, key="#p0+'-'+#p1")
public Account getByPrimaryId(String chartOfAccountsCode, String accountNumber) {
if (LOG.isDebugEnabled()) {
LOG.debug("retrieving account by primaryId (" + chartOfAccountsCode + "," + accountNumber + ")");
}
Map<String, Object> keys = new HashMap<String, Object>(2);
keys.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, chartOfAccountsCode);
keys.put(KFSPropertyConstants.ACCOUNT_NUMBER, accountNumber);
Account account = businessObjectService.findByPrimaryKey(Account.class, keys);
if (LOG.isDebugEnabled()) {
LOG.debug("retrieved account by primaryId (" + chartOfAccountsCode + "," + accountNumber + "): " + account );
}
return account;
}
/**
* Method is used by KualiAccountAttribute to enable caching of accounts for routing.
*
* @see org.kuali.kfs.coa.service.impl.AccountServiceImpl#getByPrimaryId(java.lang.String, java.lang.String)
*/
@Override
@Cacheable(value=Account.CACHE_NAME, key="#p0+'-'+#p1")
public Account getByPrimaryIdWithCaching(String chartOfAccountsCode, String accountNumber) {
Account account = getByPrimaryId(chartOfAccountsCode, accountNumber);
if ( account != null ) {
// force loading of chart reference object
account.getChartOfAccounts().getChartOfAccountsCode();
}
return account;
}
/**
* @see org.kuali.kfs.coa.service.AccountService#getAccountsThatUserIsResponsibleFor(org.kuali.bo.user.KualiUser)
*/
@Override
@Cacheable(value=Account.CACHE_NAME, key="'ResponsibleForAccounts'+#p0.principalId")
public List getAccountsThatUserIsResponsibleFor(Person person) {
if (LOG.isDebugEnabled()) {
LOG.debug("retrieving accountsResponsible list for user " + person.getName());
}
// gets the list of accounts that the user is the Fiscal Officer of
List accountList = accountDao.getAccountsThatUserIsResponsibleFor(person, dateTimeService.getCurrentDate());
if (LOG.isDebugEnabled()) {
LOG.debug("retrieved accountsResponsible list for user " + person.getName());
}
return accountList;
}
/**
* @see org.kuali.kfs.coa.service.AccountService#hasResponsibilityOnAccount(org.kuali.rice.kim.api.identity.Person,
* org.kuali.kfs.coa.businessobject.Account)
*/
@Override
@Cacheable(value=Account.CACHE_NAME, key="'ResponsibilityOnAccount'+#p0.principalId+'-'+#p1.chartOfAccountsCode+'-'+#p1.accountNumber")
public boolean hasResponsibilityOnAccount(Person kualiUser, Account account) {
return accountDao.determineUserResponsibilityOnAccount(kualiUser, account, dateTimeService.getCurrentSqlDate());
}
/**
* @see org.kuali.kfs.coa.service.AccountService#getPrimaryDelegationByExample(org.kuali.kfs.coa.businessobject.AccountDelegate,
* java.lang.String)
*/
@Override
public AccountDelegate getPrimaryDelegationByExample(AccountDelegate delegateExample, String totalDollarAmount) {
String documentTypeName = delegateExample.getFinancialDocumentTypeCode();
Date currentSqlDate = dateTimeService.getCurrentSqlDate();
List<AccountDelegate> primaryDelegations = filterAccountDelegates(delegateExample, accountDao.getPrimaryDelegationByExample(delegateExample, currentSqlDate, totalDollarAmount));
if (primaryDelegations.isEmpty()) {
return null;
}
for (Iterator<AccountDelegate> iterator = primaryDelegations.iterator(); iterator.hasNext();) {
AccountDelegate delegate = iterator.next();
if (!KFSConstants.ROOT_DOCUMENT_TYPE.equals(delegate.getFinancialDocumentTypeCode())) {
return delegate;
}
}
return primaryDelegations.iterator().next();
}
/**
* @see org.kuali.kfs.coa.service.AccountService#getSecondaryDelegationsByExample(org.kuali.kfs.coa.businessobject.AccountDelegate,
* java.lang.String)
*/
@Override
public List getSecondaryDelegationsByExample(AccountDelegate delegateExample, String totalDollarAmount) {
Date currentSqlDate = dateTimeService.getCurrentSqlDate();
List secondaryDelegations = accountDao.getSecondaryDelegationsByExample(delegateExample, currentSqlDate, totalDollarAmount);
return filterAccountDelegates(delegateExample, secondaryDelegations);
}
/**
* This method filters account delegates by
* 1) performing an exact match on the document type name of delegateExample
* 2) if no match is found for 1), then by performing an exact match on
* the closest parent document type name of delegateExample document type name.
*
* @param delegateExample
* @param accountDelegatesToFilterFrom
* @return
*/
protected List<AccountDelegate> filterAccountDelegates(AccountDelegate delegateExample, List<AccountDelegate> accountDelegatesToFilterFrom) {
String documentTypeName = delegateExample.getFinancialDocumentTypeCode();
List<AccountDelegate> filteredAccountDelegates = filterAccountDelegates(accountDelegatesToFilterFrom, documentTypeName);
if (filteredAccountDelegates.size() == 0) {
Set<String> potentialParentDocumentTypeNames = getPotentialParentDocumentTypeNames(accountDelegatesToFilterFrom);
String closestParentDocumentTypeName = KimCommonUtils.getClosestParentDocumentTypeName(documentTypeService.getDocumentTypeByName(documentTypeName), potentialParentDocumentTypeNames);
filteredAccountDelegates = filterAccountDelegates(accountDelegatesToFilterFrom, closestParentDocumentTypeName);
}
return filteredAccountDelegates;
}
/**
* This method filters account delegates by performing an exact match on the document type name passed in.
*
* @param delegations
* @param documentTypeNameToFilterOn
* @return
*/
protected List<AccountDelegate> filterAccountDelegates(List<AccountDelegate> delegations, String documentTypeNameToFilterOn) {
List<AccountDelegate> filteredSecondaryDelegations = new ArrayList<AccountDelegate>();
for (Object delegateObject : delegations) {
AccountDelegate delegate = (AccountDelegate) delegateObject;
if (StringUtils.equals(delegate.getFinancialDocumentTypeCode(), documentTypeNameToFilterOn)) {
filteredSecondaryDelegations.add(delegate);
}
}
return filteredSecondaryDelegations;
}
/**
* This method gets a list of potential parent document type names by collecting the unique doc type names from the list of
* account delegations
*
* @param delegations
* @return
*/
protected Set<String> getPotentialParentDocumentTypeNames(List<AccountDelegate> delegations) {
AccountDelegate delegate;
Set<String> potentialParentDocumentTypeNames = new HashSet<String>();
for (Object delegateObject : delegations) {
delegate = (AccountDelegate) delegateObject;
if (!potentialParentDocumentTypeNames.contains(delegate.getFinancialDocumentTypeCode())) {
potentialParentDocumentTypeNames.add(delegate.getFinancialDocumentTypeCode());
}
}
return potentialParentDocumentTypeNames;
}
/**
* get all accounts in the system. This is needed by a sufficient funds rebuilder job
*
* @return iterator of all accounts
*/
@Override
public Iterator getAllAccounts() {
LOG.debug("getAllAccounts() started");
Iterator accountIter = accountDao.getAllAccounts();
// FIXME: this loads all accounts into memory - could blow server
return IteratorUtils.toList(accountIter).iterator();
}
/**
* @see org.kuali.kfs.coa.service.AccountService#getActiveAccountsForAccountSupervisor(java.lang.String)
*/
@Override
public Iterator<Account> getActiveAccountsForAccountSupervisor(String principalId) {
return accountDao.getActiveAccountsForAccountSupervisor(principalId, dateTimeService.getCurrentSqlDate());
}
/**
* @see org.kuali.kfs.coa.service.AccountService#getActiveAccountsForFiscalOfficer(java.lang.String)
*/
@Override
public Iterator<Account> getActiveAccountsForFiscalOfficer(String principalId) {
return accountDao.getActiveAccountsForFiscalOfficer(principalId, dateTimeService.getCurrentSqlDate());
}
/**
* @see org.kuali.kfs.coa.service.AccountService#getExpiredAccountsForAccountSupervisor(java.lang.String)
*/
@Override
public Iterator<Account> getExpiredAccountsForAccountSupervisor(String principalId) {
return accountDao.getExpiredAccountsForAccountSupervisor(principalId, dateTimeService.getCurrentSqlDate());
}
/**
* @see org.kuali.kfs.coa.service.AccountService#getExpiredAccountsForFiscalOfficer(java.lang.String)
*/
@Override
public Iterator<Account> getExpiredAccountsForFiscalOfficer(String principalId) {
return accountDao.getExpiredAccountsForFiscalOfficer(principalId, dateTimeService.getCurrentSqlDate());
}
/**
* @see org.kuali.kfs.coa.service.AccountService#isPrincipalInAnyWayShapeOrFormAccountManager(java.lang.String)
*/
@Override
public boolean isPrincipalInAnyWayShapeOrFormAccountManager(String principalId) {
return accountDao.isPrincipalInAnyWayShapeOrFormAccountManager(principalId);
}
/**
* @see org.kuali.kfs.coa.service.AccountService#isPrincipalInAnyWayShapeOrFormAccountSupervisor(java.lang.String)
*/
@Override
public boolean isPrincipalInAnyWayShapeOrFormAccountSupervisor(String principalId) {
return accountDao.isPrincipalInAnyWayShapeOrFormAccountSupervisor(principalId);
}
/**
* @see org.kuali.kfs.coa.service.AccountService#isPrincipalInAnyWayShapeOrFormFiscalOfficer(java.lang.String)
*/
@Override
public boolean isPrincipalInAnyWayShapeOrFormFiscalOfficer(String principalId) {
return accountDao.isPrincipalInAnyWayShapeOrFormFiscalOfficer(principalId);
}
/**
* @see org.kuali.kfs.coa.service.AccountService#getAccountsForAccountNumber(java.lang.String)
*/
@Override
@Cacheable(value=Account.CACHE_NAME, key="'AccountsForAccountNumber'+#p0")
public Collection<Account> getAccountsForAccountNumber(String accountNumber) {
return accountDao.getAccountsForAccountNumber(accountNumber);
}
@Override
public String getDefaultLaborBenefitRateCategoryCodeForAccountType(String accountTypeCode) {
String benefitRateCategory = parameterService.getSubParameterValueAsString(Account.class, "DEFAULT_BENEFIT_RATE_CATEGORY_CODE_BY_ACCOUNT_TYPE", accountTypeCode);
if ( StringUtils.isBlank(benefitRateCategory) ) {
benefitRateCategory = parameterService.getParameterValueAsString(Account.class, KFSParameterKeyConstants.LdParameterConstants.DEFAULT_BENEFIT_RATE_CATEGORY_CODE);
}
return StringUtils.trimToEmpty(benefitRateCategory);
}
/**
* @see org.kuali.kfs.coa.service.AccountService#isFridgeBenefitCalculationEnable()
*/
@Override
public Boolean isFridgeBenefitCalculationEnable(){
Boolean isFringeBeneCalcEnable = null;
//make sure the parameter exists
if(parameterService.parameterExists(KfsParameterConstants.FINANCIAL_SYSTEM_ALL.class, KFSParameterKeyConstants.LdParameterConstants.ENABLE_FRINGE_BENEFIT_CALC_BY_BENEFIT_RATE_CATEGORY_IND)){
//check the system param to see if the labor benefit rate category should be editable
isFringeBeneCalcEnable = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(KfsParameterConstants.FINANCIAL_SYSTEM_ALL.class, KFSParameterKeyConstants.LdParameterConstants.ENABLE_FRINGE_BENEFIT_CALC_BY_BENEFIT_RATE_CATEGORY_IND);
LOG.debug("System Parameter retrieved: " + isFringeBeneCalcEnable);
}
return (Boolean)org.apache.commons.lang.ObjectUtils.defaultIfNull(isFringeBeneCalcEnable, false);
}
/**
* @see org.kuali.kfs.coa.service.AccountService#getUniqueAccountForAccountNumber(java.lang.String)
*/
@Override
@Cacheable(value=Account.CACHE_NAME, key="'UniqueAccountForAccountNumber'+#p0")
public Account getUniqueAccountForAccountNumber(String accountNumber) {
Iterator<Account> accounts = accountDao.getAccountsForAccountNumber(accountNumber).iterator();
Account account = null;
// there should be only one account in the collection
if (accounts.hasNext()) {
account = accounts.next();
}
return account;
}
/**
* @see org.kuali.kfs.coa.service.AccountService#accountsCanCrossCharts()
*/
@Override
public boolean accountsCanCrossCharts() {
return parameterService.getParameterValueAsBoolean(KfsParameterConstants.FINANCIAL_SYSTEM_ALL.class, SystemGroupParameterNames.ACCOUNTS_CAN_CROSS_CHARTS_IND);
}
/**
* @see org.kuali.kfs.coa.service.AccountService#accountsCanCrossCharts()
*/
@Override
public void populateAccountingLineChartIfNeeded(AccountingLine line) {
if (!accountsCanCrossCharts() /* && line.getChartOfAccountsCode() == null */) {
Account account = getUniqueAccountForAccountNumber(line.getAccountNumber());
if (ObjectUtils.isNotNull(account)) {
line.setChartOfAccountsCode(account.getChartOfAccountsCode());
}
}
}
/**
* @param account
* @return an unexpired continuation account for the given account, or, if one cannot be found, null
*/
@Override
public Account getUnexpiredContinuationAccountOrNull(Account account) {
int count = 0;
while (count++ < 10) { // prevents infinite loops
String continuationChartCode = account.getContinuationFinChrtOfAcctCd();
String continuationAccountNumber = account.getContinuationAccountNumber();
if (StringUtils.isBlank(continuationChartCode) || StringUtils.isBlank(continuationAccountNumber)) {
return null;
}
account = getByPrimaryId(continuationChartCode, continuationAccountNumber);
if (ObjectUtils.isNull(account)) {
return null;
}
if (account.isActive() && !account.isExpired()) {
return account;
}
}
return null;
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public void setDateTimeService(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
public void setDocumentTypeService(DocumentTypeService documentTypeService) {
this.documentTypeService = documentTypeService;
}
public void setBusinessObjectService(BusinessObjectService businessObjectService) {
this.businessObjectService = businessObjectService;
}
public void setParameterService(ParameterService parameterService) {
this.parameterService = parameterService;
}
}