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