/* * 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.service.impl; import java.math.BigDecimal; import java.sql.Date; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; 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.StringUtils; import org.kuali.kfs.module.purap.PurapConstants; import org.kuali.kfs.module.purap.PurapConstants.PurchaseOrderStatuses; import org.kuali.kfs.module.purap.PurapKeyConstants; import org.kuali.kfs.module.purap.PurapParameterConstants; import org.kuali.kfs.module.purap.PurapParameterConstants.TaxParameters; import org.kuali.kfs.module.purap.PurapPropertyConstants; import org.kuali.kfs.module.purap.PurapRuleConstants; import org.kuali.kfs.module.purap.businessobject.AccountsPayableItem; import org.kuali.kfs.module.purap.businessobject.BulkReceivingView; import org.kuali.kfs.module.purap.businessobject.CorrectionReceivingView; import org.kuali.kfs.module.purap.businessobject.CreditMemoView; import org.kuali.kfs.module.purap.businessobject.ItemType; import org.kuali.kfs.module.purap.businessobject.LineItemReceivingView; import org.kuali.kfs.module.purap.businessobject.OrganizationParameter; import org.kuali.kfs.module.purap.businessobject.PaymentRequestView; import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine; import org.kuali.kfs.module.purap.businessobject.PurApItem; import org.kuali.kfs.module.purap.businessobject.PurApItemUseTax; import org.kuali.kfs.module.purap.businessobject.PurapEnterableItem; import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem; import org.kuali.kfs.module.purap.businessobject.PurchaseOrderView; import org.kuali.kfs.module.purap.businessobject.PurchasingItem; import org.kuali.kfs.module.purap.businessobject.PurchasingItemBase; import org.kuali.kfs.module.purap.businessobject.RequisitionView; import org.kuali.kfs.module.purap.document.AccountsPayableDocument; import org.kuali.kfs.module.purap.document.AccountsPayableDocumentBase; import org.kuali.kfs.module.purap.document.PaymentRequestDocument; import org.kuali.kfs.module.purap.document.PurapItemOperations; import org.kuali.kfs.module.purap.document.PurchaseOrderDocument; import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument; import org.kuali.kfs.module.purap.document.PurchasingDocument; import org.kuali.kfs.module.purap.document.RequisitionDocument; import org.kuali.kfs.module.purap.document.VendorCreditMemoDocument; import org.kuali.kfs.module.purap.document.service.LogicContainer; import org.kuali.kfs.module.purap.document.service.PurapService; import org.kuali.kfs.module.purap.document.service.PurchaseOrderService; import org.kuali.kfs.module.purap.service.PurapAccountingService; import org.kuali.kfs.module.purap.util.PurApItemUtils; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.KFSPropertyConstants; import org.kuali.kfs.sys.businessobject.SourceAccountingLine; import org.kuali.kfs.sys.businessobject.TaxDetail; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.document.validation.event.DocumentSystemSaveEvent; import org.kuali.kfs.sys.service.NonTransactional; import org.kuali.kfs.sys.service.TaxService; import org.kuali.kfs.sys.service.UniversityDateService; import org.kuali.kfs.sys.service.impl.KfsParameterConstants; import org.kuali.kfs.vnd.businessobject.CommodityCode; import org.kuali.kfs.vnd.document.service.VendorService; import org.kuali.rice.core.api.datetime.DateTimeService; import org.kuali.rice.core.api.parameter.ParameterEvaluator; import org.kuali.rice.core.api.parameter.ParameterEvaluatorService; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.kew.api.KewApiServiceLocator; import org.kuali.rice.kew.api.WorkflowDocument; import org.kuali.rice.kew.api.document.attribute.DocumentAttributeIndexingQueue; import org.kuali.rice.kew.api.exception.WorkflowException; import org.kuali.rice.kns.service.DataDictionaryService; import org.kuali.rice.kns.util.KNSGlobalVariables; import org.kuali.rice.krad.UserSession; import org.kuali.rice.krad.bo.Note; import org.kuali.rice.krad.document.Document; import org.kuali.rice.krad.exception.InfrastructureException; import org.kuali.rice.krad.service.BusinessObjectService; import org.kuali.rice.krad.service.DocumentService; import org.kuali.rice.krad.service.NoteService; import org.kuali.rice.krad.service.PersistenceService; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.KRADPropertyConstants; import org.kuali.rice.krad.util.ObjectUtils; @NonTransactional public class PurapServiceImpl implements PurapService { private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PurapServiceImpl.class); protected BusinessObjectService businessObjectService; protected DataDictionaryService dataDictionaryService; protected DateTimeService dateTimeService; protected DocumentService documentService; protected NoteService noteService; protected ParameterService parameterService; protected PersistenceService persistenceService; protected PurchaseOrderService purchaseOrderService; protected UniversityDateService universityDateService; protected VendorService vendorService; protected TaxService taxService; protected PurapAccountingService purapAccountingService; public void setBusinessObjectService(BusinessObjectService boService) { this.businessObjectService = boService; } public void setDateTimeService(DateTimeService dateTimeService) { this.dateTimeService = dateTimeService; } public void setParameterService(ParameterService parameterService) { this.parameterService = parameterService; } public void setDocumentService(DocumentService documentService) { this.documentService = documentService; } public void setDataDictionaryService(DataDictionaryService dataDictionaryService) { this.dataDictionaryService = dataDictionaryService; } public void setVendorService(VendorService vendorService) { this.vendorService = vendorService; } public void setPersistenceService(PersistenceService persistenceService) { this.persistenceService = persistenceService; } public void setPurchaseOrderService(PurchaseOrderService purchaseOrderService) { this.purchaseOrderService = purchaseOrderService; } public void setNoteService(NoteService noteService) { this.noteService = noteService; } public void setUniversityDateService(UniversityDateService universityDateService) { this.universityDateService = universityDateService; } public void setTaxService(TaxService taxService) { this.taxService = taxService; } @Override public void saveRoutingDataForRelatedDocuments(Integer accountsPayablePurchasingDocumentLinkIdentifier) { try { //save requisition routing data List<RequisitionView> reqViews = getRelatedViews(RequisitionView.class, accountsPayablePurchasingDocumentLinkIdentifier); for (Iterator<RequisitionView> iterator = reqViews.iterator(); iterator.hasNext();) { RequisitionView view = iterator.next(); Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber()); doc.getDocumentHeader().getWorkflowDocument().saveDocumentData(); } //save purchase order routing data List<PurchaseOrderView> poViews = getRelatedViews(PurchaseOrderView.class, accountsPayablePurchasingDocumentLinkIdentifier); for (Iterator<PurchaseOrderView> iterator = poViews.iterator(); iterator.hasNext();) { PurchaseOrderView view = iterator.next(); Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber()); doc.getDocumentHeader().getWorkflowDocument().saveDocumentData(); } //save payment request routing data List<PaymentRequestView> preqViews = getRelatedViews(PaymentRequestView.class, accountsPayablePurchasingDocumentLinkIdentifier); for (Iterator<PaymentRequestView> iterator = preqViews.iterator(); iterator.hasNext();) { PaymentRequestView view = iterator.next(); Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber()); doc.getDocumentHeader().getWorkflowDocument().saveDocumentData(); } //save credit memo routing data List<CreditMemoView> cmViews = getRelatedViews(CreditMemoView.class, accountsPayablePurchasingDocumentLinkIdentifier); for (Iterator<CreditMemoView> iterator = cmViews.iterator(); iterator.hasNext();) { CreditMemoView view = iterator.next(); Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber()); doc.getDocumentHeader().getWorkflowDocument().saveDocumentData(); } //save line item receiving routing data List<LineItemReceivingView> lineViews = getRelatedViews(LineItemReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier); for (Iterator<LineItemReceivingView> iterator = lineViews.iterator(); iterator.hasNext();) { LineItemReceivingView view = iterator.next(); Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber()); doc.getDocumentHeader().getWorkflowDocument().saveDocumentData(); } //save correction receiving routing data List<CorrectionReceivingView> corrViews = getRelatedViews(CorrectionReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier); for (Iterator<CorrectionReceivingView> iterator = corrViews.iterator(); iterator.hasNext();) { CorrectionReceivingView view = iterator.next(); Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber()); doc.getDocumentHeader().getWorkflowDocument().saveDocumentData(); } //save bulk receiving routing data List<BulkReceivingView> bulkViews = getRelatedViews(BulkReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier); for (Iterator<BulkReceivingView> iterator = bulkViews.iterator(); iterator.hasNext();) { BulkReceivingView view = iterator.next(); Document doc = documentService.getByDocumentHeaderId(view.getDocumentNumber()); doc.getDocumentHeader().getWorkflowDocument().saveDocumentData(); } } catch (WorkflowException e) { throw new InfrastructureException("unable to save routing data for related docs", e); } } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#getRelatedDocumentIds(java.lang.Integer) */ @Override public List<String> getRelatedDocumentIds(Integer accountsPayablePurchasingDocumentLinkIdentifier) { LOG.debug("getRelatedDocumentIds() started"); List<String> documentIdList = new ArrayList<String>(); //get requisition views List<RequisitionView> reqViews = getRelatedViews(RequisitionView.class, accountsPayablePurchasingDocumentLinkIdentifier); for (Iterator<RequisitionView> iterator = reqViews.iterator(); iterator.hasNext();) { RequisitionView view = iterator.next(); documentIdList.add(view.getDocumentNumber()); } //get purchase order views List<PurchaseOrderView> poViews = getRelatedViews(PurchaseOrderView.class, accountsPayablePurchasingDocumentLinkIdentifier); for (Iterator<PurchaseOrderView> iterator = poViews.iterator(); iterator.hasNext();) { PurchaseOrderView view = iterator.next(); documentIdList.add(view.getDocumentNumber()); } //get payment request views List<PaymentRequestView> preqViews = getRelatedViews(PaymentRequestView.class, accountsPayablePurchasingDocumentLinkIdentifier); for (Iterator<PaymentRequestView> iterator = preqViews.iterator(); iterator.hasNext();) { PaymentRequestView view = iterator.next(); documentIdList.add(view.getDocumentNumber()); } //get credit memo views List<CreditMemoView> cmViews = getRelatedViews(CreditMemoView.class, accountsPayablePurchasingDocumentLinkIdentifier); for (Iterator<CreditMemoView> iterator = cmViews.iterator(); iterator.hasNext();) { CreditMemoView view = iterator.next(); documentIdList.add(view.getDocumentNumber()); } //get line item receiving views List<LineItemReceivingView> lineViews = getRelatedViews(LineItemReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier); for (Iterator<LineItemReceivingView> iterator = lineViews.iterator(); iterator.hasNext();) { LineItemReceivingView view = iterator.next(); documentIdList.add(view.getDocumentNumber()); } //get correction receiving views List<CorrectionReceivingView> corrViews = getRelatedViews(CorrectionReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier); for (Iterator<CorrectionReceivingView> iterator = corrViews.iterator(); iterator.hasNext();) { CorrectionReceivingView view = iterator.next(); documentIdList.add(view.getDocumentNumber()); } //get bulk receiving views List<BulkReceivingView> bulkViews = getRelatedViews(BulkReceivingView.class, accountsPayablePurchasingDocumentLinkIdentifier); for (Iterator<BulkReceivingView> iterator = bulkViews.iterator(); iterator.hasNext();) { BulkReceivingView view = iterator.next(); documentIdList.add(view.getDocumentNumber()); } //TODO (hjs)get electronic invoice reject views??? return documentIdList; } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#getRelatedViews(java.lang.Class, java.lang.Integer) */ @Override @SuppressWarnings("unchecked") public List getRelatedViews(Class clazz, Integer accountsPayablePurchasingDocumentLinkIdentifier) { LOG.debug("getRelatedViews() started"); Map criteria = new HashMap(); criteria.put("accountsPayablePurchasingDocumentLinkIdentifier", accountsPayablePurchasingDocumentLinkIdentifier); // retrieve in descending order of document number so that newer documents are in the front List boList = (List) businessObjectService.findMatchingOrderBy(clazz, criteria, KFSPropertyConstants.DOCUMENT_NUMBER, false); return boList; } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#addBelowLineItems(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument) */ @Override @SuppressWarnings("unchecked") public void addBelowLineItems(PurchasingAccountsPayableDocument document) { LOG.debug("addBelowLineItems() started"); String[] itemTypes = getBelowTheLineForDocument(document); List<PurApItem> existingItems = document.getItems(); List<PurApItem> belowTheLine = new ArrayList<PurApItem>(); // needed in case they get out of sync below won't work sortBelowTheLine(itemTypes, existingItems, belowTheLine); List<String> existingItemTypes = new ArrayList<String>(); for (PurApItem existingItem : existingItems) { existingItemTypes.add(existingItem.getItemTypeCode()); } Class itemClass = document.getItemClass(); for (int i = 0; i < itemTypes.length; i++) { int lastFound; if (!existingItemTypes.contains(itemTypes[i])) { try { if (i > 0) { lastFound = existingItemTypes.lastIndexOf(itemTypes[i - 1]) + 1; } else { lastFound = existingItemTypes.size(); } PurApItem newItem = (PurApItem) itemClass.newInstance(); newItem.setItemTypeCode(itemTypes[i]); newItem.setPurapDocument(document); existingItems.add(lastFound, newItem); existingItemTypes.add(itemTypes[i]); } catch (Exception e) { // do something } } } document.fixItemReferences(); } /** * Sorts the below the line elements * * @param itemTypes * @param existingItems * @param belowTheLine */ protected void sortBelowTheLine(String[] itemTypes, List<PurApItem> existingItems, List<PurApItem> belowTheLine) { LOG.debug("sortBelowTheLine() started"); // sort existing below the line if any for (int i = 0; i < existingItems.size(); i++) { PurApItem purApItem = existingItems.get(i); if (purApItem.getItemType().isAdditionalChargeIndicator()) { belowTheLine.add(existingItems.get(i)); } } existingItems.removeAll(belowTheLine); for (int i = 0; i < itemTypes.length; i++) { for (PurApItem purApItem : belowTheLine) { if (StringUtils.equalsIgnoreCase(purApItem.getItemTypeCode(), itemTypes[i])) { existingItems.add(purApItem); break; } } } belowTheLine.removeAll(existingItems); if (belowTheLine.size() != 0) { throw new RuntimeException("below the line item sort didn't work: trying to remove an item without adding it back"); } } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#sortBelowTheLine(java.lang.String[], java.util.List, java.util.List) */ @Override public void sortBelowTheLine(PurchasingAccountsPayableDocument document) { LOG.debug("sortBelowTheLine() started"); String[] itemTypes = getBelowTheLineForDocument(document); List<PurApItem> existingItems = document.getItems(); List<PurApItem> belowTheLine = new ArrayList<PurApItem>(); // needed in case they get out of sync below won't work sortBelowTheLine(itemTypes, existingItems, belowTheLine); } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#getBelowTheLineForDocument(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument) */ @Override public String[] getBelowTheLineForDocument(PurchasingAccountsPayableDocument document) { LOG.debug("getBelowTheLineForDocument() started"); // Obtain a list of below the line items from system parameter // String documentTypeClassName = document.getClass().getName(); // String[] documentTypeArray = StringUtils.split(documentTypeClassName, "."); // String documentType = documentTypeArray[documentTypeArray.length - 1]; //FIXME RELEASE 3 (hjs) why is this "if" here with no code in it? is it supposed to be doing somethign? // If it's a credit memo, we'll have to append the source of the credit memo // whether it's created from a Vendor, a PO or a PREQ. // if (documentType.equals("CreditMemoDocument")) { // // } String documentType = dataDictionaryService.getDocumentTypeNameByClass(document.getClass()); try { return parameterService.getParameterValuesAsString(Class.forName(PurapConstants.PURAP_DETAIL_TYPE_CODE_MAP.get(documentType)), PurapConstants.BELOW_THE_LINES_PARAMETER).toArray(new String[] {}); } catch (ClassNotFoundException e) { throw new RuntimeException("The getBelowTheLineForDocument method of PurapServiceImpl was unable to resolve the document class for type: " + PurapConstants.PURAP_DETAIL_TYPE_CODE_MAP.get(documentType), e); } } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#getBelowTheLineByType(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument, * org.kuali.kfs.module.purap.businessobject.ItemType) */ @Override public PurApItem getBelowTheLineByType(PurchasingAccountsPayableDocument document, ItemType iT) { LOG.debug("getBelowTheLineByType() started"); String[] itemTypes = getBelowTheLineForDocument(document); boolean foundItemType = false; for (String itemType : itemTypes) { if (StringUtils.equals(iT.getItemTypeCode(), itemType)) { foundItemType = true; break; } } if (!foundItemType) { return null; } PurApItem belowTheLineItem = null; for (PurApItem item : document.getItems()) { if (item.getItemType().isAdditionalChargeIndicator()) { if (StringUtils.equals(iT.getItemTypeCode(), item.getItemType().getItemTypeCode())) { belowTheLineItem = item; break; } } } return belowTheLineItem; } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#getDateFromOffsetFromToday(int) */ @Override public java.sql.Date getDateFromOffsetFromToday(int offsetDays) { Calendar calendar = dateTimeService.getCurrentCalendar(); calendar.add(Calendar.DATE, offsetDays); return new java.sql.Date(calendar.getTimeInMillis()); } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#isDateInPast(java.sql.Date) */ @Override public boolean isDateInPast(Date compareDate) { LOG.debug("isDateInPast() started"); Date today = dateTimeService.getCurrentSqlDate(); int diffFromToday = dateTimeService.dateDiff(today, compareDate, false); return (diffFromToday < 0); } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#isDateMoreThanANumberOfDaysAway(java.sql.Date, int) */ @Override public boolean isDateMoreThanANumberOfDaysAway(Date compareDate, int daysAway) { LOG.debug("isDateMoreThanANumberOfDaysAway() started"); Date todayAtMidnight = dateTimeService.getCurrentSqlDateMidnight(); Calendar daysAwayCalendar = dateTimeService.getCalendar(todayAtMidnight); daysAwayCalendar.add(Calendar.DATE, daysAway); Timestamp daysAwayTime = new Timestamp(daysAwayCalendar.getTime().getTime()); Calendar compareCalendar = dateTimeService.getCalendar(compareDate); compareCalendar.set(Calendar.HOUR, 0); compareCalendar.set(Calendar.MINUTE, 0); compareCalendar.set(Calendar.SECOND, 0); compareCalendar.set(Calendar.MILLISECOND, 0); compareCalendar.set(Calendar.AM_PM, Calendar.AM); Timestamp compareTime = new Timestamp(compareCalendar.getTime().getTime()); return (compareTime.compareTo(daysAwayTime) > 0); } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#isDateAYearAfterToday(java.sql.Date) */ @Override public boolean isDateAYearBeforeToday(Date compareDate) { LOG.debug("isDateAYearBeforeToday() started"); Calendar calendar = dateTimeService.getCurrentCalendar(); calendar.add(Calendar.YEAR, -1); java.sql.Date yearAgo = new java.sql.Date(calendar.getTimeInMillis()); int diffFromYearAgo = dateTimeService.dateDiff(compareDate, yearAgo, false); return (diffFromYearAgo > 0); } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#getApoLimit(java.lang.Integer, java.lang.String, java.lang.String) */ @Override @SuppressWarnings("unchecked") public KualiDecimal getApoLimit(Integer vendorContractGeneratedIdentifier, String chart, String org) { LOG.debug("getApoLimit() started"); KualiDecimal purchaseOrderTotalLimit = vendorService.getApoLimitFromContract(vendorContractGeneratedIdentifier, chart, org); // We didn't find the limit on the vendor contract, get it from the org parameter table. if (ObjectUtils.isNull(purchaseOrderTotalLimit) && !ObjectUtils.isNull(chart) && !ObjectUtils.isNull(org)) { OrganizationParameter organizationParameter = new OrganizationParameter(); organizationParameter.setChartOfAccountsCode(chart); organizationParameter.setOrganizationCode(org); Map orgParamKeys = persistenceService.getPrimaryKeyFieldValues(organizationParameter); orgParamKeys.put(KRADPropertyConstants.ACTIVE_INDICATOR, true); organizationParameter = businessObjectService.findByPrimaryKey(OrganizationParameter.class, orgParamKeys); purchaseOrderTotalLimit = (organizationParameter == null) ? null : organizationParameter.getOrganizationAutomaticPurchaseOrderLimit(); } if (ObjectUtils.isNull(purchaseOrderTotalLimit)) { String defaultLimit = parameterService.getParameterValueAsString(RequisitionDocument.class, PurapParameterConstants.AUTOMATIC_PURCHASE_ORDER_DEFAULT_LIMIT_AMOUNT); purchaseOrderTotalLimit = new KualiDecimal(defaultLimit); } return purchaseOrderTotalLimit; } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#isFullDocumentEntryCompleted(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument) */ @Override public boolean isFullDocumentEntryCompleted(PurchasingAccountsPayableDocument purapDocument) { LOG.debug("isFullDocumentEntryCompleted() started"); // for now just return true if not in one of the first few states boolean value = false; if (purapDocument instanceof PaymentRequestDocument) { value = PurapConstants.PaymentRequestStatuses.STATUS_ORDER.isFullDocumentEntryCompleted(purapDocument.getApplicationDocumentStatus()); } else if (purapDocument instanceof VendorCreditMemoDocument) { value = PurapConstants.CreditMemoStatuses.STATUS_ORDER.isFullDocumentEntryCompleted(purapDocument.getApplicationDocumentStatus()); } return value; } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#isPaymentRequestFullDocumentEntryCompleted(String) */ @Override public boolean isPaymentRequestFullDocumentEntryCompleted(String purapDocumentStatus) { LOG.debug("isPaymentRequestFullDocumentEntryCompleted() started"); return PurapConstants.PaymentRequestStatuses.STATUS_ORDER.isFullDocumentEntryCompleted(purapDocumentStatus); } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#isVendorCreditMemoFullDocumentEntryCompleted(String) */ @Override public boolean isVendorCreditMemoFullDocumentEntryCompleted(String purapDocumentStatus) { LOG.debug("isVendorCreditMemoFullDocumentEntryCompleted() started"); return PurapConstants.CreditMemoStatuses.STATUS_ORDER.isFullDocumentEntryCompleted(purapDocumentStatus); } /** * Main hook point for close/Reopen PO. * * @see org.kuali.kfs.module.purap.document.service.PurapService#performLogicForCloseReopenPO(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument) */ @Override public void performLogicForCloseReopenPO(PurchasingAccountsPayableDocument purapDocument) { LOG.debug("performLogicForCloseReopenPO() started"); if (purapDocument instanceof PaymentRequestDocument) { PaymentRequestDocument paymentRequest = (PaymentRequestDocument) purapDocument; if (paymentRequest.isClosePurchaseOrderIndicator() && PurapConstants.PurchaseOrderStatuses.APPDOC_OPEN.equals(paymentRequest.getPurchaseOrderDocument().getApplicationDocumentStatus())) { // get the po id and get the current po // check the current po: if status is not closed and there is no pending action... route close po as system user processCloseReopenPo((AccountsPayableDocumentBase) purapDocument, PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_CLOSE_DOCUMENT); } } else if (purapDocument instanceof VendorCreditMemoDocument) { VendorCreditMemoDocument creditMemo = (VendorCreditMemoDocument) purapDocument; if (creditMemo.isReopenPurchaseOrderIndicator() && PurapConstants.PurchaseOrderStatuses.APPDOC_CLOSED.equals(creditMemo.getPurchaseOrderDocument().getApplicationDocumentStatus())) { // get the po id and get the current PO // route 'Re-Open PO Document' if PO criteria meets requirements from business rules processCloseReopenPo((AccountsPayableDocumentBase) purapDocument, PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_REOPEN_DOCUMENT); } } else { throw new RuntimeException("Attempted to perform full entry logic for unhandled document type '" + purapDocument.getClass().getName() + "'"); } } /** * Remove items that have not been "entered" which means no data has been added to them so no more processing needs to continue * on these items. * * @param apDocument AccountsPayableDocument which contains list of items to be reviewed */ @Override public void deleteUnenteredItems(PurapItemOperations document) { LOG.debug("deleteUnenteredItems() started"); List<PurapEnterableItem> deletionList = new ArrayList<PurapEnterableItem>(); for (PurapEnterableItem item : (List<PurapEnterableItem>) document.getItems()) { if (!item.isConsideredEntered()) { deletionList.add(item); } } document.getItems().removeAll(deletionList); } /** * Actual method that will close or reopen a po. * * @param apDocument AccountsPayableDocument * @param docType */ @SuppressWarnings("unchecked") public void processCloseReopenPo(AccountsPayableDocumentBase apDocument, String docType) { LOG.debug("processCloseReopenPo() started"); String action = null; String newStatus = null; // setup text for note that will be created, will either be closed or reopened if (PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_CLOSE_DOCUMENT.equals(docType)) { action = "closed"; newStatus = PurchaseOrderStatuses.APPDOC_PENDING_CLOSE; } else if (PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_REOPEN_DOCUMENT.equals(docType)) { action = "reopened"; newStatus = PurchaseOrderStatuses.APPDOC_PENDING_REOPEN; } else { String errorMessage = "Method processCloseReopenPo called using ID + '" + apDocument.getPurapDocumentIdentifier() + "' and invalid doc type '" + docType + "'"; LOG.error(errorMessage); throw new RuntimeException(errorMessage); } Integer poId = apDocument.getPurchaseOrderIdentifier(); PurchaseOrderDocument purchaseOrderDocument = purchaseOrderService.getCurrentPurchaseOrder(poId); if (!StringUtils.equalsIgnoreCase(purchaseOrderDocument.getDocumentHeader().getWorkflowDocument().getDocumentTypeName(), docType)) { // we are skipping the validation above because it would be too late to correct any errors (i.e. because in // post-processing) purchaseOrderService.createAndRoutePotentialChangeDocument(purchaseOrderDocument.getDocumentNumber(), docType, assemblePurchaseOrderNote(apDocument, docType, action), new ArrayList(), newStatus); } /* * if we made it here, route document has not errored out, so set appropriate indicator depending on what is being * requested. */ if (PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_CLOSE_DOCUMENT.equals(docType)) { apDocument.setClosePurchaseOrderIndicator(false); //add a note to the purchase order indicating it has been closed by a payment request document String userName = apDocument.getLastActionPerformedByPersonName(); StringBuffer poNote = new StringBuffer(""); poNote.append("PO was closed manually by "); poNote.append( userName ); poNote.append(" in approving PREQ with ID "); poNote.append(apDocument.getDocumentNumber()); //save the note to the purchase order try{ Note noteObj = documentService.createNoteFromDocument(apDocument.getPurchaseOrderDocument(), poNote.toString()); noteObj.setNoteTypeCode(apDocument.getPurchaseOrderDocument().getNoteType().getCode()); apDocument.getPurchaseOrderDocument().addNote(noteObj); noteService.save(noteObj); }catch(Exception e){ String errorMessage = "Error creating and saving close note for purchase order with document service"; LOG.error("processCloseReopenPo() " + errorMessage, e); throw new RuntimeException(errorMessage, e); } } else if (PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_REOPEN_DOCUMENT.equals(docType)) { apDocument.setReopenPurchaseOrderIndicator(false); } } /** * Generate a note for the close/reopen po method. * * @param docType * @param preqId * @return Note to be saved */ protected String assemblePurchaseOrderNote(AccountsPayableDocumentBase apDocument, String docType, String action) { LOG.debug("assemblePurchaseOrderNote() started"); String documentLabel = dataDictionaryService.getDocumentLabelByClass(apDocument.getClass()); StringBuffer closeReopenNote = new StringBuffer(""); String userName = GlobalVariables.getUserSession().getPerson().getName(); closeReopenNote.append(dataDictionaryService.getDocumentLabelByTypeName(KFSConstants.FinancialDocumentTypeCodes.PURCHASE_ORDER)); closeReopenNote.append(" will be manually "); closeReopenNote.append(action); closeReopenNote.append(" by "); closeReopenNote.append(userName); closeReopenNote.append(" when approving "); closeReopenNote.append(documentLabel); closeReopenNote.append(" with "); closeReopenNote.append(dataDictionaryService.getAttributeLabel(apDocument.getClass(), PurapPropertyConstants.PURAP_DOC_ID)); closeReopenNote.append(" "); closeReopenNote.append(apDocument.getPurapDocumentIdentifier()); return closeReopenNote.toString(); } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#performLogicWithFakedUserSession(java.lang.String, org.kuali.kfs.module.purap.document.service.LogicContainer, java.lang.Object[]) */ @Override public Object performLogicWithFakedUserSession(String requiredPersonPersonUserId, LogicContainer logicToRun, Object... objects) throws WorkflowException, Exception { LOG.debug("performLogicWithFakedUserSession() started"); if (StringUtils.isBlank(requiredPersonPersonUserId)) { throw new RuntimeException("Attempted to perform logic with a fake user session with a blank user person id: '" + requiredPersonPersonUserId + "'"); } if (ObjectUtils.isNull(logicToRun)) { throw new RuntimeException("Attempted to perform logic with a fake user session with no logic to run"); } UserSession actualUserSession = GlobalVariables.getUserSession(); try { GlobalVariables.setUserSession(new UserSession(requiredPersonPersonUserId)); return logicToRun.runLogic(objects); } finally { GlobalVariables.setUserSession(actualUserSession); } } /** * @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#saveDocumentNoValidation(org.kuali.kfs.module.purap.document.PurchaseOrderDocument) */ @Override public void saveDocumentNoValidation(Document document) { try { // FIXME The following code of refreshing document header is a temporary fix for the issue that // in some cases (seem random) the doc header fields are null; and if doc header is refreshed, the workflow doc becomes null. // The root cause of this is that when some docs are retrieved manually using OJB criteria, ref objs such as doc header or workflow doc // aren't retrieved; the solution would be to add these refreshing when documents are retrieved in those OJB methods. if (document.getDocumentHeader() != null && document.getDocumentHeader().getDocumentNumber() == null) { WorkflowDocument workflowDocument = document.getDocumentHeader().getWorkflowDocument(); document.refreshReferenceObject("documentHeader"); document.getDocumentHeader().setWorkflowDocument(workflowDocument); } documentService.saveDocument(document, DocumentSystemSaveEvent.class); // At this point, the work-flow status will not change for the current document, but the document status will. // This causes the search indices for the document to become out of synch, and will show a different status type // in the RICE lookup results screen. final DocumentAttributeIndexingQueue documentAttributeIndexingQueue = KewApiServiceLocator.getDocumentAttributeIndexingQueue(); documentAttributeIndexingQueue.indexDocument(document.getDocumentNumber()); } catch (WorkflowException we) { String errorMsg = "Workflow error saving document # " + document.getDocumentHeader().getDocumentNumber() + " " + we.getMessage(); LOG.error(errorMsg, we); throw new RuntimeException(errorMsg, we); } catch (NumberFormatException ne) { String errorMsg = "Invalid document number format for document # " + document.getDocumentHeader().getDocumentNumber() + " " + ne.getMessage(); LOG.error(errorMsg, ne); throw new RuntimeException(errorMsg, ne); } } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#isDocumentStoppedInRouteNode(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument, java.lang.String) */ @Override public boolean isDocumentStoppedInRouteNode(PurchasingAccountsPayableDocument document, String nodeName) { WorkflowDocument workflowDoc = document.getDocumentHeader().getWorkflowDocument(); Set<String> currentRouteLevels = workflowDoc.getCurrentNodeNames(); if (CollectionUtils.isNotEmpty(currentRouteLevels) && currentRouteLevels.contains(nodeName) && workflowDoc.isApprovalRequested()) { return true; } return false; } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#allowEncumberNextFiscalYear() */ @Override public boolean allowEncumberNextFiscalYear() { LOG.debug("allowEncumberNextFiscalYear() started"); java.util.Date today = dateTimeService.getCurrentDate(); java.util.Date closingDate = universityDateService.getLastDateOfFiscalYear(universityDateService.getCurrentFiscalYear()); int allowEncumberNext = (Integer.parseInt(parameterService.getParameterValueAsString(RequisitionDocument.class, PurapRuleConstants.ALLOW_ENCUMBER_NEXT_YEAR_DAYS))); int diffTodayClosing = dateTimeService.dateDiff(today, closingDate, false); if (ObjectUtils.isNotNull(closingDate) && ObjectUtils.isNotNull(today) && ObjectUtils.isNotNull(allowEncumberNext)) { if ( LOG.isDebugEnabled() ) { LOG.debug("allowEncumberNextFiscalYear() today = " + dateTimeService.toDateString(today) + "; encumber next FY range = " + allowEncumberNext + " - " + dateTimeService.toDateTimeString(today)); } if (allowEncumberNext >= diffTodayClosing && diffTodayClosing >= KualiDecimal.ZERO.intValue()) { LOG.debug("allowEncumberNextFiscalYear() encumber next FY allowed; return true."); return true; } } LOG.debug("allowEncumberNextFiscalYear() encumber next FY not allowed; return false."); return false; } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#getAllowedFiscalYears() */ @Override public List<Integer> getAllowedFiscalYears() { List<Integer> allowedYears = new ArrayList<Integer>(); Integer currentFY = universityDateService.getCurrentFiscalYear(); allowedYears.add(currentFY); if (allowEncumberNextFiscalYear()) { allowedYears.add(currentFY + 1); } return allowedYears; } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#isTodayWithinApoAllowedRange() */ @Override public boolean isTodayWithinApoAllowedRange() { java.util.Date today = dateTimeService.getCurrentDate(); Integer currentFY = universityDateService.getCurrentFiscalYear(); java.util.Date closingDate = universityDateService.getLastDateOfFiscalYear(currentFY); int allowApoDate = (Integer.parseInt(parameterService.getParameterValueAsString(RequisitionDocument.class, PurapRuleConstants.ALLOW_APO_NEXT_FY_DAYS))); int diffTodayClosing = dateTimeService.dateDiff(today, closingDate, true); return diffTodayClosing <= allowApoDate; } /** * * @see org.kuali.kfs.module.purap.document.service.PurapService#clearTax(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument) */ @Override public void clearTax(PurchasingAccountsPayableDocument purapDocument, boolean useTax){ for (PurApItem item : purapDocument.getItems()) { if(useTax) { item.getUseTaxItems().clear(); } else { item.setItemTaxAmount(null); } } } @Override public void updateUseTaxIndicator(PurchasingAccountsPayableDocument purapDocument, boolean newUseTaxIndicatorValue) { boolean currentUseTaxIndicator = purapDocument.isUseTaxIndicator(); if(currentUseTaxIndicator!=newUseTaxIndicatorValue) { //i.e. if the indicator changed clear out the tax clearTax(purapDocument, currentUseTaxIndicator); } purapDocument.setUseTaxIndicator(newUseTaxIndicatorValue); } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#calculateTax(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument) */ @Override public void calculateTax(PurchasingAccountsPayableDocument purapDocument){ boolean salesTaxInd = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean(KfsParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_SALES_TAX_IND); boolean useTaxIndicator = purapDocument.isUseTaxIndicator(); String deliveryState = getDeliveryState(purapDocument); String deliveryPostalCode = getDeliveryPostalCode(purapDocument); Date transactionTaxDate = purapDocument.getTransactionTaxDate(); //calculate if sales tax enabled for purap if( salesTaxInd || useTaxIndicator ){ //iterate over items and calculate tax if taxable for(PurApItem item : purapDocument.getItems()){ if( isTaxable(useTaxIndicator, deliveryState, item) ){ calculateItemTax(useTaxIndicator, deliveryPostalCode, transactionTaxDate, item, item.getUseTaxClass(), purapDocument); } } } } @Override public String getDeliveryState(PurchasingAccountsPayableDocument purapDocument){ if (purapDocument instanceof PurchasingDocument){ PurchasingDocument document = (PurchasingDocument)purapDocument; return document.getDeliveryStateCode(); }else if (purapDocument instanceof AccountsPayableDocument){ AccountsPayableDocument document = (AccountsPayableDocument)purapDocument; if (document.getPurchaseOrderDocument() == null){ throw new RuntimeException("PurchaseOrder document does not exists"); } return document.getPurchaseOrderDocument().getDeliveryStateCode(); } return null; } protected String getDeliveryPostalCode(PurchasingAccountsPayableDocument purapDocument){ if (purapDocument instanceof PurchasingDocument){ PurchasingDocument document = (PurchasingDocument)purapDocument; return document.getDeliveryPostalCode(); }else if (purapDocument instanceof AccountsPayableDocument){ AccountsPayableDocument docBase = (AccountsPayableDocument)purapDocument; if (docBase.getPurchaseOrderDocument() == null){ throw new RuntimeException("PurchaseOrder document does not exists"); } return docBase.getPurchaseOrderDocument().getDeliveryPostalCode(); } return null; } /** * Determines if the item is taxable based on a decision tree. * * @param useTaxIndicator * @param deliveryState * @param item * @return */ @Override public boolean isTaxable(boolean useTaxIndicator, String deliveryState, PurApItem item){ boolean taxable = false; if(item.getItemType().isTaxableIndicator() && ((ObjectUtils.isNull(item.getItemTaxAmount()) && useTaxIndicator == false) || useTaxIndicator) && (doesCommodityAllowCallToTaxService(item)) && (doesAccountAllowCallToTaxService(deliveryState, item)) ){ taxable = true; } return taxable; } /** * * @see org.kuali.kfs.module.purap.document.service.PurapService#isTaxableForSummary(boolean, java.lang.String, org.kuali.kfs.module.purap.businessobject.PurApItem) */ @Override public boolean isTaxableForSummary(boolean useTaxIndicator, String deliveryState, PurApItem item){ boolean taxable = false; if(item.getItemType().isTaxableIndicator() && (doesCommodityAllowCallToTaxService(item)) && (doesAccountAllowCallToTaxService(deliveryState, item)) ){ taxable = true; } return taxable; } /** * Determines if the the tax service should be called due to the commodity code. * * @param item * @return */ protected boolean doesCommodityAllowCallToTaxService(PurApItem item) { boolean callService = true; // only check for commodity code on above the line times (additional charges don't allow commodity code) if (item.getItemType().isLineItemIndicator()) { if (item instanceof PurchasingItem) { PurchasingItemBase purItem = (PurchasingItemBase) item; if (purItem.getPurchasingCommodityCode() == null) { return callService; } callService = isCommodityCodeTaxable(purItem.getCommodityCode()); }// if not a purchasing item, then pull item from PO else if (item instanceof AccountsPayableItem) { AccountsPayableItem apItem = (AccountsPayableItem) item; PurchaseOrderItem poItem = apItem.getPurchaseOrderItem(); if (ObjectUtils.isNotNull(poItem)) { if (poItem.getPurchasingCommodityCode() == null) { return callService; } callService = isCommodityCodeTaxable(poItem.getCommodityCode()); } } } return callService; } protected boolean isCommodityCodeTaxable(CommodityCode commodityCode){ boolean isTaxable = true; if(ObjectUtils.isNotNull(commodityCode)){ if(commodityCode.isSalesTaxIndicator() == false){ //not taxable, so don't call service isTaxable = false; }//if true we want to call service }//if null, return true return isTaxable; } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#isDeliveryStateTaxable(java.lang.String) */ @Override public boolean isDeliveryStateTaxable(String deliveryState) { ParameterEvaluator parmEval = /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(KfsParameterConstants.PURCHASING_DOCUMENT.class, TaxParameters.TAXABLE_DELIVERY_STATES, deliveryState); return parmEval.evaluationSucceeds(); } /** * Checks if the account is taxable, based on the delivery state, fund/subfund groups, and object code level/consolidations. * * @param deliveryState * @param item * @return */ protected boolean doesAccountAllowCallToTaxService(String deliveryState, PurApItem item) { boolean callService = false; boolean deliveryStateTaxable = isDeliveryStateTaxable(deliveryState); for (PurApAccountingLine acctLine : item.getSourceAccountingLines()) { if(isAccountingLineTaxable(acctLine, deliveryStateTaxable)){ callService = true; break; } } return callService; } /** * @see org.kuali.kfs.module.purap.document.service.PurapService#isAccountingLineTaxable(org.kuali.kfs.module.purap.businessobject.PurApAccountingLine, boolean) */ @Override public boolean isAccountingLineTaxable(PurApAccountingLine acctLine, boolean deliveryStateTaxable){ boolean isTaxable = false; String parameterSuffix = null; if (deliveryStateTaxable) { parameterSuffix = TaxParameters.FOR_TAXABLE_STATES_SUFFIX; } else { parameterSuffix = TaxParameters.FOR_NON_TAXABLE_STATES_SUFFIX; } // is account (fund/subfund) and object code (level/consolidation) taxable? if (isAccountTaxable(parameterSuffix, acctLine) && isObjectCodeTaxable(parameterSuffix, acctLine)) { isTaxable = true; } return isTaxable; } /** * Checks if the account fund/subfund groups are in a set of parameters taking into account allowed/denied constraints and * ultimately determines if taxable. * * @param parameterSuffix * @param acctLine * @return */ protected boolean isAccountTaxable(String parameterSuffix, PurApAccountingLine acctLine){ boolean isAccountTaxable = false; String fundParam = TaxParameters.TAXABLE_FUND_GROUPS_PREFIX + parameterSuffix; String subFundParam = TaxParameters.TAXABLE_SUB_FUND_GROUPS_PREFIX + parameterSuffix; ParameterEvaluator fundParamEval = null; ParameterEvaluator subFundParamEval = null; if (ObjectUtils.isNull(acctLine.getAccount().getSubFundGroup())){ acctLine.refreshNonUpdateableReferences(); } fundParamEval = /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(KfsParameterConstants.PURCHASING_DOCUMENT.class, fundParam, acctLine.getAccount().getSubFundGroup().getFundGroupCode()); subFundParamEval = /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(KfsParameterConstants.PURCHASING_DOCUMENT.class, subFundParam, acctLine.getAccount().getSubFundGroupCode()); if( (isAllowedFound(fundParamEval) && (isAllowedFound(subFundParamEval) || isAllowedNotFound(subFundParamEval) || isDeniedNotFound(subFundParamEval))) || (isAllowedNotFound(fundParamEval) && isAllowedFound(subFundParamEval)) || (isDeniedFound(fundParamEval) && isAllowedFound(subFundParamEval)) || (isDeniedNotFound(fundParamEval) && (isAllowedFound(subFundParamEval) || isAllowedNotFound(subFundParamEval) || isDeniedNotFound(subFundParamEval))) ){ isAccountTaxable = true; } return isAccountTaxable; } /** * Checks if the object code level/consolidation groups are in a set of parameters taking into account allowed/denied constraints and * ultimately determines if taxable. * * @param parameterSuffix * @param acctLine * @return */ protected boolean isObjectCodeTaxable(String parameterSuffix, PurApAccountingLine acctLine){ boolean isObjectCodeTaxable = false; String levelParam = TaxParameters.TAXABLE_OBJECT_LEVELS_PREFIX + parameterSuffix; String consolidationParam = TaxParameters.TAXABLE_OBJECT_CONSOLIDATIONS_PREFIX + parameterSuffix; ParameterEvaluator levelParamEval = null; ParameterEvaluator consolidationParamEval = null; //refresh financial object level acctLine.getObjectCode().refreshReferenceObject("financialObjectLevel"); levelParamEval = /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(KfsParameterConstants.PURCHASING_DOCUMENT.class, levelParam, acctLine.getObjectCode().getFinancialObjectLevelCode()); consolidationParamEval = /*REFACTORME*/SpringContext.getBean(ParameterEvaluatorService.class).getParameterEvaluator(KfsParameterConstants.PURCHASING_DOCUMENT.class, consolidationParam, acctLine.getObjectCode().getFinancialObjectLevel().getFinancialConsolidationObjectCode()); if( (isAllowedFound(levelParamEval) && (isAllowedFound(consolidationParamEval) || isAllowedNotFound(consolidationParamEval) || isDeniedNotFound(consolidationParamEval))) || (isAllowedNotFound(levelParamEval) && isAllowedFound(consolidationParamEval)) || (isDeniedFound(levelParamEval) && isAllowedFound(consolidationParamEval)) || (isDeniedNotFound(levelParamEval) && (isAllowedFound(consolidationParamEval) || isAllowedNotFound(consolidationParamEval) || isDeniedNotFound(consolidationParamEval))) ){ isObjectCodeTaxable = true; } return isObjectCodeTaxable; } /** * Helper method to work with parameter evaluator to find, allowed and found in parameter value. * * @param eval * @return */ protected boolean isAllowedFound(ParameterEvaluator eval) { boolean exists = false; if (eval.evaluationSucceeds() && eval.constraintIsAllow()) { exists = true; } return exists; } /** * Helper method to work with parameter evaluator to find, allowed and not found in parameter value. * * @param eval * @return */ protected boolean isAllowedNotFound(ParameterEvaluator eval) { boolean exists = false; if (eval.evaluationSucceeds() == false && eval.constraintIsAllow()) { exists = true; } return exists; } /** * Helper method to work with parameter evaluator to find, denied and found in parameter value. * * @param eval * @return */ protected boolean isDeniedFound(ParameterEvaluator eval) { boolean exists = false; if (eval.evaluationSucceeds() == false && eval.constraintIsAllow() == false) { exists = true; } return exists; } /** * Helper method to work with parameter evaluator to find, denied and not found in parameter value. * * @param eval * @return */ protected boolean isDeniedNotFound(ParameterEvaluator eval) { boolean exists = false; if (eval.evaluationSucceeds() && eval.constraintIsAllow() == false) { exists = true; } return exists; } /** * @param useTaxIndicator * @param deliveryPostalCode * @param transactionTaxDate * @param item * @param itemUseTaxClass */ @SuppressWarnings("unchecked") protected void calculateItemTax(boolean useTaxIndicator, String deliveryPostalCode, Date transactionTaxDate, PurApItem item, Class itemUseTaxClass, PurchasingAccountsPayableDocument purapDocument){ if (!useTaxIndicator){ if (!StringUtils.equals(item.getItemTypeCode(), PurapConstants.ItemTypeCodes.ITEM_TYPE_PMT_TERMS_DISCOUNT_CODE) && !StringUtils.equals(item.getItemTypeCode(), PurapConstants.ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE)) { KualiDecimal taxAmount = taxService.getTotalSalesTaxAmount(transactionTaxDate, deliveryPostalCode, item.getExtendedPrice()); item.setItemTaxAmount(taxAmount); } } else { KualiDecimal extendedPrice = item.getExtendedPrice(); if(StringUtils.equals(item.getItemTypeCode(), PurapConstants.ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE)){ KualiDecimal taxablePrice = getFullDiscountTaxablePrice(extendedPrice, purapDocument); extendedPrice = taxablePrice; } List<TaxDetail> taxDetails = taxService.getUseTaxDetails(transactionTaxDate, deliveryPostalCode, extendedPrice); List<PurApItemUseTax> newUseTaxItems = new ArrayList<PurApItemUseTax>(); if (taxDetails != null){ for (TaxDetail taxDetail : taxDetails) { try { PurApItemUseTax useTaxitem = (PurApItemUseTax)itemUseTaxClass.newInstance(); useTaxitem.setChartOfAccountsCode(taxDetail.getChartOfAccountsCode()); useTaxitem.setFinancialObjectCode(taxDetail.getFinancialObjectCode()); useTaxitem.setAccountNumber(taxDetail.getAccountNumber()); useTaxitem.setItemIdentifier(item.getItemIdentifier()); useTaxitem.setRateCode(taxDetail.getRateCode()); useTaxitem.setTaxAmount(taxDetail.getTaxAmount()); newUseTaxItems.add(useTaxitem); } catch (Exception e) { /** * Shallow.. This never happen - InstantiationException/IllegalAccessException * To be safe, throw a runtime exception */ throw new RuntimeException(e); } } } item.setUseTaxItems(newUseTaxItems); } } public KualiDecimal getFullDiscountTaxablePrice(KualiDecimal extendedPrice, PurchasingAccountsPayableDocument purapDocument){ KualiDecimal taxablePrice = KualiDecimal.ZERO; KualiDecimal taxableLineItemPrice = KualiDecimal.ZERO; KualiDecimal totalLineItemPrice = KualiDecimal.ZERO; boolean useTaxIndicator = purapDocument.isUseTaxIndicator(); String deliveryState = getDeliveryState(purapDocument); // iterate over items and calculate tax if taxable for (PurApItem item : purapDocument.getItems()) { if (item.getItemType().isLineItemIndicator()){ //only when extended price exists if(ObjectUtils.isNotNull(item.getExtendedPrice())){ if(isTaxable(useTaxIndicator, deliveryState, item)){ taxableLineItemPrice = taxableLineItemPrice.add(item.getExtendedPrice()); totalLineItemPrice = totalLineItemPrice.add(item.getExtendedPrice()); }else{ totalLineItemPrice = totalLineItemPrice.add(item.getExtendedPrice()); } } } } //check nonzero so no divide by zero errors, and make sure extended price is not null if(totalLineItemPrice.isNonZero() && ObjectUtils.isNotNull(extendedPrice)) { taxablePrice = taxableLineItemPrice.divide(totalLineItemPrice).multiply(extendedPrice); } return taxablePrice; } @Override public void prorateForTradeInAndFullOrderDiscount(PurchasingAccountsPayableDocument purDoc) { if (purDoc instanceof VendorCreditMemoDocument){ throw new RuntimeException("This method not applicable for VCM documents"); } //TODO: are we throwing sufficient errors in this method? PurApItem fullOrderDiscount = null; PurApItem tradeIn = null; KualiDecimal totalAmount = KualiDecimal.ZERO; KualiDecimal totalTaxAmount = KualiDecimal.ZERO; List<PurApAccountingLine> distributedAccounts = null; List<SourceAccountingLine> summaryAccounts = null; // iterate through below the line and grab FoD and TrdIn. for (PurApItem item : purDoc.getItems()) { if (item.getItemTypeCode().equals(PurapConstants.ItemTypeCodes.ITEM_TYPE_ORDER_DISCOUNT_CODE)) { fullOrderDiscount = item; } else if (item.getItemTypeCode().equals(PurapConstants.ItemTypeCodes.ITEM_TYPE_TRADE_IN_CODE)) { tradeIn = item; } } // If Discount is not null or zero get proration list for all non misc items and set (if not empty?) if (fullOrderDiscount != null && fullOrderDiscount.getExtendedPrice() != null && fullOrderDiscount.getExtendedPrice().isNonZero()) { // empty KNSGlobalVariables.getMessageList().add("Full order discount accounts cleared and regenerated"); fullOrderDiscount.getSourceAccountingLines().clear(); //total amount is pretax dollars totalAmount = purDoc.getTotalDollarAmountAboveLineItems().subtract(purDoc.getTotalTaxAmountAboveLineItems()); totalTaxAmount = purDoc.getTotalTaxAmountAboveLineItems(); //Before we generate account summary, we should update the account amounts first. purapAccountingService.updateAccountAmounts(purDoc); //calculate tax boolean salesTaxInd = SpringContext.getBean(ParameterService.class).getParameterValueAsBoolean( KfsParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_SALES_TAX_IND); boolean useTaxIndicator = purDoc.isUseTaxIndicator(); if(salesTaxInd == true && (ObjectUtils.isNull(fullOrderDiscount.getItemTaxAmount()) && useTaxIndicator == false)){ KualiDecimal discountAmount = fullOrderDiscount.getExtendedPrice(); KualiDecimal discountTaxAmount = discountAmount.divide(totalAmount).multiply(totalTaxAmount); fullOrderDiscount.setItemTaxAmount(discountTaxAmount); } //generate summary summaryAccounts = purapAccountingService.generateSummary(PurApItemUtils.getAboveTheLineOnly(purDoc.getItems())); if (summaryAccounts.size() == 0) { if (purDoc.shouldGiveErrorForEmptyAccountsProration()) { GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_SUMMARY_ACCOUNTS_LIST_EMPTY, "full order discount"); } } else { //prorate accounts distributedAccounts = purapAccountingService.generateAccountDistributionForProration(summaryAccounts, totalAmount.add(totalTaxAmount), 2, fullOrderDiscount.getAccountingLineClass()); for (PurApAccountingLine distributedAccount : distributedAccounts) { BigDecimal percent = distributedAccount.getAccountLinePercent(); BigDecimal roundedPercent = new BigDecimal(Math.round(percent.doubleValue())); distributedAccount.setAccountLinePercent(roundedPercent); } //update amounts on distributed accounts purapAccountingService.updateAccountAmountsWithTotal(distributedAccounts, totalAmount, fullOrderDiscount.getTotalAmount()); fullOrderDiscount.setSourceAccountingLines(distributedAccounts); } } else if(fullOrderDiscount != null && (fullOrderDiscount.getExtendedPrice() == null || fullOrderDiscount.getExtendedPrice().isZero())) { fullOrderDiscount.getSourceAccountingLines().clear(); } // If tradeIn is not null or zero get proration list for all non misc items and set (if not empty?) if (tradeIn != null && tradeIn.getExtendedPrice() != null && tradeIn.getExtendedPrice().isNonZero()) { tradeIn.getSourceAccountingLines().clear(); totalAmount = purDoc.getTotalDollarAmountForTradeIn(); KualiDecimal tradeInTotalAmount = tradeIn.getTotalAmount(); //Before we generate account summary, we should update the account amounts first. purapAccountingService.updateAccountAmounts(purDoc); //Before generating the summary, lets replace the object code in a cloned accounts collection sothat we can //consolidate all the modified object codes during summary generation. List<PurApItem> clonedTradeInItems = new ArrayList<PurApItem>(); Collection<String> objectSubTypesRequiringQty = new ArrayList<String>( SpringContext.getBean(ParameterService.class).getParameterValuesAsString(KfsParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.OBJECT_SUB_TYPES_REQUIRING_QUANTITY) ); Collection<String> purchasingObjectSubTypes = new ArrayList<String>( SpringContext.getBean(ParameterService.class).getParameterValuesAsString( KfsParameterConstants.CAPITAL_ASSET_BUILDER_DOCUMENT.class, PurapParameterConstants.PURCHASING_OBJECT_SUB_TYPES) ); String tradeInCapitalObjectCode = SpringContext.getBean(ParameterService.class).getParameterValueAsString(PurapConstants.PURAP_NAMESPACE, "Document", "TRADE_IN_OBJECT_CODE_FOR_CAPITAL_ASSET"); String tradeInCapitalLeaseObjCd = SpringContext.getBean(ParameterService.class).getParameterValueAsString(PurapConstants.PURAP_NAMESPACE, "Document", "TRADE_IN_OBJECT_CODE_FOR_CAPITAL_LEASE"); for(PurApItem item : purDoc.getTradeInItems()){ PurApItem cloneItem = (PurApItem)ObjectUtils.deepCopy(item); List<PurApAccountingLine> sourceAccountingLines = cloneItem.getSourceAccountingLines(); for(PurApAccountingLine accountingLine : sourceAccountingLines){ if(objectSubTypesRequiringQty.contains(accountingLine.getObjectCode().getFinancialObjectSubTypeCode())){ accountingLine.setFinancialObjectCode(tradeInCapitalObjectCode); }else if(purchasingObjectSubTypes.contains(accountingLine.getObjectCode().getFinancialObjectSubTypeCode())){ accountingLine.setFinancialObjectCode(tradeInCapitalLeaseObjCd); } } clonedTradeInItems.add(cloneItem); } summaryAccounts = purapAccountingService.generateSummary(clonedTradeInItems); if (summaryAccounts.size() == 0) { if (purDoc.shouldGiveErrorForEmptyAccountsProration()) { GlobalVariables.getMessageMap().putError(PurapConstants.ITEM_TAB_ERROR_PROPERTY, PurapKeyConstants.ERROR_SUMMARY_ACCOUNTS_LIST_EMPTY, "trade in"); } } else { distributedAccounts = purapAccountingService.generateAccountDistributionForProration(summaryAccounts, totalAmount, 2, tradeIn.getAccountingLineClass()); for (PurApAccountingLine distributedAccount : distributedAccounts) { BigDecimal percent = distributedAccount.getAccountLinePercent(); BigDecimal roundedPercent = new BigDecimal(Math.round(percent.doubleValue())); distributedAccount.setAccountLinePercent(roundedPercent); // set the accountAmount same as tradeIn amount not line item's amount resetAccountAmount(distributedAccount, tradeInTotalAmount); } tradeIn.setSourceAccountingLines(distributedAccounts); } } } private void resetAccountAmount(PurApAccountingLine distributedAccount, KualiDecimal tradeInTotalAmount) { BigDecimal pct = distributedAccount.getAccountLinePercent(); BigDecimal amount = tradeInTotalAmount.bigDecimalValue().multiply(pct).divide(new BigDecimal(100)); distributedAccount.setAmount(new KualiDecimal(amount)); } @Override public void clearAllTaxes(PurchasingAccountsPayableDocument purapDoc){ if (purapDoc.getItems() != null){ for (int i = 0; i < purapDoc.getItems().size(); i++) { PurApItem item = purapDoc.getItems().get(i); if (purapDoc.isUseTaxIndicator()) { item.setUseTaxItems(new ArrayList<PurApItemUseTax>()); } else { item.setItemTaxAmount(null); } } } } /** * Determines if the item type specified conflict with the Account tax policy. * * @param purchasingDocument purchasing document to check * @param item item to check if in conflict with tax policy * @return true if item is in conflict, false otherwise */ @Override public boolean isItemTypeConflictWithTaxPolicy(PurchasingDocument purchasingDocument, PurApItem item) { boolean conflict = false; String deliveryState = getDeliveryState(purchasingDocument); if (item.getItemType().isLineItemIndicator() ) { if ( item.getItemType().isTaxableIndicator() ) { if ( isTaxDisabledForVendor(purchasingDocument)) { conflict = true; } } // only check account tax policy if accounting line exists if ( !item.getSourceAccountingLines().isEmpty() ) { if ( !doesAccountAllowCallToTaxService(deliveryState, item) ) { conflict = true; } } } return conflict; } /** * Determines if tax is disabled for vendor, in default always returns false * @param purapDocument the PurchasingDocument with a vendor to check * @return true if tax is disabled, false if it is not - in foundation KFS, tax is never disabled */ protected boolean isTaxDisabledForVendor( PurchasingDocument purapDocument ) { return false; } public PurapAccountingService getPurapAccountingService() { return purapAccountingService; } public void setPurapAccountingService(PurapAccountingService purapAccountingService) { this.purapAccountingService = purapAccountingService; } }