/* * 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.module.purap.document; import java.math.BigDecimal; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.kuali.kfs.module.purap.PurapConstants; import org.kuali.kfs.module.purap.PurapParameterConstants; import org.kuali.kfs.module.purap.PurapPropertyConstants; import org.kuali.kfs.module.purap.businessobject.ItemType; import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine; import org.kuali.kfs.module.purap.businessobject.PurApItem; import org.kuali.kfs.module.purap.businessobject.PurApItemBase; import org.kuali.kfs.module.purap.businessobject.PurApItemUseTax; import org.kuali.kfs.module.purap.businessobject.PurchaseOrderView; import org.kuali.kfs.module.purap.businessobject.SensitiveData; import org.kuali.kfs.module.purap.document.service.PurapService; import org.kuali.kfs.module.purap.document.service.impl.PurapServiceImpl; import org.kuali.kfs.module.purap.service.PurapAccountingService; import org.kuali.kfs.module.purap.service.SensitiveDataService; import org.kuali.kfs.module.purap.util.PurApRelatedViews; import org.kuali.kfs.sys.KFSConstants.AdHocPaymentIndicator; import org.kuali.kfs.sys.businessobject.AccountingLine; import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntry; import org.kuali.kfs.sys.businessobject.GeneralLedgerPendingEntrySourceDetail; import org.kuali.kfs.sys.businessobject.SourceAccountingLine; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.document.AccountingDocumentBase; import org.kuali.kfs.sys.document.AmountTotaling; import org.kuali.kfs.sys.service.UniversityDateService; import org.kuali.kfs.vnd.businessobject.VendorAddress; import org.kuali.kfs.vnd.businessobject.VendorDetail; import org.kuali.kfs.vnd.document.service.VendorService; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.kew.api.WorkflowDocument; import org.kuali.rice.krad.rules.rule.event.ApproveDocumentEvent; import org.kuali.rice.krad.rules.rule.event.KualiDocumentEvent; import org.kuali.rice.krad.rules.rule.event.RouteDocumentEvent; import org.kuali.rice.krad.service.KualiModuleService; import org.kuali.rice.krad.service.ModuleService; import org.kuali.rice.krad.util.NoteType; import org.kuali.rice.krad.util.ObjectUtils; import org.kuali.rice.location.api.LocationConstants; import org.kuali.rice.location.framework.country.CountryEbo; /** * Base class for Purchasing-Accounts Payable Documents. */ public abstract class PurchasingAccountsPayableDocumentBase extends AccountingDocumentBase implements PurchasingAccountsPayableDocument, AmountTotaling { private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PurchasingAccountsPayableDocumentBase.class); // SHARED FIELDS BETWEEN REQUISITION, PURCHASE ORDER, PAYMENT REQUEST, AND CREDIT MEMO protected Integer purapDocumentIdentifier; protected Integer vendorHeaderGeneratedIdentifier; protected Integer vendorDetailAssignedIdentifier; protected String vendorCustomerNumber; protected String vendorName; protected String vendorLine1Address; protected String vendorLine2Address; protected String vendorCityName; protected String vendorStateCode; protected String vendorAddressInternationalProvinceName; protected String vendorPostalCode; protected String vendorCountryCode; protected Integer accountsPayablePurchasingDocumentLinkIdentifier; protected boolean useTaxIndicator; protected String vendorAttentionName; protected String accountDistributionMethod; //code for account distribution method // NOT PERSISTED IN DB protected String vendorNumber; protected Integer vendorAddressGeneratedIdentifier; protected Boolean overrideWorkflowButtons = null; protected transient PurApRelatedViews relatedViews; protected boolean sensitive; protected boolean calculated; // COLLECTIONS protected List<PurApItem> items; protected List<SourceAccountingLine> accountsForRouting; // don't use me for anything else!! // REFERENCE OBJECTS protected VendorDetail vendorDetail; protected CountryEbo vendorCountry; // STATIC public transient String[] belowTheLineTypes; // workaround for purapOjbCollectionHelper - remove when merged into rice public boolean allowDeleteAwareCollection = true; /** * Default constructor to be overridden. */ public PurchasingAccountsPayableDocumentBase() { items = new ArrayList(); } protected GeneralLedgerPendingEntry getFirstPendingGLEntry() { if (ObjectUtils.isNotNull(getGeneralLedgerPendingEntries()) && !getGeneralLedgerPendingEntries().isEmpty()) { return getGeneralLedgerPendingEntries().get(0); } return null; } public Integer getPostingYearFromPendingGLEntries() { GeneralLedgerPendingEntry glpe = getFirstPendingGLEntry(); if (ObjectUtils.isNotNull(glpe)) { return glpe.getUniversityFiscalYear(); } return null; } public String getPostingPeriodCodeFromPendingGLEntries() { GeneralLedgerPendingEntry glpe = getFirstPendingGLEntry(); if (ObjectUtils.isNotNull(glpe)) { return glpe.getUniversityFiscalPeriodCode(); } return null; } public List<SourceAccountingLine> getAccountsForRouting() { if (accountsForRouting == null) { populateAccountsForRouting(); } return accountsForRouting; } public void setAccountsForRouting(List<SourceAccountingLine> accountsForRouting) { this.accountsForRouting = accountsForRouting; } /** * Makes sure that accounts for routing has been generated, so that other information can be retrieved from that */ protected void populateAccountsForRouting() { SpringContext.getBean(PurapAccountingService.class).updateAccountAmounts(this); setAccountsForRouting(SpringContext.getBean(PurapAccountingService.class).generateSummary(getItems())); // need to refresh to get the references for the searchable attributes (ie status) and for invoking route levels (ie account // objects) -hjs refreshNonUpdateableReferences(); for (SourceAccountingLine sourceLine : getAccountsForRouting()) { sourceLine.refreshNonUpdateableReferences(); } } public boolean isSensitive() { List<SensitiveData> sensitiveData = SpringContext.getBean(SensitiveDataService.class).getSensitiveDatasAssignedByRelatedDocId(getAccountsPayablePurchasingDocumentLinkIdentifier()); if (ObjectUtils.isNotNull(sensitiveData) && !sensitiveData.isEmpty()) { return true; } return false; } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#isInquiryRendered() */ @Override public boolean isInquiryRendered() { return isPostingYearPrior(); } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#isPostingYearNext() */ @Override public boolean isPostingYearNext() { Integer currentFY = SpringContext.getBean(UniversityDateService.class).getCurrentFiscalYear(); return (getPostingYear().compareTo(currentFY) > 0); } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#isPostingYearPrior() */ @Override public boolean isPostingYearPrior() { Integer currentFY = SpringContext.getBean(UniversityDateService.class).getCurrentFiscalYear(); return (getPostingYear().compareTo(currentFY) < 0); } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#getPostingYearNextOrCurrent() */ @Override public Integer getPostingYearNextOrCurrent() { if (isPostingYearNext()) { //FY is set to next; use it return getPostingYear(); } //FY is NOT set to next; use CURRENT return SpringContext.getBean(UniversityDateService.class).getCurrentFiscalYear(); } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#getItemClass() */ @Override @SuppressWarnings("rawtypes") public abstract Class getItemClass(); @SuppressWarnings("rawtypes") public abstract Class getItemUseTaxClass(); /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#getPurApSourceDocumentIfPossible() */ @Override public abstract PurchasingAccountsPayableDocument getPurApSourceDocumentIfPossible(); /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#getPurApSourceDocumentLabelIfPossible() */ @Override public abstract String getPurApSourceDocumentLabelIfPossible(); /** * @see org.kuali.rice.krad.document.DocumentBase#prepareForSave() */ @Override public void prepareForSave(KualiDocumentEvent event) { customPrepareForSave(event); super.prepareForSave(event); fixItemReferences(); } /** * PURAP documents are all overriding this method to return false because sufficient funds checking should not be performed on * route of any PURAP documents. Only the Purchase Order performs a sufficient funds check and it is manually forced during * routing. * * @see org.kuali.kfs.sys.document.GeneralLedgerPostingDocumentBase#documentPerformsSufficientFundsCheck() */ @Override public boolean documentPerformsSufficientFundsCheck() { return false; } // for app doc status @Override public boolean isDocumentStoppedInRouteNode(String nodeName) { WorkflowDocument workflowDocument = this.getFinancialSystemDocumentHeader().getWorkflowDocument(); Set<String> names = workflowDocument.getCurrentNodeNames(); if (CollectionUtils.isNotEmpty(names)) { List<String> currentRouteLevels = new ArrayList<String>(names); if (currentRouteLevels.contains(nodeName) && workflowDocument.isApprovalRequested()) { return true; } } return false; } /** * Records the specified error message into the Log file and throws a runtime exception. * * @param errorMessage the error message to be logged. */ protected void logAndThrowRuntimeException(String errorMessage) { this.logAndThrowRuntimeException(errorMessage, null); } /** * Records the specified error message into the Log file and throws the specified runtime exception. * * @param errorMessage the specified error message. * @param e the specified runtime exception. */ protected void logAndThrowRuntimeException(String errorMessage, Exception e) { if (ObjectUtils.isNotNull(e)) { LOG.error(errorMessage, e); throw new RuntimeException(errorMessage, e); } else { LOG.error(errorMessage); throw new RuntimeException(errorMessage); } } /** * Allows child PO classes to customize the prepareForSave method. Most of the subclasses need to call the super's method to get * the GL entry creation, but they each need to do different things to prepare for those entries to be created. This is only for * PO since it has children classes that need different prep work for GL creation. * * @param event the event involved in this action. */ public void customPrepareForSave(KualiDocumentEvent event) { // Need this here so that it happens before the GL work is done SpringContext.getBean(PurapAccountingService.class).updateAccountAmounts(this); if (event instanceof RouteDocumentEvent || event instanceof ApproveDocumentEvent) { if (this instanceof VendorCreditMemoDocument && ((VendorCreditMemoDocument)this).isSourceVendor()){ return; } SpringContext.getBean(PurapServiceImpl.class).calculateTax(this); } // These next 5 lines are temporary changes so that we can use PurApOjbCollectionHelper for release 2. // But these 5 lines will not be necessary anymore if the changes in PurApOjbCollectionHelper is // merge into Rice. // this.allowDeleteAwareCollection = true; // DocumentDaoOjb docDao = SpringContext.getBean(DocumentDaoOjb.class); // PurchasingAccountsPayableDocumentBase retrievedDocument = (PurchasingAccountsPayableDocumentBase) docDao.findByDocumentHeaderId(this.getClass(), this.getDocumentNumber()); // if (retrievedDocument != null) { // retrievedDocument.allowDeleteAwareCollection = true; // } // // SpringContext.getBean(PurApOjbCollectionHelper.class).processCollections(docDao, this, retrievedDocument); // this.allowDeleteAwareCollection = false; // if (retrievedDocument != null) { // retrievedDocument.allowDeleteAwareCollection = false; // } } /** * @see org.kuali.kfs.sys.document.AccountingDocumentBase#buildListOfDeletionAwareLists() */ @SuppressWarnings("rawtypes") @Override public List buildListOfDeletionAwareLists() { List managedLists = new ArrayList<List>(); managedLists.add(getDeletionAwareAccountingLines()); if (allowDeleteAwareCollection) { //From now on, the list of accounting lines would have been added when the //super.buildListOfDeletionAwareLists() is executed when it calls getSourceAccountingLines(). //So we can remove the old codes that used to exist here to add the accounts to the //managedLists and just use the one from the super.buildListOfDeletionAwareLists() managedLists.add(this.getItems()); managedLists.add(getDeletionAwareUseTaxItems()); } return managedLists; } /** * Build deletion list of accounting lines for PurAp generic use. * * @return */ @SuppressWarnings("rawtypes") protected List getDeletionAwareAccountingLines() { List<PurApAccountingLine> deletionAwareAccountingLines = new ArrayList<PurApAccountingLine>(); for (Object itemAsObject : this.getItems()) { final PurApItem item = (PurApItem)itemAsObject; for (PurApAccountingLine accountingLine : item.getSourceAccountingLines()) { deletionAwareAccountingLines.add(accountingLine); } } return deletionAwareAccountingLines; } /** * Build deletion list of use tax items for PurAp generic use. * * @return */ @SuppressWarnings("rawtypes") protected List getDeletionAwareUseTaxItems() { List<PurApItemUseTax> deletionAwareUseTaxItems = new ArrayList<PurApItemUseTax>(); List<PurApItemBase> subManageList = this.getItems(); for (PurApItemBase subManage : subManageList) { deletionAwareUseTaxItems.addAll(subManage.getUseTaxItems()); } return deletionAwareUseTaxItems; } /** * @see org.kuali.kfs.sys.document.AccountingDocumentBase#buildListOfDeletionAwareLists() * @Override public List buildListOfDeletionAwareLists() { List managedLists = new ArrayList(); if (allowDeleteAwareCollection) { List<PurApAccountingLine> purapAccountsList = new ArrayList<PurApAccountingLine>(); for (Object itemAsObject : this.getItems()) { final PurApItem item = (PurApItem)itemAsObject; purapAccountsList.addAll(item.getSourceAccountingLines()); } managedLists.add(purapAccountsList); managedLists.add(this.getItems()); } return managedLists; } */ @Override public void processAfterRetrieve() { super.processAfterRetrieve(); refreshNonUpdateableReferences(); } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#addItem(PurApItem item) */ @Override public void addItem(PurApItem item) { int itemLinePosition = getItemLinePosition(); if (ObjectUtils.isNotNull(item.getItemLineNumber()) && (item.getItemLineNumber() > 0) && (item.getItemLineNumber() <= itemLinePosition)) { itemLinePosition = item.getItemLineNumber().intValue() - 1; } item.setPurapDocumentIdentifier(this.purapDocumentIdentifier); item.setPurapDocument(this); items.add(itemLinePosition, item); renumberItems(itemLinePosition); } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#deleteItem(int lineNum) */ @Override public void deleteItem(int lineNum) { if (items.remove(lineNum) == null) { // throw error here } renumberItems(lineNum); } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#renumberItems(int start) */ @Override public void renumberItems(int start) { for (int i = start; i < items.size(); i++) { PurApItem item = items.get(i); item.refreshReferenceObject(PurapPropertyConstants.ITEM_TYPE); // only set the item line number for above the line items if (item.getItemType().isLineItemIndicator()) { item.setItemLineNumber(new Integer(i + 1)); } } } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#itemSwap(int positionFrom, int positionTo) */ @Override public void itemSwap(int positionFrom, int positionTo) { // if out of range do nothing if ((positionTo < 0) || (positionTo >= getItemLinePosition())) { return; } PurApItem item1 = this.getItem(positionFrom); PurApItem item2 = this.getItem(positionTo); Integer oldFirstPos = item1.getItemLineNumber(); // swap line numbers item1.setItemLineNumber(item2.getItemLineNumber()); item2.setItemLineNumber(oldFirstPos); // fix ordering in list items.remove(positionFrom); items.add(positionTo, item1); } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#getItemLinePosition() */ @Override public int getItemLinePosition() { int belowTheLineCount = 0; for (PurApItem item : items) { item.refreshReferenceObject(PurapPropertyConstants.ITEM_TYPE); if (item.getItemType().isAdditionalChargeIndicator()) { belowTheLineCount++; } } return items.size() - belowTheLineCount; } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#getItem(int pos) */ @Override public PurApItem getItem(int pos) { return items.get(pos); } /** * Iterates through the items of the document and returns the item with the line number equal to the number given, or null if a * match is not found. * * @param lineNumber line number to match on. * @return the PurchasingAp Item if a match is found, else null. */ @SuppressWarnings("rawtypes") public PurApItem getItemByLineNumber(int lineNumber) { for (Iterator iter = items.iterator(); iter.hasNext();) { PurApItem item = (PurApItem) iter.next(); if (item.getItemLineNumber().intValue() == lineNumber) { return item; } } return null; } /** * Find the item in the document via its string identifier. * @param itemStrID the string identifier of the item being searched for * @return the item being searched for */ @SuppressWarnings("rawtypes") public PurApItem getItemByStringIdentifier(String itemStrID) { for (Iterator iter = items.iterator(); iter.hasNext();) { PurApItem item = (PurApItem) iter.next(); if (StringUtils.equalsIgnoreCase(item.getItemIdentifierString(), itemStrID)) { return item; } } return null; } /** * Find the item in the document via its identifier. * @param itemID the string identifier of the item being searched for * @return the item being searched for */ @SuppressWarnings("rawtypes") public PurApItem getItemByItemIdentifier(Integer itemID) { for (Iterator iter = items.iterator(); iter.hasNext();) { PurApItem item = (PurApItem) iter.next(); if (item.getItemIdentifier() == itemID) { return item; } } return null; } /** * Overriding the parent method so that we can just set the posting year without the other stuff that the parent does to the * accounting period. We only store the posting year on the doc and don't want the other stuff. * * @see org.kuali.kfs.sys.document.LedgerPostingDocumentBase#setPostingYear(java.lang.Integer) */ @Override public void setPostingYear(Integer postingYear) { this.postingYear = postingYear; } /** * @see org.kuali.kfs.sys.document.AccountingDocumentBase#getTotalDollarAmount() */ @Override public KualiDecimal getTotalDollarAmount() { return getTotalDollarAmountAllItems(null); } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#setTotalDollarAmount(KualiDecimal amount) */ @Override public void setTotalDollarAmount(KualiDecimal amount) { // do nothing, this is so that the jsp won't complain about totalDollarAmount have no setter method. } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#getTotalDollarAmountAllItems(String[] excludedTypes) */ @Override public KualiDecimal getTotalDollarAmountAllItems(String[] excludedTypes) { return getTotalDollarAmountWithExclusions(excludedTypes, true); } /** * Computes the total dollar amount of all above the line items. * * @return the total dollar amount of all above the line items. */ @Override public KualiDecimal getTotalDollarAmountAboveLineItems() { return getTotalDollarAmountAboveLineItems(null); } /** * Computes the total dollar amount of all above the line items with the specified item types excluded. * * @param excludedTypes the types of items to be excluded. * @return the total dollar amount of all above the line items with the specified item types excluded.. */ public KualiDecimal getTotalDollarAmountAboveLineItems(String[] excludedTypes) { return getTotalDollarAmountWithExclusions(excludedTypes, false); } /** * Computes the total dollar amount with the specified item types and possibly below the line items excluded. * * @param excludedTypes the types of items to be excluded. * @param includeBelowTheLine indicates whether below the line items shall be included. * @return the total dollar amount with the specified item types excluded. */ public KualiDecimal getTotalDollarAmountWithExclusions(String[] excludedTypes, boolean includeBelowTheLine) { List<PurApItem> itemsForTotal = getItems(); return getTotalDollarAmountWithExclusionsSubsetItems(excludedTypes, includeBelowTheLine, itemsForTotal); } /** * This method... * @param excludedTypes * @param includeBelowTheLine * @param itemsForTotal * @return */ protected KualiDecimal getTotalDollarAmountWithExclusionsSubsetItems(String[] excludedTypes, boolean includeBelowTheLine, List<PurApItem> itemsForTotal) { if (excludedTypes == null) { excludedTypes = new String[] {}; } KualiDecimal total = new KualiDecimal(BigDecimal.ZERO); for (PurApItem item : itemsForTotal) { item.refreshReferenceObject(PurapPropertyConstants.ITEM_TYPE); ItemType it = item.getItemType(); if ((includeBelowTheLine || it.isLineItemIndicator()) && !ArrayUtils.contains(excludedTypes, it.getItemTypeCode())) { KualiDecimal totalAmount = item.getTotalAmount(); KualiDecimal itemTotal = (totalAmount != null) ? totalAmount : KualiDecimal.ZERO; total = total.add(itemTotal); } } return total; } @Override public KualiDecimal getTotalDollarAmountForTradeIn() { List<PurApItem> tradeInItems = getTradeInItems(); return getTotalDollarAmountWithExclusionsSubsetItems(null,false,tradeInItems); } /** * This method... * @param tradeInItems */ @Override public List<PurApItem> getTradeInItems() { List<PurApItem> tradeInItems = new ArrayList<PurApItem>(); for (PurApItem purApItem : (List<PurApItem>)getItems()) { if(purApItem.getItemAssignedToTradeInIndicator()) { tradeInItems.add(purApItem); } } return tradeInItems; } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#getTotalPreTaxDollarAmount() */ @Override public KualiDecimal getTotalPreTaxDollarAmount() { return getTotalPreTaxDollarAmountAllItems(null); } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#setTotalPreTaxDollarAmount(org.kuali.rice.core.api.util.type.KualiDecimal) */ @Override public void setTotalPreTaxDollarAmount(KualiDecimal amount) { // do nothing, this is so that the jsp won't complain about totalDollarAmount have no setter method. } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#getTotalPreTaxDollarAmountAllItems(java.lang.String[]) */ @Override public KualiDecimal getTotalPreTaxDollarAmountAllItems(String[] excludedTypes) { return getTotalPreTaxDollarAmountWithExclusions(excludedTypes, true); } /** * Computes the total dollar amount of all above the line items. * * @return the total dollar amount of all above the line items. */ public KualiDecimal getTotalPreTaxDollarAmountAboveLineItems() { return getTotalPreTaxDollarAmountAboveLineItems(null); } /** * Computes the total dollar amount of all above the line items with the specified item types excluded. * * @param excludedTypes the types of items to be excluded. * @return the total dollar amount of all above the line items with the specified item types excluded.. */ public KualiDecimal getTotalPreTaxDollarAmountAboveLineItems(String[] excludedTypes) { return getTotalPreTaxDollarAmountWithExclusions(excludedTypes, false); } /** * Computes the total dollar amount with the specified item types and possibly below the line items excluded. * * @param excludedTypes the types of items to be excluded. * @param includeBelowTheLine indicates whether below the line items shall be included. * @return the total dollar amount with the specified item types excluded. */ public KualiDecimal getTotalPreTaxDollarAmountWithExclusions(String[] excludedTypes, boolean includeBelowTheLine) { if (excludedTypes == null) { excludedTypes = new String[] {}; } KualiDecimal total = new KualiDecimal(BigDecimal.ZERO); for (PurApItem item : (List<PurApItem>) getItems()) { item.refreshReferenceObject(PurapPropertyConstants.ITEM_TYPE); ItemType it = item.getItemType(); if ((includeBelowTheLine || it.isLineItemIndicator()) && !ArrayUtils.contains(excludedTypes, it.getItemTypeCode())) { KualiDecimal extendedPrice = item.getExtendedPrice(); KualiDecimal itemTotal = (extendedPrice != null) ? extendedPrice : KualiDecimal.ZERO; total = total.add(itemTotal); } } return total; } @Override public KualiDecimal getTotalTaxAmount() { return getTotalTaxAmountAllItems(null); } @Override public void setTotalTaxAmount(KualiDecimal amount) { // do nothing, this is so that the jsp won't complain about totalTaxAmount have no setter method. } @Override public KualiDecimal getTotalTaxAmountAllItems(String[] excludedTypes) { return getTotalTaxAmountWithExclusions(excludedTypes, true); } @Override public KualiDecimal getTotalTaxAmountAboveLineItems() { return getTotalTaxAmountAboveLineItems(null); } @Override public KualiDecimal getTotalTaxAmountAboveLineItems(String[] excludedTypes) { return getTotalTaxAmountWithExclusions(excludedTypes, false); } @Override public KualiDecimal getTotalTaxAmountWithExclusions(String[] excludedTypes, boolean includeBelowTheLine) { if (excludedTypes == null) { excludedTypes = new String[] {}; } KualiDecimal total = new KualiDecimal(BigDecimal.ZERO); for (PurApItem item : (List<PurApItem>) getItems()) { item.refreshReferenceObject(PurapPropertyConstants.ITEM_TYPE); ItemType it = item.getItemType(); if ((includeBelowTheLine || it.isLineItemIndicator()) && !ArrayUtils.contains(excludedTypes, it.getItemTypeCode())) { KualiDecimal taxAmount = item.getItemTaxAmount(); KualiDecimal itemTotal = (taxAmount != null) ? taxAmount : KualiDecimal.ZERO; total = total.add(itemTotal); } } return total; } @Override public boolean isUseTaxIndicator() { return useTaxIndicator; } @Override public void setUseTaxIndicator(boolean useTaxIndicator) { this.useTaxIndicator = useTaxIndicator; } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#templateVendorAddress(VendorAddress) */ @Override public void templateVendorAddress(VendorAddress vendorAddress) { if (vendorAddress == null) { return; } this.setVendorLine1Address(vendorAddress.getVendorLine1Address()); this.setVendorLine2Address(vendorAddress.getVendorLine2Address()); this.setVendorCityName(vendorAddress.getVendorCityName()); this.setVendorStateCode(vendorAddress.getVendorStateCode()); this.setVendorPostalCode(vendorAddress.getVendorZipCode()); this.setVendorCountryCode(vendorAddress.getVendorCountryCode()); } /** * Returns the vendor number for this document. * * @return the vendor number for this document. * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument#getVendorNumber() */ @Override public String getVendorNumber() { if (StringUtils.isNotEmpty(vendorNumber)) { return vendorNumber; } else if (ObjectUtils.isNotNull(vendorDetail)) { return vendorDetail.getVendorNumber(); } else { return ""; } } @Override public void setVendorNumber(String vendorNumber) { this.vendorNumber = vendorNumber; } public Boolean getOverrideWorkflowButtons() { return overrideWorkflowButtons; } public void setOverrideWorkflowButtons(Boolean overrideWorkflowButtons) { this.overrideWorkflowButtons = overrideWorkflowButtons; } @Override public Integer getVendorHeaderGeneratedIdentifier() { return vendorHeaderGeneratedIdentifier; } @Override public void setVendorHeaderGeneratedIdentifier(Integer vendorHeaderGeneratedIdentifier) { this.vendorHeaderGeneratedIdentifier = vendorHeaderGeneratedIdentifier; } @Override public Integer getVendorDetailAssignedIdentifier() { return vendorDetailAssignedIdentifier; } @Override public void setVendorDetailAssignedIdentifier(Integer vendorDetailAssignedIdentifier) { this.vendorDetailAssignedIdentifier = vendorDetailAssignedIdentifier; } @Override public String getVendorCustomerNumber() { return vendorCustomerNumber; } @Override public void setVendorCustomerNumber(String vendorCustomerNumber) { this.vendorCustomerNumber = vendorCustomerNumber; } @Override public Integer getPurapDocumentIdentifier() { return purapDocumentIdentifier; } @Override public void setPurapDocumentIdentifier(Integer identifier) { this.purapDocumentIdentifier = identifier; } @Override public VendorDetail getVendorDetail() { return vendorDetail; } public void setVendorDetail(VendorDetail vendorDetail) { this.vendorDetail = vendorDetail; } @Override @SuppressWarnings("rawtypes") public List getItems() { return items; } @Override @SuppressWarnings("rawtypes") public void setItems(List items) { this.items = items; } @Override public String getVendorCityName() { return vendorCityName; } @Override public void setVendorCityName(String vendorCityName) { this.vendorCityName = vendorCityName; } @Override public String getVendorCountryCode() { return vendorCountryCode; } @Override public void setVendorCountryCode(String vendorCountryCode) { this.vendorCountryCode = vendorCountryCode; } @Override public String getVendorLine1Address() { return vendorLine1Address; } @Override public void setVendorLine1Address(String vendorLine1Address) { this.vendorLine1Address = vendorLine1Address; } @Override public String getVendorLine2Address() { return vendorLine2Address; } @Override public void setVendorLine2Address(String vendorLine2Address) { this.vendorLine2Address = vendorLine2Address; } @Override public String getVendorName() { return vendorName; } @Override public void setVendorName(String vendorName) { this.vendorName = vendorName; } @Override public String getVendorPostalCode() { return vendorPostalCode; } @Override public void setVendorPostalCode(String vendorPostalCode) { this.vendorPostalCode = vendorPostalCode; } @Override public String getVendorStateCode() { return vendorStateCode; } @Override public void setVendorStateCode(String vendorStateCode) { this.vendorStateCode = vendorStateCode; } @Override public String getVendorAddressInternationalProvinceName() { return vendorAddressInternationalProvinceName; } @Override public void setVendorAddressInternationalProvinceName(String vendorAddressInternationalProvinceName) { this.vendorAddressInternationalProvinceName = vendorAddressInternationalProvinceName; } @Override public Integer getVendorAddressGeneratedIdentifier() { return vendorAddressGeneratedIdentifier; } @Override public void setVendorAddressGeneratedIdentifier(Integer vendorAddressGeneratedIdentifier) { this.vendorAddressGeneratedIdentifier = vendorAddressGeneratedIdentifier; } @Override public Integer getAccountsPayablePurchasingDocumentLinkIdentifier() { return accountsPayablePurchasingDocumentLinkIdentifier; } @Override public void setAccountsPayablePurchasingDocumentLinkIdentifier(Integer accountsPayablePurchasingDocumentLinkIdentifier) { this.accountsPayablePurchasingDocumentLinkIdentifier = accountsPayablePurchasingDocumentLinkIdentifier; } @Override public String[] getBelowTheLineTypes() { if (this.belowTheLineTypes == null) { this.belowTheLineTypes = SpringContext.getBean(PurapService.class).getBelowTheLineForDocument(this); } return belowTheLineTypes; } @Override public CountryEbo getVendorCountry() { if ( StringUtils.isBlank(vendorCountryCode) ) { vendorCountry = null; } else { if ( vendorCountry == null || !StringUtils.equals( vendorCountry.getCode(),vendorCountryCode) ) { ModuleService moduleService = SpringContext.getBean(KualiModuleService.class).getResponsibleModuleService(CountryEbo.class); if ( moduleService != null ) { Map<String,Object> keys = new HashMap<String, Object>(1); keys.put(LocationConstants.PrimaryKeyConstants.CODE, vendorCountryCode); vendorCountry = moduleService.getExternalizableBusinessObject(CountryEbo.class, keys); } else { throw new RuntimeException( "CONFIGURATION ERROR: No responsible module found for EBO class. Unable to proceed." ); } } } return vendorCountry; } /** * Added only to allow for {@link org.kuali.kfs.module.purap.util.PurApObjectUtils} class to work correctly. * * @deprecated */ @Deprecated public void setVendorCountry(CountryEbo vendorCountry) { this.vendorCountry = vendorCountry; } public String getVendorAttentionName() { return vendorAttentionName; } public void setVendorAttentionName(String vendorAttentionName) { this.vendorAttentionName = vendorAttentionName; } /** * Gets the accountDistributionMethod attribute. * * @return Returns the accountDistributionMethod */ public String getAccountDistributionMethod() { return accountDistributionMethod; } /** * Sets the accountDistributionMethod attribute. * * @param accountDistributionMethod The accountDistributionMethod to set. */ public void setAccountDistributionMethod(String accountDistributionMethod) { this.accountDistributionMethod = accountDistributionMethod; } /** * Determines whether the account is debit. It always returns false. * * @param financialDocument The document containing the account to be validated. * @param accountingLine The account to be validated. * @return boolean false. * @see org.kuali.kfs.sys.document.validation.AccountingLineRule#isDebit(org.kuali.kfs.sys.document.AccountingDocument, * org.kuali.kfs.sys.businessobject.AccountingLine) */ @Override public boolean isDebit(GeneralLedgerPendingEntrySourceDetail postable) { return false; } public PurApRelatedViews getRelatedViews() { if (relatedViews == null) { relatedViews = new PurApRelatedViews(this.documentNumber, this.accountsPayablePurchasingDocumentLinkIdentifier); } return relatedViews; } public void setRelatedViews(PurApRelatedViews relatedViews) { this.relatedViews = relatedViews; } @Override public void refreshNonUpdateableReferences() { super.refreshNonUpdateableReferences(); for (PurApItem item : (List<PurApItem>)this.getItems()) { //refresh the accounts if they do exist... for (PurApAccountingLine account : item.getSourceAccountingLines()) { account.refreshNonUpdateableReferences(); } } fixItemReferences(); } /** * This method fixes the item references in this document if it's new */ @Override public void fixItemReferences() { //fix item and account references in case this is a new doc (since they will be lost) if(ObjectUtils.isNull(this.purapDocumentIdentifier)) { for (PurApItem item : (List<PurApItem>)this.getItems()) { item.setPurapDocument(this); item.fixAccountReferences(); } } } /** * Returns the trade in item of the document. * * @return */ @Override public PurApItem getTradeInItem() { for (PurApItem item : (List<PurApItem>)getItems()) { if (item.getItemTypeCode().equals(PurapConstants.ItemTypeCodes.ITEM_TYPE_TRADE_IN_CODE)) { return item; } } return null; } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument.getIsATypeOfPurAPRecDoc(). */ @Override public boolean getIsATypeOfPurAPRecDoc() { return true; } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument.getIsATypeOfPurDoc(). */ @Override public boolean getIsATypeOfPurDoc() { if (this instanceof PurchasingDocumentBase) { return true; } else { return false; } } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument.getIsATypeOfPODoc(). */ @Override public boolean getIsATypeOfPODoc() { if (this instanceof PurchaseOrderDocument) { return true; } else { return false; } } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument.getIsPODoc(). */ @Override public boolean getIsPODoc() { if ( (this instanceof PurchaseOrderDocument) && !(this instanceof PurchaseOrderAmendmentDocument) && !(this instanceof PurchaseOrderCloseDocument) && !(this instanceof PurchaseOrderPaymentHoldDocument) && !(this instanceof PurchaseOrderRemoveHoldDocument) && !(this instanceof PurchaseOrderReopenDocument) && !(this instanceof PurchaseOrderRetransmitDocument) && !(this instanceof PurchaseOrderSplitDocument) && !(this instanceof PurchaseOrderVoidDocument)) { return true; } else { return false; } } /** * @see org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument.getIsATypeOfREQSDoc(). */ @Override public boolean getIsReqsDoc() { if (this instanceof RequisitionDocument) { return true; } else { return false; } } /** * build document title based on the properties of current document * * @param the default document title * @return the combine information of the given title and additional payment indicators */ protected String buildDocumentTitle(String title) { if(this.getVendorDetail() == null) { return title; } Integer vendorHeaderGeneratedIdentifier = this.getVendorDetail().getVendorHeaderGeneratedIdentifier(); VendorService vendorService = SpringContext.getBean(VendorService.class); Object[] indicators = new String[2]; boolean isEmployeeVendor = vendorService.isVendorInstitutionEmployee(vendorHeaderGeneratedIdentifier); indicators[0] = isEmployeeVendor ? AdHocPaymentIndicator.EMPLOYEE_VENDOR : AdHocPaymentIndicator.OTHER; boolean isVendorForeign = vendorService.isVendorForeign(vendorHeaderGeneratedIdentifier); indicators[1] = isVendorForeign ? AdHocPaymentIndicator.ALIEN_VENDOR : AdHocPaymentIndicator.OTHER; for(Object indicator : indicators) { if(!AdHocPaymentIndicator.OTHER.equals(indicator)) { String titlePattern = title + " [{0}:{1}]"; return MessageFormat.format(titlePattern, indicators); } } return title; } /** * Overridden to return the source lines of all of the items * @see org.kuali.kfs.sys.document.AccountingDocumentBase#getSourceAccountingLines() */ @SuppressWarnings("rawtypes") @Override public List getSourceAccountingLines() { if (ObjectUtils.isNotNull(sourceAccountingLines) && !sourceAccountingLines.isEmpty()) { // do nothing because acct lines have already been set return sourceAccountingLines; } else { /* SpringContext.getBean(PurapAccountingService.class).updateAccountAmounts(this); return SpringContext.getBean(PurapAccountingService.class).generateSummary(getItems()); */ List<AccountingLine> sourceAccountingLines = new ArrayList<AccountingLine>(); for (Object itemAsObject : this.getItems()) { final PurApItem item = (PurApItem)itemAsObject; for (PurApAccountingLine accountingLine : item.getSourceAccountingLines()) { //KFSMI-9053: check if the accounting line does not already exist in the list //and if so then add to the list. Preventing duplicates if (!isDuplicateAccountingLine(sourceAccountingLines, accountingLine)) { sourceAccountingLines.add(accountingLine); } } } return sourceAccountingLines; } } /** * Helper method to check if the source accounting line is already in the list and if so return true * * @param sourceAccountingLines * @param accountingLine * @return true if it is a duplicate else return false. */ protected boolean isDuplicateAccountingLine(List<AccountingLine> sourceAccountingLines, PurApAccountingLine accountingLine) { for (AccountingLine sourceLine : sourceAccountingLines) { PurApAccountingLine purapAccountLine = (PurApAccountingLine) sourceLine; if (purapAccountLine.accountStringsAreEqual(accountingLine)) { return true; } } return false; } /** * Helper method to find the matching accountingLines in the list of sourceAccountingLines and sum up the * lines amounts. * * @param accountingLine * @return accountTotalGLEntryAmount */ protected KualiDecimal getAccountTotalGLEntryAmount(AccountingLine matchingAccountingLine) { KualiDecimal accountTotalGLEntryAmount = KualiDecimal.ZERO; for (Object itemAsObject : this.getItems()) { final PurApItem item = (PurApItem)itemAsObject; for (PurApAccountingLine accountingLine : item.getSourceAccountingLines()) { //KFSMI-9053: check if the accounting line is a duplicate then add the total if (accountingLine.accountStringsAreEqual((SourceAccountingLine)matchingAccountingLine)) { accountTotalGLEntryAmount = accountTotalGLEntryAmount.add(accountingLine.getAmount()); } } } return accountTotalGLEntryAmount; } /** * Checks whether the related purchase order views need a warning to be displayed, * i.e. if at least one of the purchase orders has never been opened. * @return true if at least one related purchase order needs a warning; false otherwise */ public boolean getNeedWarningRelatedPOs() { List<PurchaseOrderView> poViews = getRelatedViews().getRelatedPurchaseOrderViews(); for (PurchaseOrderView poView : poViews) { if (poView.getNeedWarning()) { return true; } } return false; } /** * Accounting lines that are read-only should skip validation * @see org.kuali.kfs.sys.document.AccountingDocumentBase#getPersistedSourceAccountingLinesForComparison() */ @SuppressWarnings("rawtypes") @Override protected List getPersistedSourceAccountingLinesForComparison() { LOG.info("Checking persisted source accounting lines for read-only fields"); List<String> restrictedItemTypesList = new ArrayList<String>(); try { restrictedItemTypesList = new ArrayList<String>( SpringContext.getBean(ParameterService.class).getParameterValuesAsString(this.getClass(), PurapParameterConstants.PURAP_ITEM_TYPES_RESTRICTING_ACCOUNT_EDIT) ); } catch (IllegalArgumentException iae) { // do nothing, not a problem if no restricted types are defined } PurapAccountingService purApAccountingService = SpringContext.getBean(PurapAccountingService.class); List persistedSourceLines = new ArrayList(); for (PurApItem item : (List<PurApItem>) this.getItems()) { // only check items that already have been persisted since last save if (ObjectUtils.isNotNull(item.getItemIdentifier())) { // Disable validation if the item is read-only final boolean isNotReadOnly = !((restrictedItemTypesList != null) && restrictedItemTypesList.contains(item.getItemTypeCode())); if (isNotReadOnly) { persistedSourceLines.addAll(purApAccountingService.getAccountsFromItem(item)); } } } return persistedSourceLines; } /** * Accounting lines that are read-only should skip validation * @see org.kuali.kfs.sys.document.AccountingDocumentBase#getSourceAccountingLinesForComparison() */ @SuppressWarnings("rawtypes") @Override protected List getSourceAccountingLinesForComparison() { LOG.info("Checking source accounting lines for read-only fields"); List<String> restrictedItemTypesList = new ArrayList<String>(); try { restrictedItemTypesList = new ArrayList<String>( SpringContext.getBean(ParameterService.class).getParameterValuesAsString(this.getClass(), PurapParameterConstants.PURAP_ITEM_TYPES_RESTRICTING_ACCOUNT_EDIT) ); } catch (IllegalArgumentException iae) { // do nothing, not a problem if no restricted types are defined } PurapAccountingService purApAccountingService = SpringContext.getBean(PurapAccountingService.class); List currentSourceLines = new ArrayList(); for (PurApItem item : (List<PurApItem>) this.getItems()) { // Disable validation if the item is read-only final boolean isNotReadOnly = !((restrictedItemTypesList != null) && restrictedItemTypesList.contains(item.getItemTypeCode())); if (isNotReadOnly) { currentSourceLines.addAll(item.getSourceAccountingLines()); } } return currentSourceLines; } /** * Gets the calculated attribute. * * @return Returns the calculated */ @Override public boolean isCalculated() { return calculated; } /** * Sets the calculated attribute. * * @param calculated The calculated to set. */ @Override public void setCalculated(boolean calculated) { this.calculated = calculated; } @Override public NoteType getNoteType() { return NoteType.BUSINESS_OBJECT; } }