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 }