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 }