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