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.service.impl;
017    
018    import java.sql.Date;
019    import java.text.SimpleDateFormat;
020    import java.util.ArrayList;
021    import java.util.Calendar;
022    import java.util.HashSet;
023    import java.util.List;
024    import java.util.Set;
025    
026    import org.apache.commons.lang.ArrayUtils;
027    import org.kuali.kfs.coa.businessobject.Account;
028    import org.kuali.kfs.coa.businessobject.AccountingPeriod;
029    import org.kuali.kfs.coa.businessobject.BalanceType;
030    import org.kuali.kfs.coa.businessobject.Chart;
031    import org.kuali.kfs.coa.businessobject.ObjectCode;
032    import org.kuali.kfs.coa.businessobject.ObjectType;
033    import org.kuali.kfs.coa.businessobject.ProjectCode;
034    import org.kuali.kfs.coa.businessobject.SubAccount;
035    import org.kuali.kfs.coa.businessobject.SubObjectCode;
036    import org.kuali.kfs.coa.service.AccountService;
037    import org.kuali.kfs.coa.service.BalanceTypeService;
038    import org.kuali.kfs.gl.GeneralLedgerConstants;
039    import org.kuali.kfs.gl.ObjectHelper;
040    import org.kuali.kfs.gl.batch.ScrubberStep;
041    import org.kuali.kfs.gl.batch.service.AccountingCycleCachingService;
042    import org.kuali.kfs.gl.businessobject.OriginEntryInformation;
043    import org.kuali.kfs.gl.service.ScrubberValidator;
044    import org.kuali.kfs.sys.KFSConstants;
045    import org.kuali.kfs.sys.KFSKeyConstants;
046    import org.kuali.kfs.sys.KFSPropertyConstants;
047    import org.kuali.kfs.sys.Message;
048    import org.kuali.kfs.sys.MessageBuilder;
049    import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
050    import org.kuali.kfs.sys.businessobject.OriginationCode;
051    import org.kuali.kfs.sys.businessobject.SystemOptions;
052    import org.kuali.kfs.sys.businessobject.UniversityDate;
053    import org.kuali.kfs.sys.context.SpringContext;
054    import org.kuali.kfs.sys.dataaccess.UniversityDateDao;
055    import org.kuali.kfs.sys.service.NonTransactional;
056    import org.kuali.kfs.sys.service.OriginationCodeService;
057    import org.kuali.kfs.sys.service.UniversityDateService;
058    import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
059    import org.kuali.rice.kns.service.DataDictionaryService;
060    import org.kuali.rice.kns.service.KualiConfigurationService;
061    import org.kuali.rice.kns.service.ParameterService;
062    import org.kuali.rice.kns.service.PersistenceService;
063    import org.kuali.rice.kns.service.PersistenceStructureService;
064    import org.kuali.rice.kns.util.KualiDecimal;
065    import org.springframework.util.StringUtils;
066    
067    /**
068     * The default GL implementation of ScrubberValidator
069     */
070    
071    @NonTransactional
072    public class ScrubberValidatorImpl implements ScrubberValidator {
073        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ScrubberValidatorImpl.class);
074    
075        private KualiConfigurationService kualiConfigurationService;
076        private ParameterService parameterService;
077        private PersistenceService persistenceService;
078        private UniversityDateDao universityDateDao;
079        private AccountService accountService;
080        private OriginationCodeService originationCodeService;
081        private PersistenceStructureService persistenceStructureService;
082        private BalanceTypeService balanceTypService;
083        private boolean continuationAccountIndicator;
084        
085        public static final String DATE_FORMAT_STRING = "yyyy-MM-dd";
086    
087        protected static String[] debitOrCredit = new String[] { KFSConstants.GL_DEBIT_CODE, KFSConstants.GL_CREDIT_CODE };
088    
089        private static int count = 0;
090    
091        /**
092         * Validate a transaction for use in balance inquiry
093         * 
094         * @param entry Input transaction
095         * @see org.kuali.module.gl.service.ScrubberValidator#validateForInquiry(org.kuali.kfs.bo.GeneralLedgerPendingEntry)
096         */
097        public void validateForInquiry(GeneralLedgerPendingEntry entry) {
098            LOG.debug("validateForInquiry() started");
099    
100            UniversityDate today = null;
101    
102            if (entry.getUniversityFiscalYear() == null) {
103                today = SpringContext.getBean(UniversityDateService.class).getCurrentUniversityDate();
104                entry.setUniversityFiscalYear(today.getUniversityFiscalYear());
105            }
106    
107            if (entry.getUniversityFiscalPeriodCode() == null) {
108                if (today == null) {
109                    today = SpringContext.getBean(UniversityDateService.class).getCurrentUniversityDate();
110                }
111                entry.setUniversityFiscalPeriodCode(today.getUniversityFiscalAccountingPeriod());
112            }
113    
114            if ((entry.getSubAccountNumber() == null) || (!StringUtils.hasText(entry.getSubAccountNumber()))) {
115                entry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
116            }
117            if ((entry.getFinancialSubObjectCode() == null) || (!StringUtils.hasText(entry.getFinancialSubObjectCode()))) {
118                entry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
119            }
120            if ((entry.getProjectCode() == null) || (!StringUtils.hasText(entry.getProjectCode()))) {
121                entry.setProjectCode(KFSConstants.getDashProjectCode());
122            }
123        }
124    
125        /**
126         * Validate a transaction in the scrubber
127         * 
128         * @param originEntry Input transaction (never changed)
129         * @param scrubbedEntry Output transaction (scrubbed version of input transaction)
130         * @param universityRunDate Date of scrubber run
131         * @return List of Message objects based for warnings or errors that happened when validating the transaction
132         * @see org.kuali.module.gl.service.ScrubberValidator#validateTransaction(org.kuali.module.gl.bo.OriginEntry, org.kuali.module.gl.bo.OriginEntry, org.kuali.module.gl.bo.UniversityDate, boolean)
133         */
134        public List<Message> validateTransaction(OriginEntryInformation originEntry, OriginEntryInformation scrubbedEntry, UniversityDate universityRunDate, boolean laborIndicator, AccountingCycleCachingService accountingCycleCachingService) {
135            LOG.debug("validateTransaction() started");
136            
137            continuationAccountIndicator = false;
138            
139            List<Message> errors = new ArrayList<Message>();
140    
141            count++;
142            if (count % 1000 == 0) {
143                LOG.info(count + " " + originEntry.getLine());
144            }
145    
146            // The cobol checks fdoc_nbr, trn_ldgr_entr_desc, org_doc_nbr, org_reference_id, and fdoc_ref_nbr for characters less than
147            // ascii 32 or '~'. If found, it replaces that character with a space and reports a warning. This code does the ~, but not
148            // the
149            // less than 32 part.
150            if ((originEntry.getDocumentNumber() != null) && (originEntry.getDocumentNumber().indexOf("~") > -1)) {
151                String d = originEntry.getDocumentNumber();
152                scrubbedEntry.setDocumentNumber(d.replaceAll("~", " "));
153                errors.add(new Message("** INVALID CHARACTER EDITED", Message.TYPE_WARNING));
154            }
155            if ((originEntry.getTransactionLedgerEntryDescription() != null) && (originEntry.getTransactionLedgerEntryDescription().indexOf("~") > -1)) {
156                String d = originEntry.getTransactionLedgerEntryDescription();
157                scrubbedEntry.setTransactionLedgerEntryDescription(d.replaceAll("~", " "));
158                errors.add(new Message("** INVALID CHARACTER EDITED", Message.TYPE_WARNING));
159            }
160            if ((originEntry.getOrganizationDocumentNumber() != null) && (originEntry.getOrganizationDocumentNumber().indexOf("~") > -1)) {
161                String d = originEntry.getOrganizationDocumentNumber();
162                scrubbedEntry.setOrganizationDocumentNumber(d.replaceAll("~", " "));
163                errors.add(new Message("** INVALID CHARACTER EDITED", Message.TYPE_WARNING));
164            }
165            if ((originEntry.getOrganizationReferenceId() != null) && (originEntry.getOrganizationReferenceId().indexOf("~") > -1)) {
166                String d = originEntry.getOrganizationReferenceId();
167                scrubbedEntry.setOrganizationReferenceId(d.replaceAll("~", " "));
168                errors.add(new Message("** INVALID CHARACTER EDITED", Message.TYPE_WARNING));
169            }
170            if ((originEntry.getReferenceFinancialDocumentNumber() != null) && (originEntry.getReferenceFinancialDocumentNumber().indexOf("~") > -1)) {
171                String d = originEntry.getReferenceFinancialDocumentNumber();
172                scrubbedEntry.setReferenceFinancialDocumentNumber(d.replaceAll("~", " "));
173                errors.add(new Message("** INVALID CHARACTER EDITED", Message.TYPE_WARNING));
174            }
175    
176            // It's important that this check come before the checks for object, sub-object and accountingPeriod
177            // because this validation method will set the fiscal year and reload those three objects if the fiscal
178            // year was invalid. This will also set originEntry.getOption and workingEntry.getOption. So, it's
179            // probably a good idea to validate the fiscal year first thing.
180            Message err = validateFiscalYear(originEntry, scrubbedEntry, universityRunDate, accountingCycleCachingService);
181            if (err != null) {
182                errors.add(err);
183            }
184            
185            err = validateUniversityFiscalPeriodCode(originEntry, scrubbedEntry, universityRunDate, accountingCycleCachingService);
186            if (err != null) {
187                errors.add(err);
188            }
189    
190            err = validateBalanceType(originEntry, scrubbedEntry, accountingCycleCachingService);
191            if (err != null) {
192                errors.add(err);
193            }
194    
195            err = validateTransactionDate(originEntry, scrubbedEntry, universityRunDate, accountingCycleCachingService);
196            if (err != null) {
197                errors.add(err);
198            }
199    
200            err = validateTransactionAmount(originEntry, scrubbedEntry, accountingCycleCachingService);
201            if (err != null) {
202                errors.add(err);
203            }
204    
205            err = validateChart(originEntry, scrubbedEntry, accountingCycleCachingService);
206            if (err != null) {
207                errors.add(err);
208            }
209    
210            // Labor Scrubber doesn't validate Account here.
211            if (!laborIndicator) {
212                err = validateAccount(originEntry, scrubbedEntry, universityRunDate, accountingCycleCachingService);
213                if (err != null) {
214                    errors.add(err);
215                }
216            }
217            
218            // Labor Scrubber doesn't validate SubAccount here
219            if (!laborIndicator) {
220                err = validateSubAccount(originEntry, scrubbedEntry, accountingCycleCachingService);
221                if (err != null) {
222                    errors.add(err);
223                }
224            }
225    
226            err = validateProjectCode(originEntry, scrubbedEntry, accountingCycleCachingService);
227            if (err != null) {
228                errors.add(err);
229            }
230    
231            err = validateDocumentType(originEntry, scrubbedEntry, accountingCycleCachingService);
232            if (err != null) {
233                errors.add(err);
234            }
235    
236            err = validateOrigination(originEntry, scrubbedEntry, accountingCycleCachingService);
237            if (err != null) {
238                errors.add(err);
239            }
240            
241            err = validateReferenceOrigination(originEntry, scrubbedEntry, accountingCycleCachingService);
242            if (err != null) {
243                errors.add(err);
244            }
245    
246            err = validateDocumentNumber(originEntry, scrubbedEntry);
247            if (err != null) {
248                errors.add(err);
249            }
250    
251            err = validateObjectCode(originEntry, scrubbedEntry, accountingCycleCachingService);
252            if (err != null) {
253                errors.add(err);
254            }
255    
256            // If object code is invalid, we can't check the object type
257            if (err == null) {
258                err = validateObjectType(originEntry, scrubbedEntry, accountingCycleCachingService);
259                if (err != null) {
260                    errors.add(err);
261                }
262            }
263    
264            err = validateSubObjectCode(originEntry, scrubbedEntry, accountingCycleCachingService);
265            if (err != null) {
266                errors.add(err);
267            }
268    
269            // return messages could be multiple from validateReferenceFields
270            List<Message> referenceErrors = new ArrayList<Message>();
271            referenceErrors = validateReferenceDocumentFields(originEntry, scrubbedEntry, accountingCycleCachingService);
272            if (referenceErrors != null) {
273                errors.addAll(referenceErrors);
274            }
275    
276            err = validateReversalDate(originEntry, scrubbedEntry, accountingCycleCachingService);
277            if (err != null) {
278                errors.add(err);
279            }
280            
281            err = validateDescription(originEntry);
282            if (err != null) {
283                errors.add(err);
284            }
285    
286            return errors;
287        }
288    
289        /**
290         * Validates the account of an origin entry 
291         * 
292         * @param originEntry the origin entry to find the account of
293         * @param workingEntry the copy of the entry to move the account over to if it is valid
294         * @param universityRunDate the run date of the scrubber process
295         * @return a Message if the account was invalid, or null if no error was encountered
296         */
297        protected Message validateAccount(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, UniversityDate universityRunDate, AccountingCycleCachingService accountingCycleCachingService) {
298            LOG.debug("validateAccount() started");
299    
300            Account originEntryAccount = accountingCycleCachingService.getAccount(originEntry.getChartOfAccountsCode(), originEntry.getAccountNumber());
301            if (originEntryAccount != null) {
302                originEntryAccount.setSubFundGroup(accountingCycleCachingService.getSubFundGroup(originEntryAccount.getSubFundGroupCode()));
303            }
304                
305            if (originEntryAccount == null) {
306                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_ACCOUNT_NOT_FOUND, originEntry.getChartOfAccountsCode() + "-" + originEntry.getAccountNumber(), Message.TYPE_FATAL);
307            }
308    
309            if (parameterService.getParameterValue(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, KFSConstants.SystemGroupParameterNames.GL_ANNUAL_CLOSING_DOC_TYPE).equals(originEntry.getFinancialDocumentTypeCode())) {
310                workingEntry.setAccountNumber(originEntry.getAccountNumber());
311                return null;
312            }
313    
314            if ((originEntryAccount.getAccountExpirationDate() == null) && originEntryAccount.isActive()) {
315                // account is neither closed nor expired
316                workingEntry.setAccountNumber(originEntry.getAccountNumber());
317                return null;
318            }
319    
320            String[] continuationAccountBypassOriginationCodes = parameterService.getParameterValues(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.CONTINUATION_ACCOUNT_BYPASS_ORIGINATION_CODES).toArray(new String[] {});
321    
322            String [] continuationAccountBypassBalanceTypeCodes = {"EX","IE","PE"};
323            String[] continuationAccountBypassDocumentTypeCodes = parameterService.getParameterValues(ScrubberStep.class, GeneralLedgerConstants.GlScrubberGroupRules.CONTINUATION_ACCOUNT_BYPASS_DOCUMENT_TYPE_CODES).toArray(new String[] {});
324    
325            // Has an expiration date or is closed
326            if ((ArrayUtils.contains(continuationAccountBypassOriginationCodes, originEntry.getFinancialSystemOriginationCode())) && !originEntryAccount.isActive()) {
327                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_ORIGIN_CODE_CANNOT_HAVE_CLOSED_ACCOUNT, originEntryAccount.getChartOfAccountsCode() + "-" + originEntry.getAccountNumber(), Message.TYPE_FATAL);
328            }
329    
330            if ((ArrayUtils.contains(continuationAccountBypassOriginationCodes, originEntry.getFinancialSystemOriginationCode()) || ArrayUtils.contains(continuationAccountBypassBalanceTypeCodes, originEntry.getFinancialBalanceTypeCode()) || ArrayUtils.contains(continuationAccountBypassDocumentTypeCodes, originEntry.getFinancialDocumentTypeCode().trim())) && originEntryAccount.isActive()) {
331                workingEntry.setAccountNumber(originEntry.getAccountNumber());
332                return null;
333            }
334    
335            Calendar today = Calendar.getInstance();
336            today.setTime(universityRunDate.getUniversityDate());
337            
338            if (isAccountExpired(originEntryAccount, universityRunDate) || !originEntryAccount.isActive()) {
339                Message error = continuationAccountLogic(originEntry, workingEntry, universityRunDate, accountingCycleCachingService);
340                if (error != null) {
341                    return error;
342                }
343            }
344    
345            workingEntry.setAccountNumber(originEntry.getAccountNumber());
346            return null;
347        }
348    
349        /**
350         * Called when the account of the origin entry is expired or closed, this validates the continuation account
351         * 
352         * @param originEntry the origin entry being scrubbed
353         * @param workingEntry the scrubbed version of the origin entry
354         * @param universityRunDate the run date of the scrubber (to test against expiration dates)
355         * @return a Message if an error was encountered, otherwise null
356         */
357        protected Message continuationAccountLogic(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, UniversityDate universityRunDate, AccountingCycleCachingService accountingCycleCachingService) {
358    
359            Set<String> checkedAccountNumbers = new HashSet<String>();
360    
361            Account continuationAccount = null;
362            Account originEntryAccount = accountingCycleCachingService.getAccount(originEntry.getChartOfAccountsCode(), originEntry.getAccountNumber());
363    
364            String chartCode = originEntryAccount.getContinuationFinChrtOfAcctCd();
365            String accountNumber = originEntryAccount.getContinuationAccountNumber();
366    
367            for (int i = 0; i < 10; ++i) {
368                if (checkedAccountNumbers.contains(chartCode + accountNumber)) {
369                    // Something is really wrong with the data because this account has already been evaluated.
370                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_CIRCULAR_DEPENDENCY_IN_CONTINUATION_ACCOUNT_LOGIC, Message.TYPE_FATAL);
371                }
372    
373                if ((chartCode == null) || (accountNumber == null)) {
374                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_CONTINUATION_ACCOUNT_NOT_FOUND, Message.TYPE_FATAL);
375                }
376    
377                // Lookup the account
378                continuationAccount = accountingCycleCachingService.getAccount(chartCode, accountNumber);
379                if (null == continuationAccount) {
380                    // account not found
381                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_CONTINUATION_ACCOUNT_NOT_FOUND, Message.TYPE_FATAL);
382                }
383                else {
384                    // the account exists
385                    continuationAccount.setSubFundGroup(accountingCycleCachingService.getSubFundGroup(continuationAccount.getSubFundGroupCode()));
386                    if (continuationAccount.getAccountExpirationDate() == null) {
387                        // No expiration date
388                        workingEntry.setAccountNumber(accountNumber);
389                        workingEntry.setChartOfAccountsCode(chartCode);
390                        
391                        // to set subAcount with dashes
392                        continuationAccountIndicator = true;
393                        //workingEntry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
394                        //workingEntry.setTransactionLedgerEntryDescription(kualiConfigurationService.getPropertyString(KFSKeyConstants.MSG_AUTO_FORWARD) + " " + originEntry.getChartOfAccountsCode() + originEntry.getAccountNumber() + originEntry.getTransactionLedgerEntryDescription());
395                        // TODO:- use messageBuilder and KeyConstant - also, length issue!?!??
396                        workingEntry.setTransactionLedgerEntryDescription("AUTO FR " + originEntry.getChartOfAccountsCode() + originEntry.getAccountNumber() + originEntry.getTransactionLedgerEntryDescription());
397                        // FSKD-310 : need to check the account is closed for building message. if not, it is expired.
398                        if (!originEntryAccount.isActive()){
399                            return MessageBuilder.buildMessage(KFSKeyConstants.MSG_ACCOUNT_CLOSED_TO, chartCode+accountNumber, Message.TYPE_WARNING);
400                        } else {
401                            return MessageBuilder.buildMessage(KFSKeyConstants.MSG_ACCOUNT_EXPIRED_TO, chartCode+accountNumber, Message.TYPE_WARNING);
402                        }
403                            
404                        
405                    }
406                    else {
407                        // the account does have an expiration date.
408                        // This is the only case in which we might go
409                        // on for another iteration of the loop.
410                        checkedAccountNumbers.add(chartCode + accountNumber);
411    
412                        // Check that the account has not expired.
413                        // If the account has expired go around for another iteration.
414                        if (isAccountExpired(continuationAccount, universityRunDate)) {
415                            chartCode = continuationAccount.getContinuationFinChrtOfAcctCd();
416                            accountNumber = continuationAccount.getContinuationAccountNumber();
417                        }
418                        else {
419                            workingEntry.setAccountNumber(accountNumber);
420                            workingEntry.setChartOfAccountsCode(chartCode);
421                            
422                            // to set subAccount with dashes
423                            continuationAccountIndicator = true;
424                            //workingEntry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
425                            //workingEntry.setTransactionLedgerEntryDescription(kualiConfigurationService.getPropertyString(KFSKeyConstants.MSG_AUTO_FORWARD) + originEntry.getChartOfAccountsCode() + originEntry.getAccountNumber() + originEntry.getTransactionLedgerEntryDescription());
426                            // TODO:- use messageBuilder and KeyConstant - also, length issue!?!??
427                            workingEntry.setTransactionLedgerEntryDescription("AUTO FR " + originEntry.getChartOfAccountsCode() + originEntry.getAccountNumber() + originEntry.getTransactionLedgerEntryDescription());
428                            // FSKD-310 : need to check the account is closed for building message. if not, it is expired.
429                            if (!originEntryAccount.isActive()){
430                                return MessageBuilder.buildMessage(KFSKeyConstants.MSG_ACCOUNT_CLOSED_TO, chartCode+accountNumber, Message.TYPE_WARNING);
431                            } else {
432                                return MessageBuilder.buildMessage(KFSKeyConstants.MSG_ACCOUNT_EXPIRED_TO, chartCode+accountNumber, Message.TYPE_WARNING);
433                            }
434                        }
435                    }
436                }
437            }
438    
439            // We failed to find a valid continuation account.
440            return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_CONTINUATION_ACCOUNT_LIMIT_REACHED, Message.TYPE_FATAL);
441        }
442    
443        /**
444         * Calculates the expiration date of an adjusted account
445         * 
446         * @param account the expired account
447         * @return the timestamp of the adjusted date
448         */
449        protected long getAdjustedAccountExpirationDate(Account account) {
450            long offsetAccountExpirationTime = account.getAccountExpirationDate().getTime();
451    
452            if (account.isForContractsAndGrants() && (account.isActive())) {
453    
454                String daysOffset = parameterService.getParameterValue(ScrubberStep.class, KFSConstants.SystemGroupParameterNames.GL_SCRUBBER_VALIDATION_DAYS_OFFSET);
455                int daysOffsetInt = 0; // default to 0
456    
457                if (!org.apache.commons.lang.StringUtils.isBlank(daysOffset)) {
458                    daysOffsetInt = new Integer(daysOffset).intValue();
459                }
460    
461                Calendar tempCal = Calendar.getInstance();
462                tempCal.setTimeInMillis(offsetAccountExpirationTime);
463                tempCal.add(Calendar.DAY_OF_MONTH, daysOffsetInt);
464                offsetAccountExpirationTime = tempCal.getTimeInMillis();
465            }
466    
467            return offsetAccountExpirationTime;
468        }
469    
470        /**
471         * Validates the reversal date of the origin entry
472         * 
473         * @param originEntry the origin entry being scrubbed
474         * @param workingEntry the scrubbed version of the origin entry
475         * @return a Message if an error was encountered, otherwise null
476         */
477        protected Message validateReversalDate(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, AccountingCycleCachingService accountingCycleCachingService) {
478            LOG.debug("validateReversalDate() started");
479    
480            if (originEntry.getFinancialDocumentReversalDate() != null) {
481    //            UniversityDate universityDate = universityDateDao.getByPrimaryKey(originEntry.getFinancialDocumentReversalDate());
482                UniversityDate universityDate = accountingCycleCachingService.getUniversityDate(originEntry.getFinancialDocumentReversalDate());
483                if (universityDate == null) {
484                    Date reversalDate = originEntry.getFinancialDocumentReversalDate();
485                    SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT_STRING);
486                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_REVERSAL_DATE_NOT_FOUND, format.format(reversalDate), Message.TYPE_FATAL);
487                }
488                else {
489                    workingEntry.setFinancialDocumentReversalDate(originEntry.getFinancialDocumentReversalDate());
490                }
491            }
492            return null;
493        }
494    
495        /**
496         * Validates the sub account of the origin entry
497         * 
498         * @param originEntry the origin entry being scrubbed
499         * @param workingEntry the scrubbed version of the origin entry
500         * @return a Message if an error was encountered, otherwise null
501         */
502        protected Message validateSubAccount(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, AccountingCycleCachingService accountingCycleCachingService) {
503            LOG.debug("validateSubAccount() started");
504    
505            // when continuationAccount used, the subAccountNumber should be changed to dashes and skip validation subAccount process
506            if (continuationAccountIndicator) {
507                workingEntry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
508                return null;
509            }
510                    
511            // If the sub account number is empty, set it to dashes.
512            // Otherwise set the workingEntry sub account number to the
513            // sub account number of the input origin entry.
514            String subAccount = originEntry.getSubAccountNumber();
515            if (StringUtils.hasText(subAccount)) {
516                // sub account IS specified
517                // check if need upper case
518                DataDictionaryService dataDictionaryService = SpringContext.getBean(DataDictionaryService.class);
519                // uppercase the data used to generate the collector header
520                if (dataDictionaryService.getAttributeForceUppercase(SubAccount.class, KFSPropertyConstants.SUB_ACCOUNT_NUMBER)) {
521                    subAccount = originEntry.getSubAccountNumber().toUpperCase();
522                } 
523    
524                if (!KFSConstants.getDashSubAccountNumber().equals(subAccount)) {
525                  SubAccount originEntrySubAccount = accountingCycleCachingService.getSubAccount(originEntry.getChartOfAccountsCode(), originEntry.getAccountNumber(), subAccount);
526                  //SubAccount originEntrySubAccount = getSubAccount(originEntry);
527                    if (originEntrySubAccount == null) {
528                        
529                         // sub account is not valid
530                        return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_SUB_ACCOUNT_NOT_FOUND, originEntry.getChartOfAccountsCode() + "-" + originEntry.getAccountNumber() + "-" + subAccount, Message.TYPE_FATAL);
531                    }
532                    else {
533                        // sub account IS valid
534                        if (originEntrySubAccount.isActive()) {
535                            // sub account IS active
536                            workingEntry.setSubAccountNumber(subAccount);
537                        }
538                        else {
539                            // sub account IS NOT active
540                            if (parameterService.getParameterValue(KfsParameterConstants.GENERAL_LEDGER_BATCH.class, KFSConstants.SystemGroupParameterNames.GL_ANNUAL_CLOSING_DOC_TYPE).equals(originEntry.getFinancialDocumentTypeCode())) {
541                                // document IS annual closing
542                                workingEntry.setSubAccountNumber(subAccount);
543                            }
544                            else {
545                                // document is NOT annual closing
546                                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_SUB_ACCOUNT_NOT_ACTIVE, originEntry.getChartOfAccountsCode() + "-" + originEntry.getAccountNumber() + "-" + subAccount, Message.TYPE_FATAL);
547                            }
548                        }
549                    }
550                }
551                else {
552                    // the sub account is dashes
553                    workingEntry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
554                }
555            }
556            else {
557                // No sub account is specified.
558                workingEntry.setSubAccountNumber(KFSConstants.getDashSubAccountNumber());
559            }
560            
561            
562            return null;
563            
564        }
565    
566        /**
567         * Validates the project code of the origin entry
568         * 
569         * @param originEntry the origin entry being scrubbed
570         * @param workingEntry the scrubbed version of the origin entry
571         * @return a Message if an error was encountered, otherwise null
572         */
573        protected Message validateProjectCode(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, AccountingCycleCachingService accountingCycleCachingService) {
574            LOG.debug("validateProjectCode() started");
575    
576            if (StringUtils.hasText(originEntry.getProjectCode()) && !KFSConstants.getDashProjectCode().equals(originEntry.getProjectCode())) {
577                ProjectCode originEntryProject = accountingCycleCachingService.getProjectCode(originEntry.getProjectCode());
578                if (originEntryProject == null) {
579                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_PROJECT_CODE_NOT_FOUND, originEntry.getProjectCode(), Message.TYPE_FATAL);
580                }
581                else {
582                    if (originEntryProject.isActive()) {
583                        workingEntry.setProjectCode(originEntry.getProjectCode());
584                    }
585                    else {
586                        return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_PROJECT_CODE_MUST_BE_ACTIVE, originEntry.getProjectCode(), Message.TYPE_FATAL);
587                    }
588                }
589            }
590            else {
591                workingEntry.setProjectCode(KFSConstants.getDashProjectCode());
592            }
593    
594            return null;
595        }
596    
597        /**
598         * Validates the fiscal year of the origin entry
599         * 
600         * @param originEntry the origin entry being scrubbed
601         * @param workingEntry the scrubbed version of the origin entry
602         * @param universityRunDate the university date when this scrubber process is being run
603         * @return a Message if an error was encountered, otherwise null
604         */
605        protected Message validateFiscalYear(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, UniversityDate universityRunDate, AccountingCycleCachingService accountingCycleCachingService) {
606            LOG.debug("validateFiscalYear() started");
607    
608            if ((originEntry.getUniversityFiscalYear() == null) || (originEntry.getUniversityFiscalYear().intValue() == 0)) {
609                //commented out for KULLAB-627 
610                //if (!originEntry.getFinancialBalanceTypeCode().equals(KFSConstants.BALANCE_TYPE_A21)){
611                
612                    workingEntry.setUniversityFiscalYear(universityRunDate.getUniversityFiscalYear());
613                    workingEntry.setUniversityFiscalPeriodCode(universityRunDate.getUniversityFiscalAccountingPeriod());
614                    
615                    // TODO:- to display updated values on report 
616                    // TODO:- need to check because below two lines are commented out in validateUniversityFiscalPeriodCode 
617                    originEntry.setUniversityFiscalYear(universityRunDate.getUniversityFiscalYear());
618                    originEntry.setUniversityFiscalPeriodCode(universityRunDate.getUniversityFiscalAccountingPeriod());
619    
620            }
621            else {
622                workingEntry.setUniversityFiscalYear(originEntry.getUniversityFiscalYear());
623            }
624    
625            SystemOptions originEntryOption = accountingCycleCachingService.getSystemOptions(workingEntry.getUniversityFiscalYear());
626            if (originEntryOption == null) {
627                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_UNIV_FISCAL_YR_NOT_FOUND, originEntry.getUniversityFiscalYear() + "", Message.TYPE_FATAL);
628            }
629            return null;
630        }
631    
632        /**
633         * Validates the transaction date of the origin entry, make sure it is a valid university date
634         * 
635         * @param originEntry the origin entry being scrubbed
636         * @param workingEntry the scrubbed version of the origin entry
637         * @param universityRunDate the university date when this scrubber process is being run
638         * @return a Message if an error was encountered, otherwise null
639         */
640        protected Message validateTransactionDate(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, UniversityDate universityRunDate, AccountingCycleCachingService accountingCycleCachingService) {
641            LOG.debug("validateTransactionDate() started");
642            Date transactionDate = new Date(universityRunDate.getUniversityDate().getTime());
643            if (originEntry.getTransactionDate() == null) {
644                // Set the transaction date to the run date.
645                originEntry.setTransactionDate(transactionDate);
646                workingEntry.setTransactionDate(transactionDate);
647            }
648            else {
649                workingEntry.setTransactionDate(originEntry.getTransactionDate());
650            }
651    
652            // Next, we have to validate the transaction date against the university date table.
653            if (accountingCycleCachingService.getUniversityDate(originEntry.getTransactionDate()) == null) {
654                //FSKD-193, KFSMI-5441
655                //return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_TRANSACTION_DATE_INVALID, originEntry.getTransactionDate().toString(), Message.TYPE_FATAL);
656                originEntry.setTransactionDate(transactionDate);
657                workingEntry.setTransactionDate(transactionDate);
658            }
659            return null;
660        }
661    
662        /**
663         * Validates the document type of an origin entry 
664         * @param originEntry the origin entry to check
665         * @param workingEntryInfo the copy of that entry to move good data over to
666         * @return a Message if the document type is invalid, otherwise if valid, null
667         */
668        protected Message validateDocumentType(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, AccountingCycleCachingService accountingCycleCachingService) {
669            LOG.debug("validateDocumentType() started");
670            if ((originEntry.getFinancialDocumentTypeCode() == null) || !accountingCycleCachingService.isCurrentActiveAccountingDocumentType(originEntry.getFinancialDocumentTypeCode())) {
671                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_DOCUMENT_TYPE_NOT_FOUND, originEntry.getFinancialDocumentTypeCode(), Message.TYPE_FATAL);
672            }
673            workingEntry.setFinancialDocumentTypeCode(originEntry.getFinancialDocumentTypeCode());
674            return null;
675        }
676    
677        /**
678         * Validates the origination code of the origin entry
679         * 
680         * @param originEntry the origin entry being scrubbed
681         * @param workingEntry the scrubbed version of the origin entry
682         * @return a Message if an error was encountered, otherwise null
683         */
684        protected Message validateOrigination(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, AccountingCycleCachingService accountingCycleCachingService) {
685            LOG.debug("validateOrigination() started");
686    
687            if (StringUtils.hasText(originEntry.getFinancialSystemOriginationCode())) {
688                OriginationCode originEntryOrigination = accountingCycleCachingService.getOriginationCode(originEntry.getFinancialSystemOriginationCode());
689                if (originEntryOrigination == null) {
690                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_ORIGIN_CODE_NOT_FOUND, originEntry.getFinancialSystemOriginationCode(), Message.TYPE_FATAL);
691                }
692                if (!originEntryOrigination.isActive()) {
693                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_ORIGIN_CODE_NOT_ACTIVE, originEntry.getFinancialSystemOriginationCode(), Message.TYPE_FATAL);
694                }
695                
696                workingEntry.setFinancialSystemOriginationCode(originEntry.getFinancialSystemOriginationCode());
697            }
698            else {
699                return new Message(kualiConfigurationService.getPropertyString(KFSKeyConstants.ERROR_ORIGIN_CODE_NOT_FOUND) + " (" + originEntry.getFinancialSystemOriginationCode() + ")", Message.TYPE_FATAL);
700            }
701            return null;
702        }
703        
704        
705        protected Message validateReferenceOrigination(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, AccountingCycleCachingService accountingCycleCachingService) {
706            LOG.debug("validateOrigination() started");
707            String referenceFinancialSystemOriginationCode = originEntry.getReferenceFinancialSystemOriginationCode();
708            if (StringUtils.hasText(referenceFinancialSystemOriginationCode)) {
709                OriginationCode originEntryOrigination = accountingCycleCachingService.getOriginationCode(referenceFinancialSystemOriginationCode);
710                if (originEntryOrigination == null) {
711                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_REFERENCE_ORIGIN_CODE_NOT_FOUND, " (" + referenceFinancialSystemOriginationCode + ")", Message.TYPE_FATAL);
712                }
713                else {
714                    workingEntry.setReferenceFinancialSystemOriginationCode(referenceFinancialSystemOriginationCode);
715                }
716            }
717            
718            return null;
719        }
720        
721    
722        /**
723         * Validates the document number of the origin entry
724         * 
725         * @param originEntry the origin entry being scrubbed
726         * @param workingEntry the scrubbed version of the origin entry
727         * @return a Message if an error was encountered, otherwise null
728         */
729        protected Message validateDocumentNumber(OriginEntryInformation originEntry, OriginEntryInformation workingEntry) {
730            LOG.debug("validateDocumentNumber() started");
731    
732            if (!StringUtils.hasText(originEntry.getDocumentNumber())) {
733                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_DOCUMENT_NUMBER_REQUIRED, Message.TYPE_FATAL);
734            }
735            else {
736                workingEntry.setDocumentNumber(originEntry.getDocumentNumber());
737                return null;
738            }
739        }
740    
741        /**
742         * Validates the chart of the origin entry
743         * 
744         * @param originEntry the origin entry being scrubbed
745         * @param workingEntry the scrubbed version of the origin entry
746         * @return a Message if an error was encountered, otherwise null
747         */
748        protected Message validateChart(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, AccountingCycleCachingService accountingCycleCachingService) {
749            LOG.debug("validateChart() started");
750    
751            Chart originEntryChart = accountingCycleCachingService.getChart(originEntry.getChartOfAccountsCode());
752            if (originEntryChart == null) {
753                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_CHART_NOT_FOUND, originEntry.getChartOfAccountsCode(), Message.TYPE_FATAL);
754            }
755    
756            if (!originEntryChart.isActive()) {
757                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_CHART_NOT_ACTIVE, originEntry.getChartOfAccountsCode(), Message.TYPE_FATAL);
758            }
759    
760            workingEntry.setChartOfAccountsCode(originEntry.getChartOfAccountsCode());
761            return null;
762            
763        }
764    
765        /**
766         * Validates the object code of the origin entry
767         * 
768         * @param originEntry the origin entry being scrubbed
769         * @param workingEntry the scrubbed version of the origin entry
770         * @return a Message if an error was encountered, otherwise null
771         */
772        protected Message validateObjectCode(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, AccountingCycleCachingService accountingCycleCachingService) {
773            LOG.debug("validateObjectCode() started");
774    
775            if (!StringUtils.hasText(originEntry.getFinancialObjectCode())) {
776                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_OBJECT_CODE_EMPTY, Message.TYPE_FATAL);
777            }
778    
779            // We're checking the object code based on the year & chart from the working entry.
780            workingEntry.setFinancialObjectCode(originEntry.getFinancialObjectCode());
781    
782            // the fiscal year can be blank in originEntry, but we're assuming that the year attribute is populated by the validate year
783            // method
784            ObjectCode workingEntryFinancialObject = accountingCycleCachingService.getObjectCode(workingEntry.getUniversityFiscalYear(), workingEntry.getChartOfAccountsCode(), workingEntry.getFinancialObjectCode());
785            if (workingEntryFinancialObject == null) {
786                String objectCodeString = workingEntry.getUniversityFiscalYear() + "-" + workingEntry.getChartOfAccountsCode() + "-" + workingEntry.getFinancialObjectCode();
787                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_OBJECT_CODE_NOT_FOUND, objectCodeString, Message.TYPE_FATAL);
788            }
789            
790            if (!workingEntryFinancialObject.isActive()) {
791                String objectCodeString = workingEntry.getUniversityFiscalYear() + "-" + workingEntry.getChartOfAccountsCode() + "-" + workingEntry.getFinancialObjectCode();
792                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_OBJECT_CODE_NOT_ACTIVE, objectCodeString, Message.TYPE_FATAL);
793            }
794    
795            //TODO:- need to commented back after using file --> ?? 
796            // changed OriginEntryInformation to OriginEntryFull in  ScrubberProcess line 537 (after getting entry from file) 
797            //((OriginEntryFull)workingEntry).setFinancialObject(workingEntryFinancialObject);
798            //((OriginEntryFull)originEntry).setFinancialObject(workingEntryFinancialObject);
799            
800            return null;
801        }
802    
803        /**
804         * Assuming that the object code has been validated first, validates the object type of the entry
805         * 
806         * @param originEntry the origin entry being scrubbed
807         * @param workingEntry the scrubbed version of the origin entry
808         * @return a Message if an error was encountered, otherwise null
809         * @see org.kuali.module.gl.service.ScrubberValidator#validateObjectType(org.kuali.module.gl.bo.OriginEntryFull,
810         *      org.kuali.module.gl.bo.OriginEntryFull)
811         */
812        protected Message validateObjectType(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, AccountingCycleCachingService accountingCycleCachingService) {
813            LOG.debug("validateObjectType() started");
814    
815            if (!StringUtils.hasText(originEntry.getFinancialObjectTypeCode())) {
816                // If not specified, use the object type from the object code
817                ObjectCode workingEntryFinancialObject = accountingCycleCachingService.getObjectCode(workingEntry.getUniversityFiscalYear(), workingEntry.getChartOfAccountsCode(), workingEntry.getFinancialObjectCode());
818                workingEntry.setFinancialObjectTypeCode(workingEntryFinancialObject.getFinancialObjectTypeCode());
819            }
820            else {
821                workingEntry.setFinancialObjectTypeCode(originEntry.getFinancialObjectTypeCode());
822            }
823    
824            ObjectType workingEntryObjectType = accountingCycleCachingService.getObjectType(workingEntry.getFinancialObjectTypeCode());
825            if (workingEntryObjectType == null) {
826                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_OBJECT_TYPE_NOT_FOUND, originEntry.getFinancialObjectTypeCode(), Message.TYPE_FATAL);
827            }
828            
829            if (!workingEntryObjectType.isActive()) {
830                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_OBJECT_TYPE_NOT_ACTIVE, originEntry.getFinancialObjectTypeCode(), Message.TYPE_FATAL);
831            }
832            return null;
833        }
834    
835        /**
836         * Validates the sub object code of the origin entry
837         * 
838         * @param originEntry the origin entry being scrubbed
839         * @param workingEntry the scrubbed version of the origin entry
840         * @return a Message if an error was encountered, otherwise null
841         */
842        protected Message validateSubObjectCode(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, AccountingCycleCachingService accountingCycleCachingService) {
843            LOG.debug("validateFinancialSubObjectCode() started");
844    
845            if (!StringUtils.hasText(originEntry.getFinancialSubObjectCode())) {
846                workingEntry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
847                return null;
848            }
849    
850            if (!KFSConstants.getDashFinancialSubObjectCode().equals(originEntry.getFinancialSubObjectCode())) {
851                SubObjectCode originEntrySubObject = accountingCycleCachingService.getSubObjectCode(originEntry.getUniversityFiscalYear(), originEntry.getChartOfAccountsCode(), originEntry.getAccountNumber(), originEntry.getFinancialObjectCode(), originEntry.getFinancialSubObjectCode());
852                if (originEntrySubObject != null) {
853                    // Exists
854                    if (!originEntrySubObject.isActive()) {
855                        // if NOT active, set it to dashes
856                        workingEntry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
857                        return null;
858                    }
859                }
860                else {
861                    // Doesn't exist
862                    workingEntry.setFinancialSubObjectCode(KFSConstants.getDashFinancialSubObjectCode());
863                    return null;
864                }
865            }
866            workingEntry.setFinancialSubObjectCode(originEntry.getFinancialSubObjectCode());
867            return null;
868        }
869    
870        /**
871         * Validates the balance type of the origin entry
872         * 
873         * @param originEntry the origin entry being scrubbed
874         * @param workingEntry the scrubbed version of the origin entry
875         * @return a Message if an error was encountered, otherwise null
876         */
877        protected Message validateBalanceType(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, AccountingCycleCachingService accountingCycleCachingService) {
878            LOG.debug("validateBalanceType() started");
879            
880            // balance type IS NOT empty
881            String balanceTypeCode = originEntry.getFinancialBalanceTypeCode();
882            if (StringUtils.hasText(balanceTypeCode)) {
883                BalanceType originEntryBalanceType = accountingCycleCachingService.getBalanceType(originEntry.getFinancialBalanceTypeCode());
884                if (originEntryBalanceType == null) {
885                    // balance type IS NOT valid
886                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_BALANCE_TYPE_NOT_FOUND, " (" + balanceTypeCode + ")", Message.TYPE_FATAL);
887                
888                } else if (!originEntryBalanceType.isActive()) {
889                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_BALANCE_TYPE_NOT_ACTIVE, balanceTypeCode, Message.TYPE_FATAL);
890                } else {
891                    // balance type IS valid
892                    if (originEntryBalanceType.isFinancialOffsetGenerationIndicator()) {
893                        // entry IS an offset
894                        if (originEntry.getTransactionLedgerEntryAmount().isNegative()) {
895                            // it's an INVALID non-budget transaction
896                            return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_TRANS_CANNOT_BE_NEGATIVE_IF_OFFSET, Message.TYPE_FATAL);
897                        }
898                        else {
899                            // it's a VALID non-budget transaction
900                            if (!originEntry.isCredit() && !originEntry.isDebit()) { // entries requiring an offset must be either a
901                                // debit or a credit
902                                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_DC_INDICATOR_MUST_BE_D_OR_C, originEntry.getTransactionDebitCreditCode(), Message.TYPE_FATAL);
903                            }
904                            else {
905                                workingEntry.setFinancialBalanceTypeCode(balanceTypeCode);
906                            }
907                        }
908                    }
909                    else {
910                        // entry IS NOT an offset, means it's a budget transaction
911                        if (StringUtils.hasText(originEntry.getTransactionDebitCreditCode())) {
912                            return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_DC_INDICATOR_MUST_BE_EMPTY, originEntry.getTransactionDebitCreditCode(), Message.TYPE_FATAL);
913                        }
914                        else {
915                            if (originEntry.isCredit() || originEntry.isDebit()) {
916                                // budget transactions must be neither debit nor credit
917                                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_DC_INDICATOR_MUST_BE_NEITHER_D_NOR_C, originEntry.getTransactionDebitCreditCode(), Message.TYPE_FATAL);
918                            }
919                            else {
920                                // it's a valid budget transaction
921                                workingEntry.setFinancialBalanceTypeCode(balanceTypeCode);
922                            }
923                        }
924                    }
925                }
926            }
927            else {
928                // balance type IS empty. We can't set it if the year isn't set
929                SystemOptions workingEntryOption = accountingCycleCachingService.getSystemOptions(workingEntry.getUniversityFiscalYear());
930    
931                if (workingEntryOption != null) {
932                    workingEntry.setFinancialBalanceTypeCode(workingEntryOption.getActualFinancialBalanceTypeCd());
933                }
934                else {
935                    //TODO:- need to change to use MessageBuilder
936                    return new Message("Unable to set balance type code when year is unknown: " + workingEntry.getUniversityFiscalYear(), Message.TYPE_FATAL);
937                }
938            }
939            return null;
940        }
941    
942        /**
943         * Validates the period code of the origin entry
944         * 
945         * @param originEntry the origin entry being scrubbed
946         * @param workingEntry the scrubbed version of the origin entry
947         * @param universityRunDate the university date when this scrubber process is being run
948         * @return a Message if an error was encountered, otherwise null
949         */
950        protected Message validateUniversityFiscalPeriodCode(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, UniversityDate universityRunDate, AccountingCycleCachingService accountingCycleCachingService) {
951            LOG.debug("validateUniversityFiscalPeriodCode() started");
952            
953            String periodCode = originEntry.getUniversityFiscalPeriodCode();
954            if (!StringUtils.hasText(periodCode)) {
955                if (universityRunDate.getAccountingPeriod().isOpen()) {
956                    workingEntry.setUniversityFiscalPeriodCode(universityRunDate.getUniversityFiscalAccountingPeriod());
957                    workingEntry.setUniversityFiscalYear(universityRunDate.getUniversityFiscalYear());                    
958                }
959                else {
960                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_ACCOUNTING_PERIOD_CLOSED, " (year " + universityRunDate.getUniversityFiscalYear() + ", period " + universityRunDate.getUniversityFiscalAccountingPeriod(), Message.TYPE_FATAL);
961                }
962            }
963            else {
964                AccountingPeriod originEntryAccountingPeriod = accountingCycleCachingService.getAccountingPeriod(originEntry.getUniversityFiscalYear(), originEntry.getUniversityFiscalPeriodCode());
965                if (originEntryAccountingPeriod == null) {
966                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_ACCOUNTING_PERIOD_NOT_FOUND, periodCode, Message.TYPE_FATAL);
967                }
968                else if (!originEntryAccountingPeriod.isActive()) {
969                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_ACCOUNTING_PERIOD_NOT_ACTIVE, periodCode, Message.TYPE_FATAL);
970                }
971    
972                workingEntry.setUniversityFiscalPeriodCode(periodCode);
973            }
974    
975            return null;
976        }
977    
978        /**
979         * If the encumbrance update code = R, ref doc number must exist, ref doc type must be valid and ref origin code must be valid.
980         * If encumbrance update code is not R, and ref doc number is empty, make sure ref doc number, ref doc type and ref origin code
981         * are null. If encumbrance update code is not R and the ref doc number has a value, ref doc type must be valid and ref origin
982         * code must be valid.
983         * 
984         * @param originEntry the origin entry to check
985         * @param workingEntryInfo the copy of the entry to move valid data into
986         * @return a Message if an error was encountered, otherwise null
987         */
988        protected List<Message> validateReferenceDocumentFields(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, AccountingCycleCachingService accountingCycleCachingService) {
989            LOG.debug("validateReferenceDocument() started");
990    
991            // 3148 of cobol
992            
993            List<Message> errors = new ArrayList(); 
994            
995            boolean numberNullIndicator = !StringUtils.hasText(originEntry.getReferenceFinancialDocumentNumber());
996            boolean typeCodeNullIndicator = !StringUtils.hasText(originEntry.getReferenceFinancialDocumentTypeCode());
997            boolean originCodeNullIndicator = !StringUtils.hasText(originEntry.getReferenceFinancialSystemOriginationCode());
998    
999            //TODO:- do we need this?
1000            boolean editReference = true;
1001            if (numberNullIndicator) {
1002                workingEntry.setReferenceFinancialDocumentNumber(null);
1003                workingEntry.setReferenceFinancialDocumentTypeCode(null);
1004                workingEntry.setReferenceFinancialSystemOriginationCode(null);
1005    
1006                if (KFSConstants.ENCUMB_UPDT_REFERENCE_DOCUMENT_CD.equals(originEntry.getTransactionEncumbranceUpdateCode())) {
1007                    errors.add(MessageBuilder.buildMessage(KFSKeyConstants.ERROR_REF_DOC_NOT_BE_SPACE, Message.TYPE_FATAL));
1008                }
1009            }
1010             else {
1011                workingEntry.setReferenceFinancialDocumentNumber(originEntry.getReferenceFinancialDocumentNumber());
1012    
1013                if (!typeCodeNullIndicator){
1014                    if (accountingCycleCachingService.isCurrentActiveAccountingDocumentType(originEntry.getReferenceFinancialDocumentTypeCode())) {
1015                        workingEntry.setReferenceFinancialDocumentTypeCode(originEntry.getReferenceFinancialDocumentTypeCode());
1016                    }
1017                    else {
1018                        errors.add(MessageBuilder.buildMessage(KFSKeyConstants.ERROR_REFERENCE_DOCUMENT_TYPE_NOT_FOUND, originEntry.getReferenceFinancialDocumentTypeCode(), Message.TYPE_FATAL));
1019                    }
1020                } else {
1021                    errors.add(MessageBuilder.buildMessage(KFSKeyConstants.ERROR_REFERENCE_FIELDS, " " + KFSPropertyConstants.REFERENCE_FIN_DOCUMENT_TYPE_CODE  + " is missing.", Message.TYPE_FATAL));
1022                }
1023    
1024                if (!originCodeNullIndicator){
1025                    // Validate reference origin code
1026                    OriginationCode oc = accountingCycleCachingService.getOriginationCode(originEntry.getFinancialSystemOriginationCode());
1027                    if (oc != null) {
1028                        workingEntry.setReferenceFinancialSystemOriginationCode(originEntry.getReferenceFinancialSystemOriginationCode());
1029                    }
1030                    else {
1031                        errors.add(MessageBuilder.buildMessage(KFSKeyConstants.ERROR_REFERENCE_ORIGINATION_CODE_NOT_FOUND, " (" + originEntry.getReferenceFinancialSystemOriginationCode() + ")", Message.TYPE_FATAL));
1032                    }
1033                } else {
1034                    errors.add(MessageBuilder.buildMessage(KFSKeyConstants.ERROR_REFERENCE_FIELDS, " " + KFSPropertyConstants.REFERENCE_FINANCIAL_SYSTEM_ORIGINATION_CODE + " is missing.", Message.TYPE_FATAL));
1035                }
1036            }
1037    
1038            BalanceType workingEntryBalanceType = accountingCycleCachingService.getBalanceType(workingEntry.getFinancialBalanceTypeCode());
1039    
1040            ObjectType workingEntryObjectType = accountingCycleCachingService.getObjectType(workingEntry.getFinancialObjectTypeCode());
1041    
1042            if (workingEntryBalanceType == null || workingEntryObjectType == null) {
1043                // We are unable to check this because the balance type or object type is invalid.
1044                // It would be nice if we could still validate the entry, but we can't.
1045                return errors;
1046            }
1047    
1048            if (workingEntryBalanceType.isFinBalanceTypeEncumIndicator() && !workingEntryObjectType.isFundBalanceIndicator()) {
1049                if ((KFSConstants.ENCUMB_UPDT_DOCUMENT_CD.equals(originEntry.getTransactionEncumbranceUpdateCode())) || (KFSConstants.ENCUMB_UPDT_NO_ENCUMBRANCE_CD.equals(originEntry.getTransactionEncumbranceUpdateCode())) || (KFSConstants.ENCUMB_UPDT_REFERENCE_DOCUMENT_CD.equals(originEntry.getTransactionEncumbranceUpdateCode()))) {
1050                    workingEntry.setTransactionEncumbranceUpdateCode(originEntry.getTransactionEncumbranceUpdateCode());
1051                }
1052                else {
1053                    errors.add(MessageBuilder.buildMessage(KFSKeyConstants.ERROR_ENC_UPDATE_CODE_NOT_DRN, " (" + originEntry.getTransactionEncumbranceUpdateCode() + ")", Message.TYPE_FATAL));
1054                }
1055            }
1056            else {
1057                workingEntry.setTransactionEncumbranceUpdateCode(null);
1058            }
1059            return errors;
1060        }
1061    
1062        /**
1063         * Validates the entry's transaction amount
1064         * 
1065         * @param originEntry the origin entry being scrubbed
1066         * @param workingEntry the scrubbed version of the origin entry
1067         * @return a Message if an error was encountered, otherwise null
1068         */
1069        protected Message validateTransactionAmount(OriginEntryInformation originEntry, OriginEntryInformation workingEntry, AccountingCycleCachingService accountingCycleCachingService) {
1070            LOG.debug("validateTransactionAmount() started");
1071    
1072            KualiDecimal amount = originEntry.getTransactionLedgerEntryAmount();
1073            BalanceType originEntryBalanceType = accountingCycleCachingService.getBalanceType(originEntry.getFinancialBalanceTypeCode());
1074    
1075            if (originEntryBalanceType == null) {
1076                // We can't validate the amount without a balance type code
1077                return null;
1078            }
1079    
1080            if (originEntryBalanceType.isFinancialOffsetGenerationIndicator()) {
1081                if (amount.isPositive() || amount.isZero()) {
1082                    workingEntry.setTransactionLedgerEntryAmount(originEntry.getTransactionLedgerEntryAmount());
1083                }
1084                else {
1085                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_NEGATIVE_AMOUNT, amount.toString(), Message.TYPE_FATAL);
1086                }
1087                if (StringHelper.isEmpty(originEntry.getTransactionDebitCreditCode())) {
1088                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_DEBIT_CREDIT_INDICATOR_NEITHER_D_NOR_C, originEntry.getTransactionDebitCreditCode(), Message.TYPE_FATAL);
1089                }
1090                if (ObjectHelper.isOneOf(originEntry.getTransactionDebitCreditCode(), debitOrCredit)) {
1091                    workingEntry.setTransactionDebitCreditCode(originEntry.getTransactionDebitCreditCode());
1092                }
1093                else {
1094                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_DEBIT_CREDIT_INDICATOR_NEITHER_D_NOR_C, originEntry.getTransactionDebitCreditCode(), Message.TYPE_FATAL);
1095                }
1096            }
1097            else {
1098                if ((originEntry.getTransactionDebitCreditCode() == null) || (" ".equals(originEntry.getTransactionDebitCreditCode())) || ("".equals(originEntry.getTransactionDebitCreditCode()))) {
1099                    workingEntry.setTransactionDebitCreditCode(KFSConstants.GL_BUDGET_CODE);
1100                }
1101                else {
1102                    return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_DEBIT_CREDIT_INDICATOR_MUST_BE_SPACE, originEntry.getTransactionDebitCreditCode(), Message.TYPE_FATAL);
1103                }
1104            }
1105            return null;
1106        }
1107            
1108        protected Message validateDescription(OriginEntryInformation originEntry){
1109            
1110            if (originEntry.getTransactionLedgerEntryDescription().trim().equals(KFSConstants.EMPTY_STRING)){
1111                
1112                return MessageBuilder.buildMessage(KFSKeyConstants.ERROR_DESCRIPTION_CANNOT_BE_BLANK, Message.TYPE_FATAL);
1113            }
1114            
1115            return null;
1116        }
1117    
1118        /**
1119         * @see org.kuali.kfs.gl.service.ScrubberValidator#isAccountExpired(org.kuali.kfs.coa.businessobject.Account, org.kuali.kfs.sys.businessobject.UniversityDate)
1120         */
1121        public boolean isAccountExpired(Account account, UniversityDate universityRunDate) {
1122            if (account.getAccountExpirationDate() == null) {
1123                return false;
1124            }
1125            
1126            Calendar runCalendar = Calendar.getInstance();
1127            runCalendar.setTime(universityRunDate.getUniversityDate());
1128            
1129            Calendar expirationDate = Calendar.getInstance();
1130            long offsetAccountExpirationTime = getAdjustedAccountExpirationDate(account);
1131            expirationDate.setTimeInMillis(offsetAccountExpirationTime);
1132    
1133            int expirationYear = expirationDate.get(Calendar.YEAR);
1134            int runYear = runCalendar.get(Calendar.YEAR);
1135            int expirationDoy = expirationDate.get(Calendar.DAY_OF_YEAR);
1136            int runDoy = runCalendar.get(Calendar.DAY_OF_YEAR);
1137    
1138            return (expirationYear < runYear) || (expirationYear == runYear && expirationDoy < runDoy);
1139        }
1140    
1141        public void setUniversityDateDao(UniversityDateDao udd) {
1142            universityDateDao = udd;
1143        }
1144    
1145        public void setKualiConfigurationService(KualiConfigurationService service) {
1146            kualiConfigurationService = service;
1147        }
1148    
1149        public void setPersistenceService(PersistenceService ps) {
1150            persistenceService = ps;
1151        }
1152    
1153        public void setAccountService(AccountService as) {
1154            accountService = as;
1155        }
1156    
1157        public void setOriginationCodeService(OriginationCodeService ocs) {
1158            originationCodeService = ocs;
1159        }
1160    
1161        public void setPersistenceStructureService(PersistenceStructureService persistenceStructureService) {
1162            this.persistenceStructureService = persistenceStructureService;
1163        }
1164    
1165        public void setParameterService(ParameterService parameterService) {
1166            this.parameterService = parameterService;
1167        }
1168    
1169        public void setBalanceTypService(BalanceTypeService balanceTypService) {
1170            this.balanceTypService = balanceTypService;
1171        }
1172        
1173    }
1174