001    /*
002     * Copyright 2011 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     */
016    package org.kuali.kfs.gl.batch.service.impl;
017    
018    import java.sql.Date;
019    import java.util.ArrayList;
020    import java.util.Collection;
021    import java.util.HashMap;
022    import java.util.List;
023    import java.util.Map;
024    
025    import org.apache.commons.lang.ArrayUtils;
026    import org.kuali.kfs.coa.businessobject.A21SubAccount;
027    import org.kuali.kfs.coa.businessobject.BalanceType;
028    import org.kuali.kfs.coa.businessobject.ObjectCode;
029    import org.kuali.kfs.coa.businessobject.OffsetDefinition;
030    import org.kuali.kfs.coa.businessobject.PriorYearAccount;
031    import org.kuali.kfs.coa.businessobject.SubFundGroup;
032    import org.kuali.kfs.coa.businessobject.SubObjectCode;
033    import org.kuali.kfs.coa.service.A21SubAccountService;
034    import org.kuali.kfs.coa.service.ObjectCodeService;
035    import org.kuali.kfs.coa.service.ObjectTypeService;
036    import org.kuali.kfs.coa.service.OffsetDefinitionService;
037    import org.kuali.kfs.coa.service.PriorYearAccountService;
038    import org.kuali.kfs.coa.service.SubFundGroupService;
039    import org.kuali.kfs.coa.service.SubObjectCodeService;
040    import org.kuali.kfs.gl.GeneralLedgerConstants;
041    import org.kuali.kfs.gl.batch.EncumbranceForwardStep;
042    import org.kuali.kfs.gl.batch.ScrubberStep;
043    import org.kuali.kfs.gl.batch.service.AccountingCycleCachingService;
044    import org.kuali.kfs.gl.batch.service.EncumbranceClosingOriginEntryGenerationService;
045    import org.kuali.kfs.gl.batch.service.impl.exception.FatalErrorException;
046    import org.kuali.kfs.gl.businessobject.Encumbrance;
047    import org.kuali.kfs.gl.businessobject.OriginEntryFull;
048    import org.kuali.kfs.sys.KFSConstants;
049    import org.kuali.kfs.sys.KFSPropertyConstants;
050    import org.kuali.kfs.sys.context.SpringContext;
051    import org.kuali.kfs.sys.service.FlexibleOffsetAccountService;
052    import org.kuali.kfs.sys.service.OptionsService;
053    import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
054    import org.kuali.rice.kns.service.BusinessObjectService;
055    import org.kuali.rice.kns.service.DataDictionaryService;
056    import org.kuali.rice.kns.service.ParameterEvaluator;
057    import org.kuali.rice.kns.service.ParameterService;
058    import org.kuali.rice.kns.util.KualiDecimal;
059    
060    /**
061     * The default implementation of the EncumbranceClosingOriginEntryGenerationService
062     */
063    public class EncumbranceClosingOriginEntryGenerationServiceImpl implements EncumbranceClosingOriginEntryGenerationService {
064        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(EncumbranceClosingOriginEntryGenerationServiceImpl.class);
065        private ParameterService parameterService;
066        private OffsetDefinitionService offsetDefinitionService;
067        private ObjectCodeService objectCodeService;
068        private DataDictionaryService dataDictionaryService;
069        private FlexibleOffsetAccountService flexibleOffsetAccountService;
070        private A21SubAccountService a21SubAccountService;
071        private SubObjectCodeService subObjectCodeService;
072        private OptionsService optionsService;
073        private SubFundGroupService subFundGroupService;
074        private BusinessObjectService businessObjectService;
075        private AccountingCycleCachingService accountingCycleCachingService;
076    
077        /**
078         * @see org.kuali.kfs.gl.batch.service.EncumbranceClosingOriginEntryGenerationService#createBeginningBalanceEntryOffsetPair(org.kuali.kfs.gl.businessobject.Encumbrance, java.lang.Integer, java.sql.Date)
079         */
080        public OriginEntryOffsetPair createCostShareBeginningBalanceEntryOffsetPair(Encumbrance encumbrance, Date transactionDate) {
081            final String GL_ACLO = getParameterService().getParameterValue(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, KFSConstants.SystemGroupParameterNames.GL_ANNUAL_CLOSING_DOC_TYPE);
082            final String GL_ORIGINATION_CODE = getParameterService().getParameterValue(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, KFSConstants.SystemGroupParameterNames.GL_ORIGINATION_CODE);
083    
084            OriginEntryOffsetPair pair = new OriginEntryOffsetPair();
085    
086            // Generate the entry ...
087    
088            OriginEntryFull entry = new OriginEntryFull(encumbrance.getDocumentTypeCode(), encumbrance.getOriginCode());
089    
090            String description = encumbrance.getTransactionEncumbranceDescription();
091            String fromDesc = "FR-" + encumbrance.getChartOfAccountsCode() + encumbrance.getAccountNumber();
092            int descLength = getDataDictionaryService().getAttributeMaxLength(OriginEntryFull.class, KFSPropertyConstants.TRANSACTION_LEDGER_ENTRY_DESC);
093            if ((description.length() + fromDesc.length()) < descLength) {
094                int padLength = descLength - (description.length() + fromDesc.length());
095                StringBuilder sb = new StringBuilder();
096                for (int i = 0; i < padLength; i++) {
097                    sb.append(' ');
098                }
099                sb.append(fromDesc);
100                fromDesc = sb.toString();
101                description += fromDesc;
102            }
103            else if ((description.length() + fromDesc.length()) > descLength) {
104                description = description.substring(0, (descLength - fromDesc.length())) + fromDesc;
105            }
106            else {
107                description += fromDesc;
108            }
109            entry.setTransactionLedgerEntryDescription(description);
110    
111            // SpringContext is used because this method is static.
112            A21SubAccount a21SubAccount = getA21SubAccountService().getByPrimaryKey(encumbrance.getChartOfAccountsCode(), encumbrance.getAccountNumber(), encumbrance.getSubAccountNumber());
113    
114            entry.setUniversityFiscalYear(new Integer(encumbrance.getUniversityFiscalYear().intValue() + 1));
115            entry.setChartOfAccountsCode(a21SubAccount.getCostShareChartOfAccountCode());
116            entry.setAccountNumber(a21SubAccount.getCostShareSourceAccountNumber());
117            entry.setSubAccountNumber(a21SubAccount.getCostShareSourceSubAccountNumber());
118    
119            // The subAccountNumber is set to dashes in the OriginEntryFull constructor.
120            if (entry.getSubAccountNumber() == null || KFSConstants.EMPTY_STRING.equals(entry.getSubAccountNumber().trim())) {
121                entry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
122            }
123    
124    //        ObjectCode finObjCode = accountingCycleCachingService.getObjectCode(encumbrance.getUniversityFiscalYear(), entry.getChartOfAccountsCode(), entry.getFinancialObjectCode());
125    //        if (finObjCode != null)
126    //            entry.setFinancialObjectTypeCode(finObjCode.getFinancialObjectTypeCode());
127    //        
128            
129            ObjectCode encumbranceObjectCode = accountingCycleCachingService.getObjectCode(entry.getUniversityFiscalYear(), entry.getChartOfAccountsCode(), encumbrance.getObjectCode());
130            
131            if (null != encumbranceObjectCode) {
132    
133                String financialObjectLevelCode = encumbranceObjectCode.getFinancialObjectLevelCode();
134                String financialObjectCode = encumbrance.getObjectCode();
135                
136                String overriddenObjectCode = overrideCostShareObjectCode(financialObjectLevelCode, financialObjectCode);
137                final ObjectCode overriddenObject = this.getAccountingCycleCachingService().getObjectCode(entry.getUniversityFiscalYear(), entry.getChartOfAccountsCode(), overriddenObjectCode);
138                
139                String param = parameterService.getParameterValue(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupParameters.COST_SHARE_OBJECT_CODE_BY_LEVEL_PARM_NM, overriddenObject.getFinancialObjectLevelCode());
140                if (param == null) {
141                    param = parameterService.getParameterValue(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupParameters.COST_SHARE_OBJECT_CODE_BY_LEVEL_PARM_NM, "DEFAULT");
142                    if (param == null) {
143                        throw new RuntimeException("Unable to determine cost sharing object code from object level.  Default entry missing.");
144                    }
145                }
146                financialObjectCode = param;
147    
148             // Lookup the new object code
149                ObjectCode newObjectCode = accountingCycleCachingService.getObjectCode(entry.getUniversityFiscalYear(), entry.getChartOfAccountsCode(), financialObjectCode);
150                if (newObjectCode != null) {
151                    entry.setFinancialObjectTypeCode(newObjectCode.getFinancialObjectTypeCode());
152                    entry.setFinancialObjectCode(financialObjectCode);
153                }
154                else {
155                    LOG.error("Error retrieving ObjectCode("+entry.getUniversityFiscalYear()+"/"+entry.getChartOfAccountsCode()+"/"+financialObjectCode+")");
156                    pair.setFatalErrorFlag(true);
157                    return pair;
158                }
159            } else {
160    
161                LOG.error("Error retrieving ObjectCode("+entry.getUniversityFiscalYear()+"/"+entry.getChartOfAccountsCode()+"/"+entry.getFinancialObjectCode()+")");
162                pair.setFatalErrorFlag(true);
163                return pair;
164    
165            }
166            
167            
168            entry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
169            entry.setFinancialBalanceTypeCode(KFSConstants.BALANCE_TYPE_COST_SHARE_ENCUMBRANCE);
170    
171            entry.setUniversityFiscalPeriodCode(KFSConstants.PERIOD_CODE_BEGINNING_BALANCE);
172            entry.setTransactionLedgerEntrySequenceNumber(new Integer(0));
173            entry.setDocumentNumber(encumbrance.getDocumentNumber());
174            entry.setFinancialBalanceTypeCode(KFSConstants.BALANCE_TYPE_COST_SHARE_ENCUMBRANCE);
175    
176            KualiDecimal delta = encumbrance.getAccountLineEncumbranceAmount().subtract(encumbrance.getAccountLineEncumbranceClosedAmount());
177            if (delta.isPositive()) {
178                entry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
179                entry.setTransactionLedgerEntryAmount(delta);
180            }
181            else {
182                entry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
183                entry.setTransactionLedgerEntryAmount(delta.negated());
184            }
185            entry.setTransactionEncumbranceUpdateCode(KFSConstants.ENCUMB_UPDT_DOCUMENT_CD);
186            entry.setProjectCode(KFSConstants.getDashProjectCode());
187            entry.setTransactionDate(transactionDate);
188    
189            pair.setEntry(entry);
190    
191            // And now the offset ...
192    
193            OriginEntryFull offset = new OriginEntryFull(encumbrance.getDocumentTypeCode(), encumbrance.getOriginCode());
194            final String GENERATED_TRANSACTION_LEDGER_ENTRY_DESCRIPTION = getParameterService().getParameterValue(EncumbranceForwardStep.class, GeneralLedgerConstants.EncumbranceClosingOriginEntry.GENERATED_TRANSACTION_LEDGER_ENTRY_DESCRIPTION);
195            offset.setTransactionLedgerEntryDescription(GENERATED_TRANSACTION_LEDGER_ENTRY_DESCRIPTION);
196    
197            offset.setUniversityFiscalYear(new Integer(encumbrance.getUniversityFiscalYear().intValue() + 1));
198            offset.setChartOfAccountsCode(a21SubAccount.getCostShareChartOfAccountCode());
199            offset.setAccountNumber(a21SubAccount.getCostShareSourceAccountNumber());
200            offset.setSubAccountNumber(a21SubAccount.getCostShareSourceSubAccountNumber());
201            if (offset.getSubAccountNumber() == null || KFSConstants.EMPTY_STRING.equals(offset.getSubAccountNumber().trim())) {
202                offset.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
203            }
204            // Lookup the offset definition for the explicit entry we just created.
205            OffsetDefinition offsetDefinition = getOffsetDefinitionService().getByPrimaryId(entry.getUniversityFiscalYear(), entry.getChartOfAccountsCode(), entry.getFinancialDocumentTypeCode(), entry.getFinancialBalanceTypeCode());
206            // Set values from the offset definition if it was found.
207            if (null != offsetDefinition) {
208    
209                offset.setFinancialObjectCode(offsetDefinition.getFinancialObjectCode());
210                offset.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
211            }
212            else { // Log an exception if the offset definition was not found.
213    
214                LOG.info("FATAL ERROR: One of the following errors occurred (no way to know exactly which):\n\t" + "- OFFSET DEFINITION NOT FOUND\n\t" + "- ERROR ACCESSING OFSD TABLE");
215                pair.setFatalErrorFlag(true);
216                return pair;
217    
218            }
219            offset.setFinancialBalanceTypeCode(KFSConstants.BALANCE_TYPE_COST_SHARE_ENCUMBRANCE);
220            // Validate the object code for the explicit entry.
221            ObjectCode objectCode = getObjectCodeService().getByPrimaryId(offset.getUniversityFiscalYear(), offset.getChartOfAccountsCode(), offset.getFinancialObjectCode());
222            if (null != objectCode) {
223                offset.setFinancialObjectTypeCode(objectCode.getFinancialObjectTypeCode());
224            }
225            else {
226                LOG.info("FATAL ERROR: One of the following errors occurred (no way to know exactly which):\n\t" + "- NO OBJECT FOR OBJECT ON OFSD\n\t" + "- ERROR ACCESSING OBJECT TABLE");
227                pair.setFatalErrorFlag(true);
228                return pair;
229            }
230            offset.setUniversityFiscalPeriodCode(KFSConstants.PERIOD_CODE_BEGINNING_BALANCE);
231            offset.setDocumentNumber(encumbrance.getDocumentNumber());
232            offset.setTransactionLedgerEntrySequenceNumber(new Integer(0));
233            if (delta.isPositive()) {
234                offset.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
235                offset.setTransactionLedgerEntryAmount(delta);
236            }
237            else {
238                offset.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
239                offset.setTransactionLedgerEntryAmount(delta.negated());
240            }
241    
242            offset.setTransactionEncumbranceUpdateCode(null);
243            offset.setOrganizationDocumentNumber(null);
244            offset.setProjectCode(KFSConstants.getDashProjectCode());
245            offset.setTransactionDate(transactionDate);
246            offset.setOrganizationReferenceId(null);
247            offset.setReferenceFinancialDocumentTypeCode(null);
248            offset.setReferenceFinancialSystemOriginationCode(null);
249            offset.setReferenceFinancialDocumentNumber(null);
250            offset.setReversalDate(null);
251            
252            getFlexibleOffsetAccountService().updateOffset(offset);
253    
254            pair.setOffset(offset);
255    
256            return pair;
257        }
258    
259        /**
260         * @see org.kuali.kfs.gl.batch.service.EncumbranceClosingOriginEntryGenerationService#createCostShareBeginningBalanceEntryOffsetPair(org.kuali.kfs.gl.businessobject.Encumbrance, java.sql.Date)
261         */
262        public OriginEntryOffsetPair createBeginningBalanceEntryOffsetPair(Encumbrance encumbrance, Integer closingFiscalYear, Date transactionDate) {
263            OriginEntryOffsetPair pair = new OriginEntryOffsetPair();
264    
265            // Build the entry ...
266            OriginEntryFull entry = new OriginEntryFull(encumbrance.getDocumentTypeCode(), encumbrance.getOriginCode());
267    
268            Integer thisFiscalYear = new Integer(closingFiscalYear.intValue() + 1);
269            entry.setUniversityFiscalYear(thisFiscalYear);
270            entry.setChartOfAccountsCode(encumbrance.getChartOfAccountsCode());
271            entry.setAccountNumber(encumbrance.getAccountNumber());
272            entry.setSubAccountNumber(encumbrance.getSubAccountNumber());
273    
274            ObjectCode objectCode = accountingCycleCachingService.getObjectCode(entry.getUniversityFiscalYear(), entry.getChartOfAccountsCode(), encumbrance.getObjectCode());
275            
276            if (null != objectCode) {
277    
278                entry.setFinancialObjectTypeCode(objectCode.getFinancialObjectTypeCode());
279    
280                if (null != objectCode.getNextYearFinancialObjectCode() && !KFSConstants.EMPTY_STRING.equals(objectCode.getNextYearFinancialObjectCode().trim())) {
281    
282                    entry.setFinancialObjectCode(objectCode.getNextYearFinancialObjectCode());
283    
284                }
285                else {
286    
287                    entry.setFinancialObjectCode(encumbrance.getObjectCode());
288    
289                }
290    
291            }
292            
293            
294            else {
295    
296                LOG.error("Error retrieving ObjectCode("+entry.getUniversityFiscalYear()+"/"+entry.getChartOfAccountsCode()+"/"+entry.getFinancialObjectCode()+")");
297                pair.setFatalErrorFlag(true);
298                return pair;
299    
300            }
301    
302            SubObjectCode subObjectCode = getSubObjectCodeService().getByPrimaryId(encumbrance.getUniversityFiscalYear(), encumbrance.getChartOfAccountsCode(), encumbrance.getAccountNumber(), encumbrance.getObjectCode(), encumbrance.getSubObjectCode());
303    
304            if (null != subObjectCode) {
305    
306                entry.setFinancialSubObjectCode(subObjectCode.getFinancialSubObjectCode());
307    
308            }
309            else {
310    
311                entry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
312    
313            }
314    
315            entry.setFinancialBalanceTypeCode(encumbrance.getBalanceTypeCode());
316            entry.setUniversityFiscalPeriodCode(KFSConstants.PERIOD_CODE_BEGINNING_BALANCE);
317            entry.setDocumentNumber(encumbrance.getDocumentNumber());
318            entry.setTransactionLedgerEntrySequenceNumber(new Integer(1));
319            entry.setTransactionLedgerEntryDescription(encumbrance.getTransactionEncumbranceDescription());
320            entry.setTransactionLedgerEntryAmount(encumbrance.getAccountLineEncumbranceAmount().subtract(encumbrance.getAccountLineEncumbranceClosedAmount()));
321    
322            if (entry.getTransactionLedgerEntryAmount().isNegative()) {
323    
324                entry.setTransactionLedgerEntryAmount(entry.getTransactionLedgerEntryAmount().negated());
325                entry.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
326    
327            }
328            else {
329    
330                entry.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
331    
332            }
333    
334            entry.setTransactionDate(transactionDate);
335            entry.setOrganizationDocumentNumber(null);
336            entry.setProjectCode(KFSConstants.getDashProjectCode());
337            entry.setOrganizationReferenceId(null);
338            entry.setReferenceFinancialDocumentTypeCode(null);
339            entry.setReferenceFinancialSystemOriginationCode(null);
340            entry.setReferenceFinancialDocumentNumber(null);
341            entry.setReversalDate(null);
342            entry.setTransactionEncumbranceUpdateCode(KFSConstants.ENCUMB_UPDT_DOCUMENT_CD);
343    
344            pair.setEntry(entry);
345    
346            final String OBJECT_CODE_FOR_BALANCE_TYPE_INTERNAL_ENCUMBRANCE = getParameterService().getParameterValue(EncumbranceForwardStep.class, GeneralLedgerConstants.EncumbranceClosingOriginEntry.OFFSET_OBJECT_CODE_FOR_INTERNAL_ENCUMBRANCE);
347            final String OBJECT_CODE_FOR_BALANCE_TYPE_PRE_ENCUMBRANCE = getParameterService().getParameterValue(EncumbranceForwardStep.class, GeneralLedgerConstants.EncumbranceClosingOriginEntry.OFFSET_OBJECT_CODE_FOR_PRE_ENCUMBRANCE);
348            final String OBJECT_CODE_FOR_BALANCE_TYPE_EXTERNAL_ENCUMBRANCE = getParameterService().getParameterValue(EncumbranceForwardStep.class, GeneralLedgerConstants.EncumbranceClosingOriginEntry.OFFSET_OBJECT_CODE_FOR_EXTERNAL_ENCUMBRANCE);
349            final String BEGINNING_FUND_TRANSACTION_LEDGER_ENTRY_DESCRIPTION = getParameterService().getParameterValue(EncumbranceForwardStep.class, GeneralLedgerConstants.EncumbranceClosingOriginEntry.BEGINNING_FUND_BALANCE_TRANSACTION_LEDGER_ENTRY_DESCRIPTION);
350    
351            // And now build the offset.
352            OriginEntryFull offset = new OriginEntryFull(entry);
353            offset.setTransactionLedgerEntryAmount(entry.getTransactionLedgerEntryAmount());
354            // KFSConstants.BALANCE_TYPE_INTERNAL_ENCUMBRANCE case...
355            offset.setFinancialObjectCode(OBJECT_CODE_FOR_BALANCE_TYPE_INTERNAL_ENCUMBRANCE);
356    
357            if (KFSConstants.BALANCE_TYPE_PRE_ENCUMBRANCE.equals(entry.getFinancialBalanceTypeCode())) {
358    
359                offset.setFinancialObjectCode(OBJECT_CODE_FOR_BALANCE_TYPE_PRE_ENCUMBRANCE);
360    
361            }
362            else if (KFSConstants.BALANCE_TYPE_EXTERNAL_ENCUMBRANCE.equals(entry.getFinancialBalanceTypeCode())) {
363    
364                offset.setFinancialObjectCode(OBJECT_CODE_FOR_BALANCE_TYPE_EXTERNAL_ENCUMBRANCE);
365    
366            }
367    
368            offset.setFinancialObjectTypeCode(getOptionsService().getCurrentYearOptions().getFinObjectTypeFundBalanceCd());
369            offset.setTransactionLedgerEntryDescription(BEGINNING_FUND_TRANSACTION_LEDGER_ENTRY_DESCRIPTION);
370    
371            if (KFSConstants.GL_DEBIT_CODE.equals(entry.getTransactionDebitCreditCode())) {
372    
373                offset.setTransactionDebitCreditCode(KFSConstants.GL_CREDIT_CODE);
374    
375            }
376            else {
377    
378                offset.setTransactionDebitCreditCode(KFSConstants.GL_DEBIT_CODE);
379    
380            }
381            getFlexibleOffsetAccountService().updateOffset(offset);
382            
383            pair.setOffset(offset);
384    
385            return pair;
386        }
387        
388        /**
389         * Determine whether or not an encumbrance should be carried forward from one fiscal year to the next.
390         * 
391         * @param encumbrance the encumbrance to qualify
392         * @return true if the encumbrance should be rolled forward from the closing fiscal year to the opening fiscal year.
393         */
394        public boolean shouldForwardEncumbrance(Encumbrance encumbrance) {
395            // null guard
396            if (null == encumbrance) {
397                return false;
398            }
399    
400            if (encumbrance.getAccountLineEncumbranceAmount().equals(encumbrance.getAccountLineEncumbranceClosedAmount())) {
401                return false;
402            }
403    
404            if (getEncumbranceBalanceTypeCodes().contains(encumbrance.getBalanceTypeCode())) {
405    
406                ParameterEvaluator evaluator = getParameterService().getParameterEvaluator(EncumbranceForwardStep.class, GeneralLedgerConstants.EncumbranceClosingOriginEntry.FORWARD_ENCUMBRANCE_BALANCE_TYPE_AND_ORIGIN_CODE,encumbrance.getBalanceTypeCode(),  encumbrance.getOriginCode());
407                if (!evaluator.evaluationSucceeds()) {
408                    return false;
409                }
410                else if (KFSConstants.BALANCE_TYPE_PRE_ENCUMBRANCE.equals(encumbrance.getBalanceTypeCode())) {
411                    // pre-encumbrances are forwarded, but only if they're related to contracts and grants accounts
412                    PriorYearAccount priorYearAccount = retrievePriorYearAccount(encumbrance.getChartOfAccountsCode(), encumbrance.getAccountNumber());
413                    // the account on the encumbrance must be valid
414                    if (null == priorYearAccount) {
415                        LOG.info("No prior year account for chart \"" + encumbrance.getChartOfAccountsCode() + "\" and account \"" + encumbrance.getAccountNumber() + "\"");
416                        return false;
417                    }
418                    // the sub fund group must exist for the prior year account and the
419                    // encumbrance must not be closed.
420                    return priorYearAccount.isForContractsAndGrants();
421                }
422                else {
423                    // we're still here? because we're an external encumbrance, and we always get forwarded
424                    return true;
425                }
426            }
427            // we're still here? because we're not of a valid encumbrance balance type; we don't get forwarded
428            return false;
429    
430        }
431        
432        /**
433         * @return a list of BalanceType codes which correspond to encumbrance balance types
434         */
435        protected List<String> getEncumbranceBalanceTypeCodes() {
436            List<String> balanceTypeCodes = new ArrayList<String>();
437            
438            
439            Map<String, Object> keys = new HashMap<String, Object>();
440            keys.put("active", Boolean.TRUE);
441            keys.put("finBalanceTypeEncumIndicator", Boolean.TRUE);
442            Collection balanceTypes = businessObjectService.findMatching(BalanceType.class, keys);
443            for (Object balanceTypeAsObject : balanceTypes) {
444                ParameterEvaluator evaluator = getParameterService().getParameterEvaluator(EncumbranceForwardStep.class, GeneralLedgerConstants.EncumbranceClosingOriginEntry.FORWARDING_ENCUMBRANCE_BALANCE_TYPES, ((BalanceType)balanceTypeAsObject).getCode());
445                if (evaluator.evaluationSucceeds())
446                    balanceTypeCodes.add(((BalanceType)balanceTypeAsObject).getCode()); 
447            }
448           
449            return balanceTypeCodes;
450        }
451        
452        /**
453         * Determine whether or not the encumbrance has been fully relieved.
454         * 
455         * @param encumbrance the encumbrance to qualify
456         * @return true if the amount closed on the encumbrance is NOT equal to the amount of the encumbrance itself, e.g. if the
457         *         encumbrance has not yet been paid off.
458         */
459        public boolean isEncumbranceClosed(Encumbrance encumbrance) {
460            if (encumbrance.getAccountLineEncumbranceAmount().doubleValue() == encumbrance.getAccountLineEncumbranceClosedAmount().doubleValue()) {
461                return false;
462            }
463            return true;
464        }
465        
466        /**
467         * Do some validation and make sure that the encumbrance A21SubAccount is a cost share sub-account.
468         * 
469         * @param entry not used in this implementation
470         * @param offset not used in this implementation
471         * @param encumbrance the encumbrance whose A21SubAccount must be qualified
472         * @param objectTypeCode the object type code of the generated entries
473         * @return true if the encumbrance is eligible for cost share.
474         * @throws FatalErrorException thrown if a given A21SubAccount, SubFundGroup, or PriorYearAccount record is not found in the database
475         */
476        public boolean shouldForwardCostShareForEncumbrance(OriginEntryFull entry, OriginEntryFull offset, Encumbrance encumbrance, String objectTypeCode) throws FatalErrorException {
477            PriorYearAccount priorYearAccount = retrievePriorYearAccount(encumbrance.getChartOfAccountsCode(), encumbrance.getAccountNumber());
478    
479            // the sub fund group for the prior year account must exist.
480            String subFundGroupCode = null;
481            if (null != priorYearAccount) {
482                subFundGroupCode = priorYearAccount.getSubFundGroupCode();
483            }
484            else {
485                // this message was carried over from the cobol.
486                throw new FatalErrorException("ERROR ACCESSING PRIOR YR ACCT TABLE FOR " + encumbrance.getAccountNumber());
487            }
488    
489            SubFundGroup subFundGroup = getSubFundGroupService().getByPrimaryId(subFundGroupCode);
490            if (null != subFundGroup) {
491                if (!priorYearAccount.isForContractsAndGrants()) {
492                    return false;
493                }
494            }
495            else {
496                throw new FatalErrorException("ERROR ACCESSING SUB FUND GROUP TABLE FOR " + subFundGroupCode);
497            }
498    
499            // I think this is redundant to the statement a few lines above here.
500            // In any case, the sub fund group must not be contracts and grants.
501            if (!priorYearAccount.isForContractsAndGrants()) {
502                return false;
503            }
504    
505            ObjectTypeService objectTypeService = (ObjectTypeService) SpringContext.getBean(ObjectTypeService.class);
506            List<String> expenseObjectCodeTypes = objectTypeService.getCurrentYearExpenseObjectTypes();
507    
508            String[] encumbranceBalanceTypeCodes = new String[] { KFSConstants.BALANCE_TYPE_EXTERNAL_ENCUMBRANCE, KFSConstants.BALANCE_TYPE_INTERNAL_ENCUMBRANCE, KFSConstants.BALANCE_TYPE_PRE_ENCUMBRANCE };
509    
510            // the object type code must be an expense and the encumbrance balance type code must correspond to an internal, external or
511            // pre-encumbrance
512            if (!expenseObjectCodeTypes.contains(objectTypeCode) || !ArrayUtils.contains(encumbranceBalanceTypeCodes, encumbrance.getBalanceTypeCode())) {
513                return false;
514            }
515            else if (!encumbrance.getSubAccountNumber().equals(KFSConstants.getDashSubAccountNumber())) {
516                A21SubAccount a21SubAccount = getA21SubAccountService().getByPrimaryKey(encumbrance.getChartOfAccountsCode(), encumbrance.getAccountNumber(), encumbrance.getSubAccountNumber());
517                if (null == a21SubAccount) {
518                    // Error message carried over from cobol. not very well descriptive.
519                    // Just indicates that the a21 sub account doesn't exist.
520                    throw new FatalErrorException("ERROR ACCESSING A21 SUB ACCOUNT TABLE FOR ENCUMBRANCE " + encumbrance.getChartOfAccountsCode() + "-" + encumbrance.getAccountNumber() + " " + encumbrance.getSubAccountNumber());
521                }
522                // everything is valid, return true if the a21 sub account is a cost share sub-account
523                return KFSConstants.SubAccountType.COST_SHARE.equals(a21SubAccount.getSubAccountTypeCode());
524            }
525            else {
526                return false;
527            }
528    
529        }
530        
531        /**
532         * Retrieves a prior year account from the persistence store
533         * @param chartOfAccountsCode the chart of accounts for the prior year account
534         * @param accountNumber the account number for the prior year account
535         * @return the PriorYearAccount
536         */
537        protected PriorYearAccount retrievePriorYearAccount(String chartOfAccountsCode, String accountNumber) {
538            Map pks = new HashMap();
539            pks.put(KFSPropertyConstants.CHART_OF_ACCOUNTS_CODE, chartOfAccountsCode);
540            pks.put(KFSPropertyConstants.ACCOUNT_NUMBER, accountNumber);
541            
542            return (PriorYearAccount)this.getBusinessObjectService().findByPrimaryKey(PriorYearAccount.class, pks);
543        }
544        
545        /**
546         * 
547         * This method eases the institutional customization for Cost Sharing Object Codes for OriginEntries
548         * @param levelCode of the originEntry
549         * @param objectCode of the originEntry
550         * @return the new objectCode 
551         */
552        
553        protected String overrideCostShareObjectCode(String levelCode, String objectCode){
554            return objectCode;
555        }
556    
557        /**
558         * Gets the parameterService attribute. 
559         * @return Returns the parameterService.
560         */
561        public ParameterService getParameterService() {
562            return parameterService;
563        }
564    
565        /**
566         * Sets the parameterService attribute value.
567         * @param parameterService The parameterService to set.
568         */
569        public void setParameterService(ParameterService parameterService) {
570            this.parameterService = parameterService;
571        }
572    
573        /**
574         * Gets the offsetDefinitionService attribute. 
575         * @return Returns the offsetDefinitionService.
576         */
577        public OffsetDefinitionService getOffsetDefinitionService() {
578            return offsetDefinitionService;
579        }
580    
581        /**
582         * Sets the offsetDefinitionService attribute value.
583         * @param offsetDefinitionService The offsetDefinitionService to set.
584         */
585        public void setOffsetDefinitionService(OffsetDefinitionService offsetDefinitionService) {
586            this.offsetDefinitionService = offsetDefinitionService;
587        }
588    
589        /**
590         * Gets the objectCodeService attribute. 
591         * @return Returns the objectCodeService.
592         */
593        public ObjectCodeService getObjectCodeService() {
594            return objectCodeService;
595        }
596    
597        /**
598         * Sets the objectCodeService attribute value.
599         * @param objectCodeService The objectCodeService to set.
600         */
601        public void setObjectCodeService(ObjectCodeService objectCodeService) {
602            this.objectCodeService = objectCodeService;
603        }
604    
605        /**
606         * Gets the dataDictionaryService attribute. 
607         * @return Returns the dataDictionaryService.
608         */
609        public DataDictionaryService getDataDictionaryService() {
610            return dataDictionaryService;
611        }
612    
613        /**
614         * Sets the dataDictionaryService attribute value.
615         * @param dataDictionaryService The dataDictionaryService to set.
616         */
617        public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
618            this.dataDictionaryService = dataDictionaryService;
619        }
620    
621        /**
622         * Gets the flexibleOffsetAccountService attribute. 
623         * @return Returns the flexibleOffsetAccountService.
624         */
625        public FlexibleOffsetAccountService getFlexibleOffsetAccountService() {
626            return flexibleOffsetAccountService;
627        }
628    
629        /**
630         * Sets the flexibleOffsetAccountService attribute value.
631         * @param flexibleOffsetAccountService The flexibleOffsetAccountService to set.
632         */
633        public void setFlexibleOffsetAccountService(FlexibleOffsetAccountService flexibleOffsetAccountService) {
634            this.flexibleOffsetAccountService = flexibleOffsetAccountService;
635        }
636    
637        /**
638         * Gets the a21SubAccountService attribute. 
639         * @return Returns the a21SubAccountService.
640         */
641        public A21SubAccountService getA21SubAccountService() {
642            return a21SubAccountService;
643        }
644    
645        /**
646         * Sets the a21SubAccountService attribute value.
647         * @param subAccountService The a21SubAccountService to set.
648         */
649        public void setA21SubAccountService(A21SubAccountService subAccountService) {
650            a21SubAccountService = subAccountService;
651        }
652    
653        /**
654         * Gets the subObjectCodeService attribute. 
655         * @return Returns the subObjectCodeService.
656         */
657        public SubObjectCodeService getSubObjectCodeService() {
658            return subObjectCodeService;
659        }
660    
661        /**
662         * Sets the subObjectCodeService attribute value.
663         * @param subObjectCodeService The subObjectCodeService to set.
664         */
665        public void setSubObjectCodeService(SubObjectCodeService subObjectCodeService) {
666            this.subObjectCodeService = subObjectCodeService;
667        }
668    
669        /**
670         * Gets the optionsService attribute. 
671         * @return Returns the optionsService.
672         */
673        public OptionsService getOptionsService() {
674            return optionsService;
675        }
676    
677        /**
678         * Sets the optionsService attribute value.
679         * @param optionsService The optionsService to set.
680         */
681        public void setOptionsService(OptionsService optionsService) {
682            this.optionsService = optionsService;
683        }
684    
685        /**
686         * Gets the subFundGroupService attribute. 
687         * @return Returns the subFundGroupService.
688         */
689        public SubFundGroupService getSubFundGroupService() {
690            return subFundGroupService;
691        }
692    
693        /**
694         * Sets the subFundGroupService attribute value.
695         * @param subFundGroupService The subFundGroupService to set.
696         */
697        public void setSubFundGroupService(SubFundGroupService subFundGroupService) {
698            this.subFundGroupService = subFundGroupService;
699        }
700    
701        /**
702         * Gets the businessObjectService attribute. 
703         * @return Returns the businessObjectService.
704         */
705        public BusinessObjectService getBusinessObjectService() {
706            return businessObjectService;
707        }
708    
709        /**
710         * Sets the businessObjectService attribute value.
711         * @param businessObjectService The businessObjectService to set.
712         */
713        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
714            this.businessObjectService = businessObjectService;
715        }
716    
717        /**
718         * Gets the accountingCycleCachingService attribute. 
719         * @return Returns the accountingCycleCachingService.
720         */
721        public AccountingCycleCachingService getAccountingCycleCachingService() {
722            return accountingCycleCachingService;
723        }
724    
725        /**
726         * Sets the accountingCycleCachingService attribute value.
727         * @param accountingCycleCachingService The accountingCycleCachingService to set.
728         */
729        public void setAccountingCycleCachingService(AccountingCycleCachingService accountingCycleCachingService) {
730            this.accountingCycleCachingService = accountingCycleCachingService;
731        }
732        
733    }