View Javadoc
1   /*
2    * Copyright 2007 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.sys.document;
17  import java.util.ArrayList;
18  import java.util.HashMap;
19  import java.util.Iterator;
20  import java.util.List;
21  import java.util.Map;
22  
23  import org.apache.commons.lang.StringUtils;
24  import org.kuali.ole.sys.OLEConstants;
25  import org.kuali.ole.sys.businessobject.AccountingLine;
26  import org.kuali.ole.sys.businessobject.AccountingLineBase;
27  import org.kuali.ole.sys.businessobject.AccountingLineParser;
28  import org.kuali.ole.sys.businessobject.AccountingLineParserBase;
29  import org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntry;
30  import org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper;
31  import org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntrySourceDetail;
32  import org.kuali.ole.sys.businessobject.SourceAccountingLine;
33  import org.kuali.ole.sys.businessobject.TargetAccountingLine;
34  import org.kuali.ole.sys.context.SpringContext;
35  import org.kuali.ole.sys.document.datadictionary.FinancialSystemTransactionalDocumentEntry;
36  import org.kuali.ole.sys.document.validation.event.AccountingDocumentSaveWithNoLedgerEntryGenerationEvent;
37  import org.kuali.ole.sys.document.validation.event.AccountingLineEvent;
38  import org.kuali.ole.sys.document.validation.event.AddAccountingLineEvent;
39  import org.kuali.ole.sys.document.validation.event.DeleteAccountingLineEvent;
40  import org.kuali.ole.sys.document.validation.event.ReviewAccountingLineEvent;
41  import org.kuali.ole.sys.document.validation.event.UpdateAccountingLineEvent;
42  import org.kuali.ole.sys.service.AccountingLineService;
43  import org.kuali.ole.sys.service.GeneralLedgerPendingEntryService;
44  import org.kuali.rice.core.api.util.type.KualiDecimal;
45  import org.kuali.rice.kew.api.exception.WorkflowException;
46  import org.kuali.rice.kns.service.DataDictionaryService;
47  import org.kuali.rice.krad.document.TransactionalDocument;
48  import org.kuali.rice.krad.exception.ValidationException;
49  import org.kuali.rice.krad.rules.rule.event.KualiDocumentEvent;
50  import org.kuali.rice.krad.util.ObjectUtils;
51  
52  /**
53   * Base implementation class for financial edocs.
54   */
55  public abstract class AccountingDocumentBase extends GeneralLedgerPostingDocumentBase implements AccountingDocument, GeneralLedgerPendingEntrySource {
56      protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AccountingDocumentBase.class);
57  
58      protected Integer nextSourceLineNumber;
59      protected Integer nextTargetLineNumber;
60      protected List sourceAccountingLines;
61      protected List targetAccountingLines;
62  
63      protected transient FinancialSystemTransactionalDocumentEntry dataDictionaryEntry;
64      protected transient Class sourceAccountingLineClass;
65      protected transient Class targetAccountingLineClass;
66  
67      /**
68       * Default constructor.
69       */
70      public AccountingDocumentBase() {
71          super();
72          this.nextSourceLineNumber = new Integer(1);
73          this.nextTargetLineNumber = new Integer(1);
74          setSourceAccountingLines(new ArrayList());
75          setTargetAccountingLines(new ArrayList());
76      }
77  
78      /**
79       * @see org.kuali.ole.sys.document.AccountingDocument#getSourceAccountingLines()
80       */
81      @Override
82      public List getSourceAccountingLines() {
83          return this.sourceAccountingLines;
84      }
85  
86      /**
87       * @see org.kuali.ole.sys.document.AccountingDocument#setSourceAccountingLines(java.util.List)
88       */
89      @Override
90      public void setSourceAccountingLines(List sourceLines) {
91          this.sourceAccountingLines = sourceLines;
92      }
93  
94      /**
95       * @see org.kuali.ole.sys.document.AccountingDocument#getTargetAccountingLines()
96       */
97      @Override
98      public List getTargetAccountingLines() {
99          return this.targetAccountingLines;
100     }
101 
102     /**
103      * @see org.kuali.ole.sys.document.AccountingDocument#setTargetAccountingLines(java.util.List)
104      */
105     @Override
106     public void setTargetAccountingLines(List targetLines) {
107         this.targetAccountingLines = targetLines;
108     }
109 
110     /**
111      * This implementation sets the sequence number appropriately for the passed in source accounting line using the value that has
112      * been stored in the nextSourceLineNumber variable, adds the accounting line to the list that is aggregated by this object, and
113      * then handles incrementing the nextSourceLineNumber variable for you.
114      *
115      * @see org.kuali.ole.sys.document.AccountingDocument#addSourceAccountingLine(SourceAccountingLine)
116      */
117     @Override
118     public void addSourceAccountingLine(SourceAccountingLine line) {
119         line.setSequenceNumber(this.getNextSourceLineNumber());
120         this.sourceAccountingLines.add(line);
121         this.nextSourceLineNumber = new Integer(this.getNextSourceLineNumber().intValue() + 1);
122     }
123 
124     /**
125      * This implementation sets the sequence number appropriately for the passed in target accounting line using the value that has
126      * been stored in the nextTargetLineNumber variable, adds the accounting line to the list that is aggregated by this object, and
127      * then handles incrementing the nextTargetLineNumber variable for you.
128      *
129      * @see org.kuali.ole.sys.document.AccountingDocument#addTargetAccountingLine(TargetAccountingLine)
130      */
131     @Override
132     public void addTargetAccountingLine(TargetAccountingLine line) {
133         line.setSequenceNumber(this.getNextTargetLineNumber());
134         this.targetAccountingLines.add(line);
135         this.nextTargetLineNumber = new Integer(this.getNextTargetLineNumber().intValue() + 1);
136     }
137 
138     /**
139      * This implementation is coupled tightly with some underlying issues that the Struts PojoProcessor plugin has with how objects
140      * get instantiated within lists. The first three lines are required otherwise when the PojoProcessor tries to automatically
141      * 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
142      * instances at indices before that one are not being instantiated. So changing the code below will cause adding lines to break
143      * if you add more than one item to the list.
144      *
145      * @see org.kuali.ole.sys.document.AccountingDocument#getSourceAccountingLine(int)
146      */
147     @Override
148     public SourceAccountingLine getSourceAccountingLine(int index) {
149         while (getSourceAccountingLines().size() <= index) {
150             try {
151                 getSourceAccountingLines().add(getSourceAccountingLineClass().newInstance());
152             }
153             catch (InstantiationException e) {
154                 throw new RuntimeException("Unable to get class");
155             }
156             catch (IllegalAccessException e) {
157                 throw new RuntimeException("Unable to get class");
158             }
159         }
160         return (SourceAccountingLine) getSourceAccountingLines().get(index);
161     }
162 
163     /**
164      * This implementation is coupled tightly with some underlying issues that the Struts PojoProcessor plugin has with how objects
165      * get instantiated within lists. The first three lines are required otherwise when the PojoProcessor tries to automatically
166      * 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
167      * instances at indices before that one are not being instantiated. So changing the code below will cause adding lines to break
168      * if you add more than one item to the list.
169      *
170      * @see org.kuali.ole.sys.document.AccountingDocument#getTargetAccountingLine(int)
171      */
172     @Override
173     public TargetAccountingLine getTargetAccountingLine(int index) {
174         while (getTargetAccountingLines().size() <= index) {
175             try {
176                 getTargetAccountingLines().add(getTargetAccountingLineClass().newInstance());
177             }
178             catch (InstantiationException e) {
179                 throw new RuntimeException("Unable to get class");
180             }
181             catch (IllegalAccessException e) {
182                 throw new RuntimeException("Unable to get class");
183             }
184         }
185         return (TargetAccountingLine) getTargetAccountingLines().get(index);
186     }
187 
188     /**
189      * @see org.kuali.ole.sys.document.AccountingDocument#getSourceAccountingLinesSectionTitle()
190      */
191     @Override
192     public String getSourceAccountingLinesSectionTitle() {
193         return OLEConstants.SOURCE;
194     }
195 
196     /**
197      * @see org.kuali.ole.sys.document.AccountingDocument#getTargetAccountingLinesSectionTitle()
198      */
199     @Override
200     public String getTargetAccountingLinesSectionTitle() {
201         return OLEConstants.TARGET;
202     }
203 
204     /**
205      * Since one side of the document should match the other and the document should balance, the total dollar amount for the
206      * document should either be the expense line or the income line. This is the default implementation of this interface method so
207      * it should be overridden appropriately if your document cannot make this assumption.
208      *
209      * @return if target total is zero, source total, otherwise target total
210      */
211     public KualiDecimal getTotalDollarAmount() {
212         return getTargetTotal().equals(KualiDecimal.ZERO) ? getSourceTotal().abs() : getTargetTotal().abs();
213     }
214 
215     /**
216      * @see org.kuali.ole.sys.document.AccountingDocument#getSourceTotal()
217      */
218     @Override
219     public KualiDecimal getSourceTotal() {
220         KualiDecimal total = KualiDecimal.ZERO;
221         AccountingLineBase al = null;
222         Iterator iter = getSourceAccountingLines().iterator();
223         while (iter.hasNext()) {
224             al = (AccountingLineBase) iter.next();
225 
226             KualiDecimal amount = al.getAmount();
227             if (amount != null) {
228                 total = total.add(amount);
229             }
230         }
231         return total;
232     }
233 
234     /**
235      * @see org.kuali.ole.sys.document.AccountingDocument#getTargetTotal()
236      */
237     @Override
238     public KualiDecimal getTargetTotal() {
239         KualiDecimal total = KualiDecimal.ZERO;
240         AccountingLineBase al = null;
241         Iterator iter = getTargetAccountingLines().iterator();
242         while (iter.hasNext()) {
243             al = (AccountingLineBase) iter.next();
244 
245             KualiDecimal amount = al.getAmount();
246             if (amount != null) {
247                 total = total.add(amount);
248             }
249         }
250         return total;
251     }
252 
253     /**
254      * @see org.kuali.ole.sys.document.AccountingDocument#getNextSourceLineNumber()
255      */
256     @Override
257     public Integer getNextSourceLineNumber() {
258         return this.nextSourceLineNumber;
259     }
260 
261     /**
262      * @see org.kuali.ole.sys.document.AccountingDocument#setNextSourceLineNumber(java.lang.Integer)
263      */
264     @Override
265     public void setNextSourceLineNumber(Integer nextLineNumber) {
266         this.nextSourceLineNumber = nextLineNumber;
267     }
268 
269     /**
270      * @see org.kuali.ole.sys.document.AccountingDocument#getNextTargetLineNumber()
271      */
272     @Override
273     public Integer getNextTargetLineNumber() {
274         return this.nextTargetLineNumber;
275     }
276 
277     /**
278      * @see org.kuali.ole.sys.document.AccountingDocument#setNextTargetLineNumber(java.lang.Integer)
279      */
280     @Override
281     public void setNextTargetLineNumber(Integer nextLineNumber) {
282         this.nextTargetLineNumber = nextLineNumber;
283     }
284 
285     /**
286      * Returns the default Source accounting line class.
287      *
288      * @see org.kuali.ole.sys.document.AccountingDocument#getSourceAccountingLineClass()
289      */
290     @Override
291     public Class getSourceAccountingLineClass() {
292         if (sourceAccountingLineClass == null) {
293             sourceAccountingLineClass = (getDataDictionaryEntry().getAccountingLineGroups() != null && getDataDictionaryEntry().getAccountingLineGroups().containsKey("source") && getDataDictionaryEntry().getAccountingLineGroups().get("source").getAccountingLineClass() != null) ? getDataDictionaryEntry().getAccountingLineGroups().get("source").getAccountingLineClass() : SourceAccountingLine.class;
294         }
295         return sourceAccountingLineClass;
296     }
297 
298     /**
299      * Returns the default Target accounting line class.
300      *
301      * @see org.kuali.ole.sys.document.AccountingDocument#getTargetAccountingLineClass()
302      */
303     @Override
304     public Class getTargetAccountingLineClass() {
305         if (targetAccountingLineClass == null) {
306             targetAccountingLineClass = (getDataDictionaryEntry().getAccountingLineGroups() != null && getDataDictionaryEntry().getAccountingLineGroups().containsKey("target") && getDataDictionaryEntry().getAccountingLineGroups().get("target").getAccountingLineClass() != null) ? getDataDictionaryEntry().getAccountingLineGroups().get("target").getAccountingLineClass() : TargetAccountingLine.class;
307         }
308         return targetAccountingLineClass;
309     }
310 
311     /**
312      * Used to get the appropriate <code>{@link AccountingLineParser}</code> for the <code>Document</code>
313      *
314      * @return AccountingLineParser
315      */
316     @Override
317     public AccountingLineParser getAccountingLineParser() {
318         try {
319             if (getDataDictionaryEntry().getImportedLineParserClass() != null) {
320                 return getDataDictionaryEntry().getImportedLineParserClass().newInstance();
321             }
322         }
323         catch (InstantiationException ie) {
324             throw new IllegalStateException("Accounting Line Parser class " + getDataDictionaryEntry().getImportedLineParserClass().getName() + " cannot be instantiated", ie);
325         }
326         catch (IllegalAccessException iae) {
327             throw new IllegalStateException("Illegal Access Exception while attempting to instantiate Accounting Line Parser class " + getDataDictionaryEntry().getImportedLineParserClass().getName(), iae);
328         }
329         return new AccountingLineParserBase();
330     }
331 
332     /**
333      * @return the data dictionary entry for this document
334      */
335     public FinancialSystemTransactionalDocumentEntry getDataDictionaryEntry() {
336         if (dataDictionaryEntry == null) {
337             dataDictionaryEntry = (FinancialSystemTransactionalDocumentEntry) SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getDocumentEntry(SpringContext.getBean(DataDictionaryService.class).getValidDocumentTypeNameByClass(getClass()));
338         }
339         return dataDictionaryEntry;
340     }
341 
342     @Override
343     public String getSourceAccountingLineEntryName() {
344         return this.getSourceAccountingLineClass().getName();
345     }
346 
347     @Override
348     public String getTargetAccountingLineEntryName() {
349         return this.getTargetAccountingLineClass().getName();
350     }
351 
352     @Override
353     public List<GeneralLedgerPendingEntrySourceDetail> getGeneralLedgerPendingEntrySourceDetails() {
354         List<GeneralLedgerPendingEntrySourceDetail> accountingLines = new ArrayList<GeneralLedgerPendingEntrySourceDetail>();
355         if (getSourceAccountingLines() != null) {
356             Iterator iter = getSourceAccountingLines().iterator();
357             while (iter.hasNext()) {
358                 accountingLines.add((GeneralLedgerPendingEntrySourceDetail) iter.next());
359             }
360         }
361         if (getTargetAccountingLines() != null) {
362             Iterator iter = getTargetAccountingLines().iterator();
363             while (iter.hasNext()) {
364                 accountingLines.add((GeneralLedgerPendingEntrySourceDetail) iter.next());
365             }
366         }
367         return accountingLines;
368     }
369 
370     public void customizeExplicitGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySourceDetail postable, GeneralLedgerPendingEntry explicitEntry) {
371     }
372 
373     public boolean customizeOffsetGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySourceDetail accountingLine, GeneralLedgerPendingEntry explicitEntry, GeneralLedgerPendingEntry offsetEntry) {
374         return true;
375     }
376 
377     /**
378      * @see org.kuali.ole.sys.document.GeneralLedgerPostingDocumentBase#toCopy()
379      */
380     @Override
381     public void toCopy() throws WorkflowException {
382         super.toCopy();
383         copyAccountingLines(false);
384         updatePostingYearForAccountingLines(getSourceAccountingLines());
385         updatePostingYearForAccountingLines(getTargetAccountingLines());
386     }
387 
388     /**
389      * @see org.kuali.ole.sys.document.GeneralLedgerPostingDocumentBase#toErrorCorrection()
390      */
391     @Override
392     public void toErrorCorrection() throws WorkflowException {
393         super.toErrorCorrection();
394         copyAccountingLines(true);
395     }
396 
397     /**
398      * Copies accounting lines but sets new document number and version If error correction, reverses line amount.
399      */
400     protected void copyAccountingLines(boolean isErrorCorrection) {
401         if (getSourceAccountingLines() != null) {
402             for (Iterator iter = getSourceAccountingLines().iterator(); iter.hasNext();) {
403                 AccountingLineBase sourceLine = (AccountingLineBase) iter.next();
404                 sourceLine.setDocumentNumber(getDocumentNumber());
405                 sourceLine.setVersionNumber(new Long(1));
406                 if (isErrorCorrection) {
407                     sourceLine.setAmount(sourceLine.getAmount().negated());
408                 }
409             }
410         }
411 
412         if (getTargetAccountingLines() != null) {
413             for (Iterator iter = getTargetAccountingLines().iterator(); iter.hasNext();) {
414                 AccountingLineBase targetLine = (AccountingLineBase) iter.next();
415                 targetLine.setDocumentNumber(getDocumentNumber());
416                 targetLine.setVersionNumber(new Long(1));
417                 if (isErrorCorrection) {
418                     targetLine.setAmount(targetLine.getAmount().negated());
419                 }
420             }
421         }
422     }
423 
424     /**
425      * Updates the posting year on accounting lines to be the current posting year
426      *
427      * @param lines a List of accounting lines to update
428      */
429     protected void updatePostingYearForAccountingLines(List<AccountingLine> lines) {
430         if (lines != null) {
431             for (AccountingLine line : lines) {
432                 if (!line.getPostingYear().equals(getPostingYear())) {
433                     line.setPostingYear(getPostingYear());
434                 }
435             }
436         }
437     }
438 
439     /**
440      * @see org.kuali.rice.krad.document.DocumentBase#buildListOfDeletionAwareLists()
441      */
442     @Override
443     public List buildListOfDeletionAwareLists() {
444         List managedLists = super.buildListOfDeletionAwareLists();
445 
446         managedLists.add(getSourceAccountingLines());
447         managedLists.add(getTargetAccountingLines());
448 
449         return managedLists;
450     }
451 
452     @Override
453     public void prepareForSave(KualiDocumentEvent event) {
454         if (!(event instanceof AccountingDocumentSaveWithNoLedgerEntryGenerationEvent)) { // only generate entries if the rule event
455                                                                                           // specifically allows us to
456             if (!SpringContext.getBean(GeneralLedgerPendingEntryService.class).generateGeneralLedgerPendingEntries(this)) {
457                 logErrors();
458                 throw new ValidationException("general ledger GLPE generation failed");
459             }
460         }
461         super.prepareForSave(event);
462     }
463 
464     @Override
465     public List generateSaveEvents() {
466         List events = new ArrayList();
467 
468         // foreach (source, target)
469         // 1. retrieve persisted accountingLines for document
470         // 2. retrieve current accountingLines from given document
471         // 3. compare, creating add/delete/update events as needed
472         // 4. apply rules as appropriate returned events
473         List persistedSourceLines = getPersistedSourceAccountingLinesForComparison();
474         List currentSourceLines = getSourceAccountingLinesForComparison();
475 
476         List sourceEvents = generateEvents(persistedSourceLines, currentSourceLines, OLEConstants.DOCUMENT_PROPERTY_NAME + "." + OLEConstants.SOURCE_ACCOUNTING_LINE_ERRORS, this);
477         for (Iterator i = sourceEvents.iterator(); i.hasNext();) {
478             AccountingLineEvent sourceEvent = (AccountingLineEvent) i.next();
479             events.add(sourceEvent);
480         }
481 
482         List persistedTargetLines = getPersistedTargetAccountingLinesForComparison();
483         List currentTargetLines = getTargetAccountingLinesForComparison();
484 
485         List targetEvents = generateEvents(persistedTargetLines, currentTargetLines, OLEConstants.DOCUMENT_PROPERTY_NAME + "." + OLEConstants.TARGET_ACCOUNTING_LINE_ERRORS, this);
486         for (Iterator i = targetEvents.iterator(); i.hasNext();) {
487             AccountingLineEvent targetEvent = (AccountingLineEvent) i.next();
488             events.add(targetEvent);
489         }
490 
491         return events;
492     }
493 
494     /**
495      * This method gets the Target Accounting Lines that will be used in comparisons
496      *
497      * @return
498      */
499     protected List getTargetAccountingLinesForComparison() {
500         return getTargetAccountingLines();
501     }
502 
503     /**
504      * This method gets the Persisted Target Accounting Lines that will be used in comparisons
505      *
506      * @return
507      */
508     protected List getPersistedTargetAccountingLinesForComparison() {
509         return SpringContext.getBean(AccountingLineService.class).getByDocumentHeaderId(getTargetAccountingLineClass(), getDocumentNumber());
510     }
511 
512     /**
513      * This method gets the Source Accounting Lines that will be used in comparisons
514      *
515      * @return
516      */
517     protected List getSourceAccountingLinesForComparison() {
518         return getSourceAccountingLines();
519     }
520 
521     /**
522      * This method gets the Persisted Source Accounting Lines that will be used in comparisons
523      *
524      * @return
525      */
526     protected List getPersistedSourceAccountingLinesForComparison() {
527         return SpringContext.getBean(AccountingLineService.class).getByDocumentHeaderId(getSourceAccountingLineClass(), getDocumentNumber());
528     }
529 
530     /**
531      * Generates a List of instances of AccountingLineEvent subclasses, one for each accountingLine in the union of the
532      * persistedLines and currentLines lists. Events in the list will be grouped in order by event-type (review, update, add,
533      * delete).
534      *
535      * @param persistedLines
536      * @param currentLines
537      * @param errorPathPrefix
538      * @param document
539      * @return List of AccountingLineEvent subclass instances
540      */
541     protected List generateEvents(List persistedLines, List currentLines, String errorPathPrefix, TransactionalDocument document) {
542         List addEvents = new ArrayList();
543         List updateEvents = new ArrayList();
544         List reviewEvents = new ArrayList();
545         List deleteEvents = new ArrayList();
546 
547         //
548         // generate events
549         Map persistedLineMap = buildAccountingLineMap(persistedLines);
550 
551         // (iterate through current lines to detect additions and updates, removing affected lines from persistedLineMap as we go
552         // so deletions can be detected by looking at whatever remains in persistedLineMap)
553         int index = 0;
554         for (Iterator i = currentLines.iterator(); i.hasNext(); index++) {
555             String indexedErrorPathPrefix = errorPathPrefix + "[" + index + "]";
556             AccountingLine currentLine = (AccountingLine) i.next();
557             Integer key = currentLine.getSequenceNumber();
558 
559             AccountingLine persistedLine = (AccountingLine) persistedLineMap.get(key);
560             // if line is both current and persisted...
561             if (persistedLine != null) {
562                 // ...check for updates
563                 if (!currentLine.isLike(persistedLine)) {
564                     UpdateAccountingLineEvent updateEvent = new UpdateAccountingLineEvent(indexedErrorPathPrefix, document, persistedLine, currentLine);
565                     updateEvents.add(updateEvent);
566                 }
567                 else {
568                     ReviewAccountingLineEvent reviewEvent = new ReviewAccountingLineEvent(indexedErrorPathPrefix, document, currentLine);
569                     reviewEvents.add(reviewEvent);
570                 }
571 
572                 persistedLineMap.remove(key);
573             }
574             else {
575                 // it must be a new addition
576                 AddAccountingLineEvent addEvent = new AddAccountingLineEvent(indexedErrorPathPrefix, document, currentLine);
577                 addEvents.add(addEvent);
578             }
579         }
580 
581         // detect deletions
582         for (Iterator i = persistedLineMap.entrySet().iterator(); i.hasNext();) {
583             // the deleted line is not displayed on the page, so associate the error with the whole group
584             String groupErrorPathPrefix = errorPathPrefix + OLEConstants.ACCOUNTING_LINE_GROUP_SUFFIX;
585             Map.Entry e = (Map.Entry) i.next();
586             AccountingLine persistedLine = (AccountingLine) e.getValue();
587             DeleteAccountingLineEvent deleteEvent = new DeleteAccountingLineEvent(groupErrorPathPrefix, document, persistedLine, true);
588             deleteEvents.add(deleteEvent);
589         }
590 
591 
592         //
593         // merge the lists
594         List lineEvents = new ArrayList();
595         lineEvents.addAll(reviewEvents);
596         lineEvents.addAll(updateEvents);
597         lineEvents.addAll(addEvents);
598         lineEvents.addAll(deleteEvents);
599 
600         return lineEvents;
601     }
602 
603 
604     /**
605      * @param accountingLines
606      * @return Map containing accountingLines from the given List, indexed by their sequenceNumber
607      */
608     protected Map buildAccountingLineMap(List accountingLines) {
609         Map lineMap = new HashMap();
610 
611         for (Iterator i = accountingLines.iterator(); i.hasNext();) {
612             AccountingLine accountingLine = (AccountingLine) i.next();
613             Integer sequenceNumber = accountingLine.getSequenceNumber();
614 
615             Object oldLine = lineMap.put(sequenceNumber, accountingLine);
616 
617             // verify that sequence numbers are unique...
618             if (oldLine != null) {
619                 throw new IllegalStateException("sequence number collision detected for sequence number " + sequenceNumber);
620             }
621         }
622 
623         return lineMap;
624     }
625 
626     /**
627      * Perform business rules common to all transactional documents when generating general ledger pending entries.
628      *
629      * @see org.kuali.rice.krad.rule.GenerateGeneralLedgerPendingEntriesRule#processGenerateGeneralLedgerPendingEntries(org.kuali.rice.krad.document.AccountingDocument,
630      *      org.kuali.rice.krad.bo.AccountingLine, org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper)
631      */
632     @Override
633     public boolean generateGeneralLedgerPendingEntries(GeneralLedgerPendingEntrySourceDetail glpeSourceDetail, GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
634         LOG.debug("processGenerateGeneralLedgerPendingEntries(AccountingDocument, AccountingLine, GeneralLedgerPendingEntrySequenceHelper) - start");
635 
636         // handle the explicit entry
637         // create a reference to the explicitEntry to be populated, so we can pass to the offset method later
638         GeneralLedgerPendingEntry explicitEntry = new GeneralLedgerPendingEntry();
639         processExplicitGeneralLedgerPendingEntry(sequenceHelper, glpeSourceDetail, explicitEntry);
640 
641         // increment the sequence counter
642         sequenceHelper.increment();
643 
644         // handle the offset entry
645         GeneralLedgerPendingEntry offsetEntry = new GeneralLedgerPendingEntry(explicitEntry);
646         boolean success = processOffsetGeneralLedgerPendingEntry(sequenceHelper, glpeSourceDetail, explicitEntry, offsetEntry);
647 
648         LOG.debug("processGenerateGeneralLedgerPendingEntries(AccountingDocument, AccountingLine, GeneralLedgerPendingEntrySequenceHelper) - end");
649         return success;
650     }
651 
652     /**
653      * This method processes all necessary information to build an explicit general ledger entry, and then adds that to the
654      * document.
655      *
656      * @param accountingDocument
657      * @param sequenceHelper
658      * @param accountingLine
659      * @param explicitEntry
660      * @return boolean True if the explicit entry generation was successful, false otherwise.
661      */
662     protected void processExplicitGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySequenceHelper sequenceHelper, GeneralLedgerPendingEntrySourceDetail glpeSourceDetail, GeneralLedgerPendingEntry explicitEntry) {
663         LOG.debug("processExplicitGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry) - start");
664 
665         // populate the explicit entry
666         SpringContext.getBean(GeneralLedgerPendingEntryService.class).populateExplicitGeneralLedgerPendingEntry(this, glpeSourceDetail, sequenceHelper, explicitEntry);
667 
668         // hook for children documents to implement document specific GLPE field mappings
669         customizeExplicitGeneralLedgerPendingEntry(glpeSourceDetail, explicitEntry);
670 
671         addPendingEntry(explicitEntry);
672 
673         LOG.debug("processExplicitGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry) - end");
674     }
675 
676     /**
677      * This method processes an accounting line's information to build an offset entry, and then adds that to the document.
678      *
679      * @param accountingDocument
680      * @param sequenceHelper
681      * @param accountingLine
682      * @param explicitEntry
683      * @param offsetEntry
684      * @return boolean True if the offset generation is successful.
685      */
686     protected boolean processOffsetGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySequenceHelper sequenceHelper, GeneralLedgerPendingEntrySourceDetail postable, GeneralLedgerPendingEntry explicitEntry, GeneralLedgerPendingEntry offsetEntry) {
687         LOG.debug("processOffsetGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry, GeneralLedgerPendingEntry) - start");
688 
689         // populate the offset entry
690         boolean success = SpringContext.getBean(GeneralLedgerPendingEntryService.class).populateOffsetGeneralLedgerPendingEntry(getPostingYear(), explicitEntry, sequenceHelper, offsetEntry);
691 
692         // hook for children documents to implement document specific field mappings for the GLPE
693         success &= customizeOffsetGeneralLedgerPendingEntry(postable, explicitEntry, offsetEntry);
694 
695         addPendingEntry(offsetEntry);
696 
697         LOG.debug("processOffsetGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry, GeneralLedgerPendingEntry) - end");
698         return success;
699     }
700 
701     /**
702      * Returns one of the two given String's; if the preferred String is not null and has a length > 0, then it is returned,
703      * otherwise the second String is returned
704      *
705      * @param preferredString the String you're hoping isn't blank so you can get it back
706      * @param secondaryString the "rebound" String, which you'll end up with if the preferred String is blank
707      * @return one of the String's
708      */
709     protected String getEntryValue(String preferredString, String secondaryString) {
710         return (StringUtils.isNotBlank(preferredString) ? preferredString : secondaryString);
711     }
712 
713     /**
714      * @see org.kuali.ole.document.GeneralLedgerPostingHelper#isDebit(org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntrySourceDetail)
715      */
716     @Override
717     public abstract boolean isDebit(GeneralLedgerPendingEntrySourceDetail postable);
718 
719     /**
720      * Most accounting documents don't need to generate document level GLPE's, so don't do anything in the default implementation
721      *
722      * @see org.kuali.ole.document.GeneralLedgerPostingHelper#processGenerateDocumentGeneralLedgerPendingEntries(org.kuali.ole.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper)
723      * @return always true, because we've always successfully not generating anything
724      */
725     @Override
726     public boolean generateDocumentGeneralLedgerPendingEntries(GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
727         return true;
728     }
729 
730     /**
731      * GLPE amounts are ALWAYS positive, so just take the absolute value of the accounting line's amount.
732      *
733      * @param accountingLine
734      * @return KualiDecimal The amount that will be used to populate the GLPE.
735      */
736     @Override
737     public KualiDecimal getGeneralLedgerPendingEntryAmountForDetail(GeneralLedgerPendingEntrySourceDetail postable) {
738         LOG.debug("getGeneralLedgerPendingEntryAmountForAccountingLine(AccountingLine) - start");
739 
740         KualiDecimal returnKualiDecimal = postable.getAmount().abs();
741         LOG.debug("getGeneralLedgerPendingEntryAmountForAccountingLine(AccountingLine) - end");
742         return returnKualiDecimal;
743     }
744 
745     @Override
746     public Class<? extends AccountingDocument> getDocumentClassForAccountingLineValueAllowedValidation() {
747         return this.getClass();
748     }
749 
750     /**
751      *
752      * @see org.kuali.ole.sys.document.AccountingDocument#isDocumentFinalOrProcessed()
753      */
754     @Override
755     public boolean isDocumentFinalOrProcessed() {
756         boolean isDocumentFinalOrProcessed = false;
757         if(ObjectUtils.isNotNull(getDocumentHeader().getDocumentNumber())) {
758            if(getDocumentHeader().hasWorkflowDocument()) {
759                if(getDocumentHeader().getWorkflowDocument().isFinal() || getDocumentHeader().getWorkflowDocument().isProcessed()) {
760                    isDocumentFinalOrProcessed = true;
761                }
762            }
763 
764         }
765 
766         return isDocumentFinalOrProcessed;
767 
768     }
769 }