/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.sys.document;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.fp.businessobject.SalesTax;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.businessobject.AccountingLine;
import org.kuali.kfs.sys.businessobject.AccountingLineBase;
import org.kuali.kfs.sys.businessobject.AccountingLineOverride;
import org.kuali.kfs.sys.businessobject.AccountingLineParser;
import org.kuali.kfs.sys.businessobject.AccountingLineParserBase;
import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry;
import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper;
import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail;
import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
import org.kuali.kfs.sys.businessobject.TargetAccountingLine;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.datadictionary.FinancialSystemTransactionalDocumentEntry;
import org.kuali.kfs.sys.document.validation.event.AccountingDocumentSaveWithNoLedgerEntryGenerationEvent;
import org.kuali.kfs.sys.document.validation.event.AccountingLineEvent;
import org.kuali.kfs.sys.document.validation.event.AddAccountingLineEvent;
import org.kuali.kfs.sys.document.validation.event.DeleteAccountingLineEvent;
import org.kuali.kfs.sys.document.validation.event.ReviewAccountingLineEvent;
import org.kuali.kfs.sys.document.validation.event.UpdateAccountingLineEvent;
import org.kuali.kfs.sys.service.AccountingLineService;
import org.kuali.kfs.sys.service.GeneralLedgerPendingEntryService;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kns.service.DataDictionaryService;
import org.kuali.rice.krad.document.TransactionalDocument;
import org.kuali.rice.krad.exception.ValidationException;
import org.kuali.rice.krad.rules.rule.event.KualiDocumentEvent;
import org.kuali.rice.krad.util.ObjectUtils;
/**
* Base implementation class for financial edocs.
*/
public abstract class AccountingDocumentBase extends GeneralLedgerPostingDocumentBase implements AccountingDocument, GeneralLedgerPendingEntrySource {
protected static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(AccountingDocumentBase.class);
protected Integer nextSourceLineNumber;
protected Integer nextTargetLineNumber;
protected List sourceAccountingLines;
protected List targetAccountingLines;
protected transient FinancialSystemTransactionalDocumentEntry dataDictionaryEntry;
protected transient Class sourceAccountingLineClass;
protected transient Class targetAccountingLineClass;
/**
* Default constructor.
*/
public AccountingDocumentBase() {
super();
this.nextSourceLineNumber = new Integer(1);
this.nextTargetLineNumber = new Integer(1);
setSourceAccountingLines(new ArrayList());
setTargetAccountingLines(new ArrayList());
}
/**
* @see org.kuali.kfs.sys.document.AccountingDocument#getSourceAccountingLines()
*/
@Override
public List getSourceAccountingLines() {
return this.sourceAccountingLines;
}
/**
* @see org.kuali.kfs.sys.document.AccountingDocument#setSourceAccountingLines(java.util.List)
*/
@Override
public void setSourceAccountingLines(List sourceLines) {
this.sourceAccountingLines = sourceLines;
}
/**
* @see org.kuali.kfs.sys.document.AccountingDocument#getTargetAccountingLines()
*/
@Override
public List getTargetAccountingLines() {
return this.targetAccountingLines;
}
/**
* @see org.kuali.kfs.sys.document.AccountingDocument#setTargetAccountingLines(java.util.List)
*/
@Override
public void setTargetAccountingLines(List targetLines) {
this.targetAccountingLines = targetLines;
}
/**
* This implementation sets the sequence number appropriately for the passed in source accounting line using the value that has
* been stored in the nextSourceLineNumber variable, adds the accounting line to the list that is aggregated by this object, and
* then handles incrementing the nextSourceLineNumber variable for you.
*
* @see org.kuali.kfs.sys.document.AccountingDocument#addSourceAccountingLine(SourceAccountingLine)
*/
@Override
public void addSourceAccountingLine(SourceAccountingLine line) {
line.setSequenceNumber(this.getNextSourceLineNumber());
this.sourceAccountingLines.add(line);
this.nextSourceLineNumber = new Integer(this.getNextSourceLineNumber().intValue() + 1);
}
/**
* This implementation sets the sequence number appropriately for the passed in target accounting line using the value that has
* been stored in the nextTargetLineNumber variable, adds the accounting line to the list that is aggregated by this object, and
* then handles incrementing the nextTargetLineNumber variable for you.
*
* @see org.kuali.kfs.sys.document.AccountingDocument#addTargetAccountingLine(TargetAccountingLine)
*/
@Override
public void addTargetAccountingLine(TargetAccountingLine line) {
line.setSequenceNumber(this.getNextTargetLineNumber());
this.targetAccountingLines.add(line);
this.nextTargetLineNumber = new Integer(this.getNextTargetLineNumber().intValue() + 1);
}
/**
* This implementation is coupled tightly with some underlying issues that the Struts PojoProcessor plugin has with how objects
* get instantiated within lists. The first three lines are required otherwise when the PojoProcessor tries to automatically
* 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
* instances at indices before that one are not being instantiated. So changing the code below will cause adding lines to break
* if you add more than one item to the list.
*
* @see org.kuali.kfs.sys.document.AccountingDocument#getSourceAccountingLine(int)
*/
@Override
public SourceAccountingLine getSourceAccountingLine(int index) {
while (getSourceAccountingLines().size() <= index) {
try {
getSourceAccountingLines().add(getSourceAccountingLineClass().newInstance());
}
catch (InstantiationException e) {
throw new RuntimeException("Unable to get class");
}
catch (IllegalAccessException e) {
throw new RuntimeException("Unable to get class");
}
}
return (SourceAccountingLine) getSourceAccountingLines().get(index);
}
/**
* This implementation is coupled tightly with some underlying issues that the Struts PojoProcessor plugin has with how objects
* get instantiated within lists. The first three lines are required otherwise when the PojoProcessor tries to automatically
* 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
* instances at indices before that one are not being instantiated. So changing the code below will cause adding lines to break
* if you add more than one item to the list.
*
* @see org.kuali.kfs.sys.document.AccountingDocument#getTargetAccountingLine(int)
*/
@Override
public TargetAccountingLine getTargetAccountingLine(int index) {
while (getTargetAccountingLines().size() <= index) {
try {
getTargetAccountingLines().add(getTargetAccountingLineClass().newInstance());
}
catch (InstantiationException e) {
throw new RuntimeException("Unable to get class");
}
catch (IllegalAccessException e) {
throw new RuntimeException("Unable to get class");
}
}
return (TargetAccountingLine) getTargetAccountingLines().get(index);
}
/**
* @see org.kuali.kfs.sys.document.AccountingDocument#getSourceAccountingLinesSectionTitle()
*/
@Override
public String getSourceAccountingLinesSectionTitle() {
return KFSConstants.SOURCE;
}
/**
* @see org.kuali.kfs.sys.document.AccountingDocument#getTargetAccountingLinesSectionTitle()
*/
@Override
public String getTargetAccountingLinesSectionTitle() {
return KFSConstants.TARGET;
}
/**
* Since one side of the document should match the other and the document should balance, the total dollar amount for the
* document should either be the expense line or the income line. This is the default implementation of this interface method so
* it should be overridden appropriately if your document cannot make this assumption.
*
* @return if target total is zero, source total, otherwise target total
*/
public KualiDecimal getTotalDollarAmount() {
return getTargetTotal().equals(KualiDecimal.ZERO) ? getSourceTotal() : getTargetTotal();
}
/**
* @see org.kuali.kfs.sys.document.AccountingDocument#getSourceTotal()
*/
@Override
public KualiDecimal getSourceTotal() {
KualiDecimal total = KualiDecimal.ZERO;
AccountingLineBase al = null;
Iterator iter = getSourceAccountingLines().iterator();
while (iter.hasNext()) {
al = (AccountingLineBase) iter.next();
KualiDecimal amount = al.getAmount();
if (amount != null) {
total = total.add(amount);
}
}
return total;
}
/**
* @see org.kuali.kfs.sys.document.AccountingDocument#getTargetTotal()
*/
@Override
public KualiDecimal getTargetTotal() {
KualiDecimal total = KualiDecimal.ZERO;
AccountingLineBase al = null;
Iterator iter = getTargetAccountingLines().iterator();
while (iter.hasNext()) {
al = (AccountingLineBase) iter.next();
KualiDecimal amount = al.getAmount();
if (amount != null) {
total = total.add(amount);
}
}
return total;
}
/**
* @see org.kuali.kfs.sys.document.AccountingDocument#getNextSourceLineNumber()
*/
@Override
public Integer getNextSourceLineNumber() {
return this.nextSourceLineNumber;
}
/**
* @see org.kuali.kfs.sys.document.AccountingDocument#setNextSourceLineNumber(java.lang.Integer)
*/
@Override
public void setNextSourceLineNumber(Integer nextLineNumber) {
this.nextSourceLineNumber = nextLineNumber;
}
/**
* @see org.kuali.kfs.sys.document.AccountingDocument#getNextTargetLineNumber()
*/
@Override
public Integer getNextTargetLineNumber() {
return this.nextTargetLineNumber;
}
/**
* @see org.kuali.kfs.sys.document.AccountingDocument#setNextTargetLineNumber(java.lang.Integer)
*/
@Override
public void setNextTargetLineNumber(Integer nextLineNumber) {
this.nextTargetLineNumber = nextLineNumber;
}
/**
* Returns the default Source accounting line class.
*
* @see org.kuali.kfs.sys.document.AccountingDocument#getSourceAccountingLineClass()
*/
@Override
public Class getSourceAccountingLineClass() {
if (sourceAccountingLineClass == null) {
sourceAccountingLineClass = (getDataDictionaryEntry().getAccountingLineGroups() != null && getDataDictionaryEntry().getAccountingLineGroups().containsKey("source") && getDataDictionaryEntry().getAccountingLineGroups().get("source").getAccountingLineClass() != null) ? getDataDictionaryEntry().getAccountingLineGroups().get("source").getAccountingLineClass() : SourceAccountingLine.class;
}
return sourceAccountingLineClass;
}
/**
* Returns the default Target accounting line class.
*
* @see org.kuali.kfs.sys.document.AccountingDocument#getTargetAccountingLineClass()
*/
@Override
public Class getTargetAccountingLineClass() {
if (targetAccountingLineClass == null) {
targetAccountingLineClass = (getDataDictionaryEntry().getAccountingLineGroups() != null && getDataDictionaryEntry().getAccountingLineGroups().containsKey("target") && getDataDictionaryEntry().getAccountingLineGroups().get("target").getAccountingLineClass() != null) ? getDataDictionaryEntry().getAccountingLineGroups().get("target").getAccountingLineClass() : TargetAccountingLine.class;
}
return targetAccountingLineClass;
}
/**
* Used to get the appropriate <code>{@link AccountingLineParser}</code> for the <code>Document</code>
*
* @return AccountingLineParser
*/
@Override
public AccountingLineParser getAccountingLineParser() {
try {
if (getDataDictionaryEntry().getImportedLineParserClass() != null) {
return getDataDictionaryEntry().getImportedLineParserClass().newInstance();
}
}
catch (InstantiationException ie) {
throw new IllegalStateException("Accounting Line Parser class " + getDataDictionaryEntry().getImportedLineParserClass().getName() + " cannot be instantiated", ie);
}
catch (IllegalAccessException iae) {
throw new IllegalStateException("Illegal Access Exception while attempting to instantiate Accounting Line Parser class " + getDataDictionaryEntry().getImportedLineParserClass().getName(), iae);
}
return new AccountingLineParserBase();
}
/**
* @return the data dictionary entry for this document
*/
public FinancialSystemTransactionalDocumentEntry getDataDictionaryEntry() {
if (dataDictionaryEntry == null) {
dataDictionaryEntry = (FinancialSystemTransactionalDocumentEntry) SpringContext.getBean(DataDictionaryService.class).getDataDictionary().getDocumentEntry(SpringContext.getBean(DataDictionaryService.class).getValidDocumentTypeNameByClass(getClass()));
}
return dataDictionaryEntry;
}
@Override
public String getSourceAccountingLineEntryName() {
return this.getSourceAccountingLineClass().getName();
}
@Override
public String getTargetAccountingLineEntryName() {
return this.getTargetAccountingLineClass().getName();
}
@Override
public List<GeneralLedgerPendingEntrySourceDetail> getGeneralLedgerPendingEntrySourceDetails() {
List<GeneralLedgerPendingEntrySourceDetail> accountingLines = new ArrayList<GeneralLedgerPendingEntrySourceDetail>();
if (getSourceAccountingLines() != null) {
Iterator iter = getSourceAccountingLines().iterator();
while (iter.hasNext()) {
accountingLines.add((GeneralLedgerPendingEntrySourceDetail) iter.next());
}
}
if (getTargetAccountingLines() != null) {
Iterator iter = getTargetAccountingLines().iterator();
while (iter.hasNext()) {
accountingLines.add((GeneralLedgerPendingEntrySourceDetail) iter.next());
}
}
return accountingLines;
}
public void customizeExplicitGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySourceDetail postable, GeneralLedgerPendingEntry explicitEntry) {
}
public boolean customizeOffsetGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySourceDetail accountingLine, GeneralLedgerPendingEntry explicitEntry, GeneralLedgerPendingEntry offsetEntry) {
return true;
}
/**
* @see org.kuali.kfs.sys.document.GeneralLedgerPostingDocumentBase#toCopy()
*/
@Override
public void toCopy() throws WorkflowException {
super.toCopy();
copyAccountingLines(false);
updatePostingYearForAccountingLines(getSourceAccountingLines());
updatePostingYearForAccountingLines(getTargetAccountingLines());
}
/**
* @see org.kuali.kfs.sys.document.GeneralLedgerPostingDocumentBase#toErrorCorrection()
*/
@Override
public void toErrorCorrection() throws WorkflowException {
super.toErrorCorrection();
copyAccountingLines(true);
setExpiredAccountOverrides();
correctSalesTax();
}
/**
* Checks for accounting lines that may need an account override.
*/
protected void setExpiredAccountOverrides(){
List<AccountingLine> accountingLines = new ArrayList<AccountingLine>(getSourceAccountingLines());
accountingLines.addAll(getTargetAccountingLines());
for (AccountingLine accountingLine : accountingLines){
setExpiredAccountOverride(accountingLine);
}
}
protected void setExpiredAccountOverride(AccountingLine accountingLine){
boolean needsOverride = AccountingLineOverride.needsExpiredAccountOverride(accountingLine, this.isDocumentFinalOrProcessed());
accountingLine.setAccountExpiredOverrideNeeded(needsOverride);
}
protected void correctSalesTax(){
List<AccountingLine> lines = new ArrayList<AccountingLine>(getSourceAccountingLines());
lines.addAll(getTargetAccountingLines());
for (AccountingLine accountingLine : lines) {
SalesTax tax = accountingLine.getSalesTax();
if (ObjectUtils.isNotNull(tax)) {
KualiDecimal tempAmount = tax.getFinancialDocumentGrossSalesAmount();
if (ObjectUtils.isNotNull(tempAmount)) {
tax.setFinancialDocumentGrossSalesAmount(tempAmount.negated());
}
tempAmount = tax.getFinancialDocumentTaxableSalesAmount();
if (ObjectUtils.isNotNull(tempAmount)){
tax.setFinancialDocumentTaxableSalesAmount(tempAmount.negated());
}
}
}
}
/**
* Copies accounting lines but sets new document number and version If error correction, reverses line amount.
*/
protected void copyAccountingLines(boolean isErrorCorrection) {
if (getSourceAccountingLines() != null) {
for (Iterator iter = getSourceAccountingLines().iterator(); iter.hasNext();) {
AccountingLineBase sourceLine = (AccountingLineBase) iter.next();
sourceLine.setDocumentNumber(getDocumentNumber());
sourceLine.setVersionNumber(new Long(1));
if (isErrorCorrection) {
sourceLine.setAmount(sourceLine.getAmount().negated());
}
}
}
if (getTargetAccountingLines() != null) {
for (Iterator iter = getTargetAccountingLines().iterator(); iter.hasNext();) {
AccountingLineBase targetLine = (AccountingLineBase) iter.next();
targetLine.setDocumentNumber(getDocumentNumber());
targetLine.setVersionNumber(new Long(1));
if (isErrorCorrection) {
targetLine.setAmount(targetLine.getAmount().negated());
}
}
}
}
/**
* Updates the posting year on accounting lines to be the current posting year
*
* @param lines a List of accounting lines to update
*/
protected void updatePostingYearForAccountingLines(List<AccountingLine> lines) {
if (lines != null) {
for (AccountingLine line : lines) {
if (!line.getPostingYear().equals(getPostingYear())) {
line.setPostingYear(getPostingYear());
}
}
}
}
/**
* @see org.kuali.rice.krad.document.DocumentBase#buildListOfDeletionAwareLists()
*/
@Override
public List buildListOfDeletionAwareLists() {
List managedLists = super.buildListOfDeletionAwareLists();
managedLists.add(getSourceAccountingLines());
managedLists.add(getTargetAccountingLines());
return managedLists;
}
@Override
public void prepareForSave(KualiDocumentEvent event) {
if (!(event instanceof AccountingDocumentSaveWithNoLedgerEntryGenerationEvent)) { // only generate entries if the rule event
// specifically allows us to
if (!SpringContext.getBean(GeneralLedgerPendingEntryService.class).generateGeneralLedgerPendingEntries(this)) {
logErrors();
throw new ValidationException("general ledger GLPE generation failed");
}
}
super.prepareForSave(event);
}
@Override
public List generateSaveEvents() {
List events = new ArrayList();
// foreach (source, target)
// 1. retrieve persisted accountingLines for document
// 2. retrieve current accountingLines from given document
// 3. compare, creating add/delete/update events as needed
// 4. apply rules as appropriate returned events
List persistedSourceLines = getPersistedSourceAccountingLinesForComparison();
List currentSourceLines = getSourceAccountingLinesForComparison();
List sourceEvents = generateEvents(persistedSourceLines, currentSourceLines, KFSConstants.DOCUMENT_PROPERTY_NAME + "." + KFSConstants.SOURCE_ACCOUNTING_LINE_ERRORS, this);
for (Iterator i = sourceEvents.iterator(); i.hasNext();) {
AccountingLineEvent sourceEvent = (AccountingLineEvent) i.next();
events.add(sourceEvent);
}
List persistedTargetLines = getPersistedTargetAccountingLinesForComparison();
List currentTargetLines = getTargetAccountingLinesForComparison();
List targetEvents = generateEvents(persistedTargetLines, currentTargetLines, KFSConstants.DOCUMENT_PROPERTY_NAME + "." + KFSConstants.TARGET_ACCOUNTING_LINE_ERRORS, this);
for (Iterator i = targetEvents.iterator(); i.hasNext();) {
AccountingLineEvent targetEvent = (AccountingLineEvent) i.next();
events.add(targetEvent);
}
return events;
}
/**
* This method gets the Target Accounting Lines that will be used in comparisons
*
* @return
*/
protected List getTargetAccountingLinesForComparison() {
return getTargetAccountingLines();
}
/**
* This method gets the Persisted Target Accounting Lines that will be used in comparisons
*
* @return
*/
protected List getPersistedTargetAccountingLinesForComparison() {
return SpringContext.getBean(AccountingLineService.class).getByDocumentHeaderId(getTargetAccountingLineClass(), getDocumentNumber());
}
/**
* This method gets the Source Accounting Lines that will be used in comparisons
*
* @return
*/
protected List getSourceAccountingLinesForComparison() {
return getSourceAccountingLines();
}
/**
* This method gets the Persisted Source Accounting Lines that will be used in comparisons
*
* @return
*/
protected List getPersistedSourceAccountingLinesForComparison() {
return SpringContext.getBean(AccountingLineService.class).getByDocumentHeaderId(getSourceAccountingLineClass(), getDocumentNumber());
}
/**
* Generates a List of instances of AccountingLineEvent subclasses, one for each accountingLine in the union of the
* persistedLines and currentLines lists. Events in the list will be grouped in order by event-type (review, update, add,
* delete).
*
* @param persistedLines
* @param currentLines
* @param errorPathPrefix
* @param document
* @return List of AccountingLineEvent subclass instances
*/
protected List generateEvents(List persistedLines, List currentLines, String errorPathPrefix, TransactionalDocument document) {
List addEvents = new ArrayList();
List updateEvents = new ArrayList();
List reviewEvents = new ArrayList();
List deleteEvents = new ArrayList();
//
// generate events
Map persistedLineMap = buildAccountingLineMap(persistedLines);
// (iterate through current lines to detect additions and updates, removing affected lines from persistedLineMap as we go
// so deletions can be detected by looking at whatever remains in persistedLineMap)
int index = 0;
for (Iterator i = currentLines.iterator(); i.hasNext(); index++) {
String indexedErrorPathPrefix = errorPathPrefix + "[" + index + "]";
AccountingLine currentLine = (AccountingLine) i.next();
Integer key = currentLine.getSequenceNumber();
AccountingLine persistedLine = (AccountingLine) persistedLineMap.get(key);
// if line is both current and persisted...
if (persistedLine != null) {
// ...check for updates
if (!currentLine.isLike(persistedLine)) {
UpdateAccountingLineEvent updateEvent = new UpdateAccountingLineEvent(indexedErrorPathPrefix, document, persistedLine, currentLine);
updateEvents.add(updateEvent);
}
else {
ReviewAccountingLineEvent reviewEvent = new ReviewAccountingLineEvent(indexedErrorPathPrefix, document, currentLine);
reviewEvents.add(reviewEvent);
}
persistedLineMap.remove(key);
}
else {
// it must be a new addition
AddAccountingLineEvent addEvent = new AddAccountingLineEvent(indexedErrorPathPrefix, document, currentLine);
addEvents.add(addEvent);
}
}
// detect deletions
for (Iterator i = persistedLineMap.entrySet().iterator(); i.hasNext();) {
// the deleted line is not displayed on the page, so associate the error with the whole group
String groupErrorPathPrefix = errorPathPrefix + KFSConstants.ACCOUNTING_LINE_GROUP_SUFFIX;
Map.Entry e = (Map.Entry) i.next();
AccountingLine persistedLine = (AccountingLine) e.getValue();
DeleteAccountingLineEvent deleteEvent = new DeleteAccountingLineEvent(groupErrorPathPrefix, document, persistedLine, true);
deleteEvents.add(deleteEvent);
}
//
// merge the lists
List lineEvents = new ArrayList();
lineEvents.addAll(reviewEvents);
lineEvents.addAll(updateEvents);
lineEvents.addAll(addEvents);
lineEvents.addAll(deleteEvents);
return lineEvents;
}
/**
* @param accountingLines
* @return Map containing accountingLines from the given List, indexed by their sequenceNumber
*/
protected Map buildAccountingLineMap(List accountingLines) {
Map lineMap = new HashMap();
for (Iterator i = accountingLines.iterator(); i.hasNext();) {
AccountingLine accountingLine = (AccountingLine) i.next();
Integer sequenceNumber = accountingLine.getSequenceNumber();
Object oldLine = lineMap.put(sequenceNumber, accountingLine);
// verify that sequence numbers are unique...
if (oldLine != null) {
throw new IllegalStateException("sequence number collision detected for sequence number " + sequenceNumber);
}
}
return lineMap;
}
/**
* Perform business rules common to all transactional documents when generating general ledger pending entries.
*
* @see org.kuali.rice.krad.rule.GenerateGeneralLedgerPendingEntriesRule#processGenerateGeneralLedgerPendingEntries(org.kuali.rice.krad.document.AccountingDocument,
* org.kuali.rice.krad.bo.AccountingLine, org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper)
*/
@Override
public boolean generateGeneralLedgerPendingEntries(GeneralLedgerPendingEntrySourceDetail glpeSourceDetail, GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
LOG.debug("processGenerateGeneralLedgerPendingEntries(AccountingDocument, AccountingLine, GeneralLedgerPendingEntrySequenceHelper) - start");
// handle the explicit entry
// create a reference to the explicitEntry to be populated, so we can pass to the offset method later
GeneralLedgerPendingEntry explicitEntry = new GeneralLedgerPendingEntry();
processExplicitGeneralLedgerPendingEntry(sequenceHelper, glpeSourceDetail, explicitEntry);
// increment the sequence counter
sequenceHelper.increment();
// handle the offset entry
GeneralLedgerPendingEntry offsetEntry = new GeneralLedgerPendingEntry(explicitEntry);
boolean success = processOffsetGeneralLedgerPendingEntry(sequenceHelper, glpeSourceDetail, explicitEntry, offsetEntry);
LOG.debug("processGenerateGeneralLedgerPendingEntries(AccountingDocument, AccountingLine, GeneralLedgerPendingEntrySequenceHelper) - end");
return success;
}
/**
* This method processes all necessary information to build an explicit general ledger entry, and then adds that to the
* document.
*
* @param accountingDocument
* @param sequenceHelper
* @param accountingLine
* @param explicitEntry
* @return boolean True if the explicit entry generation was successful, false otherwise.
*/
protected void processExplicitGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySequenceHelper sequenceHelper, GeneralLedgerPendingEntrySourceDetail glpeSourceDetail, GeneralLedgerPendingEntry explicitEntry) {
LOG.debug("processExplicitGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry) - start");
// populate the explicit entry
SpringContext.getBean(GeneralLedgerPendingEntryService.class).populateExplicitGeneralLedgerPendingEntry(this, glpeSourceDetail, sequenceHelper, explicitEntry);
// hook for children documents to implement document specific GLPE field mappings
customizeExplicitGeneralLedgerPendingEntry(glpeSourceDetail, explicitEntry);
addPendingEntry(explicitEntry);
LOG.debug("processExplicitGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry) - end");
}
/**
* This method processes an accounting line's information to build an offset entry, and then adds that to the document.
*
* @param accountingDocument
* @param sequenceHelper
* @param accountingLine
* @param explicitEntry
* @param offsetEntry
* @return boolean True if the offset generation is successful.
*/
protected boolean processOffsetGeneralLedgerPendingEntry(GeneralLedgerPendingEntrySequenceHelper sequenceHelper, GeneralLedgerPendingEntrySourceDetail postable, GeneralLedgerPendingEntry explicitEntry, GeneralLedgerPendingEntry offsetEntry) {
LOG.debug("processOffsetGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry, GeneralLedgerPendingEntry) - start");
// populate the offset entry
boolean success = SpringContext.getBean(GeneralLedgerPendingEntryService.class).populateOffsetGeneralLedgerPendingEntry(getPostingYear(), explicitEntry, sequenceHelper, offsetEntry);
// hook for children documents to implement document specific field mappings for the GLPE
success &= customizeOffsetGeneralLedgerPendingEntry(postable, explicitEntry, offsetEntry);
addPendingEntry(offsetEntry);
LOG.debug("processOffsetGeneralLedgerPendingEntry(AccountingDocument, GeneralLedgerPendingEntrySequenceHelper, AccountingLine, GeneralLedgerPendingEntry, GeneralLedgerPendingEntry) - end");
return success;
}
/**
* Returns one of the two given String's; if the preferred String is not null and has a length > 0, then it is returned,
* otherwise the second String is returned
*
* @param preferredString the String you're hoping isn't blank so you can get it back
* @param secondaryString the "rebound" String, which you'll end up with if the preferred String is blank
* @return one of the String's
*/
protected String getEntryValue(String preferredString, String secondaryString) {
return (StringUtils.isNotBlank(preferredString) ? preferredString : secondaryString);
}
/**
* @see org.kuali.kfs.document.GeneralLedgerPostingHelper#isDebit(org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail)
*/
@Override
public abstract boolean isDebit(GeneralLedgerPendingEntrySourceDetail postable);
/**
* Most accounting documents don't need to generate document level GLPE's, so don't do anything in the default implementation
*
* @see org.kuali.kfs.document.GeneralLedgerPostingHelper#processGenerateDocumentGeneralLedgerPendingEntries(org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySequenceHelper)
* @return always true, because we've always successfully not generating anything
*/
@Override
public boolean generateDocumentGeneralLedgerPendingEntries(GeneralLedgerPendingEntrySequenceHelper sequenceHelper) {
return true;
}
/**
* GLPE amounts are ALWAYS positive, so just take the absolute value of the accounting line's amount.
*
* @param accountingLine
* @return KualiDecimal The amount that will be used to populate the GLPE.
*/
@Override
public KualiDecimal getGeneralLedgerPendingEntryAmountForDetail(GeneralLedgerPendingEntrySourceDetail postable) {
LOG.debug("getGeneralLedgerPendingEntryAmountForAccountingLine(AccountingLine) - start");
KualiDecimal returnKualiDecimal = postable.getAmount().abs();
LOG.debug("getGeneralLedgerPendingEntryAmountForAccountingLine(AccountingLine) - end");
return returnKualiDecimal;
}
@Override
public Class<? extends AccountingDocument> getDocumentClassForAccountingLineValueAllowedValidation() {
return this.getClass();
}
/**
*
* @see org.kuali.kfs.sys.document.AccountingDocument#isDocumentFinalOrProcessed()
*/
@Override
public boolean isDocumentFinalOrProcessed() {
boolean isDocumentFinalOrProcessed = false;
if(ObjectUtils.isNotNull(getDocumentHeader().getDocumentNumber())) {
if(getDocumentHeader().hasWorkflowDocument()) {
if(getDocumentHeader().getWorkflowDocument().isFinal() || getDocumentHeader().getWorkflowDocument().isProcessed()) {
isDocumentFinalOrProcessed = true;
}
}
}
return isDocumentFinalOrProcessed;
}
}