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.sys.document;
017
018 import java.util.ArrayList;
019 import java.util.HashMap;
020 import java.util.Iterator;
021 import java.util.List;
022 import java.util.Map;
023
024 import org.apache.commons.lang.StringUtils;
025 import org.kuali.kfs.sys.KFSConstants;
026 import org.kuali.kfs.sys.businessobject.AccountingLine;
027 import org.kuali.kfs.sys.businessobject.AccountingLineBase;
028 import org.kuali.kfs.sys.businessobject.AccountingLineParser;
029 import org.kuali.kfs.sys.businessobject.AccountingLineParserBase;
030 import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
031 import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper;
032 import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail;
033 import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
034 import org.kuali.kfs.sys.businessobject.TargetAccountingLine;
035 import org.kuali.kfs.sys.context.SpringContext;
036 import org.kuali.kfs.sys.document.datadictionary.FinancialSystemTransactionalDocumentEntry;
037 import org.kuali.kfs.sys.document.validation.event.AccountingDocumentSaveWithNoLedgerEntryGenerationEvent;
038 import org.kuali.kfs.sys.document.validation.event.AccountingLineEvent;
039 import org.kuali.kfs.sys.document.validation.event.AddAccountingLineEvent;
040 import org.kuali.kfs.sys.document.validation.event.DeleteAccountingLineEvent;
041 import org.kuali.kfs.sys.document.validation.event.ReviewAccountingLineEvent;
042 import org.kuali.kfs.sys.document.validation.event.UpdateAccountingLineEvent;
043 import org.kuali.kfs.sys.service.AccountingLineService;
044 import org.kuali.kfs.sys.service.GeneralLedgerPendingEntryService;
045 import org.kuali.rice.kew.exception.WorkflowException;
046 import org.kuali.rice.kns.document.TransactionalDocument;
047 import org.kuali.rice.kns.exception.ValidationException;
048 import org.kuali.rice.kns.rule.event.KualiDocumentEvent;
049 import org.kuali.rice.kns.service.DataDictionaryService;
050 import org.kuali.rice.kns.util.KualiDecimal;
051
052 /**
053 * Base implementation class for financial edocs.
054 */
055 public abstract class AccountingDocumentBase extends GeneralLedgerPostingDocumentBase implements AccountingDocument, GeneralLedgerPendingEntrySource {
056 protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AccountingDocumentBase.class);
057
058 protected Integer nextSourceLineNumber;
059 protected Integer nextTargetLineNumber;
060 protected List sourceAccountingLines;
061 protected List targetAccountingLines;
062
063 protected transient FinancialSystemTransactionalDocumentEntry dataDictionaryEntry;
064 protected transient Class sourceAccountingLineClass;
065 protected transient Class targetAccountingLineClass;
066
067 /**
068 * Default constructor.
069 */
070 public AccountingDocumentBase() {
071 super();
072 this.nextSourceLineNumber = new Integer(1);
073 this.nextTargetLineNumber = new Integer(1);
074 setSourceAccountingLines(new ArrayList());
075 setTargetAccountingLines(new ArrayList());
076 }
077
078 /**
079 * @see org.kuali.kfs.sys.document.AccountingDocument#getSourceAccountingLines()
080 */
081 public List getSourceAccountingLines() {
082 return this.sourceAccountingLines;
083 }
084
085 /**
086 * @see org.kuali.kfs.sys.document.AccountingDocument#setSourceAccountingLines(java.util.List)
087 */
088 public void setSourceAccountingLines(List sourceLines) {
089 this.sourceAccountingLines = sourceLines;
090 }
091
092 /**
093 * @see org.kuali.kfs.sys.document.AccountingDocument#getTargetAccountingLines()
094 */
095 public List getTargetAccountingLines() {
096 return this.targetAccountingLines;
097 }
098
099 /**
100 * @see org.kuali.kfs.sys.document.AccountingDocument#setTargetAccountingLines(java.util.List)
101 */
102 public void setTargetAccountingLines(List targetLines) {
103 this.targetAccountingLines = targetLines;
104 }
105
106 /**
107 * This implementation sets the sequence number appropriately for the passed in source accounting line using the value that has
108 * been stored in the nextSourceLineNumber variable, adds the accounting line to the list that is aggregated by this object, and
109 * then handles incrementing the nextSourceLineNumber variable for you.
110 *
111 * @see org.kuali.kfs.sys.document.AccountingDocument#addSourceAccountingLine(SourceAccountingLine)
112 */
113 public void addSourceAccountingLine(SourceAccountingLine line) {
114 line.setSequenceNumber(this.getNextSourceLineNumber());
115 this.sourceAccountingLines.add(line);
116 this.nextSourceLineNumber = new Integer(this.getNextSourceLineNumber().intValue() + 1);
117 }
118
119 /**
120 * This implementation sets the sequence number appropriately for the passed in target accounting line using the value that has
121 * been stored in the nextTargetLineNumber variable, adds the accounting line to the list that is aggregated by this object, and
122 * then handles incrementing the nextTargetLineNumber variable for you.
123 *
124 * @see org.kuali.kfs.sys.document.AccountingDocument#addTargetAccountingLine(TargetAccountingLine)
125 */
126 public void addTargetAccountingLine(TargetAccountingLine line) {
127 line.setSequenceNumber(this.getNextTargetLineNumber());
128 this.targetAccountingLines.add(line);
129 this.nextTargetLineNumber = new Integer(this.getNextTargetLineNumber().intValue() + 1);
130 }
131
132 /**
133 * This implementation is coupled tightly with some underlying issues that the Struts PojoProcessor plugin has with how objects
134 * get instantiated within lists. The first three lines are required otherwise when the PojoProcessor tries to automatically
135 * inject values into the list, it will get an index out of bounds error if the instance at an index is being called and prior
136 * instances at indices before that one are not being instantiated. So changing the code below will cause adding lines to break
137 * if you add more than one item to the list.
138 *
139 * @see org.kuali.kfs.sys.document.AccountingDocument#getSourceAccountingLine(int)
140 */
141 public SourceAccountingLine getSourceAccountingLine(int index) {
142 while (getSourceAccountingLines().size() <= index) {
143 try {
144 getSourceAccountingLines().add(getSourceAccountingLineClass().newInstance());
145 }
146 catch (InstantiationException e) {
147 throw new RuntimeException("Unable to get class");
148 }
149 catch (IllegalAccessException e) {
150 throw new RuntimeException("Unable to get class");
151 }
152 }
153 return (SourceAccountingLine) getSourceAccountingLines().get(index);
154 }
155
156 /**
157 * This implementation is coupled tightly with some underlying issues that the Struts PojoProcessor plugin has with how objects
158 * get instantiated within lists. The first three lines are required otherwise when the PojoProcessor tries to automatically
159 * inject values into the list, it will get an index out of bounds error if the instance at an index is being called and prior
160 * instances at indices before that one are not being instantiated. So changing the code below will cause adding lines to break
161 * if you add more than one item to the list.
162 *
163 * @see org.kuali.kfs.sys.document.AccountingDocument#getTargetAccountingLine(int)
164 */
165 public TargetAccountingLine getTargetAccountingLine(int index) {
166 while (getTargetAccountingLines().size() <= index) {
167 try {
168 getTargetAccountingLines().add(getTargetAccountingLineClass().newInstance());
169 }
170 catch (InstantiationException e) {
171 throw new RuntimeException("Unable to get class");
172 }
173 catch (IllegalAccessException e) {
174 throw new RuntimeException("Unable to get class");
175 }
176 }
177 return (TargetAccountingLine) getTargetAccountingLines().get(index);
178 }
179
180 /**
181 * @see org.kuali.kfs.sys.document.AccountingDocument#getSourceAccountingLinesSectionTitle()
182 */
183 public String getSourceAccountingLinesSectionTitle() {
184 return KFSConstants.SOURCE;
185 }
186
187 /**
188 * @see org.kuali.kfs.sys.document.AccountingDocument#getTargetAccountingLinesSectionTitle()
189 */
190 public String getTargetAccountingLinesSectionTitle() {
191 return KFSConstants.TARGET;
192 }
193
194 /**
195 * Since one side of the document should match the other and the document should balance, the total dollar amount for the
196 * document should either be the expense line or the income line. This is the default implementation of this interface method so
197 * it should be overridden appropriately if your document cannot make this assumption.
198 *
199 * @return if target total is zero, source total, otherwise target total
200 */
201 public KualiDecimal getTotalDollarAmount() {
202 return getTargetTotal().equals(KualiDecimal.ZERO) ? getSourceTotal() : getTargetTotal();
203 }
204
205 /**
206 * @see org.kuali.kfs.sys.document.AccountingDocument#getSourceTotal()
207 */
208 public KualiDecimal getSourceTotal() {
209 KualiDecimal total = KualiDecimal.ZERO;
210 AccountingLineBase al = null;
211 Iterator iter = getSourceAccountingLines().iterator();
212 while (iter.hasNext()) {
213 al = (AccountingLineBase) iter.next();
214
215 KualiDecimal amount = al.getAmount();
216 if (amount != null) {
217 total = total.add(amount);
218 }
219 }
220 return total;
221 }
222
223 /**
224 * @see org.kuali.kfs.sys.document.AccountingDocument#getTargetTotal()
225 */
226 public KualiDecimal getTargetTotal() {
227 KualiDecimal total = KualiDecimal.ZERO;
228 AccountingLineBase al = null;
229 Iterator iter = getTargetAccountingLines().iterator();
230 while (iter.hasNext()) {
231 al = (AccountingLineBase) iter.next();
232
233 KualiDecimal amount = al.getAmount();
234 if (amount != null) {
235 total = total.add(amount);
236 }
237 }
238 return total;
239 }
240
241 /**
242 * @see org.kuali.kfs.sys.document.AccountingDocument#getNextSourceLineNumber()
243 */
244 public Integer getNextSourceLineNumber() {
245 return this.nextSourceLineNumber;
246 }
247
248 /**
249 * @see org.kuali.kfs.sys.document.AccountingDocument#setNextSourceLineNumber(java.lang.Integer)
250 */
251 public void setNextSourceLineNumber(Integer nextLineNumber) {
252 this.nextSourceLineNumber = nextLineNumber;
253 }
254
255 /**
256 * @see org.kuali.kfs.sys.document.AccountingDocument#getNextTargetLineNumber()
257 */
258 public Integer getNextTargetLineNumber() {
259 return this.nextTargetLineNumber;
260 }
261
262 /**
263 * @see org.kuali.kfs.sys.document.AccountingDocument#setNextTargetLineNumber(java.lang.Integer)
264 */
265 public void setNextTargetLineNumber(Integer nextLineNumber) {
266 this.nextTargetLineNumber = nextLineNumber;
267 }
268
269 /**
270 * Returns the default Source accounting line class.
271 *
272 * @see org.kuali.kfs.sys.document.AccountingDocument#getSourceAccountingLineClass()
273 */
274 public Class getSourceAccountingLineClass() {
275 if (sourceAccountingLineClass == null) {
276 sourceAccountingLineClass = (getDataDictionaryEntry().getAccountingLineGroups() != null && getDataDictionaryEntry().getAccountingLineGroups().containsKey("source") && getDataDictionaryEntry().getAccountingLineGroups().get("source").getAccountingLineClass() != null) ? getDataDictionaryEntry().getAccountingLineGroups().get("source").getAccountingLineClass() : SourceAccountingLine.class;
277 }
278 return sourceAccountingLineClass;
279 }
280
281 /**
282 * Returns the default Target accounting line class.
283 *
284 * @see org.kuali.kfs.sys.document.AccountingDocument#getTargetAccountingLineClass()
285 */
286 public Class getTargetAccountingLineClass() {
287 if (targetAccountingLineClass == null) {
288 targetAccountingLineClass = (getDataDictionaryEntry().getAccountingLineGroups() != null && getDataDictionaryEntry().getAccountingLineGroups().containsKey("target") && getDataDictionaryEntry().getAccountingLineGroups().get("target").getAccountingLineClass() != null) ? getDataDictionaryEntry().getAccountingLineGroups().get("target").getAccountingLineClass() : TargetAccountingLine.class;
289 }
290 return targetAccountingLineClass;
291 }
292
293 /**
294 * Used to get the appropriate <code>{@link AccountingLineParser}</code> for the <code>Document</code>
295 *
296 * @return AccountingLineParser
297 */
298 public AccountingLineParser getAccountingLineParser() {
299 try {
300 if (getDataDictionaryEntry().getImportedLineParserClass() != null) {
301 return getDataDictionaryEntry().getImportedLineParserClass().newInstance();
302 }
303 }
304 catch (InstantiationException ie) {
305 throw new IllegalStateException("Accounting Line Parser class " + getDataDictionaryEntry().getImportedLineParserClass().getName() + " cannot be instantiated", ie);
306 }
307 catch (IllegalAccessException iae) {
308 throw new IllegalStateException("Illegal Access Exception while attempting to instantiate Accounting Line Parser class " + getDataDictionaryEntry().getImportedLineParserClass().getName(), iae);
309 }
310 return new AccountingLineParserBase();
311 }
312
313 /**
314 * @return the data dictionary entry for this document
315 */
316 public FinancialSystemTransactionalDocumentEntry getDataDictionaryEntry() {
317 if (dataDictionaryEntry == null) {
318 dataDictionaryEntry = (FinancialSystemTransactionalDocumentEntry) SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getDocumentEntry(SpringContext.getBean(DataDictionaryService.class).getValidDocumentTypeNameByClass(getClass()));
319 }
320 return dataDictionaryEntry;
321 }
322
323 public String getSourceAccountingLineEntryName() {
324 return this.getSourceAccountingLineClass().getName();
325 }
326
327 public String getTargetAccountingLineEntryName() {
328 return this.getTargetAccountingLineClass().getName();
329 }
330
331 public List<GeneralLedgerPendingEntrySourceDetail> getGeneralLedgerPendingEntrySourceDetails() {
332 List<GeneralLedgerPendingEntrySourceDetail> accountingLines = new ArrayList<GeneralLedgerPendingEntrySourceDetail>();
333 if (getSourceAccountingLines() != null) {
334 Iterator iter = getSourceAccountingLines().iterator();
335 while (iter.hasNext()) {
336 accountingLines.add((GeneralLedgerPendingEntrySourceDetail) iter.next());
337 }
338 }
339 if (getTargetAccountingLines() != null) {
340 Iterator iter = getTargetAccountingLines().iterator();
341 while (iter.hasNext()) {
342 accountingLines.add((GeneralLedgerPendingEntrySourceDetail) iter.next());
343 }
344 }
345 return accountingLines;
346 }
347
348 public void customizeExplicitGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySourceDetail postable, GeneralLedgerPendingEntry explicitEntry) {
349 }
350
351 public boolean customizeOffsetGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySourceDetail accountingLine, GeneralLedgerPendingEntry explicitEntry, GeneralLedgerPendingEntry offsetEntry) {
352 return true;
353 }
354
355 /**
356 * @see org.kuali.kfs.sys.document.GeneralLedgerPostingDocumentBase#toCopy()
357 */
358 @Override
359 public void toCopy() throws WorkflowException {
360 super.toCopy();
361 copyAccountingLines(false);
362 updatePostingYearForAccountingLines(getSourceAccountingLines());
363 updatePostingYearForAccountingLines(getTargetAccountingLines());
364 }
365
366 /**
367 * @see org.kuali.kfs.sys.document.GeneralLedgerPostingDocumentBase#toErrorCorrection()
368 */
369 @Override
370 public void toErrorCorrection() throws WorkflowException {
371 super.toErrorCorrection();
372 copyAccountingLines(true);
373 }
374
375 /**
376 * Copies accounting lines but sets new document number and version If error correction, reverses line amount.
377 */
378 protected void copyAccountingLines(boolean isErrorCorrection) {
379 if (getSourceAccountingLines() != null) {
380 for (Iterator iter = getSourceAccountingLines().iterator(); iter.hasNext();) {
381 AccountingLineBase sourceLine = (AccountingLineBase) iter.next();
382 sourceLine.setDocumentNumber(getDocumentNumber());
383 sourceLine.setVersionNumber(new Long(1));
384 if (isErrorCorrection) {
385 sourceLine.setAmount(sourceLine.getAmount().negated());
386 }
387 }
388 }
389
390 if (getTargetAccountingLines() != null) {
391 for (Iterator iter = getTargetAccountingLines().iterator(); iter.hasNext();) {
392 AccountingLineBase targetLine = (AccountingLineBase) iter.next();
393 targetLine.setDocumentNumber(getDocumentNumber());
394 targetLine.setVersionNumber(new Long(1));
395 if (isErrorCorrection) {
396 targetLine.setAmount(targetLine.getAmount().negated());
397 }
398 }
399 }
400 }
401
402 /**
403 * Updates the posting year on accounting lines to be the current posting year
404 *
405 * @param lines a List of accounting lines to update
406 */
407 protected void updatePostingYearForAccountingLines(List<AccountingLine> lines) {
408 if (lines != null) {
409 for (AccountingLine line : lines) {
410 if (!line.getPostingYear().equals(getPostingYear())) {
411 line.setPostingYear(getPostingYear());
412 }
413 }
414 }
415 }
416
417 /**
418 * @see org.kuali.rice.kns.document.DocumentBase#buildListOfDeletionAwareLists()
419 */
420 @Override
421 public List buildListOfDeletionAwareLists() {
422 List managedLists = super.buildListOfDeletionAwareLists();
423
424 managedLists.add(getSourceAccountingLines());
425 managedLists.add(getTargetAccountingLines());
426
427 return managedLists;
428 }
429
430 public void prepareForSave(KualiDocumentEvent event) {
431 if (!(event instanceof AccountingDocumentSaveWithNoLedgerEntryGenerationEvent)) { // only generate entries if the rule event
432 // specifically allows us to
433 if (!SpringContext.getBean(GeneralLedgerPendingEntryService.class).generateGeneralLedgerPendingEntries(this)) {
434 logErrors();
435 throw new ValidationException("general ledger GLPE generation failed");
436 }
437 }
438 super.prepareForSave(event);
439 }
440
441 @Override
442 public List generateSaveEvents() {
443 List events = new ArrayList();
444
445 // foreach (source, target)
446 // 1. retrieve persisted accountingLines for document
447 // 2. retrieve current accountingLines from given document
448 // 3. compare, creating add/delete/update events as needed
449 // 4. apply rules as appropriate returned events
450 List persistedSourceLines = getPersistedSourceAccountingLinesForComparison();
451 List currentSourceLines = getSourceAccountingLinesForComparison();
452
453 List sourceEvents = generateEvents(persistedSourceLines, currentSourceLines, KFSConstants.DOCUMENT_PROPERTY_NAME + "." + KFSConstants.SOURCE_ACCOUNTING_LINE_ERRORS, this);
454 for (Iterator i = sourceEvents.iterator(); i.hasNext();) {
455 AccountingLineEvent sourceEvent = (AccountingLineEvent) i.next();
456 events.add(sourceEvent);
457 }
458
459 List persistedTargetLines = getPersistedTargetAccountingLinesForComparison();
460 List currentTargetLines = getTargetAccountingLinesForComparison();
461
462 List targetEvents = generateEvents(persistedTargetLines, currentTargetLines, KFSConstants.DOCUMENT_PROPERTY_NAME + "." + KFSConstants.TARGET_ACCOUNTING_LINE_ERRORS, this);
463 for (Iterator i = targetEvents.iterator(); i.hasNext();) {
464 AccountingLineEvent targetEvent = (AccountingLineEvent) i.next();
465 events.add(targetEvent);
466 }
467
468 return events;
469 }
470
471 /**
472 * This method gets the Target Accounting Lines that will be used in comparisons
473 *
474 * @return
475 */
476 protected List getTargetAccountingLinesForComparison() {
477 return getTargetAccountingLines();
478 }
479
480 /**
481 * This method gets the Persisted Target Accounting Lines that will be used in comparisons
482 *
483 * @return
484 */
485 protected List getPersistedTargetAccountingLinesForComparison() {
486 return SpringContext.getBean(AccountingLineService.class).getByDocumentHeaderId(getTargetAccountingLineClass(), getDocumentNumber());
487 }
488
489 /**
490 * This method gets the Source Accounting Lines that will be used in comparisons
491 *
492 * @return
493 */
494 protected List getSourceAccountingLinesForComparison() {
495 return getSourceAccountingLines();
496 }
497
498 /**
499 * This method gets the Persisted Source Accounting Lines that will be used in comparisons
500 *
501 * @return
502 */
503 protected List getPersistedSourceAccountingLinesForComparison() {
504 return SpringContext.getBean(AccountingLineService.class).getByDocumentHeaderId(getSourceAccountingLineClass(), getDocumentNumber());
505 }
506
507 /**
508 * Generates a List of instances of AccountingLineEvent subclasses, one for each accountingLine in the union of the
509 * persistedLines and currentLines lists. Events in the list will be grouped in order by event-type (review, update, add,
510 * delete).
511 *
512 * @param persistedLines
513 * @param currentLines
514 * @param errorPathPrefix
515 * @param document
516 * @return List of AccountingLineEvent subclass instances
517 */
518 protected List generateEvents(List persistedLines, List currentLines, String errorPathPrefix, TransactionalDocument document) {
519 List addEvents = new ArrayList();
520 List updateEvents = new ArrayList();
521 List reviewEvents = new ArrayList();
522 List deleteEvents = new ArrayList();
523
524 //
525 // generate events
526 Map persistedLineMap = buildAccountingLineMap(persistedLines);
527
528 // (iterate through current lines to detect additions and updates, removing affected lines from persistedLineMap as we go
529 // so deletions can be detected by looking at whatever remains in persistedLineMap)
530 int index = 0;
531 for (Iterator i = currentLines.iterator(); i.hasNext(); index++) {
532 String indexedErrorPathPrefix = errorPathPrefix + "[" + index + "]";
533 AccountingLine currentLine = (AccountingLine) i.next();
534 Integer key = currentLine.getSequenceNumber();
535
536 AccountingLine persistedLine = (AccountingLine) persistedLineMap.get(key);
537 // if line is both current and persisted...
538 if (persistedLine != null) {
539 // ...check for updates
540 if (!currentLine.isLike(persistedLine)) {
541 UpdateAccountingLineEvent updateEvent = new UpdateAccountingLineEvent(indexedErrorPathPrefix, document, persistedLine, currentLine);
542 updateEvents.add(updateEvent);
543 }
544 else {
545 ReviewAccountingLineEvent reviewEvent = new ReviewAccountingLineEvent(indexedErrorPathPrefix, document, currentLine);
546 reviewEvents.add(reviewEvent);
547 }
548
549 persistedLineMap.remove(key);
550 }
551 else {
552 // it must be a new addition
553 AddAccountingLineEvent addEvent = new AddAccountingLineEvent(indexedErrorPathPrefix, document, currentLine);
554 addEvents.add(addEvent);
555 }
556 }
557
558 // detect deletions
559 for (Iterator i = persistedLineMap.entrySet().iterator(); i.hasNext();) {
560 // the deleted line is not displayed on the page, so associate the error with the whole group
561 String groupErrorPathPrefix = errorPathPrefix + KFSConstants.ACCOUNTING_LINE_GROUP_SUFFIX;
562 Map.Entry e = (Map.Entry) i.next();
563 AccountingLine persistedLine = (AccountingLine) e.getValue();
564 DeleteAccountingLineEvent deleteEvent = new DeleteAccountingLineEvent(groupErrorPathPrefix, document, persistedLine, true);
565 deleteEvents.add(deleteEvent);
566 }
567
568
569 //
570 // merge the lists
571 List lineEvents = new ArrayList();
572 lineEvents.addAll(reviewEvents);
573 lineEvents.addAll(updateEvents);
574 lineEvents.addAll(addEvents);
575 lineEvents.addAll(deleteEvents);
576
577 return lineEvents;
578 }
579
580
581 /**
582 * @param accountingLines
583 * @return Map containing accountingLines from the given List, indexed by their sequenceNumber
584 */
585 protected Map buildAccountingLineMap(List accountingLines) {
586 Map lineMap = new HashMap();
587
588 for (Iterator i = accountingLines.iterator(); i.hasNext();) {
589 AccountingLine accountingLine = (AccountingLine) i.next();
590 Integer sequenceNumber = accountingLine.getSequenceNumber();
591
592 Object oldLine = lineMap.put(sequenceNumber, accountingLine);
593
594 // verify that sequence numbers are unique...
595 if (oldLine != null) {
596 throw new IllegalStateException("sequence number collision detected for sequence number " + sequenceNumber);
597 }
598 }
599
600 return lineMap;
601 }
602
603 /**
604 * Perform business rules common to all transactional documents when generating general ledger pending entries.
605 *
606 * @see org.kuali.rice.kns.rule.GenerateGeneralLedgerPendingEntriesRule#processGenerateGeneralLedgerPendingEntries(org.kuali.rice.kns.document.AccountingDocument,
607 * org.kuali.rice.kns.bo.AccountingLine, org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper)
608 */
609 public boolean generateGeneralLedgerPendingEntries(GeneralLedgerPendingEntrySourceDetail glpeSourceDetail, GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
610 LOG.debug("processGenerateGeneralLedgerPendingEntries(AccountingDocument, AccountingLine, GeneralLedgerPendingEntrySequenceHelper) - start");
611
612 // handle the explicit entry
613 // create a reference to the explicitEntry to be populated, so we can pass to the offset method later
614 GeneralLedgerPendingEntry explicitEntry = new GeneralLedgerPendingEntry();
615 processExplicitGeneralLedgerPendingEntry(sequenceHelper, glpeSourceDetail, explicitEntry);
616
617 // increment the sequence counter
618 sequenceHelper.increment();
619
620 // handle the offset entry
621 GeneralLedgerPendingEntry offsetEntry = new GeneralLedgerPendingEntry(explicitEntry);
622 boolean success = processOffsetGeneralLedgerPendingEntry(sequenceHelper, glpeSourceDetail, explicitEntry, offsetEntry);
623
624 LOG.debug("processGenerateGeneralLedgerPendingEntries(AccountingDocument, AccountingLine, GeneralLedgerPendingEntrySequenceHelper) - end");
625 return success;
626 }
627
628 /**
629 * This method processes all necessary information to build an explicit general ledger entry, and then adds that to the
630 * document.
631 *
632 * @param accountingDocument
633 * @param sequenceHelper
634 * @param accountingLine
635 * @param explicitEntry
636 * @return boolean True if the explicit entry generation was successful, false otherwise.
637 */
638 protected void processExplicitGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySequenceHelper sequenceHelper, GeneralLedgerPendingEntrySourceDetail glpeSourceDetail, GeneralLedgerPendingEntry explicitEntry) {
639 LOG.debug("processExplicitGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry) - start");
640
641 // populate the explicit entry
642 SpringContext.getBean(GeneralLedgerPendingEntryService.class).populateExplicitGeneralLedgerPendingEntry(this, glpeSourceDetail, sequenceHelper, explicitEntry);
643
644 // hook for children documents to implement document specific GLPE field mappings
645 customizeExplicitGeneralLedgerPendingEntry(glpeSourceDetail, explicitEntry);
646
647 addPendingEntry(explicitEntry);
648
649 LOG.debug("processExplicitGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry) - end");
650 }
651
652 /**
653 * This method processes an accounting line's information to build an offset entry, and then adds that to the document.
654 *
655 * @param accountingDocument
656 * @param sequenceHelper
657 * @param accountingLine
658 * @param explicitEntry
659 * @param offsetEntry
660 * @return boolean True if the offset generation is successful.
661 */
662 protected boolean processOffsetGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySequenceHelper sequenceHelper, GeneralLedgerPendingEntrySourceDetail postable, GeneralLedgerPendingEntry explicitEntry, GeneralLedgerPendingEntry offsetEntry) {
663 LOG.debug("processOffsetGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry, GeneralLedgerPendingEntry) - start");
664
665 // populate the offset entry
666 boolean success = SpringContext.getBean(GeneralLedgerPendingEntryService.class).populateOffsetGeneralLedgerPendingEntry(getPostingYear(), explicitEntry, sequenceHelper, offsetEntry);
667
668 // hook for children documents to implement document specific field mappings for the GLPE
669 success &= customizeOffsetGeneralLedgerPendingEntry(postable, explicitEntry, offsetEntry);
670
671 addPendingEntry(offsetEntry);
672
673 LOG.debug("processOffsetGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry, GeneralLedgerPendingEntry) - end");
674 return success;
675 }
676
677 /**
678 * Returns one of the two given String's; if the preferred String is not null and has a length > 0, then it is returned,
679 * otherwise the second String is returned
680 *
681 * @param preferredString the String you're hoping isn't blank so you can get it back
682 * @param secondaryString the "rebound" String, which you'll end up with if the preferred String is blank
683 * @return one of the String's
684 */
685 protected String getEntryValue(String preferredString, String secondaryString) {
686 return (StringUtils.isNotBlank(preferredString) ? preferredString : secondaryString);
687 }
688
689 /**
690 * @see org.kuali.kfs.document.GeneralLedgerPostingHelper#isDebit(org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail)
691 */
692 public abstract boolean isDebit(GeneralLedgerPendingEntrySourceDetail postable);
693
694 /**
695 * Most accounting documents don't need to generate document level GLPE's, so don't do anything in the default implementation
696 *
697 * @see org.kuali.kfs.document.GeneralLedgerPostingHelper#processGenerateDocumentGeneralLedgerPendingEntries(org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper)
698 * @return always true, because we've always successfully not generating anything
699 */
700 public boolean generateDocumentGeneralLedgerPendingEntries(GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
701 return true;
702 }
703
704 /**
705 * GLPE amounts are ALWAYS positive, so just take the absolute value of the accounting line's amount.
706 *
707 * @param accountingLine
708 * @return KualiDecimal The amount that will be used to populate the GLPE.
709 */
710 public KualiDecimal getGeneralLedgerPendingEntryAmountForDetail(GeneralLedgerPendingEntrySourceDetail postable) {
711 LOG.debug("getGeneralLedgerPendingEntryAmountForAccountingLine(AccountingLine) - start");
712
713 KualiDecimal returnKualiDecimal = postable.getAmount().abs();
714 LOG.debug("getGeneralLedgerPendingEntryAmountForAccountingLine(AccountingLine) - end");
715 return returnKualiDecimal;
716 }
717
718 public Class<? extends AccountingDocument> getDocumentClassForAccountingLineValueAllowedValidation() {
719 return this.getClass();
720 }
721 }