/*
* 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.io.ByteArrayOutputStream;
import java.sql.Timestamp;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
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.coa.businessobject.Account;
import org.kuali.kfs.coa.businessobject.AccountDelegate;
import org.kuali.kfs.coa.service.AccountService;
import org.kuali.kfs.integration.purap.CapitalAssetSystem;
import org.kuali.kfs.module.purap.PurapConstants;
import org.kuali.kfs.module.purap.PurapConstants.PODocumentsStrings;
import org.kuali.kfs.module.purap.PurapConstants.POTransmissionMethods;
import org.kuali.kfs.module.purap.PurapConstants.PurchaseOrderDocTypes;
import org.kuali.kfs.module.purap.PurapConstants.PurchaseOrderStatuses;
import org.kuali.kfs.module.purap.PurapConstants.RequisitionSources;
import org.kuali.kfs.module.purap.PurapKeyConstants;
import org.kuali.kfs.module.purap.PurapParameterConstants;
import org.kuali.kfs.module.purap.PurapPropertyConstants;
import org.kuali.kfs.module.purap.PurapRuleConstants;
import org.kuali.kfs.module.purap.batch.AutoCloseRecurringOrdersStep;
import org.kuali.kfs.module.purap.businessobject.AutoClosePurchaseOrderView;
import org.kuali.kfs.module.purap.businessobject.ContractManagerAssignmentDetail;
import org.kuali.kfs.module.purap.businessobject.CreditMemoView;
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.PurchaseOrderCapitalAssetItem;
import org.kuali.kfs.module.purap.businessobject.PurchaseOrderCapitalAssetSystem;
import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem;
import org.kuali.kfs.module.purap.businessobject.PurchaseOrderQuoteStatus;
import org.kuali.kfs.module.purap.businessobject.PurchaseOrderVendorQuote;
import org.kuali.kfs.module.purap.businessobject.PurchasingCapitalAssetItem;
import org.kuali.kfs.module.purap.businessobject.ReceivingThreshold;
import org.kuali.kfs.module.purap.document.ContractManagerAssignmentDocument;
import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
import org.kuali.kfs.module.purap.document.PurchaseOrderSplitDocument;
import org.kuali.kfs.module.purap.document.PurchasingDocument;
import org.kuali.kfs.module.purap.document.RequisitionDocument;
import org.kuali.kfs.module.purap.document.dataaccess.PurchaseOrderDao;
import org.kuali.kfs.module.purap.document.service.B2BPurchaseOrderService;
import org.kuali.kfs.module.purap.document.service.LogicContainer;
import org.kuali.kfs.module.purap.document.service.PaymentRequestService;
import org.kuali.kfs.module.purap.document.service.PrintService;
import org.kuali.kfs.module.purap.document.service.PurApWorkflowIntegrationService;
import org.kuali.kfs.module.purap.document.service.PurapService;
import org.kuali.kfs.module.purap.document.service.PurchaseOrderService;
import org.kuali.kfs.module.purap.document.service.RequisitionService;
import org.kuali.kfs.module.purap.util.PurApObjectUtils;
import org.kuali.kfs.module.purap.util.ThresholdHelper;
import org.kuali.kfs.module.purap.util.ThresholdHelper.ThresholdSummary;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.KFSPropertyConstants;
import org.kuali.kfs.sys.businessobject.SourceAccountingLine;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.kfs.sys.document.FinancialSystemTransactionalDocumentBase;
import org.kuali.kfs.sys.document.service.FinancialSystemDocumentService;
import org.kuali.kfs.sys.document.validation.event.AttributedRouteDocumentEvent;
import org.kuali.kfs.sys.document.validation.event.DocumentSystemSaveEvent;
import org.kuali.kfs.sys.service.impl.KfsParameterConstants;
import org.kuali.kfs.vnd.VendorConstants;
import org.kuali.kfs.vnd.VendorConstants.AddressTypes;
import org.kuali.kfs.vnd.businessobject.CommodityCode;
import org.kuali.kfs.vnd.businessobject.VendorAddress;
import org.kuali.kfs.vnd.businessobject.VendorCommodityCode;
import org.kuali.kfs.vnd.businessobject.VendorDetail;
import org.kuali.kfs.vnd.businessobject.VendorPhoneNumber;
import org.kuali.kfs.vnd.document.service.VendorService;
import org.kuali.rice.core.api.config.property.ConfigurationService;
import org.kuali.rice.core.api.datetime.DateTimeService;
import org.kuali.rice.core.api.mail.MailMessage;
import org.kuali.rice.core.api.util.type.KualiDecimal;
import org.kuali.rice.coreservice.api.parameter.Parameter;
import org.kuali.rice.coreservice.framework.parameter.ParameterService;
import org.kuali.rice.kew.api.KewApiConstants;
import org.kuali.rice.kew.api.KewApiServiceLocator;
import org.kuali.rice.kew.api.WorkflowDocument;
import org.kuali.rice.kew.api.action.ActionRequestType;
import org.kuali.rice.kew.api.document.WorkflowDocumentService;
import org.kuali.rice.kew.api.document.attribute.DocumentAttributeIndexingQueue;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.PersonService;
import org.kuali.rice.kim.api.services.KimApiServiceLocator;
import org.kuali.rice.kns.document.MaintenanceDocument;
import org.kuali.rice.kns.maintenance.Maintainable;
import org.kuali.rice.kns.service.DataDictionaryService;
import org.kuali.rice.kns.util.KNSGlobalVariables;
import org.kuali.rice.krad.bo.AdHocRoutePerson;
import org.kuali.rice.krad.bo.AdHocRouteRecipient;
import org.kuali.rice.krad.bo.Note;
import org.kuali.rice.krad.datadictionary.exception.UnknownDocumentTypeException;
import org.kuali.rice.krad.document.Document;
import org.kuali.rice.krad.document.DocumentBase;
import org.kuali.rice.krad.exception.ValidationException;
import org.kuali.rice.krad.rules.rule.event.RouteDocumentEvent;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.service.DocumentService;
import org.kuali.rice.krad.service.KRADServiceLocator;
import org.kuali.rice.krad.service.KualiRuleService;
import org.kuali.rice.krad.service.MailService;
import org.kuali.rice.krad.service.MaintenanceDocumentService;
import org.kuali.rice.krad.service.NoteService;
import org.kuali.rice.krad.service.SequenceAccessorService;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.MessageMap;
import org.kuali.rice.krad.util.ObjectUtils;
import org.springframework.transaction.annotation.Transactional;
@Transactional
public class PurchaseOrderServiceImpl implements PurchaseOrderService {
private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PurchaseOrderServiceImpl.class);
protected BusinessObjectService businessObjectService;
protected DateTimeService dateTimeService;
protected DocumentService documentService;
protected NoteService noteService;
protected PurapService purapService;
protected PrintService printService;
protected PurchaseOrderDao purchaseOrderDao;
protected WorkflowDocumentService workflowDocumentService;
protected ConfigurationService kualiConfigurationService;
protected KualiRuleService kualiRuleService;
protected VendorService vendorService;
protected RequisitionService requisitionService;
protected PurApWorkflowIntegrationService purapWorkflowIntegrationService;
protected MaintenanceDocumentService maintenanceDocumentService;
protected ParameterService parameterService;
protected PersonService personService;
protected MailService mailService;
protected B2BPurchaseOrderService b2bPurchaseOrderService;
protected DataDictionaryService dataDictionaryService;
protected FinancialSystemDocumentService financialSystemDocumentService;
@Override
public boolean isPurchaseOrderOpenForProcessing(Integer poId) {
return isPurchaseOrderOpenForProcessing(getCurrentPurchaseOrder(poId));
}
@Override
public boolean isPurchaseOrderOpenForProcessing(PurchaseOrderDocument purchaseOrderDocument) {
boolean can = PurchaseOrderStatuses.APPDOC_OPEN.equals(purchaseOrderDocument.getApplicationDocumentStatus());
can = can && purchaseOrderDocument.isPurchaseOrderCurrentIndicator() && !purchaseOrderDocument.isPendingActionIndicator();
// can't be any PREQ or CM that have not completed fullDocumentEntry
if (can) {
List<PaymentRequestView> preqViews = purchaseOrderDocument.getRelatedViews().getRelatedPaymentRequestViews();
if (preqViews != null) {
for (PaymentRequestView preqView : preqViews) {
if (!purapService.isPaymentRequestFullDocumentEntryCompleted(preqView.getApplicationDocumentStatus())) {
return false;
}
}
}
List<CreditMemoView> cmViews = purchaseOrderDocument.getRelatedViews().getRelatedCreditMemoViews();
if (cmViews != null) {
for (CreditMemoView cmView : cmViews) {
if (!purapService.isVendorCreditMemoFullDocumentEntryCompleted(cmView.getApplicationDocumentStatus())) {
return false;
}
}
}
}
// passed all conditions; return true
return can;
}
@Override
public boolean isCommodityCodeRequiredOnPurchaseOrder() {
boolean enableCommodityCode = parameterService.getParameterValueAsBoolean(KfsParameterConstants.PURCHASING_DOCUMENT.class, PurapParameterConstants.ENABLE_COMMODITY_CODE_IND);
if (!enableCommodityCode) {
return false;
}
else {
return parameterService.getParameterValueAsBoolean(PurchaseOrderDocument.class, PurapRuleConstants.ITEMS_REQUIRE_COMMODITY_CODE_IND);
}
}
/**
* Sets the error map to a new, empty error map before calling saveDocumentNoValidation to save the document.
*
* @param document The purchase order document to be saved.
*/
protected void saveDocumentNoValidationUsingClearMessageMap(PurchaseOrderDocument document) {
MessageMap errorHolder = GlobalVariables.getMessageMap();
GlobalVariables.setMessageMap(new MessageMap());
try {
purapService.saveDocumentNoValidation(document);
}
finally {
GlobalVariables.setMessageMap(errorHolder);
}
}
/**
* Calls the saveDocument method of documentService to save the document.
*
* @param document The document to be saved.
*/
protected void saveDocumentStandardSave(PurchaseOrderDocument document) {
try {
documentService.saveDocument(document);
}
catch (WorkflowException we) {
String errorMsg = "Workflow Error saving document # " + document.getDocumentHeader().getDocumentNumber() + " " + we.getMessage();
LOG.error(errorMsg, we);
throw new RuntimeException(errorMsg, we);
}
}
@Override
public PurchasingCapitalAssetItem createCamsItem(PurchasingDocument purDoc, PurApItem purapItem) {
PurchasingCapitalAssetItem camsItem = new PurchaseOrderCapitalAssetItem();
camsItem.setItemIdentifier(purapItem.getItemIdentifier());
// If the system type is INDIVIDUAL then for each of the capital asset items, we need a system attached to it.
if (purDoc.getCapitalAssetSystemTypeCode().equals(PurapConstants.CapitalAssetTabStrings.INDIVIDUAL_ASSETS)) {
CapitalAssetSystem resultSystem = new PurchaseOrderCapitalAssetSystem();
camsItem.setPurchasingCapitalAssetSystem(resultSystem);
}
camsItem.setPurchasingDocument(purDoc);
return camsItem;
}
@Override
public CapitalAssetSystem createCapitalAssetSystem() {
CapitalAssetSystem resultSystem = new PurchaseOrderCapitalAssetSystem();
return resultSystem;
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#createAutomaticPurchaseOrderDocument(org.kuali.kfs.module.purap.document.RequisitionDocument)
*/
@Override
public void createAutomaticPurchaseOrderDocument(RequisitionDocument reqDocument) {
String newSessionUserId = KFSConstants.SYSTEM_USER;
try {
LogicContainer logicToRun = new LogicContainer() {
@Override
public Object runLogic(Object[] objects) throws Exception {
RequisitionDocument doc = (RequisitionDocument) objects[0];
// update REQ data
doc.setPurchaseOrderAutomaticIndicator(Boolean.TRUE);
// create PO and populate with default data
PurchaseOrderDocument po = generatePurchaseOrderFromRequisition(doc);
po.setDefaultValuesForAPO();
// check for print transmission method.. if print is selected
// the doc status needs to be "Pending To Print"..
checkForPrintTransmission(po);
po.setContractManagerCode(PurapConstants.APO_CONTRACT_MANAGER);
documentService.routeDocument(po, null, null);
final DocumentAttributeIndexingQueue documentAttributeIndexingQueue = KewApiServiceLocator.getDocumentAttributeIndexingQueue();
documentAttributeIndexingQueue.indexDocument(po.getDocumentNumber());
return null;
}
};
purapService.performLogicWithFakedUserSession(newSessionUserId, logicToRun, new Object[] { reqDocument });
}
catch (WorkflowException e) {
String errorMsg = "Workflow Exception caught: " + e.getLocalizedMessage();
LOG.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* checks for print option and if chosen then sets the app doc status to Pending To Print.
*
* @param po
*/
protected void checkForPrintTransmission(PurchaseOrderDocument po) throws WorkflowException {
if (PurapConstants.POTransmissionMethods.PRINT.equals(po.getPurchaseOrderRetransmissionMethodCode())) {
po.updateAndSaveAppDocStatus(PurapConstants.PurchaseOrderStatuses.APPDOC_PENDING_PRINT);
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#createPurchaseOrderDocument(org.kuali.kfs.module.purap.document.RequisitionDocument,
* java.lang.String, java.lang.Integer)
*/
@Override
public PurchaseOrderDocument createPurchaseOrderDocument(RequisitionDocument reqDocument, String newSessionUserId, Integer contractManagerCode) {
try {
LogicContainer logicToRun = new LogicContainer() {
@Override
public Object runLogic(Object[] objects) throws Exception {
RequisitionDocument doc = (RequisitionDocument) objects[0];
PurchaseOrderDocument po = generatePurchaseOrderFromRequisition(doc);
Integer cmCode = (Integer) objects[1];
po.setContractManagerCode(cmCode);
purapService.saveDocumentNoValidation(po);
return po;
}
};
return (PurchaseOrderDocument) purapService.performLogicWithFakedUserSession(newSessionUserId, logicToRun, new Object[] { reqDocument, contractManagerCode });
}
catch (WorkflowException e) {
String errorMsg = "Workflow Exception caught: " + e.getLocalizedMessage();
LOG.error(errorMsg, e);
throw new RuntimeException(errorMsg, e);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Create Purchase Order and populate with data from Requisition and other default data
*
* @param reqDocument The requisition document from which we create the purchase order document.
* @return The purchase order document created by this method.
* @throws WorkflowException
*/
protected PurchaseOrderDocument generatePurchaseOrderFromRequisition(RequisitionDocument reqDocument) throws WorkflowException {
PurchaseOrderDocument poDocument = null;
poDocument = (PurchaseOrderDocument) documentService.getNewDocument(PurchaseOrderDocTypes.PURCHASE_ORDER_DOCUMENT);
poDocument.populatePurchaseOrderFromRequisition(reqDocument);
poDocument.updateAndSaveAppDocStatus(PurapConstants.PurchaseOrderStatuses.APPDOC_IN_PROCESS);
poDocument.setPurchaseOrderCurrentIndicator(true);
poDocument.setPendingActionIndicator(false);
if (RequisitionSources.B2B.equals(poDocument.getRequisitionSourceCode())) {
String paramName = PurapParameterConstants.DEFAULT_B2B_VENDOR_CHOICE;
String paramValue = parameterService.getParameterValueAsString(PurchaseOrderDocument.class, paramName);
poDocument.setPurchaseOrderVendorChoiceCode(paramValue);
}
if (ObjectUtils.isNotNull(poDocument.getVendorContract())) {
poDocument.setVendorPaymentTermsCode(poDocument.getVendorContract().getVendorPaymentTermsCode());
poDocument.setVendorShippingPaymentTermsCode(poDocument.getVendorContract().getVendorShippingPaymentTermsCode());
poDocument.setVendorShippingTitleCode(poDocument.getVendorContract().getVendorShippingTitleCode());
}
else {
VendorDetail vendor = vendorService.getVendorDetail(poDocument.getVendorHeaderGeneratedIdentifier(), poDocument.getVendorDetailAssignedIdentifier());
if (ObjectUtils.isNotNull(vendor)) {
poDocument.setVendorPaymentTermsCode(vendor.getVendorPaymentTermsCode());
poDocument.setVendorShippingPaymentTermsCode(vendor.getVendorShippingPaymentTermsCode());
poDocument.setVendorShippingTitleCode(vendor.getVendorShippingTitleCode());
}
}
if (!PurapConstants.RequisitionSources.B2B.equals(poDocument.getRequisitionSourceCode())) {
purapService.addBelowLineItems(poDocument);
}
poDocument.fixItemReferences();
return poDocument;
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#getInternalPurchasingDollarLimit(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
*/
@Override
public KualiDecimal getInternalPurchasingDollarLimit(PurchaseOrderDocument document) {
if ((document.getVendorContract() != null) && (document.getContractManager() != null)) {
KualiDecimal contractDollarLimit = vendorService.getApoLimitFromContract(document.getVendorContract().getVendorContractGeneratedIdentifier(), document.getChartOfAccountsCode(), document.getOrganizationCode());
// FIXME somehow data fields such as contractManagerDelegationDollarLimit in reference object contractManager didn't get
// retrieved
// (are null) as supposed to be (this happens whether or not proxy is set to true), even though contractManager is not
// null;
// so here we have to manually refresh the contractManager to retrieve the fields
if (document.getContractManager().getContractManagerDelegationDollarLimit() == null) {
document.refreshReferenceObject(PurapPropertyConstants.CONTRACT_MANAGER);
}
KualiDecimal contractManagerLimit = document.getContractManager().getContractManagerDelegationDollarLimit();
if ((contractDollarLimit != null) && (contractManagerLimit != null)) {
if (contractDollarLimit.compareTo(contractManagerLimit) > 0) {
return contractDollarLimit;
}
else {
return contractManagerLimit;
}
}
else if (contractDollarLimit != null) {
return contractDollarLimit;
}
else {
return contractManagerLimit;
}
}
else if ((document.getVendorContract() == null) && (document.getContractManager() != null)) {
// FIXME As above, here we have to manually refresh the contractManager to retrieve its field
if (document.getContractManager().getContractManagerDelegationDollarLimit() == null) {
document.refreshReferenceObject(PurapPropertyConstants.CONTRACT_MANAGER);
}
return document.getContractManager().getContractManagerDelegationDollarLimit();
}
else if ((document.getVendorContract() != null) && (document.getContractManager() == null)) {
return purapService.getApoLimit(document.getVendorContract().getVendorContractGeneratedIdentifier(), document.getChartOfAccountsCode(), document.getOrganizationCode());
}
else {
String errorMsg = "No internal purchase order dollar limit found for purchase order '" + document.getPurapDocumentIdentifier() + "'.";
LOG.warn(errorMsg);
return null;
}
}
/**
* Loops through the collection of error messages and adding each of them to the error map.
*
* @param errorKey The resource key used to retrieve the error text from the error message resource bundle.
* @param errors The collection of error messages.
*/
protected void addStringErrorMessagesToMessageMap(String errorKey, Collection<String> errors) {
if (ObjectUtils.isNotNull(errors)) {
for (String error : errors) {
LOG.error("Adding error message using error key '" + errorKey + "' with text '" + error + "'");
GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, errorKey, error);
}
}
}
/**
* TODO RELEASE 3 - QUOTE
*
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#printPurchaseOrderQuoteRequestsListPDF(org.kuali.kfs.module.purap.document.PurchaseOrderDocument,
* java.io.ByteArrayOutputStream)
*/
@Override
public boolean printPurchaseOrderQuoteRequestsListPDF(String documentNumber, ByteArrayOutputStream baosPDF) {
PurchaseOrderDocument po = getPurchaseOrderByDocumentNumber(documentNumber);
String environment = kualiConfigurationService.getPropertyValueAsString(KFSConstants.ENVIRONMENT_KEY);
Collection<String> generatePDFErrors = printService.generatePurchaseOrderQuoteRequestsListPdf(po, baosPDF);
if (generatePDFErrors.size() > 0) {
addStringErrorMessagesToMessageMap(PurapKeyConstants.ERROR_PURCHASE_ORDER_PDF, generatePDFErrors);
return false;
}
else {
return true;
}
}
/**
* TODO RELEASE 3 - QUOTE
*
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#printPurchaseOrderQuotePDF(org.kuali.kfs.module.purap.document.PurchaseOrderDocument,
* org.kuali.kfs.module.purap.businessobject.PurchaseOrderVendorQuote, java.io.ByteArrayOutputStream)
*/
@Override
public boolean printPurchaseOrderQuotePDF(PurchaseOrderDocument po, PurchaseOrderVendorQuote povq, ByteArrayOutputStream baosPDF) {
String environment = kualiConfigurationService.getPropertyValueAsString(KFSConstants.ENVIRONMENT_KEY);
Collection<String> generatePDFErrors = printService.generatePurchaseOrderQuotePdf(po, povq, baosPDF, environment);
if (generatePDFErrors.size() > 0) {
addStringErrorMessagesToMessageMap(PurapKeyConstants.ERROR_PURCHASE_ORDER_PDF, generatePDFErrors);
return false;
}
else {
return true;
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#performPurchaseOrderFirstTransmitViaPrinting(java.lang.String,
* java.io.ByteArrayOutputStream)
*/
@Override
public void performPurchaseOrderFirstTransmitViaPrinting(String documentNumber, ByteArrayOutputStream baosPDF) {
PurchaseOrderDocument po = getPurchaseOrderByDocumentNumber(documentNumber);
String environment = kualiConfigurationService.getPropertyValueAsString(KFSConstants.ENVIRONMENT_KEY);
Collection<String> generatePDFErrors = printService.generatePurchaseOrderPdf(po, baosPDF, environment, null);
if (!generatePDFErrors.isEmpty()) {
addStringErrorMessagesToMessageMap(PurapKeyConstants.ERROR_PURCHASE_ORDER_PDF, generatePDFErrors);
throw new ValidationException("printing purchase order for first transmission failed");
}
if (ObjectUtils.isNotNull(po.getPurchaseOrderFirstTransmissionTimestamp())) {
// should not call this method for first transmission if document has already been transmitted
String errorMsg = "Method to perform first transmit was called on document (doc id " + documentNumber + ") with already filled in 'first transmit date'";
LOG.error(errorMsg);
throw new RuntimeException(errorMsg);
}
Timestamp currentDate = dateTimeService.getCurrentTimestamp();
po.setPurchaseOrderFirstTransmissionTimestamp(currentDate);
po.setPurchaseOrderLastTransmitTimestamp(currentDate);
po.setOverrideWorkflowButtons(Boolean.FALSE);
boolean performedAction = purapWorkflowIntegrationService.takeAllActionsForGivenCriteria(po, "Action taken automatically as part of document initial print transmission", PurapConstants.PurchaseOrderStatuses.NODE_DOCUMENT_TRANSMISSION, GlobalVariables.getUserSession().getPerson(), null);
if (!performedAction) {
Person systemUserPerson = getPersonService().getPersonByPrincipalName(KFSConstants.SYSTEM_USER);
purapWorkflowIntegrationService.takeAllActionsForGivenCriteria(po, "Action taken automatically as part of document initial print transmission by user " + GlobalVariables.getUserSession().getPerson().getName(), PurapConstants.PurchaseOrderStatuses.NODE_DOCUMENT_TRANSMISSION, systemUserPerson, KFSConstants.SYSTEM_USER);
}
po.setOverrideWorkflowButtons(Boolean.TRUE);
if (!po.getApplicationDocumentStatus().equals(PurapConstants.PurchaseOrderStatuses.APPDOC_OPEN)) {
attemptSetupOfInitialOpenOfDocument(po);
if (shouldAdhocFyi(po.getRequisitionSourceCode())) {
sendAdhocFyi(po);
}
}
purapService.saveDocumentNoValidation(po);
}
/**
* This method retrieves the parameter which holds the list of Requisition source codes which does not need FYI Notifications .
*
* @return
*/
private boolean shouldAdhocFyi(String reqSourceCode) {
Collection<String> excludeList = new ArrayList<String>();
if (parameterService.parameterExists(PurchaseOrderDocument.class, PurapParameterConstants.PO_NOTIFY_EXCLUSIONS)) {
excludeList = parameterService.getParameterValuesAsString(PurchaseOrderDocument.class, PurapParameterConstants.PO_NOTIFY_EXCLUSIONS);
}
return !excludeList.contains(reqSourceCode);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#performPurchaseOrderPreviewPrinting(java.lang.String,
* java.io.ByteArrayOutputStream)
*/
@Override
public void performPurchaseOrderPreviewPrinting(String documentNumber, ByteArrayOutputStream baosPDF) {
performPrintPurchaseOrderPDFOnly(documentNumber, baosPDF);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#performPrintPurchaseOrderPDFOnly(java.lang.String,
* java.io.ByteArrayOutputStream)
*/
@Override
public void performPrintPurchaseOrderPDFOnly(String documentNumber, ByteArrayOutputStream baosPDF) {
PurchaseOrderDocument po = getPurchaseOrderByDocumentNumber(documentNumber);
String environment = kualiConfigurationService.getPropertyValueAsString(KFSConstants.ENVIRONMENT_KEY);
Collection<String> generatePDFErrors = printService.generatePurchaseOrderPdf(po, baosPDF, environment, null);
if (!generatePDFErrors.isEmpty()) {
addStringErrorMessagesToMessageMap(PurapKeyConstants.ERROR_PURCHASE_ORDER_PDF, generatePDFErrors);
throw new ValidationException("printing purchase order for first transmission failed");
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#retransmitPurchaseOrderPDF(org.kuali.kfs.module.purap.document.PurchaseOrderDocument,
* java.io.ByteArrayOutputStream)
*/
@Override
public void retransmitPurchaseOrderPDF(PurchaseOrderDocument po, ByteArrayOutputStream baosPDF) {
String environment = kualiConfigurationService.getPropertyValueAsString(KFSConstants.ENVIRONMENT_KEY);
List<PurchaseOrderItem> items = po.getItems();
List<PurchaseOrderItem> retransmitItems = new ArrayList<PurchaseOrderItem>();
for (PurchaseOrderItem item : items) {
if (item.isItemSelectedForRetransmitIndicator()) {
retransmitItems.add(item);
}
}
Collection<String> generatePDFErrors = printService.generatePurchaseOrderPdfForRetransmission(po, baosPDF, environment, retransmitItems);
if (generatePDFErrors.size() > 0) {
addStringErrorMessagesToMessageMap(PurapKeyConstants.ERROR_PURCHASE_ORDER_PDF, generatePDFErrors);
throw new ValidationException("found errors while trying to print po with doc id " + po.getDocumentNumber());
}
po.setPurchaseOrderLastTransmitTimestamp(dateTimeService.getCurrentTimestamp());
purapService.saveDocumentNoValidation(po);
}
/**
* This method creates a new Purchase Order Document using the given document type based off the given source document. This
* method will return null if the source document given is null.<br>
* <br>
* ** THIS METHOD DOES NOT SAVE EITHER THE GIVEN SOURCE DOCUMENT OR THE NEW DOCUMENT CREATED
*
* @param sourceDocument - document the new Purchase Order Document should be based off of in terms of data
* @param docType - document type of the potential new Purchase Order Document
* @return the new Purchase Order Document of the given document type or null if the given source document is null
* @throws WorkflowException if a new document cannot be created using the given type
*/
protected PurchaseOrderDocument createPurchaseOrderDocumentFromSourceDocument(PurchaseOrderDocument sourceDocument, String docType) throws WorkflowException {
if (ObjectUtils.isNull(sourceDocument)) {
String errorMsg = "Attempting to create new PO of type '" + docType + "' from source PO doc that is null";
LOG.error(errorMsg);
throw new RuntimeException(errorMsg);
}
PurchaseOrderDocument newPurchaseOrderChangeDocument = (PurchaseOrderDocument) documentService.getNewDocument(docType);
newPurchaseOrderChangeDocument.setAccountDistributionMethod(sourceDocument.getAccountDistributionMethod());
Set classesToExclude = new HashSet();
Class sourceObjectClass = FinancialSystemTransactionalDocumentBase.class;
classesToExclude.add(sourceObjectClass);
while (sourceObjectClass.getSuperclass() != null) {
sourceObjectClass = sourceObjectClass.getSuperclass();
classesToExclude.add(sourceObjectClass);
}
PurApObjectUtils.populateFromBaseWithSuper(sourceDocument, newPurchaseOrderChangeDocument, PurapConstants.uncopyableFieldsForPurchaseOrder(), classesToExclude);
newPurchaseOrderChangeDocument.getDocumentHeader().setDocumentDescription(sourceDocument.getDocumentHeader().getDocumentDescription());
newPurchaseOrderChangeDocument.getDocumentHeader().setOrganizationDocumentNumber(sourceDocument.getDocumentHeader().getOrganizationDocumentNumber());
newPurchaseOrderChangeDocument.getDocumentHeader().setExplanation(sourceDocument.getDocumentHeader().getExplanation());
newPurchaseOrderChangeDocument.setPurchaseOrderCurrentIndicator(false);
newPurchaseOrderChangeDocument.setPendingActionIndicator(false);
// TODO f2f: what is this doing?
// Need to find a way to make the ManageableArrayList to expand and populating the items and
// accounts, otherwise it will complain about the account on item 1 is missing.
for (PurApItem item : (List<PurApItem>) newPurchaseOrderChangeDocument.getItems()) {
item.getSourceAccountingLines().iterator();
// we only need to do this once to apply to all items, so we can break out of the loop now
SequenceAccessorService sas = SpringContext.getBean(SequenceAccessorService.class);
Integer itemIdentifier = sas.getNextAvailableSequenceNumber("PO_ITM_ID", PurApItem.class).intValue();
item.setItemIdentifier(itemIdentifier);
}
updateCapitalAssetRelatedCollections(newPurchaseOrderChangeDocument);
newPurchaseOrderChangeDocument.refreshNonUpdateableReferences();
return newPurchaseOrderChangeDocument;
}
protected void updateCapitalAssetRelatedCollections(PurchaseOrderDocument newDocument) {
for (PurchasingCapitalAssetItem capitalAssetItem : newDocument.getPurchasingCapitalAssetItems()) {
Integer lineNumber = capitalAssetItem.getPurchasingItem().getItemLineNumber();
PurApItem newItem = newDocument.getItemByLineNumber(lineNumber.intValue());
capitalAssetItem.setItemIdentifier(newItem.getItemIdentifier());
capitalAssetItem.setPurchasingDocument(newDocument);
capitalAssetItem.setCapitalAssetSystemIdentifier(null);
CapitalAssetSystem oldSystem = capitalAssetItem.getPurchasingCapitalAssetSystem();
capitalAssetItem.setPurchasingCapitalAssetSystem(new PurchaseOrderCapitalAssetSystem(oldSystem));
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#createAndSavePotentialChangeDocument(java.lang.String,
* java.lang.String, java.lang.String)
*/
@Override
public PurchaseOrderDocument createAndSavePotentialChangeDocument(String documentNumber, String docType, String currentDocumentStatusCode) {
PurchaseOrderDocument currentDocument = getPurchaseOrderByDocumentNumber(documentNumber);
try {
PurchaseOrderDocument newDocument = createPurchaseOrderDocumentFromSourceDocument(currentDocument, docType);
if (ObjectUtils.isNotNull(newDocument)) {
newDocument.updateAndSaveAppDocStatus(PurapConstants.PurchaseOrderStatuses.APPDOC_CHANGE_IN_PROCESS);
// set status if needed
if (StringUtils.isNotBlank(currentDocumentStatusCode)) {
currentDocument.updateAndSaveAppDocStatus(currentDocumentStatusCode);
}
try {
documentService.saveDocument(newDocument, DocumentSystemSaveEvent.class);
}
// if we catch a ValidationException it means the new PO doc found errors
catch (ValidationException ve) {
throw ve;
}
// if no validation exception was thrown then rules have passed and we are ok to edit the current PO
currentDocument.setPendingActionIndicator(true);
savePurchaseOrderData(currentDocument);
return newDocument;
}
else {
String errorMsg = "Attempting to create new PO of type '" + docType + "' from source PO doc id " + documentNumber + " returned null for new document";
LOG.error(errorMsg);
throw new RuntimeException(errorMsg);
}
}
catch (WorkflowException we) {
String errorMsg = "Workflow Exception caught trying to create and save PO document of type '" + docType + "' using source document with doc id '" + documentNumber + "'";
LOG.error(errorMsg, we);
throw new RuntimeException(errorMsg, we);
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#createAndRoutePotentialChangeDocument(java.lang.String,
* java.lang.String, java.lang.String, java.util.List, java.lang.String)
*/
@Override
public PurchaseOrderDocument createAndRoutePotentialChangeDocument(String documentNumber, String docType, String annotation, List adhocRoutingRecipients, String currentDocumentStatusCode) {
PurchaseOrderDocument currentDocument = getPurchaseOrderByDocumentNumber(documentNumber);
try {
currentDocument.updateAndSaveAppDocStatus(currentDocumentStatusCode);
}
catch (WorkflowException e) {
throw new RuntimeException("Error saving routing data while saving document with id " + currentDocument.getDocumentNumber(), e);
}
try {
PurchaseOrderDocument newDocument = createPurchaseOrderDocumentFromSourceDocument(currentDocument, docType);
newDocument.updateAndSaveAppDocStatus(PurapConstants.PurchaseOrderStatuses.APPDOC_CHANGE_IN_PROCESS);
if (ObjectUtils.isNotNull(newDocument)) {
try {
// set the pending indictor before routing, so that when routing is done in synch mode, the pending indicator
// won't be set again after route finishes and cause inconsistency
currentDocument.setPendingActionIndicator(true);
documentService.routeDocument(newDocument, annotation, adhocRoutingRecipients);
}
// if we catch a ValidationException it means the new PO doc found errors
catch (ValidationException ve) {
// clear the pending indictor if an exception occurs, to leave the existing PO intact
currentDocument.setPendingActionIndicator(false);
savePurchaseOrderData(currentDocument);
throw ve;
}
return newDocument;
}
else {
String errorMsg = "Attempting to create new PO of type '" + docType + "' from source PO doc id " + documentNumber + " returned null for new document";
LOG.error(errorMsg);
throw new RuntimeException(errorMsg);
}
}
catch (WorkflowException we) {
String errorMsg = "Workflow Exception caught trying to create and route PO document of type '" + docType + "' using source document with doc id '" + documentNumber + "'";
LOG.error(errorMsg, we);
throw new RuntimeException(errorMsg, we);
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#createAndSavePurchaseOrderSplitDocument(java.util.List,
* java.lang.String, boolean)
*/
@Override
public PurchaseOrderSplitDocument createAndSavePurchaseOrderSplitDocument(List<PurchaseOrderItem> newPOItems, PurchaseOrderDocument currentDocument, boolean copyNotes, String splitNoteText) {
if (ObjectUtils.isNull(currentDocument)) {
String errorMsg = "Attempting to create new PO of type PurchaseOrderSplitDocument from source PO doc that is null";
LOG.error(errorMsg);
throw new RuntimeException(errorMsg);
}
String documentNumber = currentDocument.getDocumentNumber();
try {
// Create the new Split PO document (throws WorkflowException)
// Assign PO's initiator to Split PO.
Person person = SpringContext.getBean(PersonService.class).getPerson(currentDocument.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId());
PurchaseOrderSplitDocument newDocument = (PurchaseOrderSplitDocument) documentService.getNewDocument(PurchaseOrderDocTypes.PURCHASE_ORDER_SPLIT_DOCUMENT,person.getPrincipalName());
if (ObjectUtils.isNotNull(newDocument)) {
// Prepare for copying fields over from the current document.
Set<Class> classesToExclude = getClassesToExcludeFromCopy();
Map<String, Class> uncopyableFields = PurapConstants.UNCOPYABLE_FIELDS_FOR_PO;
uncopyableFields.putAll(PurapConstants.uncopyableFieldsForSplitPurchaseOrder());
// Copy all fields over from the current document except the items and the above-specified fields.
PurApObjectUtils.populateFromBaseWithSuper(currentDocument, newDocument, uncopyableFields, classesToExclude);
newDocument.getDocumentHeader().setDocumentDescription(currentDocument.getDocumentHeader().getDocumentDescription());
newDocument.getDocumentHeader().setOrganizationDocumentNumber(currentDocument.getDocumentHeader().getOrganizationDocumentNumber());
newDocument.setPurchaseOrderCurrentIndicator(true);
newDocument.setPendingActionIndicator(false);
newDocument.setAccountDistributionMethod(currentDocument.getAccountDistributionMethod());
// Add in and renumber the items that the new document should have.
newDocument.setItems(newPOItems);
purapService.addBelowLineItems(newDocument);
newDocument.renumberItems(0);
newDocument.setPostingYear(currentDocument.getPostingYear());
if (copyNotes) {
// Copy the old notes, except for the one that contains the split note text.
List<Note> notes = currentDocument.getNotes();
int noteLength = notes.size();
if (noteLength > 0) {
notes.subList(noteLength - 1, noteLength).clear();
for (Note note : notes) {
try {
Note copyingNote = documentService.createNoteFromDocument(newDocument, note.getNoteText());
newDocument.addNote(copyingNote);
noteService.saveNoteList(notes);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
newDocument.updateAndSaveAppDocStatus(PurapConstants.PurchaseOrderStatuses.APPDOC_IN_PROCESS);
// fix references before saving
fixItemReferences(newDocument);
newDocument.clearCapitalAssetFields();
// need to save the document first before creating the note
purapService.saveDocumentNoValidation(newDocument);
// Modify the split note text and add the note.
splitNoteText = splitNoteText.substring(splitNoteText.indexOf(":") + 1);
splitNoteText = PurapConstants.PODocumentsStrings.SPLIT_NOTE_PREFIX_NEW_DOC + currentDocument.getPurapDocumentIdentifier() + " : " + splitNoteText;
try {
Note splitNote = documentService.createNoteFromDocument(newDocument, splitNoteText);
newDocument.addNote(splitNote);
noteService.save(splitNote);
}
catch (Exception e) {
throw new RuntimeException(e);
}
return newDocument;
}
else {
String errorMsg = "Attempting to create new PO of type 'PurchaseOrderSplitDocument' from source PO doc id " + documentNumber + " returned null for new document";
LOG.error(errorMsg);
throw new RuntimeException(errorMsg);
}
}
catch (WorkflowException we) {
String errorMsg = "Workflow Exception caught trying to create and save PO document of type PurchaseOrderSplitDocument using source document with doc id '" + documentNumber + "'";
LOG.error(errorMsg, we);
throw new RuntimeException(errorMsg, we);
}
}
/**
* Gets a set of classes to exclude from those whose fields will be copied during a copy operation from one Document to another.
*
* @return A Set<Class>
*/
protected Set<Class> getClassesToExcludeFromCopy() {
Set<Class> classesToExclude = new HashSet<Class>();
Class sourceObjectClass = DocumentBase.class;
classesToExclude.add(sourceObjectClass);
while (sourceObjectClass.getSuperclass() != null) {
sourceObjectClass = sourceObjectClass.getSuperclass();
classesToExclude.add(sourceObjectClass);
}
return classesToExclude;
}
/**
* Returns the current route node name.
*
* @param wd The KualiWorkflowDocument object whose current route node we're trying to get.
* @return The current route node name.
* @throws WorkflowException
*/
protected String getCurrentRouteNodeName(WorkflowDocument wd) throws WorkflowException {
String[] nodeNames = (String[]) wd.getNodeNames().toArray();
if ((nodeNames == null) || (nodeNames.length == 0)) {
return null;
}
else {
return nodeNames[0];
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#completePurchaseOrder(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
*/
@Override
public void completePurchaseOrder(PurchaseOrderDocument po) {
LOG.debug("completePurchaseOrder() started");
setCurrentAndPendingIndicatorsForApprovedPODocuments(po);
setupDocumentForPendingFirstTransmission(po);
// check thresholds to see if receiving is required for purchase order
if (!po.isReceivingDocumentRequiredIndicator()) {
setReceivingRequiredIndicatorForPurchaseOrder(po);
}
// update the vendor record if the commodity code used on the PO is not already associated with the vendor.
updateVendorCommodityCode(po);
// PERFORM ANY LOGIC THAT COULD POTENTIALLY CAUSE THE DOCUMENT TO FAIL BEFORE THIS LINE
// FOLLOWING LINES COULD INVOLVE TRANSMITTING THE PO TO THE VENDOR WHICH WILL NOT BE REVERSED IN A TRANSACTION ROLLBACK
// if the document is set in a Pending Transmission status then don't OPEN the PO just leave it as is
if (!PurchaseOrderStatuses.STATUSES_BY_TRANSMISSION_TYPE.values().contains(po.getApplicationDocumentStatus())) {
attemptSetupOfInitialOpenOfDocument(po);
}
else if (PurchaseOrderStatuses.APPDOC_PENDING_CXML.equals(po.getApplicationDocumentStatus())) {
completeB2BPurchaseOrder(po);
}
else if (PurchaseOrderStatuses.APPDOC_PENDING_PRINT.equals(po.getApplicationDocumentStatus())) {
// default to using user that routed PO
String userToRouteFyi = po.getDocumentHeader().getWorkflowDocument().getRoutedByPrincipalId();
if (po.getPurchaseOrderAutomaticIndicator()) {
// if APO, use the user that initiated the requisition
RequisitionDocument req = requisitionService.getRequisitionById(po.getRequisitionIdentifier());
userToRouteFyi = req.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId();
}
Set<String> currentNodes = po.getDocumentHeader().getWorkflowDocument().getCurrentNodeNames();
if (CollectionUtils.isNotEmpty(currentNodes)) {
po.getDocumentHeader().getWorkflowDocument().adHocToPrincipal(ActionRequestType.FYI, currentNodes.iterator().next(), "This PO is ready for printing and distribution.", userToRouteFyi, "", true, "PRINT");
}
}
}
protected boolean completeB2BPurchaseOrder(PurchaseOrderDocument po) {
String errors = b2bPurchaseOrderService.sendPurchaseOrder(po);
if (StringUtils.isEmpty(errors)) {
// PO sent successfully; change status to OPEN
attemptSetupOfInitialOpenOfDocument(po);
po.setPurchaseOrderLastTransmitTimestamp(dateTimeService.getCurrentTimestamp());
return true;
}
else {
// PO transmission failed; record errors and change status to "cxml failed"
try {
String noteText = "Unable to transmit the PO for the following reasons:\n" + errors;
int noteMaxSize = dataDictionaryService.getAttributeMaxLength("Note", "noteText");
// Break up the note into multiple pieces if the note is too large to fit in the database field.
while (noteText.length() > noteMaxSize) {
int fromIndex = 0;
String noteText1 = noteText.substring(0, noteMaxSize);
Note note1 = documentService.createNoteFromDocument(po, noteText1);
po.addNote(note1);
noteText = noteText.substring(noteMaxSize);
}
Note note = documentService.createNoteFromDocument(po, noteText);
po.addNote(note);
documentService.saveDocumentNotes(po);
}
catch (Exception e) {
throw new RuntimeException(e);
}
try {
po.updateAndSaveAppDocStatus(PurchaseOrderStatuses.APPDOC_CXML_ERROR);
}
catch (WorkflowException e) {
throw new RuntimeException("Error saving routing data while saving document with id " + po.getDocumentNumber(), e);
}
return false;
}
}
@Override
public void retransmitB2BPurchaseOrder(PurchaseOrderDocument po) {
if (completeB2BPurchaseOrder(po)) {
KNSGlobalVariables.getMessageList().add(PurapKeyConstants.B2B_PO_RETRANSMIT_SUCCESS);
}
else {
GlobalVariables.getMessageMap().putError(KFSConstants.GLOBAL_ERRORS, PurapKeyConstants.B2B_PO_RETRANSMIT_FAILED);
}
purapService.saveDocumentNoValidation(po);
}
@Override
public void completePurchaseOrderAmendment(PurchaseOrderDocument poa) {
LOG.debug("completePurchaseOrderAmendment() started");
setCurrentAndPendingIndicatorsForApprovedPODocuments(poa);
// check thresholds to see if receiving is required for purchase order amendment
if (!poa.isReceivingDocumentRequiredIndicator() && !SpringContext.getBean(PaymentRequestService.class).hasActivePaymentRequestsForPurchaseOrder(poa.getPurapDocumentIdentifier())) {
setReceivingRequiredIndicatorForPurchaseOrder(poa);
}
// if unordered items have been added to the PO then send an FYI to all fiscal officers
if (hasNewUnorderedItem(poa)) {
sendFyiForNewUnorderedItems(poa);
}
}
/**
* First we check that vendor commodity codes should indeed be added, and if so-
* If there are commodity codes on the items on the PurchaseOrderDocument that haven't existed yet on the vendor that the
* PurchaseOrderDocument is using, then we will spawn a new VendorDetailMaintenanceDocument automatically to update the vendor
* with the commodity codes that aren't already existing on the vendor.
*
* @param po The PurchaseOrderDocument containing the vendor that we want to update.
*/
@Override
public void updateVendorCommodityCode(PurchaseOrderDocument po) {
String noteText = "";
VendorDetail oldVendorDetail = po.getVendorDetail();
VendorDetail newVendorDetail = updateVendorWithMissingCommodityCodesIfNecessary(po);
//we default to adding vendor commodity codes.
Boolean shouldUpdate= parameterService.getParameterValueAsBoolean(RequisitionDocument.class, PurapParameterConstants.UPDATE_VENDOR_SETTING, Boolean.TRUE);
if (shouldUpdate && newVendorDetail != null) {
try {
// spawn a new vendor maintenance document to add the note
MaintenanceDocument vendorMaintDoc = null;
try {
vendorMaintDoc = (MaintenanceDocument) documentService.getNewDocument("PVEN");
vendorMaintDoc.getDocumentHeader().setDocumentDescription("Automatically spawned from PO");
vendorMaintDoc.getOldMaintainableObject().setBusinessObject(oldVendorDetail);
vendorMaintDoc.getNewMaintainableObject().setBusinessObject(newVendorDetail);
vendorMaintDoc.getNewMaintainableObject().setMaintenanceAction(KFSConstants.MAINTENANCE_EDIT_ACTION);
vendorMaintDoc.getNewMaintainableObject().setDocumentNumber(vendorMaintDoc.getDocumentNumber());
boolean isVendorLocked = checkForLockingDocument(vendorMaintDoc);
if (!isVendorLocked) {
// validating vendor doc to capture exception before trying to route which if exception happens in
// docService, then PO will fail too
vendorMaintDoc.validateBusinessRules(new RouteDocumentEvent(vendorMaintDoc));
addNoteForCommodityCodeToVendor(vendorMaintDoc.getNewMaintainableObject(), vendorMaintDoc.getDocumentNumber(), po.getPurapDocumentIdentifier());
documentService.routeDocument(vendorMaintDoc, null, null);
}
else {
// Add a note to the PO to tell the users that we can't automatically update the vendor because it's locked.
noteText = "Unable to automatically update vendor because it is locked";
}
}
catch (Exception e) {
if (ObjectUtils.isNull(vendorMaintDoc)) {
noteText = "Unable to create a new VendorDetailMaintenanceDocument to update the vendor with new commodity codes";
}
else {
noteText = "Unable to route a new VendorDetailMaintenanceDocument to update the vendor with new commodity codes";
}
}
finally {
if (StringUtils.isNotBlank(noteText)) {
// update on purchase order notes
Note note = documentService.createNoteFromDocument(po, noteText);
po.addNote(note);
noteService.save(note);
if(GlobalVariables.getMessageMap().hasErrors()) {
// clear out GlobalVariable message map, since we have taken care of the errors
//If errors were discovered during the routing of the Vendor, although the exception is caught, the errors are still added to the message map.
//This is causing the PO to go into exception routing although the error was only with the Vendor doc
GlobalVariables.setMessageMap(new MessageMap());
}
}
}
}
catch (Exception e) {
LOG.error("updateVendorCommodityCode() unable to add a note(" + noteText + ") to PO document " + po.getDocumentNumber());
}
}
}
/**
* Creates a note to be added to the Vendor Maintenance Document which is spawned from the PurchaseOrderDocument.
*
* @param maintainable
* @param documentNumber
* @param poID
*/
protected void addNoteForCommodityCodeToVendor(Maintainable maintainable, String documentNumber, Integer poID) {
Note newBONote = new Note();
newBONote.setNoteText("Change vendor document ID <" + documentNumber + ">. Document was automatically created from PO <" + poID + "> to add commodity codes used on this PO that were not yet assigned to this vendor.");
try {
newBONote = noteService.createNote(newBONote, maintainable.getBusinessObject(), GlobalVariables.getUserSession().getPrincipalId());
newBONote.setNotePostedTimestampToCurrent();
}
catch (Exception e) {
throw new RuntimeException("Caught Exception While Trying To Add Note to Vendor", e);
}
List<Note> noteList = noteService.getByRemoteObjectId(maintainable.getBusinessObject().getObjectId());
noteList.add(newBONote);
noteService.saveNoteList(noteList);
}
/**
* Checks whether the vendor is currently locked.
*
* @param document The MaintenanceDocument containing the vendor.
* @return boolean true if the vendor is currently locked and false otherwise.
*/
protected boolean checkForLockingDocument(MaintenanceDocument document) {
String blockingDocId = maintenanceDocumentService.getLockingDocumentId(document);
if (StringUtils.isBlank(blockingDocId)) {
return false;
}
else {
return true;
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#updateVendorWithMissingCommodityCodesIfNecessary(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
*/
@Override
public VendorDetail updateVendorWithMissingCommodityCodesIfNecessary(PurchaseOrderDocument po) {
List<CommodityCode> result = new ArrayList<CommodityCode>();
boolean foundDefault = false;
VendorDetail vendor = (VendorDetail) ObjectUtils.deepCopy(po.getVendorDetail());
for (PurchaseOrderItem item : (List<PurchaseOrderItem>) po.getItems()) {
// Only check on commodity codes if the item is active and is above the line item type.
if (item.getItemType().isLineItemIndicator() && item.isItemActiveIndicator()) {
CommodityCode cc = item.getCommodityCode();
if (cc != null && !result.contains(cc)) {
List<VendorCommodityCode> vendorCommodityCodes = po.getVendorDetail().getVendorCommodities();
boolean foundMatching = false;
for (VendorCommodityCode vcc : vendorCommodityCodes) {
if (vcc.getCommodityCode().getPurchasingCommodityCode().equals(cc.getPurchasingCommodityCode())) {
foundMatching = true;
}
if (!foundDefault && vcc.isCommodityDefaultIndicator()) {
foundDefault = true;
}
}
if (!foundMatching) {
result.add(cc);
VendorCommodityCode vcc = new VendorCommodityCode(vendor.getVendorHeaderGeneratedIdentifier(), vendor.getVendorDetailAssignedIdentifier(), cc, true);
vcc.setActive(true);
if (!foundDefault) {
vcc.setCommodityDefaultIndicator(true);
foundDefault = true;
}
vendor.getVendorCommodities().add(vcc);
}
}
}
}
if (result.size() > 0) {
// We also have to add to the old vendor detail's vendorCommodities if we're adding to the new
// vendor detail's vendorCommodities.
for (int i = 0; i < result.size(); i++) {
po.getVendorDetail().getVendorCommodities().add(new VendorCommodityCode());
}
return vendor;
}
else {
return null;
}
}
/**
* Update the purchase order document with the appropriate status for pending first transmission based on the transmission type.
*
* @param po The purchase order document whose status to be updated.
*/
protected void setupDocumentForPendingFirstTransmission(PurchaseOrderDocument po) {
if (POTransmissionMethods.PRINT.equals(po.getPurchaseOrderTransmissionMethodCode()) || POTransmissionMethods.FAX.equals(po.getPurchaseOrderTransmissionMethodCode()) || POTransmissionMethods.ELECTRONIC.equals(po.getPurchaseOrderTransmissionMethodCode())) {
String newStatusCode = PurchaseOrderStatuses.STATUSES_BY_TRANSMISSION_TYPE.get(po.getPurchaseOrderTransmissionMethodCode());
if (LOG.isDebugEnabled()) {
LOG.debug("setupDocumentForPendingFirstTransmission() Purchase Order Transmission Type is '" + po.getPurchaseOrderTransmissionMethodCode() + "' setting status to '" + newStatusCode + "'");
}
try {
po.updateAndSaveAppDocStatus(newStatusCode);
}
catch (WorkflowException e) {
throw new RuntimeException("Error saving routing data while saving document with id " + po.getDocumentNumber(), e);
}
}
}
/**
* If the status of the purchase order is not OPEN and the initial open date is null, sets the initial open date to current date
* and update the status to OPEN, then save the purchase order.
*
* @param po The purchase order document whose initial open date and status we want to update.
*/
protected void attemptSetupOfInitialOpenOfDocument(PurchaseOrderDocument po) {
if (LOG.isDebugEnabled()) {
LOG.debug("attemptSetupOfInitialOpenOfDocument() started using document with doc id " + po.getDocumentNumber());
}
if (!PurchaseOrderStatuses.APPDOC_OPEN.equals(po.getApplicationDocumentStatus())) {
if (ObjectUtils.isNull(po.getPurchaseOrderInitialOpenTimestamp())) {
LOG.debug("attemptSetupOfInitialOpenOfDocument() setting initial open date on document");
po.setPurchaseOrderInitialOpenTimestamp(dateTimeService.getCurrentTimestamp());
}
else {
throw new RuntimeException("Document does not have status code '" + PurchaseOrderStatuses.APPDOC_OPEN + "' on it but value of initial open date is " + po.getPurchaseOrderInitialOpenTimestamp());
}
LOG.info("attemptSetupOfInitialOpenOfDocument() Setting po document id " + po.getDocumentNumber() + " status from '" + po.getApplicationDocumentStatus() + "' to '" + PurchaseOrderStatuses.APPDOC_OPEN + "'");
try {
po.updateAndSaveAppDocStatus(PurchaseOrderStatuses.APPDOC_OPEN);
}
catch (WorkflowException we) {
throw new RuntimeException("Unable to load a WorkflowDocument object for " + po.getDocumentNumber(), we);
}
}
else {
LOG.error("attemptSetupOfInitialOpenOfDocument() Found document already in '" + PurchaseOrderStatuses.APPDOC_OPEN + "' status for PO#" + po.getPurapDocumentIdentifier() + "; will not change or update");
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#getCurrentPurchaseOrder(java.lang.Integer)
*/
@Override
public PurchaseOrderDocument getCurrentPurchaseOrder(Integer id) {
return getPurchaseOrderByDocumentNumber(purchaseOrderDao.getDocumentNumberForCurrentPurchaseOrder(id));
// TODO hjs: code review (why is this DB call so complicated? wouldn't this method be cleaner and less db calls?)
// return purchaseOrderDao.getCurrentPurchaseOrder(id);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#getPurchaseOrderByDocumentNumber(java.lang.String)
*/
@Override
public PurchaseOrderDocument getPurchaseOrderByDocumentNumber(String documentNumber) {
if (ObjectUtils.isNotNull(documentNumber)) {
try {
PurchaseOrderDocument doc = (PurchaseOrderDocument) documentService.getByDocumentHeaderId(documentNumber);
if (ObjectUtils.isNotNull(doc)) {
WorkflowDocument workflowDocument = doc.getDocumentHeader().getWorkflowDocument();
doc.refreshReferenceObject(KFSPropertyConstants.DOCUMENT_HEADER);
doc.getDocumentHeader().setWorkflowDocument(workflowDocument);
}
return doc;
}
catch (WorkflowException e) {
String errorMessage = "Error getting purchase order document from document service";
LOG.error("getPurchaseOrderByDocumentNumber() " + errorMessage, e);
throw new RuntimeException(errorMessage, e);
}
}
return null;
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#getOldestPurchaseOrder(org.kuali.kfs.module.purap.document.PurchaseOrderDocument,
* org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
*/
@Override
public PurchaseOrderDocument getOldestPurchaseOrder(PurchaseOrderDocument po, PurchaseOrderDocument documentBusinessObject) {
LOG.debug("entering getOldestPO(PurchaseOrderDocument)");
if (ObjectUtils.isNotNull(po)) {
String oldestDocumentNumber = purchaseOrderDao.getOldestPurchaseOrderDocumentNumber(po.getPurapDocumentIdentifier());
// KFSMI-9746 -- See Harsha's comments...
if (StringUtils.isBlank(oldestDocumentNumber)) {
return null;
}
if (StringUtils.equals(oldestDocumentNumber, po.getDocumentNumber())) {
// manually set bo notes - this is mainly done for performance reasons (preferably we could call
// retrieve doc notes in PersistableBusinessObjectBase but that is protected)
updateNotes(po, documentBusinessObject);
LOG.debug("exiting getOldestPO(PurchaseOrderDocument)");
return po;
}
else {
PurchaseOrderDocument oldestPurchaseOrder = getPurchaseOrderByDocumentNumber(oldestDocumentNumber);
updateNotes(oldestPurchaseOrder, documentBusinessObject);
LOG.debug("exiting getOldestPO(PurchaseOrderDocument)");
return oldestPurchaseOrder;
}
}
return null;
}
/**
* If the purchase order's object id is not null (I think this means if it's an existing purchase order that had already been
* saved to the db previously), get the notes of the purchase order from the database, fix the notes' fields by calling the
* fixDbNoteFields, then set the notes to the purchase order. Otherwise (I think this means if it's a new purchase order), set
* the notes of this purchase order to be the notes of the documentBusinessObject.
*
* @param po The current purchase order.
* @param documentBusinessObject The oldest purchase order whose purapDocumentIdentifier is the same as the po's
* purapDocumentIdentifier.
*/
protected void updateNotes(PurchaseOrderDocument po, PurchaseOrderDocument documentBusinessObject) {
if (ObjectUtils.isNotNull(documentBusinessObject)) {
if (ObjectUtils.isNotNull(po.getObjectId())) {
List<Note> dbNotes = noteService.getByRemoteObjectId(po.getObjectId());
// need to set fields that are not ojb managed (i.e. the notes on the documentBusinessObject may have been modified
// independently of the ones in the db)
fixDbNoteFields(documentBusinessObject, dbNotes);
po.setNotes(dbNotes);
}
else {
po.setNotes(documentBusinessObject.getNotes());
}
}
}
/**
* This method fixes non ojb managed missing fields from the db
*
* @param documentBusinessObject The oldest purchase order whose purapDocumentIdentifier is the same as the po's
* purapDocumentIdentifier.
* @param dbNotes The notes of the purchase order obtained from the database.
*/
protected void fixDbNoteFields(PurchaseOrderDocument documentBusinessObject, List<Note> dbNotes) {
for (int i = 0; i < dbNotes.size(); i++) {
Note dbNote = dbNotes.get(i);
List<Note> currentNotes = documentBusinessObject.getNotes();
if (i < currentNotes.size()) {
Note currentNote = (currentNotes).get(i);
// set the fyi from the current note if not empty
AdHocRouteRecipient fyiNoteRecipient = currentNote.getAdHocRouteRecipient();
if (ObjectUtils.isNotNull(fyiNoteRecipient)) {
dbNote.setAdHocRouteRecipient(fyiNoteRecipient);
}
}
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#getPurchaseOrderNotes(java.lang.Integer)
*/
@Override
public List<Note> getPurchaseOrderNotes(Integer id) {
List<Note> notes = new ArrayList<Note>();
PurchaseOrderDocument po = getPurchaseOrderByDocumentNumber(purchaseOrderDao.getOldestPurchaseOrderDocumentNumber(id));
if (ObjectUtils.isNotNull(po)) {
notes = noteService.getByRemoteObjectId(po.getDocumentHeader().getObjectId());
}
return notes;
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForApprovedPODocuments(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
*/
@Override
public void setCurrentAndPendingIndicatorsForApprovedPODocuments(PurchaseOrderDocument newPO) {
// Get the "current PO" that's in the database, i.e. the PO row that contains current indicator = Y
PurchaseOrderDocument oldPO = getCurrentPurchaseOrder(newPO.getPurapDocumentIdentifier());
// If the document numbers between the oldPO and the newPO are different, then this is a PO change document.
if (!oldPO.getDocumentNumber().equals(newPO.getDocumentNumber())) {
// First, we set the indicators for the oldPO to : Current = N and Pending = N
oldPO.setPurchaseOrderCurrentIndicator(false);
oldPO.setPendingActionIndicator(false);
// set the status and status history of the oldPO to retired version
try {
oldPO.updateAndSaveAppDocStatus(PurapConstants.PurchaseOrderStatuses.APPDOC_RETIRED_VERSION);
}
catch (WorkflowException e) {
throw new RuntimeException("Error saving routing data while saving document with id " + oldPO.getDocumentNumber(), e);
}
savePurchaseOrderData(oldPO);
}
// Now, we set the "new PO" indicators so that Current = Y and Pending = N
newPO.setPurchaseOrderCurrentIndicator(true);
newPO.setPendingActionIndicator(false);
// KFSMI-9879 - Don't save the newPO here - if it's a PO Close Doc, that could delete previously
// saved GLPEs.
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForDisapprovedChangePODocuments(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
*/
@Override
public void setCurrentAndPendingIndicatorsForDisapprovedChangePODocuments(PurchaseOrderDocument newPO) {
updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.APPDOC_DISAPPROVED_CHANGE, PurapConstants.PurchaseOrderStatuses.APPDOC_OPEN);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForCancelledChangePODocuments(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
*/
@Override
public void setCurrentAndPendingIndicatorsForCancelledChangePODocuments(PurchaseOrderDocument newPO) {
updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.APPDOC_CANCELLED_CHANGE, PurapConstants.PurchaseOrderStatuses.APPDOC_OPEN);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForCancelledReopenPODocuments(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
*/
@Override
public void setCurrentAndPendingIndicatorsForCancelledReopenPODocuments(PurchaseOrderDocument newPO) {
updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.APPDOC_CANCELLED_CHANGE, PurapConstants.PurchaseOrderStatuses.APPDOC_CLOSED);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForDisapprovedReopenPODocuments(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
*/
@Override
public void setCurrentAndPendingIndicatorsForDisapprovedReopenPODocuments(PurchaseOrderDocument newPO) {
updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.APPDOC_DISAPPROVED_CHANGE, PurapConstants.PurchaseOrderStatuses.APPDOC_CLOSED);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForCancelledRemoveHoldPODocuments(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
*/
@Override
public void setCurrentAndPendingIndicatorsForCancelledRemoveHoldPODocuments(PurchaseOrderDocument newPO) {
updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.APPDOC_CANCELLED_CHANGE, PurapConstants.PurchaseOrderStatuses.APPDOC_PAYMENT_HOLD);
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#setCurrentAndPendingIndicatorsForDisapprovedRemoveHoldPODocuments(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
*/
@Override
public void setCurrentAndPendingIndicatorsForDisapprovedRemoveHoldPODocuments(PurchaseOrderDocument newPO) {
updateCurrentDocumentForNoPendingAction(newPO, PurapConstants.PurchaseOrderStatuses.APPDOC_DISAPPROVED_CHANGE, PurapConstants.PurchaseOrderStatuses.APPDOC_PAYMENT_HOLD);
}
/**
* Update the statuses of both the old purchase order and the new purchase orders, then save the old and the new purchase
* orders.
*
* @param newPO The new change purchase order document (e.g. the PurchaseOrderAmendmentDocument that was resulted from the user
* clicking on the amend button).
* @param newPOStatus The status to be set on the new change purchase order document.
* @param oldPOStatus The status to be set on the existing (old) purchase order document.
*/
protected void updateCurrentDocumentForNoPendingAction(PurchaseOrderDocument newPO, String newPOStatus, String oldPOStatus) {
// Get the "current PO" that's in the database, i.e. the PO row that contains current indicator = Y
PurchaseOrderDocument oldPO = getCurrentPurchaseOrder(newPO.getPurapDocumentIdentifier());
// Set the Pending indicator for the oldPO to N
oldPO.setPendingActionIndicator(false);
try {
oldPO.updateAndSaveAppDocStatus(oldPOStatus);
newPO.updateAndSaveAppDocStatus(newPOStatus);
}
catch (WorkflowException e) {
throw new RuntimeException("Error saving routing data while saving document", e);
}
savePurchaseOrderData(oldPO);
saveDocumentNoValidationUsingClearMessageMap(newPO);
}
@Override
public List<PurchaseOrderQuoteStatus> getPurchaseOrderQuoteStatusCodes() {
List<PurchaseOrderQuoteStatus> poQuoteStatuses = new ArrayList<PurchaseOrderQuoteStatus>();
poQuoteStatuses = (List<PurchaseOrderQuoteStatus>) businessObjectService.findAll(PurchaseOrderQuoteStatus.class);
return poQuoteStatuses;
}
@Override
public void setReceivingRequiredIndicatorForPurchaseOrder(PurchaseOrderDocument po) {
ThresholdHelper thresholdHelper = new ThresholdHelper(po);
boolean result = thresholdHelper.isReceivingDocumentRequired();
if (result) {
ThresholdSummary thresholdSummary = thresholdHelper.getThresholdSummary();
ReceivingThreshold receivingThreshold = thresholdHelper.getReceivingThreshold();
po.setReceivingDocumentRequiredIndicator(true);
String notetxt = "Receiving is set to be required because the threshold summary with a total amount of " + thresholdSummary.getTotalAmount();
notetxt += " exceeds the receiving threshold of " + receivingThreshold.getThresholdAmount();
notetxt += " with respect to the threshold criteria ";
if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART) {
notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();
}
else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_ACCOUNTTYPE) {
notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();
notetxt += " - Account Type " + receivingThreshold.getAccountTypeCode();
}
else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_SUBFUND) {
notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();
notetxt += " - Sub-Fund " + receivingThreshold.getSubFundGroupCode();
}
else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_COMMODITYCODE) {
notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();
notetxt += " - Commodity Code " + receivingThreshold.getPurchasingCommodityCode();
}
else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_OBJECTCODE) {
notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();
notetxt += " - Object code " + receivingThreshold.getFinancialObjectCode();
}
else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_ORGANIZATIONCODE) {
notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();
notetxt += " - Organization " + receivingThreshold.getOrganizationCode();
}
else if (thresholdSummary.getThresholdCriteria() == ThresholdHelper.CHART_AND_VENDOR) {
notetxt += " Chart " + receivingThreshold.getChartOfAccountsCode();
notetxt += " - Vendor " + receivingThreshold.getVendorNumber();
}
try {
Note note = documentService.createNoteFromDocument(po, notetxt);
// documentService.addNoteToDocument(po, note);
noteService.save(note);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#hasNewUnorderedItem(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
*/
@Override
public boolean hasNewUnorderedItem(PurchaseOrderDocument po) {
boolean itemAdded = false;
for (PurchaseOrderItem poItem : (List<PurchaseOrderItem>) po.getItems()) {
// only check, active, above the line, unordered items
if (poItem.isItemActiveIndicator() && poItem.getItemType().isLineItemIndicator() && PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE.equals(poItem.getItemTypeCode())) {
// if the item identifier is null its new, or if the item doesn't exist on the current purchase order it's new
if (poItem.getItemIdentifier() == null || !purchaseOrderDao.itemExistsOnPurchaseOrder(poItem.getItemLineNumber(), purchaseOrderDao.getDocumentNumberForCurrentPurchaseOrder(po.getPurapDocumentIdentifier()))) {
itemAdded = true;
break;
}
}
}
return itemAdded;
}
@Override
public boolean isNewUnorderedItem(PurchaseOrderItem poItem) {
boolean itemAdded = false;
// only check, active, above the line, unordered items
if (poItem.isItemActiveIndicator() && poItem.getItemType().isLineItemIndicator() && PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE.equals(poItem.getItemTypeCode())) {
// if the item identifier is null its new, or if the item doesn't exist on the current purchase order it's new
if (poItem.getItemIdentifier() == null || !purchaseOrderDao.itemExistsOnPurchaseOrder(poItem.getItemLineNumber(), purchaseOrderDao.getDocumentNumberForCurrentPurchaseOrder(poItem.getPurchaseOrder().getPurapDocumentIdentifier()))) {
itemAdded = true;
}
}
return itemAdded;
}
@Override
public boolean isNewItemForAmendment(PurchaseOrderItem poItem) {
boolean itemAdded = false;
// only check, active, above the line, unordered items
if (poItem.isItemActiveIndicator() && poItem.getItemType().isLineItemIndicator()) {
// if the item identifier is null its new, or if the item doesn't exist on the current purchase order it's new
Integer docId = poItem.getPurapDocumentIdentifier();
if (docId == null) {
docId = poItem.getPurchaseOrder().getPurapDocumentIdentifier();
}
if (poItem.getItemIdentifier() == null || !purchaseOrderDao.itemExistsOnPurchaseOrder(poItem.getItemLineNumber(), purchaseOrderDao.getDocumentNumberForCurrentPurchaseOrder(docId))) {
itemAdded = true;
}
}
return itemAdded;
}
/**
* Sends an FYI to fiscal officers for new unordered items.
*
* @param po
*/
protected void sendFyiForNewUnorderedItems(PurchaseOrderDocument po) {
List<AdHocRoutePerson> fyiList = createFyiFiscalOfficerListForNewUnorderedItems(po);
String annotation = "Notification of New Unordered Items for Purchase Order" + po.getPurapDocumentIdentifier() + "(document id " + po.getDocumentNumber() + ")";
String responsibilityNote = "Purchase Order Amendment Routed By User";
for (AdHocRoutePerson adHocPerson : fyiList) {
try {
po.appSpecificRouteDocumentToUser(po.getDocumentHeader().getWorkflowDocument(), adHocPerson.getPerson().getPrincipalId(), annotation, responsibilityNote);
}
catch (WorkflowException e) {
throw new RuntimeException("Error routing fyi for document with id " + po.getDocumentNumber(), e);
}
}
}
/**
* Creates a list of fiscal officers for new unordered items added to a purchase order.
*
* @param po
* @return
*/
protected List<AdHocRoutePerson> createFyiFiscalOfficerListForNewUnorderedItems(PurchaseOrderDocument po) {
List<AdHocRoutePerson> adHocRoutePersons = new ArrayList<AdHocRoutePerson>();
Map fiscalOfficers = new HashMap();
AdHocRoutePerson adHocRoutePerson = null;
for (PurchaseOrderItem poItem : (List<PurchaseOrderItem>) po.getItems()) {
// only check, active, above the line, unordered items
if (poItem.isItemActiveIndicator() && poItem.getItemType().isLineItemIndicator() && PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE.equals(poItem.getItemTypeCode())) {
// if the item identifier is null its new, or if the item doesn't exist on the current purchase order it's new
if (poItem.getItemIdentifier() == null || !purchaseOrderDao.itemExistsOnPurchaseOrder(poItem.getItemLineNumber(), purchaseOrderDao.getDocumentNumberForCurrentPurchaseOrder(po.getPurapDocumentIdentifier()))) {
// loop through accounts and pull off fiscal officer
for (PurApAccountingLine account : poItem.getSourceAccountingLines()) {
// check for dupes of fiscal officer
if (fiscalOfficers.containsKey(account.getAccount().getAccountFiscalOfficerUser().getPrincipalName()) == false) {
// add fiscal officer to list
fiscalOfficers.put(account.getAccount().getAccountFiscalOfficerUser().getPrincipalName(), account.getAccount().getAccountFiscalOfficerUser().getPrincipalName());
// create AdHocRoutePerson object and add to list
adHocRoutePerson = new AdHocRoutePerson();
adHocRoutePerson.setId(account.getAccount().getAccountFiscalOfficerUser().getPrincipalName());
adHocRoutePerson.setActionRequested(KFSConstants.WORKFLOW_FYI_REQUEST);
adHocRoutePersons.add(adHocRoutePerson);
}
}
}
}
}
return adHocRoutePersons;
}
/**
* Sends an FYI to fiscal officers for general ledger entries created for amend purchase order
*
* @param po
*/
@Override
public void sendFyiForGLEntries(PurchaseOrderDocument po) {
List<AdHocRoutePerson> fyiList = createFyiFiscalOfficerListForAmendGlEntries(po);
String annotation = "Amendment to Purchase Order " + po.getPurapDocumentIdentifier() + "( Document id " + po.getDocumentNumber() + ")" + " resulted in the generation of Pending General Ledger Entries.";
String responsibilityNote = "Purchase Order Amendment Routed By User";
for (AdHocRoutePerson adHocPerson : fyiList) {
try {
po.appSpecificRouteDocumentToUser(po.getDocumentHeader().getWorkflowDocument(), adHocPerson.getPerson().getPrincipalId(), annotation, responsibilityNote);
}
catch (WorkflowException e) {
throw new RuntimeException("Error routing fyi for document with id " + po.getDocumentNumber(), e);
}
}
}
@Override
public void sendAdhocFyi(PurchaseOrderDocument po) {
RequisitionDocument req = po.getPurApSourceDocumentIfPossible();
String reqInitiator=null;
if(ObjectUtils.isNotNull(req)){
reqInitiator =req.getDocumentHeader().getWorkflowDocument().getInitiatorPrincipalId();
}
String currentDocumentTypeName = po.getDocumentHeader().getWorkflowDocument().getDocumentTypeName();
Set<String> fiscalOfficerIds = new HashSet<String>();
Set<Account> accounts = new HashSet<Account>();
try {
if(reqInitiator!=null) {
po.appSpecificRouteDocumentToUser(po.getDocumentHeader().getWorkflowDocument(), reqInitiator, getAdhocFyiAnnotation(po) + KFSConstants.BLANK_SPACE + req.getPurapDocumentIdentifier() + KFSConstants.BLANK_SPACE + "(document Id " + req.getDocumentNumber() + ")", "Requisition Routed By User");
}
if(!PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_AMENDMENT_DOCUMENT.equalsIgnoreCase(currentDocumentTypeName)){
List<PurchaseOrderItem> items = po.getItemsActiveOnly();
for (PurchaseOrderItem item : items) {
List<PurApAccountingLine> lines = item.getSourceAccountingLines();
for (PurApAccountingLine line : lines) {
accounts.add(line.getAccount());
}
}
for (Account account : accounts) {
String principalId = account.getAccountFiscalOfficerUser().getPrincipalId();
if (!fiscalOfficerIds.contains(principalId)) {
fiscalOfficerIds.add(principalId);
AccountDelegate accountDelegate = getAccountPrimaryDelegate(account);
if (ObjectUtils.isNotNull(accountDelegate)) {
String delegateName =KimApiServiceLocator.getPersonService().getPerson(accountDelegate.getAccountDelegateSystemId()).getPrincipalName();
String annotationText = "Delegation of: " + KFSConstants.CoreModuleNamespaces.KFS + KFSConstants.BLANK_SPACE + KFSConstants.SysKimApiConstants.FISCAL_OFFICER_KIM_ROLE_NAME + KFSConstants.BLANK_SPACE + account.getChartOfAccountsCode() + KFSConstants.BLANK_SPACE + account.getAccountNumber() + KFSConstants.BLANK_SPACE + "to principal" + KFSConstants.BLANK_SPACE + delegateName;
po.appSpecificRouteDocumentToUser(po.getDocumentHeader().getWorkflowDocument(), accountDelegate.getAccountDelegateSystemId(), annotationText, "Fiscal Officer Notification");
}
else {
String annotationText = KFSConstants.CoreModuleNamespaces.KFS + KFSConstants.BLANK_SPACE + KFSConstants.SysKimApiConstants.FISCAL_OFFICER_KIM_ROLE_NAME + KFSConstants.BLANK_SPACE + account.getChartOfAccountsCode() + KFSConstants.BLANK_SPACE + account.getAccountNumber();
po.appSpecificRouteDocumentToUser(po.getDocumentHeader().getWorkflowDocument(), principalId, annotationText, "Fiscal Officer Notification");
}
}
}
}
}
catch (WorkflowException ex) {
throw new RuntimeException("Error routing fyi for document with id " + po.getDocumentNumber(), ex);
}
}
private AccountDelegate getAccountPrimaryDelegate(Account account) {
AccountDelegate delegateExample = new AccountDelegate();
delegateExample.setChartOfAccountsCode(account.getChartOfAccountsCode());
delegateExample.setAccountNumber( account.getAccountNumber());
delegateExample.setFinancialDocumentTypeCode(PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_DOCUMENT);
AccountDelegate accountDelegate= SpringContext.getBean(AccountService.class).getPrimaryDelegationByExample(delegateExample, null);
return accountDelegate;
}
protected String getAdhocFyiAnnotation(PurchaseOrderDocument po) {
String annotation = "";
if (po.getDocumentHeader().getWorkflowDocument().isDisapproved()) {
annotation = KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString(PurapConstants.PO_DISAPPROVAL_ANNOTATION_TEXT);
}
if (po.getDocumentHeader().getWorkflowDocument().isFinal()) {
annotation = KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString(PurapConstants.PO_FINAL_ANNOTATION_TEXT);
}
if (po.getDocumentHeader().getWorkflowDocument().isCanceled()) {
annotation =KRADServiceLocator.getKualiConfigurationService().getPropertyValueAsString(PurapConstants.PO_CANCEL_ANNOTATION_TEXT);
}
return annotation;
}
/**
* Creates a list of fiscal officers for amend genera
*
* @param po
* @return
*/
protected List<AdHocRoutePerson> createFyiFiscalOfficerListForAmendGlEntries(PurchaseOrderDocument po) {
List<AdHocRoutePerson> adHocRoutePersons = new ArrayList<AdHocRoutePerson>();
Map fiscalOfficers = new HashMap();
AdHocRoutePerson adHocRoutePerson = null;
for (SourceAccountingLine account : po.getGlOnlySourceAccountingLines()) {
// loop through accounts and pull off fiscal officer
// for(PurApAccountingLine account : poItem.getSourceAccountingLines()){
// check for dupes of fiscal officer
Account acct = SpringContext.getBean(AccountService.class).getByPrimaryId(account.getChartOfAccountsCode(), account.getAccountNumber());
String principalName = acct.getAccountFiscalOfficerUser().getPrincipalName();
// String principalName = account.getAccount().getAccountFiscalOfficerUser().getPrincipalName();
if (fiscalOfficers.containsKey(principalName) == false) {
// add fiscal officer to list
fiscalOfficers.put(principalName, principalName);
// create AdHocRoutePerson object and add to list
adHocRoutePerson = new AdHocRoutePerson();
adHocRoutePerson.setId(principalName);
adHocRoutePerson.setActionRequested(KewApiConstants.ACTION_REQUEST_FYI_REQ);
adHocRoutePersons.add(adHocRoutePerson);
}
// }
}
return adHocRoutePersons;
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#categorizeItemsForSplit(java.util.List)
*/
@Override
public HashMap<String, List<PurchaseOrderItem>> categorizeItemsForSplit(List<PurchaseOrderItem> items) {
HashMap<String, List<PurchaseOrderItem>> movingOrNot = new HashMap<String, List<PurchaseOrderItem>>(3);
List<PurchaseOrderItem> movingPOItems = new ArrayList<PurchaseOrderItem>();
List<PurchaseOrderItem> remainingPOItems = new ArrayList<PurchaseOrderItem>();
List<PurchaseOrderItem> remainingPOLineItems = new ArrayList<PurchaseOrderItem>();
for (PurchaseOrderItem item : items) {
if (item.isMovingToSplit()) {
movingPOItems.add(item);
}
else {
remainingPOItems.add(item);
if (item.getItemType().isLineItemIndicator()) {
remainingPOLineItems.add(item);
}
}
}
movingOrNot.put(PODocumentsStrings.ITEMS_MOVING_TO_SPLIT, movingPOItems);
movingOrNot.put(PODocumentsStrings.ITEMS_REMAINING, remainingPOItems);
movingOrNot.put(PODocumentsStrings.LINE_ITEMS_REMAINING, remainingPOLineItems);
return movingOrNot;
}
/**
* @see org.kuali.module.purap.service.PurchaseOrderService#populateQuoteWithVendor(java.lang.Integer, java.lang.Integer,
* java.lang.String)
*/
@Override
public PurchaseOrderVendorQuote populateQuoteWithVendor(Integer headerId, Integer detailId, String documentNumber) {
VendorDetail vendor = vendorService.getVendorDetail(headerId, detailId);
updateDefaultVendorAddress(vendor);
PurchaseOrderVendorQuote newPOVendorQuote = populateAddressForPOVendorQuote(vendor, documentNumber);
// Set the vendorPhoneNumber on the quote to be the first "phone number" type phone
// found on the list. If there's no "phone number" type found, the quote's
// vendorPhoneNumber will be blank regardless of any other types of phone found on the list.
for (VendorPhoneNumber phone : vendor.getVendorPhoneNumbers()) {
if (VendorConstants.PhoneTypes.PHONE.equals(phone.getVendorPhoneTypeCode())) {
newPOVendorQuote.setVendorPhoneNumber(phone.getVendorPhoneNumber());
break;
}
}
return newPOVendorQuote;
}
/**
* Creates the new PurchaseOrderVendorQuote and populate the address fields for it.
*
* @param newVendor The VendorDetail object from which we obtain the values for the address fields.
* @param documentNumber The documentNumber of the PurchaseOrderDocument containing the PurchaseOrderVendorQuote.
* @return
*/
protected PurchaseOrderVendorQuote populateAddressForPOVendorQuote(VendorDetail newVendor, String documentNumber) {
PurchaseOrderVendorQuote newPOVendorQuote = new PurchaseOrderVendorQuote();
newPOVendorQuote.setVendorName(newVendor.getVendorName());
newPOVendorQuote.setVendorHeaderGeneratedIdentifier(newVendor.getVendorHeaderGeneratedIdentifier());
newPOVendorQuote.setVendorDetailAssignedIdentifier(newVendor.getVendorDetailAssignedIdentifier());
newPOVendorQuote.setDocumentNumber(documentNumber);
boolean foundAddress = false;
for (VendorAddress address : newVendor.getVendorAddresses()) {
if (AddressTypes.QUOTE.equals(address.getVendorAddressTypeCode())) {
newPOVendorQuote.setVendorCityName(address.getVendorCityName());
newPOVendorQuote.setVendorCountryCode(address.getVendorCountryCode());
newPOVendorQuote.setVendorLine1Address(address.getVendorLine1Address());
newPOVendorQuote.setVendorLine2Address(address.getVendorLine2Address());
newPOVendorQuote.setVendorPostalCode(address.getVendorZipCode());
newPOVendorQuote.setVendorStateCode(address.getVendorStateCode());
newPOVendorQuote.setVendorFaxNumber(address.getVendorFaxNumber());
foundAddress = true;
break;
}
}
if (!foundAddress) {
newPOVendorQuote.setVendorCityName(newVendor.getDefaultAddressCity());
newPOVendorQuote.setVendorCountryCode(newVendor.getDefaultAddressCountryCode());
newPOVendorQuote.setVendorLine1Address(newVendor.getDefaultAddressLine1());
newPOVendorQuote.setVendorLine2Address(newVendor.getDefaultAddressLine2());
newPOVendorQuote.setVendorPostalCode(newVendor.getDefaultAddressPostalCode());
newPOVendorQuote.setVendorStateCode(newVendor.getDefaultAddressStateCode());
newPOVendorQuote.setVendorFaxNumber(newVendor.getDefaultFaxNumber());
}
return newPOVendorQuote;
}
/**
* Obtains the defaultAddress of the vendor and setting the default address fields on the vendor.
*
* @param vendor The VendorDetail object whose default address we'll obtain and set the fields.
*/
protected void updateDefaultVendorAddress(VendorDetail vendor) {
VendorAddress defaultAddress = vendorService.getVendorDefaultAddress(vendor.getVendorHeaderGeneratedIdentifier(),vendor.getVendorDetailAssignedIdentifier(), vendor.getVendorHeader().getVendorType().getAddressType().getVendorAddressTypeCode(), "",false);
if (defaultAddress != null) {
if (defaultAddress.getVendorState() != null) {
vendor.setVendorStateForLookup(defaultAddress.getVendorState().getName());
}
vendor.setDefaultAddressLine1(defaultAddress.getVendorLine1Address());
vendor.setDefaultAddressLine2(defaultAddress.getVendorLine2Address());
vendor.setDefaultAddressCity(defaultAddress.getVendorCityName());
vendor.setDefaultAddressPostalCode(defaultAddress.getVendorZipCode());
vendor.setDefaultAddressStateCode(defaultAddress.getVendorStateCode());
vendor.setDefaultAddressInternationalProvince(defaultAddress.getVendorAddressInternationalProvinceName());
vendor.setDefaultAddressCountryCode(defaultAddress.getVendorCountryCode());
vendor.setDefaultFaxNumber(defaultAddress.getVendorFaxNumber());
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#processACMReq(org.kuali.kfs.module.purap.document.ContractManagerAssignmentDocument)
*/
@Override
public void processACMReq(ContractManagerAssignmentDocument acmDoc) {
List<ContractManagerAssignmentDetail> acmDetails = acmDoc.getContractManagerAssignmentDetails();
for (Iterator iter = acmDetails.iterator(); iter.hasNext();) {
ContractManagerAssignmentDetail detail = (ContractManagerAssignmentDetail) iter.next();
if (ObjectUtils.isNotNull(detail.getContractManagerCode())) {
// Get the requisition for this ContractManagerAssignmentDetail.
RequisitionDocument req = requisitionService.getRequisitionById(detail.getRequisitionIdentifier());
if (PurapConstants.RequisitionStatuses.APPDOC_AWAIT_CONTRACT_MANAGER_ASSGN.equals(req.getApplicationDocumentStatus())) {
// only update REQ if code is empty and status is correct
try {
req.updateAndSaveAppDocStatus(PurapConstants.RequisitionStatuses.APPDOC_CLOSED);
}
catch (WorkflowException e) {
throw new RuntimeException("Error saving routing data while saving document with id " + req.getDocumentNumber(), e);
}
purapService.saveDocumentNoValidation(req);
createPurchaseOrderDocument(req, KFSConstants.SYSTEM_USER, detail.getContractManagerCode());
}
}
}// endfor
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#autoCloseFullyDisencumberedOrders()
*/
@Override
public boolean autoCloseFullyDisencumberedOrders() {
LOG.debug("autoCloseFullyDisencumberedOrders() started");
List<AutoClosePurchaseOrderView> autoCloseList = purchaseOrderDao.getAllOpenPurchaseOrders(getExcludedVendorChoiceCodes());
// we need to eliminate the AutoClosePurchaseOrderView whose workflowdocument status is not OPEN..
// KFSMI-7533
List<AutoClosePurchaseOrderView> purchaseOrderAutoCloseList = filterDocumentsForAppDocStatusOpen(autoCloseList);
for (AutoClosePurchaseOrderView poAutoClose : purchaseOrderAutoCloseList) {
if ((poAutoClose.getTotalAmount() != null) && ((KualiDecimal.ZERO.compareTo(poAutoClose.getTotalAmount())) != 0)) {
LOG.info("autoCloseFullyDisencumberedOrders() PO ID " + poAutoClose.getPurapDocumentIdentifier() + " with total " + poAutoClose.getTotalAmount().doubleValue() + " will be closed");
String newStatus = PurapConstants.PurchaseOrderStatuses.APPDOC_PENDING_CLOSE;
String annotation = "This PO was automatically closed in batch.";
String documentType = PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_CLOSE_DOCUMENT;
PurchaseOrderDocument document = getPurchaseOrderByDocumentNumber(poAutoClose.getDocumentNumber());
createNoteForAutoCloseOrders(document, annotation);
createAndRoutePotentialChangeDocument(poAutoClose.getDocumentNumber(), documentType, annotation, null, newStatus);
}
}
LOG.debug("autoCloseFullyDisencumberedOrders() ended");
return true;
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#autoCloseRecurringOrders()
*/
@Override
public boolean autoCloseRecurringOrders() {
LOG.debug("autoCloseRecurringOrders() started");
boolean shouldSendEmail = true;
MailMessage message = new MailMessage();
String parameterEmail = parameterService.getParameterValueAsString(AutoCloseRecurringOrdersStep.class, PurapParameterConstants.AUTO_CLOSE_RECURRING_PO_TO_EMAIL_ADDRESSES);
if (StringUtils.isEmpty(parameterEmail)) {
// Don't stop the show if the email address is wrong, log it and continue.
LOG.error("autoCloseRecurringOrders(): parameterEmail is missing, we'll not send out any emails for this job.");
shouldSendEmail = false;
}
if (shouldSendEmail) {
message = setMessageAddressesAndSubject(message, parameterEmail);
}
StringBuffer emailBody = new StringBuffer();
// There should always be a "AUTO_CLOSE_RECURRING_ORDER_DT"
// row in the table, this method sets it to "mm/dd/yyyy" after processing.
String recurringOrderDateString = parameterService.getParameterValueAsString(AutoCloseRecurringOrdersStep.class, PurapParameterConstants.AUTO_CLOSE_RECURRING_PO_DATE);
boolean validDate = true;
java.util.Date recurringOrderDate = null;
try {
recurringOrderDate = dateTimeService.convertToDate(recurringOrderDateString);
}
catch (ParseException pe) {
validDate = false;
}
if (StringUtils.isEmpty(recurringOrderDateString) || recurringOrderDateString.equalsIgnoreCase("mm/dd/yyyy") || (!validDate)) {
if (recurringOrderDateString.equalsIgnoreCase("mm/dd/yyyy")) {
if (LOG.isDebugEnabled()) {
LOG.debug("autoCloseRecurringOrders(): mm/dd/yyyy " + "was found in the Application Settings table. No orders will be closed, method will end.");
}
if (shouldSendEmail) {
emailBody.append("The AUTO_CLOSE_RECURRING_ORDER_DT found in the Application Settings table " + "was mm/dd/yyyy. No recurring PO's were closed.");
}
}
else {
if (LOG.isDebugEnabled()) {
LOG.debug("autoCloseRecurringOrders(): An invalid autoCloseRecurringOrdersDate " + "was found in the Application Settings table: " + recurringOrderDateString + ". Method will end.");
}
if (shouldSendEmail) {
emailBody.append("An invalid AUTO_CLOSE_RECURRING_ORDER_DT was found in the Application Settings table: " + recurringOrderDateString + ". No recurring PO's were closed.");
}
}
if (shouldSendEmail) {
sendMessage(message, emailBody.toString());
}
LOG.info("autoCloseRecurringOrders() ended");
return false;
}
LOG.info("autoCloseRecurringOrders() The autoCloseRecurringOrdersDate found in the Application Settings table was " + recurringOrderDateString);
if (shouldSendEmail) {
emailBody.append("The autoCloseRecurringOrdersDate found in the Application Settings table was " + recurringOrderDateString + ".");
}
Calendar appSettingsDate = dateTimeService.getCalendar(recurringOrderDate);
Timestamp appSettingsDay = new Timestamp(appSettingsDate.getTime().getTime());
Calendar todayMinusThreeMonths = getTodayMinusThreeMonths();
Timestamp threeMonthsAgo = new Timestamp(todayMinusThreeMonths.getTime().getTime());
if (appSettingsDate.after(todayMinusThreeMonths)) {
LOG.info("autoCloseRecurringOrders() The appSettingsDate: " + appSettingsDay + " is after todayMinusThreeMonths: " + threeMonthsAgo + ". The program will end.");
if (shouldSendEmail) {
emailBody.append("\n\nThe autoCloseRecurringOrdersDate: " + appSettingsDay + " is after todayMinusThreeMonths: " + threeMonthsAgo + ". The program will end.");
sendMessage(message, emailBody.toString());
}
LOG.info("autoCloseRecurringOrders() ended");
return false;
}
List<AutoClosePurchaseOrderView> closeList = purchaseOrderDao.getAutoCloseRecurringPurchaseOrders(getExcludedVendorChoiceCodes());
// we need to eliminate the AutoClosePurchaseOrderView whose workflowdocument status is not OPEN..
// KFSMI-7533
List<AutoClosePurchaseOrderView> purchaseOrderAutoCloseList = filterDocumentsForAppDocStatusOpen(closeList);
LOG.info("autoCloseRecurringOrders(): " + purchaseOrderAutoCloseList.size() + " PO's were returned for processing.");
int counter = 0;
for (AutoClosePurchaseOrderView poAutoClose : purchaseOrderAutoCloseList) {
LOG.info("autoCloseRecurringOrders(): Testing PO ID " + poAutoClose.getPurapDocumentIdentifier() + ". recurringPaymentEndDate: " + poAutoClose.getRecurringPaymentEndDate());
if (poAutoClose.getRecurringPaymentEndDate().before(threeMonthsAgo)) {
String newStatus = PurapConstants.PurchaseOrderStatuses.APPDOC_PENDING_CLOSE;
String annotation = "This recurring PO was automatically closed in batch.";
String documentType = PurapConstants.PurchaseOrderDocTypes.PURCHASE_ORDER_CLOSE_DOCUMENT;
PurchaseOrderDocument document = getPurchaseOrderByDocumentNumber(poAutoClose.getDocumentNumber());
boolean rulePassed = kualiRuleService.applyRules(new AttributedRouteDocumentEvent("", document));
boolean success = true;
if (success) {
++counter;
if (counter == 1) {
emailBody.append("\n\nThe following recurring Purchase Orders will be closed by auto close recurring batch job \n");
}
LOG.info("autoCloseRecurringOrders() PO ID " + poAutoClose.getPurapDocumentIdentifier() + " will be closed.");
createNoteForAutoCloseOrders(document, annotation);
createAndRoutePotentialChangeDocument(poAutoClose.getDocumentNumber(), documentType, annotation, null, newStatus);
if (shouldSendEmail) {
emailBody.append("\n\n" + counter + " PO ID: " + poAutoClose.getPurapDocumentIdentifier() + ", End Date: " + poAutoClose.getRecurringPaymentEndDate() + ", Status: " + poAutoClose.getApplicationDocumentStatus() + ", VendorChoice: " + poAutoClose.getVendorChoiceCode() + ", RecurringPaymentType: " + poAutoClose.getRecurringPaymentTypeCode());
}
}
else {
// If it was unsuccessful, we have to clear the error map in the GlobalVariables so that the previous
// error would not still be lingering around and the next PO in the list can be validated.
GlobalVariables.getMessageMap().clearErrorMessages();
}
}
}
if (counter == 0) {
LOG.info("\n\nNo recurring PO's fit the conditions for closing.");
if (shouldSendEmail) {
emailBody.append("\n\nNo recurring PO's fit the conditions for closing.");
}
}
if (shouldSendEmail) {
sendMessage(message, emailBody.toString());
}
resetAutoCloseRecurringOrderDateParameter();
LOG.debug("autoCloseRecurringOrders() ended");
return true;
}
/**
* Filter out the auto close purchase order view documents for the appDocStatus with status open For each document in the list,
* check if there is workflowdocument whose appdocstatus is open add add to the return list.
*
* @param List<AutoClosePurchaseOrderView>
* @param appDocStatus
* @return filteredAutoClosePOView filtered auto close po view documents where appdocstatus is open
*/
protected List<AutoClosePurchaseOrderView> filterDocumentsForAppDocStatusOpen(List<AutoClosePurchaseOrderView> autoClosePurchaseOrderViews) {
List<AutoClosePurchaseOrderView> filteredAutoClosePOView = new ArrayList<AutoClosePurchaseOrderView>();
for (AutoClosePurchaseOrderView autoClosePurchaseOrderView : autoClosePurchaseOrderViews) {
Document document = findDocument(autoClosePurchaseOrderView.getDocumentNumber());
if (document != null) {
if (PurapConstants.PurchaseOrderStatuses.APPDOC_OPEN.equalsIgnoreCase(document.getDocumentHeader().getWorkflowDocument().getApplicationDocumentStatus())) {
// found the matched Awaiting Contract Manager Assignment status, retrieve the routeHeaderId and add to the list
filteredAutoClosePOView.add(autoClosePurchaseOrderView);
}
}
}
return filteredAutoClosePOView;
}
/**
* This method finds the document for the given document header id
*
* @param documentHeaderId
* @return document The document in the workflow that matches the document header id.
*/
protected Document findDocument(String documentHeaderId) {
Document document = null;
try {
document = documentService.getByDocumentHeaderId(documentHeaderId);
}
catch (WorkflowException ex) {
LOG.error("Exception encountered on finding the document: " + documentHeaderId, ex);
}
catch (UnknownDocumentTypeException ex) {
// don't blow up just because a document type is not installed (but don't return it either)
LOG.error("Exception encountered on finding the document: " + documentHeaderId, ex);
}
return document;
}
/**
* Creates and returns a Calendar object of today minus three months.
*
* @return Calendar object of today minus three months.
*/
protected Calendar getTodayMinusThreeMonths() {
Calendar todayMinusThreeMonths = Calendar.getInstance(); // Set to today.
todayMinusThreeMonths.add(Calendar.MONTH, -3); // Back up 3 months.
todayMinusThreeMonths.set(Calendar.HOUR, 12);
todayMinusThreeMonths.set(Calendar.MINUTE, 0);
todayMinusThreeMonths.set(Calendar.SECOND, 0);
todayMinusThreeMonths.set(Calendar.MILLISECOND, 0);
todayMinusThreeMonths.set(Calendar.AM_PM, Calendar.AM);
return todayMinusThreeMonths;
}
/**
* Sets the to addresses, from address and the subject of the email.
*
* @param message The MailMessage object of the email to be sent.
* @param parameterEmail The String of email addresses with delimiters of ";" obtained from the system parameter.
* @return The MailMessage object after the to addresses, from address and the subject have been set.
*/
protected MailMessage setMessageAddressesAndSubject(MailMessage message, String parameterEmail) {
String toAddressList[] = parameterEmail.split(";");
if (toAddressList.length > 0) {
for (int i = 0; i < toAddressList.length; i++) {
if (toAddressList[i] != null) {
message.addToAddress(toAddressList[i].trim());
}
}
}
message.setFromAddress(toAddressList[0]);
message.setSubject("Auto Close Recurring Purchase Orders");
return message;
}
/**
* Sends the email by calling the sendMessage method in mailService and log error if exception occurs during the attempt to send
* the message.
*
* @param message The MailMessage object containing information to be sent.
* @param emailBody The String containing the body of the email to be sent.
*/
protected void sendMessage(MailMessage message, String emailBody) {
message.setMessage(emailBody);
try {
mailService.sendMessage(message);
}
catch (Exception e) {
// Don't stop the show if the email has problem, log it and continue.
LOG.error("autoCloseRecurringOrders(): email problem. Message not sent.", e);
}
}
/**
* Resets the AUTO_CLOSE_RECURRING_ORDER_DT system parameter to "mm/dd/yyyy".
*/
protected void resetAutoCloseRecurringOrderDateParameter() {
Parameter autoCloseRecurringPODate = parameterService.getParameter(AutoCloseRecurringOrdersStep.class, PurapParameterConstants.AUTO_CLOSE_RECURRING_PO_DATE);
if (autoCloseRecurringPODate != null) {
Parameter.Builder updatedParameter = Parameter.Builder.create(autoCloseRecurringPODate);
updatedParameter.setValue("mm/dd/yyyy");
parameterService.updateParameter(updatedParameter.build());
}
}
/**
* Gets a List of excluded vendor choice codes from PurapConstants.
*
* @return a List of excluded vendor choice codes
*/
protected List<String> getExcludedVendorChoiceCodes() {
List<String> excludedVendorChoiceCodes = new ArrayList<String>();
for (int i = 0; i < PurapConstants.AUTO_CLOSE_EXCLUSION_VNDR_CHOICE_CODES.length; i++) {
String excludedCode = PurapConstants.AUTO_CLOSE_EXCLUSION_VNDR_CHOICE_CODES[i];
excludedVendorChoiceCodes.add(excludedCode);
}
return excludedVendorChoiceCodes;
}
/**
* Creates and add a note to the purchase order document using the annotation String in the input parameter. This method is used
* by the autoCloseRecurringOrders() and autoCloseFullyDisencumberedOrders to add a note to the purchase order to indicate that
* the purchase order was closed by the batch job.
*
* @param purchaseOrderDocument The purchase order document that is being closed by the batch job.
* @param annotation The string to appear on the note to be attached to the purchase order.
*/
protected void createNoteForAutoCloseOrders(PurchaseOrderDocument purchaseOrderDocument, String annotation) {
try {
Note noteObj = documentService.createNoteFromDocument(purchaseOrderDocument, annotation);
// documentService.addNoteToDocument(purchaseOrderDocument, noteObj);
noteService.save(noteObj);
}
catch (Exception e) {
String errorMessage = "Error creating and saving close note for purchase order with document service";
LOG.error("createNoteForAutoCloseRecurringOrders " + errorMessage, e);
throw new RuntimeException(errorMessage, e);
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#retrieveCapitalAssetItemsForIndividual(java.lang.Integer)
*/
@Override
public List<PurchasingCapitalAssetItem> retrieveCapitalAssetItemsForIndividual(Integer poId) {
PurchaseOrderDocument po = getCurrentPurchaseOrder(poId);
if (ObjectUtils.isNotNull(po)) {
return po.getPurchasingCapitalAssetItems();
}
return null;
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#retrieveCapitalAssetSystemForOneSystem(java.lang.Integer)
*/
@Override
public CapitalAssetSystem retrieveCapitalAssetSystemForOneSystem(Integer poId) {
PurchaseOrderDocument po = getCurrentPurchaseOrder(poId);
if (ObjectUtils.isNotNull(po)) {
List<CapitalAssetSystem> systems = po.getPurchasingCapitalAssetSystems();
if (ObjectUtils.isNotNull(systems)) {
// for one system, there should only ever be one system
return systems.get(0);
}
}
return null;
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurchaseOrderService#retrieveCapitalAssetSystemsForMultipleSystem(java.lang.Integer)
*/
@Override
public List<CapitalAssetSystem> retrieveCapitalAssetSystemsForMultipleSystem(Integer poId) {
PurchaseOrderDocument po = getCurrentPurchaseOrder(poId);
if (ObjectUtils.isNotNull(po)) {
return po.getPurchasingCapitalAssetSystems();
}
return null;
}
/**
* This method fixes the item references in this document
*/
protected void fixItemReferences(PurchaseOrderDocument po) {
// fix item and account references in case this is a new doc (since they will be lost)
for (PurApItem item : (List<PurApItem>) po.getItems()) {
item.setPurapDocument(po);
item.fixAccountReferences();
}
}
@Override
public List getPendingPurchaseOrderFaxes() {
List<PurchaseOrderDocument> purchaseOrderList = purchaseOrderDao.getPendingPurchaseOrdersForFaxing();
return filterPurchaseOrderDocumentByAppDocStatus(purchaseOrderList, PurapConstants.PurchaseOrderStatuses.APPDOC_PENDING_FAX);
}
/**
* This method queries financialSystemDocumentHeader and filter payment requests against the provided status.
*
* @param paymentRequestDocuments
* @param appDocStatus
* @return
*/
protected List<PurchaseOrderDocument> filterPurchaseOrderDocumentByAppDocStatus(Collection<PurchaseOrderDocument> purchaseOrderDocuments, String... appDocStatus) {
List<String> appDocStatusList = Arrays.asList(appDocStatus);
List<PurchaseOrderDocument> filteredPaymentRequestDocuments = new ArrayList<PurchaseOrderDocument>();
// add to filtered collection if the app doc list contains payment request's application document status.
for (PurchaseOrderDocument po : purchaseOrderDocuments) {
if(appDocStatusList.contains(po.getApplicationDocumentStatus())) {
filteredPaymentRequestDocuments.add(po);
}
}
return filteredPaymentRequestDocuments;
}
/**
* Query appDocStatus using financialSystemDocumentService and filter against the provided status list
*
* @param lookupDocNumbers
* @param appDocStatus
* @return List<String> purchaseOrderDocumentNumbers
*/
@Deprecated
protected List<String> filterPurchaseOrderDocumentNumbersByAppDocStatus(List<String> lookupDocNumbers, String... appDocStatus) {
List<String> purchaseOrderDocNumbers = new ArrayList<String>();
List<String> appDocStatusList = Arrays.asList(appDocStatus);
for (String docNumber : lookupDocNumbers) {
if(appDocStatusList.contains(financialSystemDocumentService.findByDocumentNumber(docNumber).getApplicationDocumentStatus())) {
purchaseOrderDocNumbers.add(docNumber);
}
}
return purchaseOrderDocNumbers;
}
@Override
public String getPurchaseOrderAppDocStatus(Integer poId) {
// TODO: This could be kind of expensive for one field
PurchaseOrderDocument po = getCurrentPurchaseOrder(poId);
if (ObjectUtils.isNotNull(po)) {
return po.getApplicationDocumentStatus();
}
return null;
}
/**
* helper method to take the po and save it using businessObjectService so that only the po related data is saved since most
* often we are only updating the flags on the document. It will then reindex the document.
*
* @param po
*/
protected void savePurchaseOrderData(PurchaseOrderDocument po) {
// saving old PO using the business object service because the documentService saveDocument
// will try to save the notes again and will cause ojb lock exception.
// since only values that is changed on PO is pendingActionIndicator, save on businessObjectService is used
// KFSMI-9741
businessObjectService.save(po);
// reindex the document so that the app doc status gets updated in the results for the PO lookups.
final DocumentAttributeIndexingQueue documentAttributeIndexingQueue = KewApiServiceLocator.getDocumentAttributeIndexingQueue();
documentAttributeIndexingQueue.indexDocument(po.getDocumentNumber());
}
/**
* @return Returns the personService.
*/
protected PersonService getPersonService() {
if (personService == null) {
personService = SpringContext.getBean(PersonService.class);
}
return personService;
}
public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
this.dataDictionaryService = dataDictionaryService;
}
public void setB2bPurchaseOrderService(B2BPurchaseOrderService purchaseOrderService) {
this.b2bPurchaseOrderService = purchaseOrderService;
}
public void setBusinessObjectService(BusinessObjectService boService) {
this.businessObjectService = boService;
}
public void setDateTimeService(DateTimeService dateTimeService) {
this.dateTimeService = dateTimeService;
}
public void setDocumentService(DocumentService documentService) {
this.documentService = documentService;
}
public void setNoteService(NoteService noteService) {
this.noteService = noteService;
}
public void setPurapService(PurapService purapService) {
this.purapService = purapService;
}
public void setPrintService(PrintService printService) {
this.printService = printService;
}
public void setPurchaseOrderDao(PurchaseOrderDao purchaseOrderDao) {
this.purchaseOrderDao = purchaseOrderDao;
}
public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) {
this.workflowDocumentService = workflowDocumentService;
}
public void setConfigurationService(ConfigurationService kualiConfigurationService) {
this.kualiConfigurationService = kualiConfigurationService;
}
public void setKualiRuleService(KualiRuleService kualiRuleService) {
this.kualiRuleService = kualiRuleService;
}
public void setVendorService(VendorService vendorService) {
this.vendorService = vendorService;
}
public void setRequisitionService(RequisitionService requisitionService) {
this.requisitionService = requisitionService;
}
public void setPurapWorkflowIntegrationService(PurApWorkflowIntegrationService purapWorkflowIntegrationService) {
this.purapWorkflowIntegrationService = purapWorkflowIntegrationService;
}
public void setMaintenanceDocumentService(MaintenanceDocumentService maintenanceDocumentService) {
this.maintenanceDocumentService = maintenanceDocumentService;
}
public void setParameterService(ParameterService parameterService) {
this.parameterService = parameterService;
}
public void setMailService(MailService mailService) {
this.mailService = mailService;
}
public void setFinancialSystemDocumentService(FinancialSystemDocumentService financialSystemDocumentService) {
this.financialSystemDocumentService = financialSystemDocumentService;
}
}