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