001/*
002 * Copyright 2005-2006 The Kuali Foundation
003 * 
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * 
008 * http://www.opensource.org/licenses/ecl2.php
009 * 
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.ole.coa.service.impl;
017
018import java.sql.Date;
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026import java.util.Set;
027
028import org.apache.commons.collections.IteratorUtils;
029import org.apache.commons.lang.StringUtils;
030import org.apache.log4j.Logger;
031import org.kuali.ole.coa.businessobject.Account;
032import org.kuali.ole.coa.businessobject.AccountDelegate;
033import org.kuali.ole.coa.dataaccess.AccountDao;
034import org.kuali.ole.coa.service.AccountService;
035import org.kuali.ole.sys.OLEConstants;
036import org.kuali.ole.sys.OLEConstants.SystemGroupParameterNames;
037import org.kuali.ole.sys.OLEPropertyConstants;
038import org.kuali.ole.sys.businessobject.AccountingLine;
039import org.kuali.ole.sys.context.SpringContext;
040import org.kuali.ole.sys.service.NonTransactional;
041import org.kuali.ole.sys.service.impl.OleParameterConstants;
042import org.kuali.rice.core.api.datetime.DateTimeService;
043import org.kuali.rice.coreservice.framework.parameter.ParameterService;
044import org.kuali.rice.kew.api.doctype.DocumentTypeService;
045import org.kuali.rice.kim.api.identity.Person;
046import org.kuali.rice.kim.util.KimCommonUtils;
047import org.kuali.rice.krad.service.BusinessObjectService;
048import org.kuali.rice.krad.util.ObjectUtils;
049import org.springframework.cache.annotation.Cacheable;
050
051/**
052 * This class is the service implementation for the Account structure. This is the default, Kuali provided implementation.
053 */
054
055@NonTransactional
056public class AccountServiceImpl implements AccountService {
057    private static final Logger LOG = Logger.getLogger(AccountServiceImpl.class);
058
059    protected ParameterService parameterService;
060    protected AccountDao accountDao;
061    protected DateTimeService dateTimeService;
062    protected DocumentTypeService documentTypeService;
063    protected BusinessObjectService businessObjectService;
064
065    /**
066     * Retrieves an Account object based on primary key.
067     * 
068     * @param chartOfAccountsCode - Chart of Accounts Code
069     * @param accountNumber - Account Number
070     * @return Account
071     * @see AccountService
072     */
073    @Override
074    @Cacheable(value=Account.CACHE_NAME, key="#p0+'-'+#p1")
075    public Account getByPrimaryId(String chartOfAccountsCode, String accountNumber) {
076        if (LOG.isDebugEnabled()) {
077            LOG.debug("retrieving account by primaryId (" + chartOfAccountsCode + "," + accountNumber + ")");
078        }
079        Map<String, Object> keys = new HashMap<String, Object>(2);
080        keys.put(OLEPropertyConstants.CHART_OF_ACCOUNTS_CODE, chartOfAccountsCode);
081        keys.put(OLEPropertyConstants.ACCOUNT_NUMBER, accountNumber);
082        Account account = businessObjectService.findByPrimaryKey(Account.class, keys);
083
084        if (LOG.isDebugEnabled()) {
085            LOG.debug("retrieved account by primaryId (" + chartOfAccountsCode + "," + accountNumber + "): " + account );
086        }
087        return account;
088    }
089
090    /**
091     * Method is used by KualiAccountAttribute to enable caching of accounts for routing.
092     * 
093     * @see org.kuali.ole.coa.service.impl.AccountServiceImpl#getByPrimaryId(java.lang.String, java.lang.String)
094     */
095    @Override
096    @Cacheable(value=Account.CACHE_NAME, key="#p0+'-'+#p1")
097    public Account getByPrimaryIdWithCaching(String chartOfAccountsCode, String accountNumber) {
098        Account account = getByPrimaryId(chartOfAccountsCode, accountNumber);
099        if ( account != null ) {
100            // force loading of chart reference object
101            account.getChartOfAccounts().getChartOfAccountsCode();
102        }
103        return account;
104    }
105
106    /**
107     * @see org.kuali.ole.coa.service.AccountService#getAccountsThatUserIsResponsibleFor(org.kuali.bo.user.KualiUser)
108     */
109    @Override
110    @Cacheable(value=Account.CACHE_NAME, key="'ResponsibleForAccounts'+#p0.principalId")
111    public List getAccountsThatUserIsResponsibleFor(Person person) {
112        if (LOG.isDebugEnabled()) {
113            LOG.debug("retrieving accountsResponsible list for user " + person.getName());
114        }
115
116        // gets the list of accounts that the user is the Fiscal Officer of
117        List accountList = accountDao.getAccountsThatUserIsResponsibleFor(person, dateTimeService.getCurrentDate());
118        if (LOG.isDebugEnabled()) {
119            LOG.debug("retrieved accountsResponsible list for user " + person.getName());
120        }
121        return accountList;
122    }
123
124    /**
125     * @see org.kuali.ole.coa.service.AccountService#hasResponsibilityOnAccount(org.kuali.rice.kim.api.identity.Person,
126     *      org.kuali.ole.coa.businessobject.Account)
127     */
128    @Override
129    @Cacheable(value=Account.CACHE_NAME, key="'ResponsibilityOnAccount'+#p0.principalId+'-'+#p1.chartOfAccountsCode+'-'+#p1.accountNumber")
130    public boolean hasResponsibilityOnAccount(Person kualiUser, Account account) {
131        return accountDao.determineUserResponsibilityOnAccount(kualiUser, account, dateTimeService.getCurrentSqlDate());
132    }
133
134    /**
135     * @see org.kuali.ole.coa.service.AccountService#getPrimaryDelegationByExample(org.kuali.ole.coa.businessobject.AccountDelegate,
136     *      java.lang.String)
137     */
138
139    @Override
140    public AccountDelegate getPrimaryDelegationByExample(AccountDelegate delegateExample, String totalDollarAmount) {
141        String documentTypeName = delegateExample.getFinancialDocumentTypeCode();
142        Date currentSqlDate = dateTimeService.getCurrentSqlDate();
143        List<AccountDelegate> primaryDelegations = filterAccountDelegates(delegateExample, accountDao.getPrimaryDelegationByExample(delegateExample, currentSqlDate, totalDollarAmount));
144        if (primaryDelegations.isEmpty()) {
145            return null;
146        }
147        for (Iterator<AccountDelegate> iterator = primaryDelegations.iterator(); iterator.hasNext();) {
148            AccountDelegate delegate = iterator.next();
149            if (!OLEConstants.ROOT_DOCUMENT_TYPE.equals(delegate.getFinancialDocumentTypeCode())) {
150                return delegate;
151            }
152        }
153        return primaryDelegations.iterator().next();
154    }
155
156    /**
157     * @see org.kuali.ole.coa.service.AccountService#getSecondaryDelegationsByExample(org.kuali.ole.coa.businessobject.AccountDelegate,
158     *      java.lang.String)
159     */
160    @Override
161    public List getSecondaryDelegationsByExample(AccountDelegate delegateExample, String totalDollarAmount) {
162        Date currentSqlDate = dateTimeService.getCurrentSqlDate();
163        List secondaryDelegations = accountDao.getSecondaryDelegationsByExample(delegateExample, currentSqlDate, totalDollarAmount);
164        return filterAccountDelegates(delegateExample, secondaryDelegations);
165    }
166
167    /**
168     * This method filters account delegates by 
169     * 1) performing an exact match on the document type name of delegateExample
170     * 2) if no match is found for 1), then by performing an exact match on 
171     * the closest parent document type name of delegateExample document type name.
172     * 
173     * @param delegateExample
174     * @param accountDelegatesToFilterFrom
175     * @return
176     */
177    protected List<AccountDelegate> filterAccountDelegates(AccountDelegate delegateExample, List<AccountDelegate> accountDelegatesToFilterFrom) {
178        String documentTypeName = delegateExample.getFinancialDocumentTypeCode();
179        List<AccountDelegate> filteredAccountDelegates = filterAccountDelegates(accountDelegatesToFilterFrom, documentTypeName);
180        if (filteredAccountDelegates.size() == 0) {
181            Set<String> potentialParentDocumentTypeNames = getPotentialParentDocumentTypeNames(accountDelegatesToFilterFrom);
182            String closestParentDocumentTypeName = KimCommonUtils.getClosestParentDocumentTypeName(documentTypeService.getDocumentTypeByName(documentTypeName), potentialParentDocumentTypeNames);
183            filteredAccountDelegates = filterAccountDelegates(accountDelegatesToFilterFrom, closestParentDocumentTypeName);
184        }
185        return filteredAccountDelegates;
186    }
187
188    /**
189     * This method filters account delegates by performing an exact match on the document type name passed in.
190     * 
191     * @param delegations
192     * @param documentTypeNameToFilterOn
193     * @return
194     */
195    protected List<AccountDelegate> filterAccountDelegates(List<AccountDelegate> delegations, String documentTypeNameToFilterOn) {
196        List<AccountDelegate> filteredSecondaryDelegations = new ArrayList<AccountDelegate>();
197        for (Object delegateObject : delegations) {
198            AccountDelegate delegate = (AccountDelegate) delegateObject;
199            if (StringUtils.equals(delegate.getFinancialDocumentTypeCode(), documentTypeNameToFilterOn)) {
200                filteredSecondaryDelegations.add(delegate);
201            }
202        }
203        return filteredSecondaryDelegations;
204    }
205
206    /**
207     * This method gets a list of potential parent document type names by collecting the unique doc type names from the list of
208     * account delegations
209     * 
210     * @param delegations
211     * @return
212     */
213    protected Set<String> getPotentialParentDocumentTypeNames(List<AccountDelegate> delegations) {
214        AccountDelegate delegate;
215        Set<String> potentialParentDocumentTypeNames = new HashSet<String>();
216        for (Object delegateObject : delegations) {
217            delegate = (AccountDelegate) delegateObject;
218            if (!potentialParentDocumentTypeNames.contains(delegate.getFinancialDocumentTypeCode())) {
219                potentialParentDocumentTypeNames.add(delegate.getFinancialDocumentTypeCode());
220        }
221        }
222        return potentialParentDocumentTypeNames;
223    }
224
225    /**
226     * get all accounts in the system. This is needed by a sufficient funds rebuilder job
227     * 
228     * @return iterator of all accounts
229     */
230    @Override
231    public Iterator getAllAccounts() {
232        LOG.debug("getAllAccounts() started");
233
234        Iterator accountIter = accountDao.getAllAccounts();
235        // FIXME: this loads all accounts into memory - could blow server
236        return IteratorUtils.toList(accountIter).iterator();
237    }
238
239    /**
240     * @see org.kuali.ole.coa.service.AccountService#getActiveAccountsForAccountSupervisor(java.lang.String)
241     */
242    @Override
243    public Iterator<Account> getActiveAccountsForAccountSupervisor(String principalId) {
244        return accountDao.getActiveAccountsForAccountSupervisor(principalId, dateTimeService.getCurrentSqlDate());
245    }
246
247    /**
248     * @see org.kuali.ole.coa.service.AccountService#getActiveAccountsForFiscalOfficer(java.lang.String)
249     */
250    @Override
251    public Iterator<Account> getActiveAccountsForFiscalOfficer(String principalId) {
252        return accountDao.getActiveAccountsForFiscalOfficer(principalId, dateTimeService.getCurrentSqlDate());
253    }
254
255    /**
256     * @see org.kuali.ole.coa.service.AccountService#getExpiredAccountsForAccountSupervisor(java.lang.String)
257     */
258    @Override
259    public Iterator<Account> getExpiredAccountsForAccountSupervisor(String principalId) {
260        return accountDao.getExpiredAccountsForAccountSupervisor(principalId, dateTimeService.getCurrentSqlDate());
261    }
262
263    /**
264     * @see org.kuali.ole.coa.service.AccountService#getExpiredAccountsForFiscalOfficer(java.lang.String)
265     */
266    @Override
267    public Iterator<Account> getExpiredAccountsForFiscalOfficer(String principalId) {
268        return accountDao.getExpiredAccountsForFiscalOfficer(principalId, dateTimeService.getCurrentSqlDate());
269    }
270
271    /**
272     * @see org.kuali.ole.coa.service.AccountService#isPrincipalInAnyWayShapeOrFormAccountManager(java.lang.String)
273     */
274    @Override
275    public boolean isPrincipalInAnyWayShapeOrFormAccountManager(String principalId) {
276        return accountDao.isPrincipalInAnyWayShapeOrFormAccountManager(principalId);
277    }
278
279    /**
280     * @see org.kuali.ole.coa.service.AccountService#isPrincipalInAnyWayShapeOrFormAccountSupervisor(java.lang.String)
281     */
282    @Override
283    public boolean isPrincipalInAnyWayShapeOrFormAccountSupervisor(String principalId) {
284        return accountDao.isPrincipalInAnyWayShapeOrFormAccountSupervisor(principalId);
285    }
286
287    /**
288     * @see org.kuali.ole.coa.service.AccountService#isPrincipalInAnyWayShapeOrFormFiscalOfficer(java.lang.String)
289     */
290    @Override
291    public boolean isPrincipalInAnyWayShapeOrFormFiscalOfficer(String principalId) {
292        return accountDao.isPrincipalInAnyWayShapeOrFormFiscalOfficer(principalId);
293    }
294
295    /**
296     * @see org.kuali.ole.coa.service.AccountService#getAccountsForAccountNumber(java.lang.String)
297     */
298    @Override
299    @Cacheable(value=Account.CACHE_NAME, key="'AccountsForAccountNumber'+#p0")
300    public Collection<Account> getAccountsForAccountNumber(String accountNumber) {
301        return accountDao.getAccountsForAccountNumber(accountNumber);
302    }
303
304
305    @Override
306    public String getDefaultLaborBenefitRateCategoryCodeForAccountType(String accountTypeCode) {
307        String benefitRateCategory = parameterService.getSubParameterValueAsString(Account.class, "DEFAULT_BENEFIT_RATE_CATEGORY_CODE_BY_ACCOUNT_TYPE", accountTypeCode);
308        if ( StringUtils.isBlank(benefitRateCategory) ) {
309            benefitRateCategory = parameterService.getParameterValueAsString(Account.class, "DEFAULT_BENEFIT_RATE_CATEGORY_CODE");
310        }
311        return StringUtils.trimToEmpty(benefitRateCategory);
312    }
313
314    /**
315     * @see org.kuali.ole.coa.service.AccountService#isFridgeBenefitCalculationEnable()
316     */
317    @Override
318    public Boolean isFridgeBenefitCalculationEnable(){
319        Boolean isFringeBeneCalcEnable = null;
320
321        //make sure the parameter exists
322        if(parameterService.parameterExists(OleParameterConstants.FINANCIAL_SYSTEM_ALL.class, "ENABLE_FRINGE_BENEFIT_CALC_BY_BENEFIT_RATE_CATEGORY_IND")){
323          //check the system param to see if the labor benefit rate category should be editable
324            isFringeBeneCalcEnable = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(OleParameterConstants.FINANCIAL_SYSTEM_ALL.class, "ENABLE_FRINGE_BENEFIT_CALC_BY_BENEFIT_RATE_CATEGORY_IND");
325            LOG.debug("System Parameter retrieved: " + isFringeBeneCalcEnable);
326        }
327
328        return (Boolean)org.apache.commons.lang.ObjectUtils.defaultIfNull(isFringeBeneCalcEnable, false);
329    }
330
331
332    /**
333     * @see org.kuali.ole.coa.service.AccountService#getUniqueAccountForAccountNumber(java.lang.String)
334     */
335    @Override
336    @Cacheable(value=Account.CACHE_NAME, key="'UniqueAccountForAccountNumber'+#p0")
337    public Account getUniqueAccountForAccountNumber(String accountNumber) {
338        Iterator<Account> accounts = accountDao.getAccountsForAccountNumber(accountNumber).iterator();
339        Account account = null;
340        // there should be only one account in the collection
341        if (accounts.hasNext()) {
342            account = accounts.next();
343        }
344        return account;
345    }
346
347    /**
348     * @see org.kuali.ole.coa.service.AccountService#accountsCanCrossCharts()
349     */
350    @Override
351    public boolean accountsCanCrossCharts() {
352        return parameterService.getParameterValueAsBoolean(OleParameterConstants.FINANCIAL_SYSTEM_ALL.class, SystemGroupParameterNames.ACCOUNTS_CAN_CROSS_CHARTS_IND);
353    }
354
355    /**
356     * @see org.kuali.ole.coa.service.AccountService#accountsCanCrossCharts()
357     */
358    @Override
359    public void populateAccountingLineChartIfNeeded(AccountingLine line) {
360        if (!accountsCanCrossCharts() /* && line.getChartOfAccountsCode() == null */) {
361            Account account = getUniqueAccountForAccountNumber(line.getAccountNumber());
362            if (ObjectUtils.isNotNull(account)) {
363                line.setChartOfAccountsCode(account.getChartOfAccountsCode());
364            }
365        }
366    }
367
368    public void setAccountDao(AccountDao accountDao) {
369        this.accountDao = accountDao;
370    }
371
372    public void setDateTimeService(DateTimeService dateTimeService) {
373        this.dateTimeService = dateTimeService;
374    }
375
376    public void setDocumentTypeService(DocumentTypeService documentTypeService) {
377        this.documentTypeService = documentTypeService;
378    }
379
380    public void setBusinessObjectService(BusinessObjectService businessObjectService) {
381        this.businessObjectService = businessObjectService;
382    }
383    
384    public void setParameterService(ParameterService parameterService) {
385        this.parameterService = parameterService;
386    }
387}