/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.fp.document; import static org.kuali.kfs.sys.KFSConstants.EMPTY_STRING; import static org.kuali.kfs.sys.KFSConstants.GL_CREDIT_CODE; import static org.kuali.kfs.sys.KFSConstants.GL_DEBIT_CODE; import static org.kuali.kfs.sys.KFSPropertyConstants.BALANCE_TYPE; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.coa.businessobject.BalanceType; import org.kuali.kfs.fp.businessobject.JournalVoucherAccountingLineParser; import org.kuali.kfs.fp.businessobject.VoucherSourceAccountingLine; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.businessobject.AccountingLine; import org.kuali.kfs.sys.businessobject.AccountingLineBase; import org.kuali.kfs.sys.businessobject.AccountingLineParser; import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry; import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper; import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail; import org.kuali.kfs.sys.businessobject.SourceAccountingLine; import org.kuali.kfs.sys.businessobject.SufficientFundsItem; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.document.AccountingDocumentBase; import org.kuali.kfs.sys.document.AmountTotaling; import org.kuali.kfs.sys.document.Correctable; import org.kuali.kfs.sys.document.service.DebitDeterminerService; import org.kuali.kfs.sys.service.OptionsService; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.kew.api.exception.WorkflowException; import org.kuali.rice.krad.document.Copyable; /** * This is the business object that represents the JournalVoucherDocument in Kuali. This is a transactional document that will * eventually post transactions to the G/L. It integrates with workflow and contains a single group of accounting lines. The Journal * Voucher is unique in that we only make use of one accounting line list: the source accounting lines seeing as a JV only records * accounting lines as debits or credits. */ public class JournalVoucherDocument extends AccountingDocumentBase implements VoucherDocument, Copyable, Correctable, AmountTotaling { protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(JournalVoucherDocument.class); // document specific attributes protected String balanceTypeCode; // balanceType key protected BalanceType balanceType; protected java.sql.Date reversalDate; /** * Constructs a JournalVoucherDocument instance. */ public JournalVoucherDocument() { super(); this.balanceType = new BalanceType(); } /** * @see org.kuali.kfs.sys.document.AccountingDocumentBase#checkSufficientFunds() */ @Override public List<SufficientFundsItem> checkSufficientFunds() { LOG.debug("checkSufficientFunds() started"); // This document does not do sufficient funds checking return new ArrayList<SufficientFundsItem>(); } /** * @see org.kuali.kfs.sys.document.AccountingDocumentBase#getSourceAccountingLineClass() */ @Override public Class getSourceAccountingLineClass() { return VoucherSourceAccountingLine.class; } /** * This method retrieves the balance typ associated with this document. * * @return BalanceTyp */ public BalanceType getBalanceType() { return balanceType; } /** * This method sets the balance type associated with this document. * * @param balanceType * @deprecated */ @Deprecated public void setBalanceType(BalanceType balanceType) { this.balanceType = balanceType; } /** * Gets the balanceTypeCode attribute. * * @return Returns the balanceTypeCode. */ public String getBalanceTypeCode() { return balanceTypeCode; } /** * Sets the balanceTypeCode attribute value. * * @param balanceTypeCode The balanceTypeCode to set. */ public void setBalanceTypeCode(String balanceTypeCode) { this.balanceTypeCode = balanceTypeCode; } /** * This method retrieves the reversal date associated with this document. * * @return java.sql.Date */ public java.sql.Date getReversalDate() { return reversalDate; } /** * This method sets the reversal date associated with this document. * * @param reversalDate */ public void setReversalDate(java.sql.Date reversalDate) { this.reversalDate = reversalDate; } /** * Overrides the base implementation to return an empty string. * * @return String */ @Override public String getSourceAccountingLinesSectionTitle() { return EMPTY_STRING; } /** * Overrides the base implementation to return an empty string. * * @return String */ @Override public String getTargetAccountingLinesSectionTitle() { return EMPTY_STRING; } /** * This method calculates the debit total for a JV document keying off of the debit/debit code, only summing the accounting * lines with a debitDebitCode that matched the debit constant, and returns the results. * * @return KualiDecimal */ public KualiDecimal getDebitTotal() { KualiDecimal debitTotal = KualiDecimal.ZERO; AccountingLineBase al = null; Iterator iter = sourceAccountingLines.iterator(); while (iter.hasNext()) { al = (AccountingLineBase) iter.next(); if (StringUtils.isNotBlank(al.getDebitCreditCode()) && al.getDebitCreditCode().equals(GL_DEBIT_CODE)) { debitTotal = debitTotal.add(al.getAmount()); } } return debitTotal; } /** * This method calculates the credit total for a JV document keying off of the debit/credit code, only summing the accounting * lines with a debitCreditCode that matched the debit constant, and returns the results. * * @return KualiDecimal */ public KualiDecimal getCreditTotal() { KualiDecimal creditTotal = KualiDecimal.ZERO; AccountingLineBase al = null; Iterator iter = sourceAccountingLines.iterator(); while (iter.hasNext()) { al = (AccountingLineBase) iter.next(); if (StringUtils.isNotBlank(al.getDebitCreditCode()) && al.getDebitCreditCode().equals(GL_CREDIT_CODE)) { creditTotal = creditTotal.add(al.getAmount()); } } return creditTotal; } /** * This method determines the "total" for the JV document. If the selected balance type is an offset generation, then the method * returns the total debits amount when it is greater than the total credit amount. otherwise, it returns total credit amount. * When selected balance type is not an offset generation, the method returns the sum of all accounting line debit amounts. * * @return KualiDecimal the total of the JV document. */ public KualiDecimal getTotalDollarAmount() { KualiDecimal total = KualiDecimal.ZERO; this.refreshReferenceObject("balanceType"); if (this.balanceType.isFinancialOffsetGenerationIndicator()) { if (getCreditTotal().isGreaterThan(getDebitTotal())) { total = getCreditTotal(); } else { total = getDebitTotal(); } } else { total = getDebitTotal(); } return total; } /** * Used to get the appropriate <code>{@link AccountingLineParser}</code> for the <code>Document</code> * * @return AccountingLineParser */ @Override public AccountingLineParser getAccountingLineParser() { return new JournalVoucherAccountingLineParser(getBalanceTypeCode()); } /** * @see org.kuali.kfs.sys.document.AccountingDocumentBase#toErrorCorrection() */ @Override public void toErrorCorrection() throws WorkflowException { super.toErrorCorrection(); processJournalVoucherErrorCorrections(); } /** * This method checks to make sure that the JV that we are dealing with was one that was created in debit/credit mode, not * single amount entry mode. If this is a debit/credit JV, then iterate over each source line and flip the sign on the amount to * nullify the super's effect, then flip the debit/credit code b/c an error corrected JV flips the debit/credit code. */ protected void processJournalVoucherErrorCorrections() { Iterator i = getSourceAccountingLines().iterator(); this.refreshReferenceObject(BALANCE_TYPE); if (this.getBalanceType().isFinancialOffsetGenerationIndicator()) { // make sure this is not a single amount entered JV int index = 0; while (i.hasNext()) { SourceAccountingLine sLine = (SourceAccountingLine) i.next(); String debitCreditCode = sLine.getDebitCreditCode(); if (StringUtils.isNotBlank(debitCreditCode)) { // negate the amount to to nullify the effects of the super, b/c super flipped it the first time through sLine.setAmount(sLine.getAmount().negated()); // offsets the effect the super // now just flip the debit/credit code if (GL_DEBIT_CODE.equals(debitCreditCode)) { sLine.setDebitCreditCode(GL_CREDIT_CODE); } else if (GL_CREDIT_CODE.equals(debitCreditCode)) { sLine.setDebitCreditCode(GL_DEBIT_CODE); } else { 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."); } index++; } } } } /** * The following are credits (return false) * <ol> * <li> (debitCreditCode isNotBlank) && debitCreditCode != 'D' * </ol> * * The following are debits (return true) * <ol> * <li> debitCreditCode == 'D' * <li> debitCreditCode isBlank * </ol> * * @param financialDocument The document which contains the accounting line being analyzed. * @param accountingLine The accounting line which will be analyzed to determine if it is a debit line. * @return True if the accounting line provided is a debit accounting line, false otherwise. * @throws IllegalStateException Thrown by method IsDebitUtiles.isDebitCode() * * @see org.kuali.rice.krad.rule.AccountingLineRule#isDebit(org.kuali.rice.krad.document.FinancialDocument, * org.kuali.rice.krad.bo.AccountingLine) * @see org.kuali.kfs.sys.document.validation.impl.AccountingDocumentRuleBase.IsDebitUtils#isDebitCode(String) */ public boolean isDebit(GeneralLedgerPendingEntrySourceDetail postable) throws IllegalStateException { AccountingLine accountingLine = (AccountingLine)postable; String debitCreditCode = accountingLine.getDebitCreditCode(); DebitDeterminerService isDebitUtils = SpringContext.getBean(DebitDeterminerService.class); boolean isDebit = StringUtils.isBlank(debitCreditCode) || isDebitUtils.isDebitCode(debitCreditCode); return isDebit; } /** * This method sets attributes on the explicitly general ledger pending entry specific to JournalVoucher documents. * This includes setting the accounting period code and year (as selected by the user, the object type code, * the balance type code, the debit/credit code, the encumbrance update code and the reversal date. * * @param financialDocument The document which contains the general ledger pending entry being modified. * @param accountingLine The accounting line the explicit entry was generated from. * @param explicitEntry The explicit entry being updated. * * @see org.kuali.module.financial.rules.FinancialDocumentRuleBase#customizeExplicitGeneralLedgerPendingEntry(org.kuali.rice.krad.document.FinancialDocument, * org.kuali.rice.krad.bo.AccountingLine, org.kuali.module.gl.bo.GeneralLedgerPendingEntry) */ @Override public void customizeExplicitGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySourceDetail postable, GeneralLedgerPendingEntry explicitEntry) { AccountingLine accountingLine = (AccountingLine)postable; // set the appropriate accounting period values according to the values chosen by the user explicitEntry.setUniversityFiscalPeriodCode(getPostingPeriodCode()); explicitEntry.setUniversityFiscalYear(getPostingYear()); // set the object type code directly from what was entered in the interface explicitEntry.setFinancialObjectTypeCode(accountingLine.getObjectTypeCode()); // set the balance type code directly from what was entered in the interface explicitEntry.setFinancialBalanceTypeCode(accountingLine.getBalanceTypeCode()); // set the debit/credit code appropriately refreshReferenceObject(BALANCE_TYPE); if (getBalanceType().isFinancialOffsetGenerationIndicator()) { explicitEntry.setTransactionDebitCreditCode(StringUtils.isNotBlank(accountingLine.getDebitCreditCode()) ? accountingLine.getDebitCreditCode() : KFSConstants.BLANK_SPACE); } else { explicitEntry.setTransactionDebitCreditCode(KFSConstants.BLANK_SPACE); } // set the encumbrance update code explicitEntry.setTransactionEncumbranceUpdateCode(StringUtils.isNotBlank(accountingLine.getEncumbranceUpdateCode()) ? accountingLine.getEncumbranceUpdateCode() : KFSConstants.BLANK_SPACE); // set the reversal date to what what specified at the document level if (getReversalDate() != null) { explicitEntry.setFinancialDocumentReversalDate(getReversalDate()); } } /** * A Journal Voucher document doesn't generate an offset entry at all, so this method overrides to do nothing more than return * true. This will be called by the parent's processGeneralLedgerPendingEntries method. * * @param financialDocument The document the offset will be stored within. * @param sequenceHelper The sequence object to be modified. * @param accountingLineToCopy The accounting line the offset is generated for. * @param explicitEntry The explicit entry the offset will be generated for. * @param offsetEntry The offset entry to be processed. * @return This method always returns true. * * @see org.kuali.module.financial.rules.FinancialDocumentRuleBase#processOffsetGeneralLedgerPendingEntry(org.kuali.rice.krad.document.FinancialDocument, * org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper, org.kuali.rice.krad.bo.AccountingLine, * org.kuali.module.gl.bo.GeneralLedgerPendingEntry, org.kuali.module.gl.bo.GeneralLedgerPendingEntry) */ @Override public boolean processOffsetGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySequenceHelper sequenceHelper, GeneralLedgerPendingEntrySourceDetail glpeSourceDetail, GeneralLedgerPendingEntry explicitEntry, GeneralLedgerPendingEntry offsetEntry) { sequenceHelper.decrement(); // the parent already increments; assuming that all documents have offset entries return true; } /** * * @see org.kuali.kfs.sys.document.validation.impl.AccountingDocumentRuleBase#getGeneralLedgerPendingEntryAmountForAccountingLine(org.kuali.kfs.sys.businessobject.AccountingLine) */ @Override public KualiDecimal getGeneralLedgerPendingEntryAmountForDetail(GeneralLedgerPendingEntrySourceDetail accountingLine) { LOG.debug("getGeneralLedgerPendingEntryAmountForAccountingLine(AccountingLine) - start"); KualiDecimal returnKualiDecimal; String budgetCodes = SpringContext.getBean(OptionsService.class).getOptions(accountingLine.getPostingYear()).getBudgetCheckingBalanceTypeCd(); if (!this.balanceType.isFinancialOffsetGenerationIndicator()) { returnKualiDecimal = accountingLine.getAmount(); } else { returnKualiDecimal = accountingLine.getAmount().abs(); } LOG.debug("getGeneralLedgerPendingEntryAmountForAccountingLine(AccountingLine) - end"); return returnKualiDecimal; } }