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