View Javadoc
1   /*
2    * Copyright 2005 The Kuali Foundation
3    * 
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    * http://www.opensource.org/licenses/ecl2.php
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.fp.document;
17  
18  import static org.kuali.ole.fp.document.validation.impl.AuxiliaryVoucherDocumentRuleConstants.AUXILIARY_VOUCHER_ACCOUNTING_PERIOD_GRACE_PERIOD;
19  import static org.kuali.ole.fp.document.validation.impl.AuxiliaryVoucherDocumentRuleConstants.GENERAL_LEDGER_PENDING_ENTRY_OFFSET_CODE;
20  import static org.kuali.ole.sys.OLEConstants.GL_CREDIT_CODE;
21  import static org.kuali.ole.sys.OLEConstants.GL_DEBIT_CODE;
22  import static org.kuali.ole.sys.OLEConstants.AuxiliaryVoucher.ACCRUAL_DOC_TYPE;
23  import static org.kuali.ole.sys.OLEConstants.AuxiliaryVoucher.ADJUSTMENT_DOC_TYPE;
24  import static org.kuali.ole.sys.OLEConstants.AuxiliaryVoucher.RECODE_DOC_TYPE;
25  
26  import java.sql.Date;
27  import java.util.Iterator;
28  import java.util.List;
29  
30  import org.apache.commons.lang.StringUtils;
31  import org.kuali.ole.coa.businessobject.AccountingPeriod;
32  import org.kuali.ole.coa.service.AccountingPeriodService;
33  import org.kuali.ole.gl.service.SufficientFundsService;
34  import org.kuali.ole.sys.OLEConstants;
35  import org.kuali.ole.sys.businessobject.AccountingLine;
36  import org.kuali.ole.sys.businessobject.AccountingLineBase;
37  import org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntry;
38  import org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper;
39  import org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntrySourceDetail;
40  import org.kuali.ole.sys.businessobject.SourceAccountingLine;
41  import org.kuali.ole.sys.businessobject.SystemOptions;
42  import org.kuali.ole.sys.context.SpringContext;
43  import org.kuali.ole.sys.document.AccountingDocumentBase;
44  import org.kuali.ole.sys.document.AmountTotaling;
45  import org.kuali.ole.sys.document.Correctable;
46  import org.kuali.ole.sys.document.service.DebitDeterminerService;
47  import org.kuali.ole.sys.service.OptionsService;
48  import org.kuali.ole.sys.service.UniversityDateService;
49  import org.kuali.rice.core.api.datetime.DateTimeService;
50  import org.kuali.rice.core.api.util.type.KualiDecimal;
51  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
52  import org.kuali.rice.kew.api.exception.WorkflowException;
53  import org.kuali.rice.kew.framework.postprocessor.DocumentRouteStatusChange;
54  import org.kuali.rice.krad.document.Copyable;
55  
56  /**
57   * This is the business object that represents the AuxiliaryVoucherDocument in Kuali. This is a transactional document that will
58   * eventually post transactions to the G/L. It integrates with workflow and also contains two groupings of accounting lines: Expense
59   * and target. Expense is the expense and target is the income lines.
60   */
61  public class AuxiliaryVoucherDocument extends AccountingDocumentBase implements VoucherDocument, Copyable, Correctable, AmountTotaling {
62      protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AuxiliaryVoucherDocument.class);
63  
64      protected String typeCode = ADJUSTMENT_DOC_TYPE;
65      protected java.sql.Date reversalDate;
66      
67      /**
68       * @see org.kuali.ole.sys.document.AccountingDocumentBase#documentPerformsSufficientFundsCheck()
69       */
70      @Override
71      public boolean documentPerformsSufficientFundsCheck() {
72          if (isRecodeType()) {
73              return super.documentPerformsSufficientFundsCheck();
74          }
75          else {
76              return false;
77          }
78      }
79  
80      /**
81       * Initializes the array lists and some basic info.
82       */
83      public AuxiliaryVoucherDocument() {
84          super();
85      }
86  
87      /**
88       * Read Accessor for Reversal Date
89       * 
90       * @return java.sql.Date
91       */
92      public java.sql.Date getReversalDate() {
93          return reversalDate;
94      }
95  
96      /**
97       * Write Accessor for Reversal Date
98       * 
99       * @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 }