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.sys.KFSConstants.EMPTY_STRING;
019    import static org.kuali.kfs.sys.KFSConstants.GL_CREDIT_CODE;
020    import static org.kuali.kfs.sys.KFSConstants.GL_DEBIT_CODE;
021    import static org.kuali.kfs.sys.KFSPropertyConstants.BALANCE_TYPE;
022    
023    import java.util.ArrayList;
024    import java.util.Iterator;
025    import java.util.List;
026    
027    import org.apache.commons.lang.StringUtils;
028    import org.kuali.kfs.coa.businessobject.BalanceType;
029    import org.kuali.kfs.fp.businessobject.JournalVoucherAccountingLineParser;
030    import org.kuali.kfs.fp.businessobject.VoucherSourceAccountingLine;
031    import org.kuali.kfs.sys.KFSConstants;
032    import org.kuali.kfs.sys.businessobject.AccountingLine;
033    import org.kuali.kfs.sys.businessobject.AccountingLineBase;
034    import org.kuali.kfs.sys.businessobject.AccountingLineParser;
035    import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
036    import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper;
037    import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail;
038    import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
039    import org.kuali.kfs.sys.businessobject.SufficientFundsItem;
040    import org.kuali.kfs.sys.context.SpringContext;
041    import org.kuali.kfs.sys.document.AccountingDocumentBase;
042    import org.kuali.kfs.sys.document.AmountTotaling;
043    import org.kuali.kfs.sys.document.Correctable;
044    import org.kuali.kfs.sys.document.service.DebitDeterminerService;
045    import org.kuali.kfs.sys.service.OptionsService;
046    import org.kuali.rice.kew.exception.WorkflowException;
047    import org.kuali.rice.kns.document.Copyable;
048    import org.kuali.rice.kns.util.KualiDecimal;
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     */
056    public 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.kfs.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.kfs.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.kfs.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.kns.rule.AccountingLineRule#isDebit(org.kuali.rice.kns.document.FinancialDocument,
308         *      org.kuali.rice.kns.bo.AccountingLine)
309         * @see org.kuali.kfs.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.kns.document.FinancialDocument,
331         *      org.kuali.rice.kns.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() : KFSConstants.BLANK_SPACE);
351            }
352            else {
353                explicitEntry.setTransactionDebitCreditCode(KFSConstants.BLANK_SPACE);
354            }
355    
356            // set the encumbrance update code
357            explicitEntry.setTransactionEncumbranceUpdateCode(StringUtils.isNotBlank(accountingLine.getEncumbranceUpdateCode()) ? accountingLine.getEncumbranceUpdateCode() : KFSConstants.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.kns.document.FinancialDocument,
377         *      org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper, org.kuali.rice.kns.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.kfs.sys.document.validation.impl.AccountingDocumentRuleBase#getGeneralLedgerPendingEntryAmountForAccountingLine(org.kuali.kfs.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    }