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    }