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.module.ld.batch.service.impl;
017    
018    import java.util.ArrayList;
019    import java.util.Calendar;
020    import java.util.List;
021    import java.util.Map;
022    
023    import org.apache.commons.lang.StringUtils;
024    import org.kuali.kfs.coa.businessobject.Account;
025    import org.kuali.kfs.coa.businessobject.AccountingPeriod;
026    import org.kuali.kfs.coa.businessobject.SubAccount;
027    import org.kuali.kfs.coa.service.AccountService;
028    import org.kuali.kfs.coa.service.BalanceTypeService;
029    import org.kuali.kfs.gl.batch.ScrubberStep;
030    import org.kuali.kfs.gl.batch.service.AccountingCycleCachingService;
031    import org.kuali.kfs.gl.businessobject.OriginEntryInformation;
032    import org.kuali.kfs.gl.businessobject.OriginEntryFull;
033    import org.kuali.kfs.gl.service.ScrubberValidator;
034    import org.kuali.kfs.module.ld.LaborConstants;
035    import org.kuali.kfs.module.ld.LaborKeyConstants;
036    import org.kuali.kfs.module.ld.batch.LaborScrubberStep;
037    import org.kuali.kfs.module.ld.batch.service.LaborAccountingCycleCachingService;
038    import org.kuali.kfs.module.ld.businessobject.LaborObject;
039    import org.kuali.kfs.module.ld.businessobject.LaborOriginEntry;
040    import org.kuali.kfs.sys.KFSConstants;
041    import org.kuali.kfs.sys.KFSKeyConstants;
042    import org.kuali.kfs.sys.KFSPropertyConstants;
043    import org.kuali.kfs.sys.Message;
044    import org.kuali.kfs.sys.MessageBuilder;
045    import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
046    import org.kuali.kfs.sys.businessobject.SystemOptions;
047    import org.kuali.kfs.sys.businessobject.UniversityDate;
048    import org.kuali.kfs.sys.service.OptionsService;
049    import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
050    import org.kuali.rice.kns.service.BusinessObjectService;
051    import org.kuali.rice.kns.service.KualiConfigurationService;
052    import org.kuali.rice.kns.service.ParameterService;
053    import org.kuali.rice.kns.service.PersistenceService;
054    import org.kuali.rice.kns.service.PersistenceStructureService;
055    import org.kuali.rice.kns.util.ObjectUtils;
056    
057    /**
058     * Service implementation of ScrubberValidator.
059     */
060    public class ScrubberValidatorImpl implements ScrubberValidator {
061        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ScrubberValidatorImpl.class);
062    
063        private KualiConfigurationService kualiConfigurationService;
064        private BusinessObjectService businessObjectService;
065        private ParameterService parameterService;
066        private AccountService accountService;
067        private BalanceTypeService balanceTypService;
068        private OptionsService optionsService;
069    
070        private PersistenceService persistenceService;
071        private ScrubberValidator scrubberValidator;
072        private PersistenceStructureService persistenceStructureService;
073        private boolean continuationAccountIndicator;
074        
075    
076        /**
077         * @see org.kuali.module.labor.service.LaborScrubberValidator#validateTransaction(owrg.kuali.module.labor.bo.LaborOriginEntry,
078         *      org.kuali.kfs.module.ld.businessobject.LaborOriginEntry, org.kuali.kfs.gl.businessobject.UniversityDate)
079         */
080        public List<Message> validateTransaction(OriginEntryInformation originEntry, OriginEntryInformation scrubbedEntry, UniversityDate universityRunDate, boolean laborIndicator, AccountingCycleCachingService laborAccountingCycleCachingService) {
081            LOG.debug("validateTransaction() started");
082            List<Message> errors = new ArrayList<Message>();
083            continuationAccountIndicator = false;
084            
085            LaborOriginEntry laborOriginEntry = (LaborOriginEntry) originEntry;
086            LaborOriginEntry laborScrubbedEntry = (LaborOriginEntry) scrubbedEntry;
087    
088            // gl scrubber validation
089            errors = scrubberValidator.validateTransaction(laborOriginEntry, laborScrubbedEntry, universityRunDate, laborIndicator, laborAccountingCycleCachingService);
090    
091            refreshOriginEntryReferences(laborOriginEntry);
092            refreshOriginEntryReferences(laborScrubbedEntry);
093    
094            if (StringUtils.isBlank(laborOriginEntry.getEmplid())) {
095                laborScrubbedEntry.setEmplid(LaborConstants.getDashEmplId());
096            }
097    
098            if (StringUtils.isBlank(laborOriginEntry.getPositionNumber())) {
099                laborScrubbedEntry.setPositionNumber(LaborConstants.getDashPositionNumber());
100            }
101    
102            Message err = null;
103    
104            //this validation is duplicated.  This is in ScrubberValidatorImpl under GL
105    //        err = this.validateClosedPeriodCode(laborOriginEntry, laborScrubbedEntry);
106    //        if (err != null) {
107    //            errors.add(err);
108    //        }
109    
110            err = validatePayrollEndFiscalYear(laborOriginEntry, laborScrubbedEntry, universityRunDate, (LaborAccountingCycleCachingService) laborAccountingCycleCachingService);
111            if (err != null) {
112                errors.add(err);
113            }
114    
115            err = validatePayrollEndFiscalPeriodCode(laborOriginEntry, laborScrubbedEntry, universityRunDate, (LaborAccountingCycleCachingService) laborAccountingCycleCachingService);
116            if (err != null) {
117                errors.add(err);
118            }
119    
120            err = validateAccount(laborOriginEntry, laborScrubbedEntry, universityRunDate, (LaborAccountingCycleCachingService) laborAccountingCycleCachingService);
121            if (err != null) {
122                errors.add(err);
123            }
124            
125            err = validateSubAccount(laborOriginEntry, laborScrubbedEntry, (LaborAccountingCycleCachingService) laborAccountingCycleCachingService);
126            if (err != null) {
127                errors.add(err);
128            }
129    
130            return errors;
131        }
132    
133        /**
134         * This method is for refreshing References of Origin Entry
135         */
136        protected void refreshOriginEntryReferences(OriginEntryFull originEntry) {
137            Map<String, Class> referenceClasses = persistenceStructureService.listReferenceObjectFields(originEntry.getClass());
138            for (String reference : referenceClasses.keySet()) {
139                if (KFSPropertyConstants.PROJECT.equals(reference)) {
140                    if (KFSConstants.getDashProjectCode().equals(originEntry.getProjectCode())) {
141                        originEntry.setProject(null);
142                    }
143                    else {
144                        persistenceService.retrieveReferenceObject(originEntry, reference);
145                    }
146                }
147                else if (KFSPropertyConstants.FINANCIAL_SUB_OBJECT.equals(reference)) {
148                    if (KFSConstants.getDashFinancialSubObjectCode().equals(originEntry.getFinancialSubObjectCode())) {
149                        originEntry.setFinancialSubObject(null);
150                    }
151                    else {
152                        persistenceService.retrieveReferenceObject(originEntry, reference);
153                    }
154                }
155                else if (KFSPropertyConstants.SUB_ACCOUNT.equals(reference)) {
156                    if (KFSConstants.getDashSubAccountNumber().equals(originEntry.getSubAccountNumber())) {
157                        originEntry.setSubAccount(null);
158                    }
159                    else {
160                        persistenceService.retrieveReferenceObject(originEntry, reference);
161                    }
162                }
163                else {
164                    persistenceService.retrieveReferenceObject(originEntry, reference);
165                }
166            }
167        }
168    
169        /**
170         * Validates the closed period code of the origin entry. Scrubber accepts closed fiscal periods for the specified balance type.
171         * 
172         * @param originEntry the origin entry being scrubbed
173         * @param workingEntry the scrubbed version of the origin entry
174         * @return a Message if an error was encountered, otherwise null
175         */
176        //this validation is duplicated.  This is in ScrubberValidatorImpl under GL
177    //    protected Message validateClosedPeriodCode(LaborOriginEntry laborOriginEntry, LaborOriginEntry laborWorkingEntry) {
178    //        LOG.debug("validateClosedPeriodCode() started");
179    //
180    //        String periodCode = laborOriginEntry.getUniversityFiscalPeriodCode();
181    //        if (StringUtils.isBlank(periodCode)) {
182    //            return null;
183    //        }
184    //
185    //        // Scrubber accepts closed fiscal periods for A21 Balance
186    //        AccountingPeriod accountingPeriod = referenceLookup.get().getAccountingPeriod(laborOriginEntry);
187    //        if (ObjectUtils.isNotNull(accountingPeriod) && !accountingPeriod.isActive()) {
188    //            String bypassBalanceType = parameterService.getParameterValue(LaborScrubberStep.class, LaborConstants.Scrubber.CLOSED_FISCAL_PERIOD_BYPASS_BALANCE_TYPES);
189    //
190    //            if (!laborWorkingEntry.getFinancialBalanceTypeCode().equals(bypassBalanceType)) {
191    //                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_FISCAL_PERIOD_CLOSED, periodCode, Message.TYPE_FATAL);
192    //            }
193    //
194    //            laborWorkingEntry.setUniversityFiscalPeriodCode(periodCode);
195    //        }
196    //
197    //        return null;
198    //    }
199    
200        /**
201         * This method is for validation of payrollEndFiscalYear
202         */
203        protected Message validatePayrollEndFiscalYear(LaborOriginEntry laborOriginEntry, LaborOriginEntry laborWorkingEntry, UniversityDate universityRunDate, LaborAccountingCycleCachingService laborAccountingCycleCachingService) {
204            LOG.debug("validatePayrollEndFiscalYear() started");
205            SystemOptions scrubbedEntryOption = null;
206            if (laborOriginEntry.getPayrollEndDateFiscalYear() != null){
207                scrubbedEntryOption = laborAccountingCycleCachingService.getSystemOptions(laborOriginEntry.getPayrollEndDateFiscalYear());
208                
209                if (scrubbedEntryOption == null) {
210                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_PAYROLL_END_DATE_FISCAL_YEAR, "" + laborOriginEntry.getPayrollEndDateFiscalYear(), Message.TYPE_FATAL);
211                }
212                
213            }
214    
215            return null;
216        }
217    
218        /**
219         * This method is for validation of PayrollEndFiscalPeriodCode
220         */
221        protected Message validatePayrollEndFiscalPeriodCode(LaborOriginEntry laborOriginEntry, LaborOriginEntry laborWorkingEntry, UniversityDate universityRunDate, LaborAccountingCycleCachingService laborAccountingCycleCachingService) {
222            LOG.debug("validateUniversityFiscalPeriodCode() started");
223    
224            AccountingPeriod accountingPeriod = null;
225            Integer tempPayrollFiscalYear = 0;
226            if (laborOriginEntry.getPayrollEndDateFiscalYear()== null ){
227                tempPayrollFiscalYear = universityRunDate.getUniversityFiscalYear();
228            } else {
229                tempPayrollFiscalYear = laborOriginEntry.getPayrollEndDateFiscalYear();
230            }
231            
232            if (!laborOriginEntry.getPayrollEndDateFiscalPeriodCode().equals("")  ){
233                accountingPeriod = laborAccountingCycleCachingService.getAccountingPeriod(tempPayrollFiscalYear, laborOriginEntry.getPayrollEndDateFiscalPeriodCode());
234                if (accountingPeriod == null) {
235                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_PAYROLL_END_DATE_FISCAL_PERIOD, laborOriginEntry.getPayrollEndDateFiscalPeriodCode(), Message.TYPE_FATAL);
236                }
237            }
238    
239    
240            return null;
241        }
242    
243        /**
244         * Performs Account Validation.
245         */
246        protected Message validateAccount(LaborOriginEntry laborOriginEntry, LaborOriginEntry laborWorkingEntry, UniversityDate universityRunDate, LaborAccountingCycleCachingService laborAccountingCycleCachingService) {
247            LOG.debug("validateAccount() started");
248    
249            Account account = laborOriginEntry.getAccount();
250            boolean suspenseAccountLogicInd = parameterService.getIndicatorParameter(LaborScrubberStep.class, LaborConstants.Scrubber.SUSPENSE_ACCOUNT_LOGIC_PARAMETER);
251            if (ObjectUtils.isNull(account)) {
252                if (suspenseAccountLogicInd) {
253                    return useSuspenseAccount(laborWorkingEntry);
254                }
255                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_ACCOUNT_NOT_FOUND, laborOriginEntry.getChartOfAccountsCode() + "-" + laborOriginEntry.getAccountNumber(), Message.TYPE_FATAL);
256            }
257    
258            // default
259            laborWorkingEntry.setAccount(account);
260            laborWorkingEntry.setChartOfAccountsCode(account.getChartOfAccountsCode());
261            laborWorkingEntry.setAccountNumber(account.getAccountNumber());
262    
263            // no further validation for gl annual doc type
264            String glAnnualClosingType = parameterService.getParameterValue(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, KFSConstants.SystemGroupParameterNames.GL_ANNUAL_CLOSING_DOC_TYPE);
265            if (glAnnualClosingType.equals(laborOriginEntry.getFinancialDocumentTypeCode())) {
266                return null;
267            }
268    
269            // Sub-Fund Wage Exclusion
270            String orginationCode = laborOriginEntry.getFinancialSystemOriginationCode();
271            List<String> nonWageSubfundBypassOriginationCodes = parameterService.getParameterValues(LaborScrubberStep.class, LaborConstants.Scrubber.NON_WAGE_SUB_FUND_BYPASS_ORIGINATIONS);
272            boolean subfundWageExclusionInd = parameterService.getIndicatorParameter(LaborScrubberStep.class, LaborConstants.Scrubber.SUBFUND_WAGE_EXCLUSION_PARAMETER);
273    
274            if (subfundWageExclusionInd && !account.getSubFundGroup().isSubFundGroupWagesIndicator() && !nonWageSubfundBypassOriginationCodes.contains(orginationCode)) {
275                if (suspenseAccountLogicInd) {
276                    return useSuspenseAccount(laborWorkingEntry);
277                }
278    
279                return MessageBuilder.buildMessage(LaborKeyConstants.ERROR_SUN_FUND_NOT_ACCEPT_WAGES, Message.TYPE_FATAL);
280            }
281    
282            // Account Fringe Validation
283            List<String> nonFringeAccountBypassOriginationCodes = parameterService.getParameterValues(LaborScrubberStep.class, LaborConstants.Scrubber.NON_FRINGE_ACCOUNT_BYPASS_ORIGINATIONS);
284            boolean accountFringeExclusionInd = parameterService.getIndicatorParameter(LaborScrubberStep.class, LaborConstants.Scrubber.ACCOUNT_FRINGE_EXCLUSION_PARAMETER);
285    
286            if (accountFringeExclusionInd && !nonFringeAccountBypassOriginationCodes.contains(orginationCode)) {
287                return checkAccountFringeIndicator(laborOriginEntry, laborWorkingEntry, account, universityRunDate, laborAccountingCycleCachingService);
288            }
289    
290            // Expired/Closed Validation
291            return handleExpiredClosedAccount(laborOriginEntry.getAccount(), laborOriginEntry, laborWorkingEntry, universityRunDate);
292        }
293    
294        /**
295         * Checks the continuation account system indicator. If on checks whether the account is expired or closed, and if so calls the
296         * contination logic.
297         */
298        protected Message handleExpiredClosedAccount(Account account, LaborOriginEntry laborOriginEntry, LaborOriginEntry laborWorkingEntry, UniversityDate universityRunDate) {
299            List<String> continuationAccountBypassBalanceTypeCodes = balanceTypService.getContinuationAccountBypassBalanceTypeCodes(universityRunDate.getUniversityFiscalYear());
300            List<String> continuationAccountBypassOriginationCodes = parameterService.getParameterValues(LaborScrubberStep.class, LaborConstants.Scrubber.CONTINUATION_ACCOUNT_BYPASS_ORIGINATION_CODES);
301            List<String> continuationAccountBypassDocumentTypeCodes = parameterService.getParameterValues(LaborScrubberStep.class, LaborConstants.Scrubber.CONTINUATION_ACCOUNT_BYPASS_DOCUMENT_TYPE_CODES);
302    
303            Calendar today = Calendar.getInstance();
304            today.setTime(universityRunDate.getUniversityDate());
305    
306            long offsetAccountExpirationTime = getAdjustedAccountExpirationDate(account);
307            boolean isAccountExpiredOrClosed = (account.getAccountExpirationDate() != null && isAccountExpired(account, universityRunDate)) || !account.isActive();
308            boolean continuationAccountLogicInd = parameterService.getIndicatorParameter(LaborScrubberStep.class, LaborConstants.Scrubber.CONTINUATION_ACCOUNT_LOGIC_PARAMETER);
309    
310            if (continuationAccountLogicInd && isAccountExpiredOrClosed) {
311                // special checks for origination codes that have override ability
312                boolean isOverrideOriginCode = continuationAccountBypassOriginationCodes.contains(laborOriginEntry.getFinancialSystemOriginationCode());
313                if (isOverrideOriginCode && !account.isActive()) {
314                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_ORIGIN_CODE_CANNOT_HAVE_CLOSED_ACCOUNT, laborOriginEntry.getChartOfAccountsCode() + "-" + laborOriginEntry.getAccountNumber(), Message.TYPE_FATAL);
315                }
316    
317                boolean canBypass = isOverrideOriginCode || continuationAccountBypassBalanceTypeCodes.contains(laborOriginEntry.getFinancialBalanceTypeCode()) || continuationAccountBypassDocumentTypeCodes.contains(laborOriginEntry.getFinancialDocumentTypeCode().trim());
318                if (account.isActive() && canBypass) {
319                    return null;
320                }
321    
322                return continuationAccountLogic(account, laborOriginEntry, laborWorkingEntry, universityRunDate);
323            }
324    
325            return null;
326        }
327    
328        /**
329         * Loops through continuation accounts for 10 tries or until it finds an account that is not expired.
330         */
331        protected Message continuationAccountLogic(Account expiredClosedAccount, LaborOriginEntry laborOriginEntry, LaborOriginEntry laborWorkingEntry, UniversityDate universityRunDate) {
332            String chartCode = expiredClosedAccount.getContinuationFinChrtOfAcctCd();
333            String accountNumber = expiredClosedAccount.getContinuationAccountNumber();
334    
335            List<String> checkedAccountNumbers = new ArrayList<String>();
336            for (int i = 0; i < 10; ++i) {
337                if (checkedAccountNumbers.contains(chartCode + accountNumber)) {
338                    // Something is really wrong with the data because this account has already been evaluated.
339                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_CIRCULAR_DEPENDENCY_IN_CONTINUATION_ACCOUNT_LOGIC, Message.TYPE_FATAL);
340                }
341    
342                checkedAccountNumbers.add(chartCode + accountNumber);
343    
344                if (chartCode == null || accountNumber == null) {
345                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_CONTINUATION_ACCOUNT_NOT_FOUND, Message.TYPE_FATAL);
346                }
347    
348                // Lookup the account
349                Account account = accountService.getByPrimaryId(chartCode, accountNumber);
350                if (ObjectUtils.isNull(account)) {
351                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_CONTINUATION_ACCOUNT_NOT_FOUND, Message.TYPE_FATAL);
352                }
353    
354                // check account expiration
355                long offsetAccountExpirationTime = getAdjustedAccountExpirationDate(account);
356                if (ObjectUtils.isNotNull(account.getAccountExpirationDate()) && isAccountExpired(account, universityRunDate)) {
357                    chartCode = account.getContinuationFinChrtOfAcctCd();
358                    accountNumber = account.getContinuationAccountNumber();
359                }
360                else {
361                    
362                    // set continuationAccountLogicIndi
363                    continuationAccountIndicator = true;
364                    
365                    laborWorkingEntry.setAccount(account);
366                    laborWorkingEntry.setAccountNumber(accountNumber);
367                    laborWorkingEntry.setChartOfAccountsCode(chartCode);
368                    laborWorkingEntry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
369                    laborWorkingEntry.setTransactionLedgerEntryDescription(kualiConfigurationService.getPropertyString(KFSKeyConstants.MSG_AUTO_FORWARD) + " " + expiredClosedAccount.getChartOfAccountsCode() + expiredClosedAccount.getAccountNumber() + laborOriginEntry.getTransactionLedgerEntryDescription());
370    
371                    return MessageBuilder.buildMessage(KFSKeyConstants.MSG_ACCOUNT_CLOSED_TO, laborWorkingEntry.getChartOfAccountsCode() + "-" + laborWorkingEntry.getAccountNumber(), Message.TYPE_WARNING);
372                }
373            }
374    
375            // We failed to find a valid continuation account.
376            boolean suspenseAccountLogicInd = parameterService.getIndicatorParameter(LaborScrubberStep.class, LaborConstants.Scrubber.SUSPENSE_ACCOUNT_LOGIC_PARAMETER);
377            if (suspenseAccountLogicInd) {
378                return useSuspenseAccount(laborWorkingEntry);
379            }
380            else {
381                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_CONTINUATION_ACCOUNT_LIMIT_REACHED, Message.TYPE_FATAL);
382            }
383        }
384    
385        /**
386         * For fringe transaction types checks if the account accepts fringe benefits. If not, retrieves the alternative account, then
387         * calls expiration checking on either the alternative account or the account passed in.
388         */
389        protected Message checkAccountFringeIndicator(LaborOriginEntry laborOriginEntry, LaborOriginEntry laborWorkingEntry, Account account, UniversityDate universityRunDate, LaborAccountingCycleCachingService laborAccountingCycleCachingService) {
390            // check for fringe tranaction type
391            //LaborObject laborObject = (LaborObject) businessObjectService.findByPrimaryKey(LaborObject.class, fieldValues);
392            LaborObject laborObject = laborAccountingCycleCachingService.getLaborObject(laborOriginEntry.getUniversityFiscalYear(), laborOriginEntry.getChartOfAccountsCode(), laborOriginEntry.getFinancialObjectCode());
393            boolean isFringeTransaction = laborObject != null && org.apache.commons.lang.StringUtils.equals(LaborConstants.BenefitExpenseTransfer.LABOR_LEDGER_BENEFIT_CODE, laborObject.getFinancialObjectFringeOrSalaryCode());
394    
395            // alternative account handling for non fringe accounts
396            if (isFringeTransaction && !account.isAccountsFringesBnftIndicator()) {
397                Account altAccount = accountService.getByPrimaryId(laborOriginEntry.getAccount().getReportsToChartOfAccountsCode(), laborOriginEntry.getAccount().getReportsToAccountNumber());
398                if (ObjectUtils.isNotNull(altAccount)) {
399                    laborWorkingEntry.setAccount(altAccount);
400                    laborWorkingEntry.setAccountNumber(altAccount.getAccountNumber());
401                    laborWorkingEntry.setChartOfAccountsCode(altAccount.getChartOfAccountsCode());
402    
403                    return handleExpiredClosedAccount(altAccount, laborOriginEntry, laborWorkingEntry, universityRunDate);
404                }
405    
406                // no alt acct, use suspense acct if active
407                boolean suspenseAccountLogicInd = parameterService.getIndicatorParameter(LaborScrubberStep.class, LaborConstants.Scrubber.SUSPENSE_ACCOUNT_LOGIC_PARAMETER);
408                if (suspenseAccountLogicInd) {
409                    return useSuspenseAccount(laborWorkingEntry);
410                }
411    
412                return MessageBuilder.buildMessage(LaborKeyConstants.ERROR_NON_FRINGE_ACCOUNT_ALTERNATIVE_NOT_FOUND, Message.TYPE_FATAL);
413            }
414    
415            return handleExpiredClosedAccount(account, laborOriginEntry, laborWorkingEntry, universityRunDate);
416        }
417    
418        /**
419         * Adjustment of Account if it is contracts and grants
420         */
421        protected long getAdjustedAccountExpirationDate(Account account) {
422            long offsetAccountExpirationTime = 0;
423    
424            if (account.getAccountExpirationDate() != null) {
425                offsetAccountExpirationTime = account.getAccountExpirationDate().getTime();
426    
427                if (account.isForContractsAndGrants() && account.isActive()) {
428                    String daysOffset = parameterService.getParameterValue(ScrubberStep.class, KFSConstants.SystemGroupParameterNames.GL_SCRUBBER_VALIDATION_DAYS_OFFSET);
429                    int daysOffsetInt = 0; // default to 0
430    
431                    if (!org.apache.commons.lang.StringUtils.isBlank(daysOffset)) {
432                        daysOffsetInt = new Integer(daysOffset).intValue();
433                    }
434    
435                    Calendar tempCal = Calendar.getInstance();
436                    tempCal.setTimeInMillis(offsetAccountExpirationTime);
437                    tempCal.add(Calendar.DAY_OF_MONTH, daysOffsetInt);
438                    offsetAccountExpirationTime = tempCal.getTimeInMillis();
439                }
440            }
441    
442            return offsetAccountExpirationTime;
443        }
444    
445        /**
446         * This method changes account to suspenseAccount
447         */
448        protected Message useSuspenseAccount(LaborOriginEntry workingEntry) {
449            String suspenseAccountNumber = parameterService.getParameterValue(LaborScrubberStep.class, LaborConstants.Scrubber.SUSPENSE_ACCOUNT);
450            String suspenseCOAcode = parameterService.getParameterValue(LaborScrubberStep.class, LaborConstants.Scrubber.SUSPENSE_CHART);
451            String suspenseSubAccountNumber = parameterService.getParameterValue(LaborScrubberStep.class, LaborConstants.Scrubber.SUSPENSE_SUB_ACCOUNT);
452    
453            Account account = accountService.getByPrimaryId(suspenseCOAcode, suspenseAccountNumber);
454    
455            if (ObjectUtils.isNull(account)) {
456                return MessageBuilder.buildMessage(LaborKeyConstants.ERROR_INVALID_SUSPENSE_ACCOUNT, Message.TYPE_FATAL);
457            }
458    
459            workingEntry.setAccount(account);
460            workingEntry.setAccountNumber(suspenseAccountNumber);
461            workingEntry.setChartOfAccountsCode(suspenseCOAcode);
462            workingEntry.setSubAccountNumber(suspenseSubAccountNumber);
463    
464            return MessageBuilder.buildMessageWithPlaceHolder(LaborKeyConstants.MESSAGE_SUSPENSE_ACCOUNT_APPLIED, Message.TYPE_WARNING, suspenseCOAcode, suspenseAccountNumber, suspenseSubAccountNumber);
465        }
466        
467        /**
468         * Validates the sub account of the origin entry
469         * 
470         * @param originEntry the origin entry being scrubbed
471         * @param workingEntry the scrubbed version of the origin entry
472         * @return a Message if an error was encountered, otherwise null
473         */
474    
475        protected Message validateSubAccount(LaborOriginEntry originEntry, LaborOriginEntry workingEntry, LaborAccountingCycleCachingService laborAccountingCycleCachingService) {
476            LOG.debug("validateSubAccount() started");
477    
478            // when continuationAccount used, the subAccountNumber should be changed to dashes and skip validation subAccount process
479            if (continuationAccountIndicator) {
480                workingEntry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
481                return null;
482            }
483                    
484            // If the sub account number is empty, set it to dashes.
485            // Otherwise set the workingEntry sub account number to the
486            // sub account number of the input origin entry.
487            if (org.springframework.util.StringUtils.hasText(originEntry.getSubAccountNumber())) {
488                // sub account IS specified
489                if (!KFSConstants.getDashSubAccountNumber().equals(originEntry.getSubAccountNumber())) {
490                  SubAccount originEntrySubAccount = laborAccountingCycleCachingService.getSubAccount(originEntry.getChartOfAccountsCode(), originEntry.getAccountNumber(), originEntry.getSubAccountNumber());
491                  //SubAccount originEntrySubAccount = getSubAccount(originEntry);
492                    if (originEntrySubAccount == null) {
493                        // sub account is not valid
494                        return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_SUB_ACCOUNT_NOT_FOUND, originEntry.getChartOfAccountsCode() + "-" + originEntry.getAccountNumber() + "-" + originEntry.getSubAccountNumber(), Message.TYPE_FATAL);
495                    }
496                    else {
497                        // sub account IS valid
498                        if (originEntrySubAccount.isActive()) {
499                            // sub account IS active
500                            workingEntry.setSubAccountNumber(originEntry.getSubAccountNumber());
501                        }
502                        else {
503                            // sub account IS NOT active
504                            if (parameterService.getParameterValue(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, KFSConstants.SystemGroupParameterNames.GL_ANNUAL_CLOSING_DOC_TYPE).equals(originEntry.getFinancialDocumentTypeCode())) {
505                                // document IS annual closing
506                                workingEntry.setSubAccountNumber(originEntry.getSubAccountNumber());
507                            }
508                            else {
509                                // document is NOT annual closing
510                                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_SUB_ACCOUNT_NOT_ACTIVE, originEntry.getChartOfAccountsCode() + "-" + originEntry.getAccountNumber() + "-" + originEntry.getSubAccountNumber(), Message.TYPE_FATAL);
511                            }
512                        }
513                    }
514                }
515                else {
516                    // the sub account is dashes
517                    workingEntry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
518                }
519            }
520            else {
521                // No sub account is specified.
522                workingEntry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
523            }
524            
525            
526            return null;
527            
528        }
529        
530    
531        /**
532         * @see org.kuali.kfs.gl.service.ScrubberValidator#isAccountExpired(org.kuali.kfs.coa.businessobject.Account, org.kuali.kfs.sys.businessobject.UniversityDate)
533         */
534        public boolean isAccountExpired(Account account, UniversityDate universityRunDate) {
535            return scrubberValidator.isAccountExpired(account, universityRunDate);
536        }
537    
538        public void validateForInquiry(GeneralLedgerPendingEntry entry) {
539        }
540    
541        /**
542         * Sets the parameterService attribute value.
543         * 
544         * @param parameterService The parameterService to set.
545         */
546        public void setParameterService(ParameterService parameterService) {
547            this.parameterService = parameterService;
548        }
549    
550        /**
551         * Sets the kualiConfigurationService attribute value.
552         * 
553         * @param service The kualiConfigurationService to set.
554         */
555        public void setKualiConfigurationService(KualiConfigurationService service) {
556            kualiConfigurationService = service;
557        }
558    
559        /**
560         * Sets the accountService attribute value.
561         * 
562         * @param as The accountService to set.
563         */
564        public void setAccountService(AccountService as) {
565            accountService = as;
566        }
567    
568        /**
569         * Sets the persistenceService attribute value.
570         * 
571         * @param ps The persistenceService to set.
572         */
573        public void setPersistenceService(PersistenceService ps) {
574            persistenceService = ps;
575        }
576    
577        /**
578         * Sets the businessObjectService attribute value.
579         * 
580         * @param businessObjectService The businessObjectService to set.
581         */
582        public void setBusinessObjectService(BusinessObjectService businessObjectService) {
583            this.businessObjectService = businessObjectService;
584        }
585    
586        /**
587         * Sets the balanceTypService attribute value.
588         * 
589         * @param balanceTypService The balanceTypService to set.
590         */
591        public void setBalanceTypService(BalanceTypeService balanceTypService) {
592            this.balanceTypService = balanceTypService;
593        }
594    
595        /**
596         * Sets the scrubberValidator attribute value.
597         * 
598         * @param sv The scrubberValidator to set.
599         */
600        public void setScrubberValidator(ScrubberValidator sv) {
601            scrubberValidator = sv;
602        }
603    
604        /**
605         * Sets the persistenceStructureService attribute value.
606         * 
607         * @param persistenceStructureService The persistenceStructureService to set.
608         */
609        public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
610            this.persistenceStructureService = persistenceStructureService;
611        }
612    
613        /**
614         * Sets the optionsService attribute value.
615         * 
616         * @param optionsService The optionsService to set.
617         */
618        public void setOptionsService(OptionsService optionsService) {
619            this.optionsService = optionsService;
620        }
621        
622        
623    }