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 }