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.sys.OLEConstants.EMPTY_STRING;
019import static org.kuali.ole.sys.OLEConstants.GL_CREDIT_CODE;
020import static org.kuali.ole.sys.OLEConstants.GL_DEBIT_CODE;
021import static org.kuali.ole.sys.OLEPropertyConstants.BALANCE_TYPE;
022
023import java.util.ArrayList;
024import java.util.Iterator;
025import java.util.List;
026
027import org.apache.commons.lang.StringUtils;
028import org.kuali.ole.coa.businessobject.BalanceType;
029import org.kuali.ole.fp.businessobject.JournalVoucherAccountingLineParser;
030import org.kuali.ole.fp.businessobject.VoucherSourceAccountingLine;
031import org.kuali.ole.sys.OLEConstants;
032import org.kuali.ole.sys.businessobject.AccountingLine;
033import org.kuali.ole.sys.businessobject.AccountingLineBase;
034import org.kuali.ole.sys.businessobject.AccountingLineParser;
035import org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntry;
036import org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper;
037import org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntrySourceDetail;
038import org.kuali.ole.sys.businessobject.SourceAccountingLine;
039import org.kuali.ole.sys.businessobject.SufficientFundsItem;
040import org.kuali.ole.sys.context.SpringContext;
041import org.kuali.ole.sys.document.AccountingDocumentBase;
042import org.kuali.ole.sys.document.AmountTotaling;
043import org.kuali.ole.sys.document.Correctable;
044import org.kuali.ole.sys.document.service.DebitDeterminerService;
045import org.kuali.ole.sys.service.OptionsService;
046import org.kuali.rice.core.api.util.type.KualiDecimal;
047import org.kuali.rice.kew.api.exception.WorkflowException;
048import org.kuali.rice.krad.document.Copyable;
049
050/**
051 * This is the business object that represents the JournalVoucherDocument in Kuali. This is a transactional document that will
052 * eventually post transactions to the G/L. It integrates with workflow and contains a single group of accounting lines. The Journal
053 * Voucher is unique in that we only make use of one accounting line list: the source accounting lines seeing as a JV only records
054 * accounting lines as debits or credits.
055 */
056public class JournalVoucherDocument extends AccountingDocumentBase implements VoucherDocument, Copyable, Correctable, AmountTotaling {
057    protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(JournalVoucherDocument.class);
058
059    // document specific attributes
060    protected String balanceTypeCode; // balanceType key
061    protected BalanceType balanceType;
062    protected java.sql.Date reversalDate;
063
064    /**
065     * Constructs a JournalVoucherDocument instance.
066     */
067    public JournalVoucherDocument() {
068        super();
069        this.balanceType = new BalanceType();
070    }
071
072    /**
073     * @see org.kuali.ole.sys.document.AccountingDocumentBase#checkSufficientFunds()
074     */
075    @Override
076    public List<SufficientFundsItem> checkSufficientFunds() {
077        LOG.debug("checkSufficientFunds() started");
078
079        // This document does not do sufficient funds checking
080        return new ArrayList<SufficientFundsItem>();
081    }
082
083    /**
084     * @see org.kuali.ole.sys.document.AccountingDocumentBase#getSourceAccountingLineClass()
085     */
086    @Override
087    public Class getSourceAccountingLineClass() {
088        return VoucherSourceAccountingLine.class;
089    }
090
091    /**
092     * This method retrieves the balance typ associated with this document.
093     * 
094     * @return BalanceTyp
095     */
096    public BalanceType getBalanceType() {
097        return balanceType;
098    }
099
100    /**
101     * This method sets the balance type associated with this document.
102     * 
103     * @param balanceType
104     * @deprecated
105     */
106    @Deprecated
107    public void setBalanceType(BalanceType balanceType) {
108        this.balanceType = balanceType;
109    }
110
111    /**
112     * Gets the balanceTypeCode attribute.
113     * 
114     * @return Returns the balanceTypeCode.
115     */
116    public String getBalanceTypeCode() {
117        return balanceTypeCode;
118    }
119
120    /**
121     * Sets the balanceTypeCode attribute value.
122     * 
123     * @param balanceTypeCode The balanceTypeCode to set.
124     */
125    public void setBalanceTypeCode(String balanceTypeCode) {
126        this.balanceTypeCode = balanceTypeCode;
127    }
128
129    /**
130     * This method retrieves the reversal date associated with this document.
131     * 
132     * @return java.sql.Date
133     */
134    public java.sql.Date getReversalDate() {
135        return reversalDate;
136    }
137
138    /**
139     * This method sets the reversal date associated with this document.
140     * 
141     * @param reversalDate
142     */
143    public void setReversalDate(java.sql.Date reversalDate) {
144        this.reversalDate = reversalDate;
145    }
146
147    /**
148     * Overrides the base implementation to return an empty string.
149     * 
150     * @return String
151     */
152    @Override
153    public String getSourceAccountingLinesSectionTitle() {
154        return EMPTY_STRING;
155    }
156
157    /**
158     * Overrides the base implementation to return an empty string.
159     * 
160     * @return String
161     */
162    @Override
163    public String getTargetAccountingLinesSectionTitle() {
164        return EMPTY_STRING;
165    }
166
167    /**
168     * This method calculates the debit total for a JV document keying off of the debit/debit code, only summing the accounting
169     * lines with a debitDebitCode that matched the debit constant, and returns the results.
170     * 
171     * @return KualiDecimal
172     */
173    public KualiDecimal getDebitTotal() {
174        KualiDecimal debitTotal = KualiDecimal.ZERO;
175        AccountingLineBase al = null;
176        Iterator iter = sourceAccountingLines.iterator();
177        while (iter.hasNext()) {
178            al = (AccountingLineBase) iter.next();
179            if (StringUtils.isNotBlank(al.getDebitCreditCode()) && al.getDebitCreditCode().equals(GL_DEBIT_CODE)) {
180                debitTotal = debitTotal.add(al.getAmount());
181            }
182        }
183
184        return debitTotal;
185    }
186
187    /**
188     * This method calculates the credit total for a JV document keying off of the debit/credit code, only summing the accounting
189     * lines with a debitCreditCode that matched the debit constant, and returns the results.
190     * 
191     * @return KualiDecimal
192     */
193    public KualiDecimal getCreditTotal() {
194        KualiDecimal creditTotal = KualiDecimal.ZERO;
195        AccountingLineBase al = null;
196        Iterator iter = sourceAccountingLines.iterator();
197        while (iter.hasNext()) {
198            al = (AccountingLineBase) iter.next();
199            if (StringUtils.isNotBlank(al.getDebitCreditCode()) && al.getDebitCreditCode().equals(GL_CREDIT_CODE)) {
200                creditTotal = creditTotal.add(al.getAmount());
201            }
202        }
203        return creditTotal;
204    }
205
206    /**
207     * This method determines the "total" for the JV document. If the selected balance type is an offset generation, then the method
208     * returns the total debits amount when it is greater than the total credit amount. otherwise, it returns total credit amount.
209     * When selected balance type is not an offset generation, the method returns the sum of all accounting line debit amounts.
210     * 
211     * @return KualiDecimal the total of the JV document.
212     */
213    public KualiDecimal getTotalDollarAmount() {
214
215        KualiDecimal total = KualiDecimal.ZERO;
216
217        this.refreshReferenceObject("balanceType");
218
219        if (this.balanceType.isFinancialOffsetGenerationIndicator()) {
220            if (getCreditTotal().isGreaterThan(getDebitTotal())) {
221                total = getCreditTotal();
222            }
223            else {
224                total = getDebitTotal();
225            }
226        }
227        else {
228            total = getDebitTotal();
229        }
230        return total;
231    }
232
233    /**
234     * Used to get the appropriate <code>{@link AccountingLineParser}</code> for the <code>Document</code>
235     * 
236     * @return AccountingLineParser
237     */
238    @Override
239    public AccountingLineParser getAccountingLineParser() {
240        return new JournalVoucherAccountingLineParser(getBalanceTypeCode());
241    }
242
243    /**
244     * @see org.kuali.ole.sys.document.AccountingDocumentBase#toErrorCorrection()
245     */
246    @Override
247    public void toErrorCorrection() throws WorkflowException {
248        super.toErrorCorrection();
249        processJournalVoucherErrorCorrections();
250    }
251
252    /**
253     * This method checks to make sure that the JV that we are dealing with was one that was created in debit/credit mode, not
254     * 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
255     * nullify the super's effect, then flip the debit/credit code b/c an error corrected JV flips the debit/credit code.
256     */
257    protected void processJournalVoucherErrorCorrections() {
258        Iterator i = getSourceAccountingLines().iterator();
259
260        this.refreshReferenceObject(BALANCE_TYPE);
261
262        if (this.getBalanceType().isFinancialOffsetGenerationIndicator()) { // make sure this is not a single amount entered JV
263            int index = 0;
264            while (i.hasNext()) {
265                SourceAccountingLine sLine = (SourceAccountingLine) i.next();
266
267                String debitCreditCode = sLine.getDebitCreditCode();
268
269                if (StringUtils.isNotBlank(debitCreditCode)) {
270                    // negate the amount to to nullify the effects of the super, b/c super flipped it the first time through
271                    sLine.setAmount(sLine.getAmount().negated()); // offsets the effect the super
272
273                    // now just flip the debit/credit code
274                    if (GL_DEBIT_CODE.equals(debitCreditCode)) {
275                        sLine.setDebitCreditCode(GL_CREDIT_CODE);
276                    }
277                    else if (GL_CREDIT_CODE.equals(debitCreditCode)) {
278                        sLine.setDebitCreditCode(GL_DEBIT_CODE);
279                    }
280                    else {
281                        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.");
282
283                    }
284                    index++;
285                }
286            }
287        }
288    }
289    
290    /**
291     * The following are credits (return false)
292     * <ol>
293     * <li> (debitCreditCode isNotBlank) && debitCreditCode != 'D'
294     * </ol>
295     * 
296     * The following are debits (return true)
297     * <ol>
298     * <li> debitCreditCode == 'D'
299     * <li> debitCreditCode isBlank
300     * </ol>
301     * 
302     * @param financialDocument The document which contains the accounting line being analyzed.
303     * @param accountingLine The accounting line which will be analyzed to determine if it is a debit line.
304     * @return True if the accounting line provided is a debit accounting line, false otherwise.
305     * @throws IllegalStateException Thrown by method IsDebitUtiles.isDebitCode()
306     * 
307     * @see org.kuali.rice.krad.rule.AccountingLineRule#isDebit(org.kuali.rice.krad.document.FinancialDocument,
308     *      org.kuali.rice.krad.bo.AccountingLine)
309     * @see org.kuali.ole.sys.document.validation.impl.AccountingDocumentRuleBase.IsDebitUtils#isDebitCode(String)
310     */
311    public boolean isDebit(GeneralLedgerPendingEntrySourceDetail postable) throws IllegalStateException {
312        AccountingLine accountingLine = (AccountingLine)postable;
313        String debitCreditCode = accountingLine.getDebitCreditCode();
314
315        DebitDeterminerService isDebitUtils = SpringContext.getBean(DebitDeterminerService.class);
316        boolean isDebit = StringUtils.isBlank(debitCreditCode) || isDebitUtils.isDebitCode(debitCreditCode);
317
318        return isDebit;
319    }
320    
321    /**
322     * This method sets attributes on the explicitly general ledger pending entry specific to JournalVoucher documents.
323     * This includes setting the accounting period code and year (as selected by the user, the object type code,
324     * the balance type code, the debit/credit code, the encumbrance update code and the reversal date.
325     * 
326     * @param financialDocument The document which contains the general ledger pending entry being modified.
327     * @param accountingLine The accounting line the explicit entry was generated from.
328     * @param explicitEntry The explicit entry being updated.
329     * 
330     * @see org.kuali.module.financial.rules.FinancialDocumentRuleBase#customizeExplicitGeneralLedgerPendingEntry(org.kuali.rice.krad.document.FinancialDocument,
331     *      org.kuali.rice.krad.bo.AccountingLine, org.kuali.module.gl.bo.GeneralLedgerPendingEntry)
332     */
333    @Override
334    public void customizeExplicitGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySourceDetail postable, GeneralLedgerPendingEntry explicitEntry) {
335        AccountingLine accountingLine = (AccountingLine)postable;
336
337        // set the appropriate accounting period values according to the values chosen by the user
338        explicitEntry.setUniversityFiscalPeriodCode(getPostingPeriodCode());
339        explicitEntry.setUniversityFiscalYear(getPostingYear());
340
341        // set the object type code directly from what was entered in the interface
342        explicitEntry.setFinancialObjectTypeCode(accountingLine.getObjectTypeCode());
343
344        // set the balance type code directly from what was entered in the interface
345        explicitEntry.setFinancialBalanceTypeCode(accountingLine.getBalanceTypeCode());
346
347        // set the debit/credit code appropriately
348        refreshReferenceObject(BALANCE_TYPE);
349        if (getBalanceType().isFinancialOffsetGenerationIndicator()) {
350            explicitEntry.setTransactionDebitCreditCode(StringUtils.isNotBlank(accountingLine.getDebitCreditCode()) ? accountingLine.getDebitCreditCode() : OLEConstants.BLANK_SPACE);
351        }
352        else {
353            explicitEntry.setTransactionDebitCreditCode(OLEConstants.BLANK_SPACE);
354        }
355
356        // set the encumbrance update code
357        explicitEntry.setTransactionEncumbranceUpdateCode(StringUtils.isNotBlank(accountingLine.getEncumbranceUpdateCode()) ? accountingLine.getEncumbranceUpdateCode() : OLEConstants.BLANK_SPACE);
358
359        // set the reversal date to what what specified at the document level
360        if (getReversalDate() != null) {
361            explicitEntry.setFinancialDocumentReversalDate(getReversalDate());
362        }
363    }
364
365    /**
366     * A Journal Voucher document doesn't generate an offset entry at all, so this method overrides to do nothing more than return
367     * true. This will be called by the parent's processGeneralLedgerPendingEntries method.
368     * 
369     * @param financialDocument The document the offset will be stored within.
370     * @param sequenceHelper The sequence object to be modified.
371     * @param accountingLineToCopy The accounting line the offset is generated for.
372     * @param explicitEntry The explicit entry the offset will be generated for.
373     * @param offsetEntry The offset entry to be processed.
374     * @return This method always returns true.
375     * 
376     * @see org.kuali.module.financial.rules.FinancialDocumentRuleBase#processOffsetGeneralLedgerPendingEntry(org.kuali.rice.krad.document.FinancialDocument,
377     *      org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper, org.kuali.rice.krad.bo.AccountingLine,
378     *      org.kuali.module.gl.bo.GeneralLedgerPendingEntry, org.kuali.module.gl.bo.GeneralLedgerPendingEntry)
379     */
380    @Override
381    public boolean processOffsetGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySequenceHelper sequenceHelper, GeneralLedgerPendingEntrySourceDetail glpeSourceDetail, GeneralLedgerPendingEntry explicitEntry, GeneralLedgerPendingEntry offsetEntry) {
382        sequenceHelper.decrement(); // the parent already increments; assuming that all documents have offset entries
383        return true;
384    }
385    
386    /**
387     * 
388     * @see org.kuali.ole.sys.document.validation.impl.AccountingDocumentRuleBase#getGeneralLedgerPendingEntryAmountForAccountingLine(org.kuali.ole.sys.businessobject.AccountingLine)
389     */
390    @Override
391    public KualiDecimal getGeneralLedgerPendingEntryAmountForDetail(GeneralLedgerPendingEntrySourceDetail accountingLine) {
392        LOG.debug("getGeneralLedgerPendingEntryAmountForAccountingLine(AccountingLine) - start");
393        KualiDecimal returnKualiDecimal;
394
395        String budgetCodes = SpringContext.getBean(OptionsService.class).getOptions(accountingLine.getPostingYear()).getBudgetCheckingBalanceTypeCd();
396        if (!this.balanceType.isFinancialOffsetGenerationIndicator()) {
397            returnKualiDecimal = accountingLine.getAmount();
398        }
399        else {
400            returnKualiDecimal = accountingLine.getAmount().abs();
401        }
402        LOG.debug("getGeneralLedgerPendingEntryAmountForAccountingLine(AccountingLine) - end");
403        return returnKualiDecimal;
404    }
405}