001/* 002 * Copyright 2005 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 */ 016package org.kuali.ole.fp.document; 017 018import static org.kuali.ole.fp.document.validation.impl.AuxiliaryVoucherDocumentRuleConstants.AUXILIARY_VOUCHER_ACCOUNTING_PERIOD_GRACE_PERIOD; 019import static org.kuali.ole.fp.document.validation.impl.AuxiliaryVoucherDocumentRuleConstants.GENERAL_LEDGER_PENDING_ENTRY_OFFSET_CODE; 020import static org.kuali.ole.sys.OLEConstants.GL_CREDIT_CODE; 021import static org.kuali.ole.sys.OLEConstants.GL_DEBIT_CODE; 022import static org.kuali.ole.sys.OLEConstants.AuxiliaryVoucher.ACCRUAL_DOC_TYPE; 023import static org.kuali.ole.sys.OLEConstants.AuxiliaryVoucher.ADJUSTMENT_DOC_TYPE; 024import static org.kuali.ole.sys.OLEConstants.AuxiliaryVoucher.RECODE_DOC_TYPE; 025 026import java.sql.Date; 027import java.util.Iterator; 028import java.util.List; 029 030import org.apache.commons.lang.StringUtils; 031import org.kuali.ole.coa.businessobject.AccountingPeriod; 032import org.kuali.ole.coa.service.AccountingPeriodService; 033import org.kuali.ole.gl.service.SufficientFundsService; 034import org.kuali.ole.sys.OLEConstants; 035import org.kuali.ole.sys.businessobject.AccountingLine; 036import org.kuali.ole.sys.businessobject.AccountingLineBase; 037import org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntry; 038import org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper; 039import org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntrySourceDetail; 040import org.kuali.ole.sys.businessobject.SourceAccountingLine; 041import org.kuali.ole.sys.businessobject.SystemOptions; 042import org.kuali.ole.sys.context.SpringContext; 043import org.kuali.ole.sys.document.AccountingDocumentBase; 044import org.kuali.ole.sys.document.AmountTotaling; 045import org.kuali.ole.sys.document.Correctable; 046import org.kuali.ole.sys.document.service.DebitDeterminerService; 047import org.kuali.ole.sys.service.OptionsService; 048import org.kuali.ole.sys.service.UniversityDateService; 049import org.kuali.rice.core.api.datetime.DateTimeService; 050import org.kuali.rice.core.api.util.type.KualiDecimal; 051import org.kuali.rice.coreservice.framework.parameter.ParameterService; 052import org.kuali.rice.kew.api.exception.WorkflowException; 053import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange; 054import org.kuali.rice.krad.document.Copyable; 055 056/** 057 * This is the business object that represents the AuxiliaryVoucherDocument in Kuali. This is a transactional document that will 058 * eventually post transactions to the G/L. It integrates with workflow and also contains two groupings of accounting lines: Expense 059 * and target. Expense is the expense and target is the income lines. 060 */ 061public class AuxiliaryVoucherDocument extends AccountingDocumentBase implements VoucherDocument, Copyable, Correctable, AmountTotaling { 062 protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AuxiliaryVoucherDocument.class); 063 064 protected String typeCode = ADJUSTMENT_DOC_TYPE; 065 protected java.sql.Date reversalDate; 066 067 /** 068 * @see org.kuali.ole.sys.document.AccountingDocumentBase#documentPerformsSufficientFundsCheck() 069 */ 070 @Override 071 public boolean documentPerformsSufficientFundsCheck() { 072 if (isRecodeType()) { 073 return super.documentPerformsSufficientFundsCheck(); 074 } 075 else { 076 return false; 077 } 078 } 079 080 /** 081 * Initializes the array lists and some basic info. 082 */ 083 public AuxiliaryVoucherDocument() { 084 super(); 085 } 086 087 /** 088 * Read Accessor for Reversal Date 089 * 090 * @return java.sql.Date 091 */ 092 public java.sql.Date getReversalDate() { 093 return reversalDate; 094 } 095 096 /** 097 * Write Accessor for Reversal Date 098 * 099 * @param reversalDate 100 */ 101 public void setReversalDate(java.sql.Date reversalDate) { 102 this.reversalDate = reversalDate; 103 } 104 105 /** 106 * Read Accessor for Auxiliary Voucher Type 107 * 108 * @return String 109 */ 110 public String getTypeCode() { 111 return typeCode; 112 } 113 114 /** 115 * Write Accessor for Auxiliary Voucher Type 116 * 117 * @param typeCode 118 */ 119 public void setTypeCode(String typeCode) { 120 this.typeCode = typeCode; 121 } 122 123 /** 124 * A helper to test whether this document is an adjustment type AV. 125 * 126 * @return boolean 127 */ 128 public boolean isAdjustmentType() { 129 return ADJUSTMENT_DOC_TYPE.equals(typeCode); 130 } 131 132 /** 133 * A helper to test whether this document is an recode type AV. 134 * 135 * @return boolean 136 */ 137 public boolean isRecodeType() { 138 return RECODE_DOC_TYPE.equals(typeCode); 139 } 140 141 /** 142 * A helper to test whether this document is an accrual type AV. 143 * 144 * @return boolean 145 */ 146 public boolean isAccrualType() { 147 return ACCRUAL_DOC_TYPE.equals(typeCode); 148 } 149 150 /** 151 * This method calculates the debit total for a JV document keying off of the debit/debit code, only summing the accounting 152 * lines with a debitDebitCode that matched the debit constant, and returns the results. 153 * 154 * @return KualiDecimal 155 */ 156 public KualiDecimal getDebitTotal() { 157 KualiDecimal debitTotal = KualiDecimal.ZERO; 158 AccountingLineBase al = null; 159 Iterator<?> iter = sourceAccountingLines.iterator(); 160 while (iter.hasNext()) { 161 al = (AccountingLineBase) iter.next(); 162 if (StringUtils.isNotBlank(al.getDebitCreditCode()) && al.getDebitCreditCode().equals(OLEConstants.GL_DEBIT_CODE)) { 163 debitTotal = debitTotal.add(al.getAmount()); 164 } 165 } 166 return debitTotal; 167 } 168 169 /** 170 * This method calculates the credit total for a JV document keying off of the debit/credit code, only summing the accounting 171 * lines with a debitCreditCode that matched the debit constant, and returns the results. 172 * 173 * @return KualiDecimal 174 */ 175 public KualiDecimal getCreditTotal() { 176 KualiDecimal creditTotal = KualiDecimal.ZERO; 177 AccountingLineBase al = null; 178 Iterator<?> iter = sourceAccountingLines.iterator(); 179 while (iter.hasNext()) { 180 al = (AccountingLineBase) iter.next(); 181 if (StringUtils.isNotBlank(al.getDebitCreditCode()) && al.getDebitCreditCode().equals(OLEConstants.GL_CREDIT_CODE)) { 182 creditTotal = creditTotal.add(al.getAmount()); 183 } 184 } 185 return creditTotal; 186 } 187 188 /** 189 * Same as default implementation but uses debit / credit totals instead. Meaning it returns either credit or if 0, debit. 190 * 191 * @see org.kuali.ole.sys.document.AccountingDocumentBase#getTotalDollarAmount() 192 * @return KualiDecimal 193 */ 194 @Override 195 public KualiDecimal getTotalDollarAmount() { 196 return getCreditTotal().equals(KualiDecimal.ZERO) ? getDebitTotal() : getCreditTotal(); 197 } 198 199 /** 200 * @see org.kuali.ole.sys.document.AccountingDocumentBase#toCopy() 201 */ 202 @Override 203 public void toCopy() throws WorkflowException { 204 super.toCopy(); 205 refreshReversalDate(); 206 } 207 208 /** 209 * Overrides to call super and then change the reversal date if the type is accrual and the date is greater than the set 210 * reversal date. 211 */ 212 @Override 213 public void doRouteStatusChange(DocumentRouteStatusChange statusChangeEvent) { 214 LOG.debug("In doRouteStatusChange() for AV documents"); 215 super.doRouteStatusChange(statusChangeEvent); 216 217 if (this.getDocumentHeader().getWorkflowDocument().isProcessed()) { // only do this stuff if the document has been 218 // processed and approved 219 // update the reversal data accoringdingly 220 updateReversalDate(); 221 } 222 } 223 224 /** 225 * This method handles updating the reversal data on the document in addition to all of the GLPEs, but only for the accrual and 226 * recode types. 227 */ 228 protected void updateReversalDate() { 229 if (refreshReversalDate()) { 230 // set the reversal date on each GLPE for the document too 231 List<GeneralLedgerPendingEntry> glpes = getGeneralLedgerPendingEntries(); 232 for (GeneralLedgerPendingEntry entry : glpes) { 233 entry.setFinancialDocumentReversalDate(getReversalDate()); 234 } 235 } 236 } 237 238 /** 239 * If the reversal date on this document is in need of refreshing, refreshes the reveral date. THIS METHOD MAY CHANGE DOCUMENT STATE! 240 * @return true if the reversal date ended up getting refreshed, false otherwise 241 */ 242 protected boolean refreshReversalDate() { 243 boolean refreshed = false; 244 if ((isAccrualType() || isRecodeType()) && getReversalDate() != null) { 245 java.sql.Date today = SpringContext.getBean(DateTimeService.class).getCurrentSqlDateMidnight(); 246 if (getReversalDate().before(today)) { 247 // set the reversal date on the document 248 setReversalDate(today); 249 refreshed = true; 250 } 251 } 252 return refreshed; 253 } 254 255 /** 256 * @see org.kuali.ole.sys.document.AccountingDocumentBase#toErrorCorrection() 257 */ 258 @Override 259 public void toErrorCorrection() throws WorkflowException { 260 super.toErrorCorrection(); 261 processAuxiliaryVoucherErrorCorrections(); 262 } 263 264 /** 265 * KULEDOCS-1700 This method iterates over each source line and flip the sign on the amount to nullify the super's effect, then 266 * flip the debit/credit code b/c an error corrected AV flips the debit/credit code. 267 */ 268 protected void processAuxiliaryVoucherErrorCorrections() { 269 Iterator<?> i = getSourceAccountingLines().iterator(); 270 271 int index = 0; 272 while (i.hasNext()) { 273 SourceAccountingLine sLine = (SourceAccountingLine) i.next(); 274 275 String debitCreditCode = sLine.getDebitCreditCode(); 276 277 if (StringUtils.isNotBlank(debitCreditCode)) { 278 // negate the amount to to nullify the effects of the super, b/c super flipped it the first time through 279 sLine.setAmount(sLine.getAmount().negated()); // offsets the effect the super 280 281 // now just flip the debit/credit code 282 if (GL_DEBIT_CODE.equals(debitCreditCode)) { 283 sLine.setDebitCreditCode(GL_CREDIT_CODE); 284 } 285 else if (GL_CREDIT_CODE.equals(debitCreditCode)) { 286 sLine.setDebitCreditCode(GL_DEBIT_CODE); 287 } 288 else { 289 throw new IllegalStateException("SourceAccountingLine at index " + index + " does not have a debit/credit " + "code associated with it. This should never have occured. Please contact your system administrator."); 290 291 } 292 index++; 293 } 294 } 295 } 296 297 /** 298 * Returns true if an accounting line is a debit or credit The following are credits (return false) 299 * <ol> 300 * <li> debitCreditCode != 'D' 301 * </ol> 302 * the following are debits (return true) 303 * <ol> 304 * <li> debitCreditCode == 'D' 305 * </ol> 306 * the following are invalid ( throws an <code>IllegalStateException</code>) 307 * <ol> 308 * <li> debitCreditCode isBlank 309 * </ol> 310 * 311 * @param financialDocument submitted accounting document 312 * @param accounttingLine accounting line being tested if it is a debit or not 313 * @see org.kuali.rice.krad.rule.AccountingLineRule#isDebit(org.kuali.rice.krad.document.FinancialDocument, 314 * org.kuali.rice.krad.bo.AccountingLine) 315 */ 316 public boolean isDebit(GeneralLedgerPendingEntrySourceDetail postable) throws IllegalStateException { 317 String debitCreditCode = ((AccountingLine)postable).getDebitCreditCode(); 318 DebitDeterminerService isDebitUtils = SpringContext.getBean(DebitDeterminerService.class); 319 if (StringUtils.isBlank(debitCreditCode)) { 320 throw new IllegalStateException(isDebitUtils.getDebitCalculationIllegalStateExceptionMessage()); 321 } 322 return isDebitUtils.isDebitCode(debitCreditCode); 323 } 324 325 /** 326 * This method sets the appropriate document type and object type codes into the GLPEs based on the type of AV document chosen. 327 * 328 * @param document submitted AccountingDocument 329 * @param accountingLine represents accounting line where object type code is retrieved from 330 * @param explicitEntry GeneralPendingLedgerEntry object that has its document type, object type, period code, and fiscal year 331 * set 332 * @see FinancialDocumentRuleBase#customizeExplicitGeneralLedgerPendingEntry(FinancialDocument, AccountingLine, 333 * GeneralLedgerPendingEntry) 334 * @see org.kuali.module.financial.rules.FinancialDocumentRuleBase#processExplicitGeneralLedgerPendingEntry(org.kuali.rice.krad.document.FinancialDocument, 335 * org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper, org.kuali.rice.krad.bo.AccountingLine, 336 * org.kuali.module.gl.bo.GeneralLedgerPendingEntry) 337 */ 338 @Override 339 public void customizeExplicitGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySourceDetail postable, GeneralLedgerPendingEntry explicitEntry) { 340 341 java.sql.Date reversalDate = getReversalDate(); 342 if (reversalDate != null) { 343 explicitEntry.setFinancialDocumentReversalDate(reversalDate); 344 } 345 else { 346 explicitEntry.setFinancialDocumentReversalDate(null); 347 } 348 explicitEntry.setFinancialDocumentTypeCode(getTypeCode()); // make sure to use the accrual type as the document 349 // type 350 explicitEntry.setFinancialObjectTypeCode(getObjectTypeCode(postable)); 351 explicitEntry.setUniversityFiscalPeriodCode(getPostingPeriodCode()); // use chosen posting period code 352 explicitEntry.setUniversityFiscalYear(getPostingYear()); // use chosen posting year 353 } 354 355 /** 356 * Offset entries are created for recodes (AVRC) always, so this method is one of 2 offsets that get created for an AVRC. Its 357 * document type is set to DI. This uses the explicit entry as its model. In addition, an offset is generated for accruals 358 * (AVAE) and adjustments (AVAD), but only if the document contains accounting lines for more than one account. 359 * 360 * @param financialDocument submitted accounting document 361 * @param accountingLine accounting line from accounting document 362 * @param explicitEntry represents explicit entry 363 * @param offsetEntry represents offset entry 364 * @see org.kuali.module.financial.rules.FinancialDocumentRuleBase#customizeOffsetGeneralLedgerPendingEntry(org.kuali.rice.krad.document.FinancialDocument, 365 * org.kuali.rice.krad.bo.AccountingLine, org.kuali.module.gl.bo.GeneralLedgerPendingEntry, 366 * org.kuali.module.gl.bo.GeneralLedgerPendingEntry) 367 */ 368 @Override 369 public boolean customizeOffsetGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySourceDetail postable, GeneralLedgerPendingEntry explicitEntry, GeneralLedgerPendingEntry offsetEntry) { 370 // set the document type to that of a Distrib. Of Income and Expense if it's a recode 371 if (isRecodeType()) { 372 offsetEntry.setFinancialDocumentTypeCode(OLEConstants.FinancialDocumentTypeCodes.DISTRIBUTION_OF_INCOME_AND_EXPENSE); 373 374 // set the posting period 375 java.sql.Date today = SpringContext.getBean(DateTimeService.class).getCurrentSqlDateMidnight(); 376 offsetEntry.setUniversityFiscalPeriodCode(SpringContext.getBean(AccountingPeriodService.class).getByDate(today).getUniversityFiscalPeriodCode()); // use 377 } 378 379 // now set the offset entry to the specific offset object code for the AV generated offset fund balance; only if it's an 380 // accrual or adjustment 381 if (isAccrualType() || isAdjustmentType()) { 382 String glpeOffsetObjectCode = SpringContext.getBean(ParameterService.class).getParameterValueAsString(this.getClass(), getGeneralLedgerPendingEntryOffsetObjectCode()); 383 offsetEntry.setFinancialObjectCode(glpeOffsetObjectCode); 384 385 // set the posting period 386 offsetEntry.setUniversityFiscalPeriodCode(getPostingPeriodCode()); // use chosen posting period code 387 } 388 389 // set the reversal date to null 390 offsetEntry.setFinancialDocumentReversalDate(null); 391 392 // set the year to current 393 offsetEntry.setUniversityFiscalYear(getPostingYear()); // use chosen posting year 394 395 // although they are offsets, we need to set the offset indicator to false 396 offsetEntry.setTransactionEntryOffsetIndicator(false); 397 398 //KFSMI-798 - refreshNonUpdatableReferences() used instead of refresh(), 399 //GeneralLedgerPendingEntry does not have any updatable references 400 offsetEntry.refreshNonUpdateableReferences(); // may have changed foreign keys here; need accurate object code and account BOs at least 401 offsetEntry.setAcctSufficientFundsFinObjCd(SpringContext.getBean(SufficientFundsService.class).getSufficientFundsObjectCode(offsetEntry.getFinancialObject(), offsetEntry.getAccount().getAccountSufficientFundsCode())); 402 403 return true; 404 } 405 406 /** 407 * This method examines the accounting line passed in and returns the appropriate object type code. This rule converts specific 408 * objects types from an object code on an accounting line to more general values. This is specific to the AV document. 409 * 410 * @param line accounting line where object type code is retrieved from 411 * @return object type from a accounting line ((either financial object type code, financial object type not expenditure code, 412 * or financial object type income not cash code)) 413 */ 414 protected String getObjectTypeCode(GeneralLedgerPendingEntrySourceDetail line) { 415 SystemOptions options = SpringContext.getBean(OptionsService.class).getCurrentYearOptions(); 416 String objectTypeCode = line.getObjectCode().getFinancialObjectTypeCode(); 417 418 if (options.getFinObjTypeExpenditureexpCd().equals(objectTypeCode) || options.getFinObjTypeExpendNotExpCode().equals(objectTypeCode)) { 419 objectTypeCode = options.getFinObjTypeExpNotExpendCode(); 420 } 421 else if (options.getFinObjectTypeIncomecashCode().equals(objectTypeCode) || options.getFinObjTypeExpendNotExpCode().equals(objectTypeCode)) { 422 objectTypeCode = options.getFinObjTypeIncomeNotCashCd(); 423 } 424 425 return objectTypeCode; 426 } 427 428 /** 429 * Get from APC the offset object code that is used for the <code>{@link GeneralLedgerPendingEntry}</code> 430 * 431 * @return String returns GLPE parameter name 432 */ 433 protected String getGeneralLedgerPendingEntryOffsetObjectCode() { 434 return GENERAL_LEDGER_PENDING_ENTRY_OFFSET_CODE; 435 } 436 437 /** 438 * An Accrual Voucher only generates offsets if it is a recode (AVRC). So this method overrides to do nothing more than return 439 * true if it's not a recode. If it is a recode, then it is responsible for generating two offsets with a document type of DI. 440 * 441 * @param financialDocument submitted accounting document 442 * @param sequenceHelper helper class which will allows us to increment a reference without using an Integer 443 * @param accountingLineCopy accounting line from accounting document 444 * @param explicitEntry represents explicit entry 445 * @param offsetEntry represents offset entry 446 * @return true if general ledger pending entry is processed successfully for accurals, adjustments, and recodes 447 * @see org.kuali.module.financial.rules.FinancialDocumentRuleBase#processOffsetGeneralLedgerPendingEntry(org.kuali.rice.krad.document.FinancialDocument, 448 * org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper, org.kuali.rice.krad.bo.AccountingLine, 449 * org.kuali.module.gl.bo.GeneralLedgerPendingEntry, org.kuali.module.gl.bo.GeneralLedgerPendingEntry) 450 */ 451 @Override 452 public boolean processOffsetGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySequenceHelper sequenceHelper, GeneralLedgerPendingEntrySourceDetail glpeSourceDetail, GeneralLedgerPendingEntry explicitEntry, GeneralLedgerPendingEntry offsetEntry) { 453 AccountingLine accountingLineCopy = (AccountingLine)glpeSourceDetail; 454 boolean success = true; 455 456 // do not generate an offset entry if this is a normal or adjustment AV type 457 if (isAccrualType() || isAdjustmentType()) { 458 success &= processOffsetGeneralLedgerPendingEntryForAccrualsAndAdjustments(sequenceHelper, accountingLineCopy, explicitEntry, offsetEntry); 459 } 460 else if (isRecodeType()) { // recodes generate offsets 461 success &= processOffsetGeneralLedgerPendingEntryForRecodes(sequenceHelper, accountingLineCopy, explicitEntry, offsetEntry); 462 } 463 else { 464 throw new IllegalStateException("Illegal auxiliary voucher type: " + getTypeCode()); 465 } 466 return success; 467 } 468 469 /** 470 * This method handles generating or not generating the appropriate offsets if the AV type is a recode. 471 * 472 * @param financialDocument submitted accounting document 473 * @param sequenceHelper helper class which will allows us to increment a reference without using an Integer 474 * @param accountingLineCopy accounting line from accounting document 475 * @param explicitEntry represents explicit entry 476 * @param offsetEntry represents offset entry 477 * @return true if offset general ledger pending entry is processed 478 */ 479 protected boolean processOffsetGeneralLedgerPendingEntryForRecodes(GeneralLedgerPendingEntrySequenceHelper sequenceHelper, AccountingLine accountingLineCopy, GeneralLedgerPendingEntry explicitEntry, GeneralLedgerPendingEntry offsetEntry) { 480 // the explicit entry has already been generated and added to the list, so to get the right offset, we have to set the value 481 // of the document type code on the explicit 482 // to the type code for a DI document so that it gets passed into the next call and we retrieve the right offset definition 483 // since these offsets are 484 // specific to Distrib. of Income and Expense documents - we need to do a deep copy though so we don't do this by reference 485 GeneralLedgerPendingEntry explicitEntryDeepCopy = new GeneralLedgerPendingEntry(explicitEntry); 486 explicitEntryDeepCopy.setFinancialDocumentTypeCode(OLEConstants.FinancialDocumentTypeCodes.DISTRIBUTION_OF_INCOME_AND_EXPENSE); 487 488 // set the posting period to current, because DI GLPEs for recodes should post to the current period 489 java.sql.Date today = SpringContext.getBean(DateTimeService.class).getCurrentSqlDateMidnight(); 490 explicitEntryDeepCopy.setUniversityFiscalPeriodCode(SpringContext.getBean(AccountingPeriodService.class).getByDate(today).getUniversityFiscalPeriodCode()); // use 491 492 // call the super to process an offset entry; see the customize method below for AVRC specific attribute values 493 // pass in the explicit deep copy 494 boolean success = super.processOffsetGeneralLedgerPendingEntry(sequenceHelper, accountingLineCopy, explicitEntryDeepCopy, offsetEntry); 495 496 // increment the sequence appropriately 497 sequenceHelper.increment(); 498 499 // now generate the AVRC DI entry 500 // pass in the explicit deep copy 501 processAuxiliaryVoucherRecodeDistributionOfIncomeAndExpenseGeneralLedgerPendingEntry( sequenceHelper, explicitEntryDeepCopy); 502 return success; 503 } 504 505 /** 506 * This method handles generating or not generating the appropriate offsets if the AV type is accrual or adjustment. 507 * 508 * @param financialDocument submitted accounting document 509 * @param sequenceHelper helper class which will allows us to increment a reference without using an Integer 510 * @param accountingLineCopy accounting line from accounting document 511 * @param explicitEntry represents explicit entry 512 * @param offsetEntry represents offset entry 513 * @return true if offset general ledger pending entry is processed successfully 514 */ 515 protected boolean processOffsetGeneralLedgerPendingEntryForAccrualsAndAdjustments(GeneralLedgerPendingEntrySequenceHelper sequenceHelper, AccountingLine accountingLineCopy, GeneralLedgerPendingEntry explicitEntry, GeneralLedgerPendingEntry offsetEntry) { 516 boolean success = true; 517 if (this.isDocumentForMultipleAccounts()) { 518 success = super.processOffsetGeneralLedgerPendingEntry(sequenceHelper, accountingLineCopy, explicitEntry, offsetEntry); 519 } 520 else { 521 sequenceHelper.decrement(); // the parent already increments; b/c it assumes that all documents have offset entries all 522 // of the time 523 } 524 return success; 525 } 526 527 /** 528 * This method is responsible for iterating through all of the accounting lines in the document (source only) and checking to 529 * see if they are all for the same account or not. It recognizes the first account element as the base, and then it iterates 530 * through the rest. If it comes across one that doesn't match, then we know it's for multiple accounts. 531 * 532 * @param financialDocument submitted accounting document 533 * @return true if multiple accounts are being used 534 */ 535 protected boolean isDocumentForMultipleAccounts() { 536 String baseAccountNumber = ""; 537 538 int index = 0; 539 List<AccountingLine> lines = this.getSourceAccountingLines(); 540 for (AccountingLine line : lines) { 541 if (index == 0) { 542 baseAccountNumber = line.getAccountNumber(); 543 } 544 else if (!baseAccountNumber.equals(line.getAccountNumber())) { 545 return true; 546 } 547 index++; 548 } 549 550 return false; 551 } 552 553 /** 554 * This method creates an AV recode specific GLPE with a document type of DI. The sequence is managed outside of this method. It 555 * uses the explicit entry as its model and then tweaks values appropriately. 556 * 557 * @param financialDocument submitted accounting document 558 * @param sequenceHelper helper class which will allows us to increment a reference without using an Integer 559 * @param explicitEntry represents explicit entry 560 * @return true if recode GLPE is added to the financial document successfully 561 */ 562 protected void processAuxiliaryVoucherRecodeDistributionOfIncomeAndExpenseGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySequenceHelper sequenceHelper, GeneralLedgerPendingEntry explicitEntry) { 563 // create a new instance based off of the explicit entry 564 GeneralLedgerPendingEntry recodeGlpe = new GeneralLedgerPendingEntry(explicitEntry); 565 566 // set the sequence number according to what was passed in - this is managed external to this method 567 recodeGlpe.setTransactionLedgerEntrySequenceNumber(new Integer(sequenceHelper.getSequenceCounter())); 568 569 // set the document type to that of a Distrib. Of Income and Expense 570 recodeGlpe.setFinancialDocumentTypeCode(OLEConstants.FinancialDocumentTypeCodes.DISTRIBUTION_OF_INCOME_AND_EXPENSE); 571 572 // set the object type code base on the value of the explicit entry 573 recodeGlpe.setFinancialObjectTypeCode(getObjectTypeCodeForRecodeDistributionOfIncomeAndExpenseEntry(explicitEntry)); 574 575 // set the reversal date to null 576 recodeGlpe.setFinancialDocumentReversalDate(null); 577 578 // although this is an offsets, we need to set the offset indicator to false 579 recodeGlpe.setTransactionEntryOffsetIndicator(false); 580 581 // add the new recode offset entry to the document now 582 addPendingEntry(recodeGlpe); 583 } 584 585 /** 586 * This method examines the explicit entry's object type and returns the appropriate object type code. This is specific to AV 587 * recodes (AVRCs). 588 * 589 * @param explicitEntry 590 * @return object type code from explicit entry (either financial object type code, financial object type expenditure code, or 591 * financial object type income cash code) 592 */ 593 protected String getObjectTypeCodeForRecodeDistributionOfIncomeAndExpenseEntry(GeneralLedgerPendingEntry explicitEntry) { 594 SystemOptions options = SpringContext.getBean(OptionsService.class).getCurrentYearOptions(); 595 String objectTypeCode = explicitEntry.getFinancialObjectTypeCode(); 596 597 if (options.getFinObjTypeExpNotExpendCode().equals(objectTypeCode)) { 598 objectTypeCode = options.getFinObjTypeExpenditureexpCd(); 599 } 600 else if (options.getFinObjTypeIncomeNotCashCd().equals(objectTypeCode)) { 601 objectTypeCode = options.getFinObjectTypeIncomecashCode(); 602 } 603 604 return objectTypeCode; 605 } 606 607 /** 608 * This method checks if a given moment of time is within an accounting period, or its auxiliary voucher grace period. 609 * 610 * @param today a date to check if it is within the period 611 * @param periodToCheck the account period to check against 612 * @return true if a given moment in time is within an accounting period or an auxiliary voucher grace period 613 */ 614 public boolean calculateIfWithinGracePeriod(Date today, AccountingPeriod periodToCheck) { 615 boolean result = false; 616 final int todayAsComparableDate = comparableDateForm(today); 617 final int periodClose = new Integer(comparableDateForm(periodToCheck.getUniversityFiscalPeriodEndDate())); 618 final int periodBegin = comparableDateForm(calculateFirstDayOfMonth(periodToCheck.getUniversityFiscalPeriodEndDate())); 619 final int gracePeriodClose = periodClose + new Integer(SpringContext.getBean(ParameterService.class).getParameterValueAsString(getClass(), AUXILIARY_VOUCHER_ACCOUNTING_PERIOD_GRACE_PERIOD)).intValue(); 620 return (todayAsComparableDate >= periodBegin && todayAsComparableDate <= gracePeriodClose); 621 } 622 623 /** 624 * This method returns a date as an approximate count of days since the BCE epoch. 625 * 626 * @param d the date to convert 627 * @return an integer count of days, very approximate 628 */ 629 public int comparableDateForm(Date d) { 630 java.util.Calendar cal = new java.util.GregorianCalendar(); 631 cal.setTime(d); 632 return cal.get(java.util.Calendar.YEAR) * 365 + cal.get(java.util.Calendar.DAY_OF_YEAR); 633 } 634 635 /** 636 * Given a day, this method calculates what the first day of that month was. 637 * 638 * @param d date to find first of month for 639 * @return date of the first day of the month 640 */ 641 public Date calculateFirstDayOfMonth(Date d) { 642 java.util.Calendar cal = new java.util.GregorianCalendar(); 643 cal.setTime(d); 644 int dayOfMonth = cal.get(java.util.Calendar.DAY_OF_MONTH) - 1; 645 cal.add(java.util.Calendar.DAY_OF_YEAR, -1 * dayOfMonth); 646 return new Date(cal.getTimeInMillis()); 647 } 648 649 /** 650 * This method checks if the given accounting period ends on the last day of the previous fiscal year 651 * 652 * @param acctPeriod accounting period to check 653 * @return true if the accounting period ends with the fiscal year, false if otherwise 654 */ 655 public boolean isEndOfPreviousFiscalYear(AccountingPeriod acctPeriod) { 656 final UniversityDateService universityDateService = SpringContext.getBean(UniversityDateService.class); 657 final Date firstDayOfCurrFiscalYear = new Date(universityDateService.getFirstDateOfFiscalYear(universityDateService.getCurrentFiscalYear()).getTime()); 658 final Date periodClose = acctPeriod.getUniversityFiscalPeriodEndDate(); 659 java.util.Calendar cal = new java.util.GregorianCalendar(); 660 cal.setTime(periodClose); 661 cal.add(java.util.Calendar.DATE, 1); 662 return (firstDayOfCurrFiscalYear.equals(new Date(cal.getTimeInMillis()))); 663 } 664 665}