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}