/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.module.purap.document.service.impl; import java.math.BigDecimal; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; 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.joda.time.DateTime; import org.kuali.kfs.module.purap.PurapConstants; import org.kuali.kfs.module.purap.PurapConstants.PurchaseOrderDocTypes; import org.kuali.kfs.module.purap.PurapConstants.PurchaseOrderStatuses; import org.kuali.kfs.module.purap.PurapKeyConstants; import org.kuali.kfs.module.purap.PurapParameterConstants; import org.kuali.kfs.module.purap.businessobject.CorrectionReceivingItem; import org.kuali.kfs.module.purap.businessobject.ItemType; import org.kuali.kfs.module.purap.businessobject.LineItemReceivingItem; import org.kuali.kfs.module.purap.businessobject.LineItemReceivingView; import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine; import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem; import org.kuali.kfs.module.purap.businessobject.ReceivingItem; import org.kuali.kfs.module.purap.document.CorrectionReceivingDocument; import org.kuali.kfs.module.purap.document.LineItemReceivingDocument; import org.kuali.kfs.module.purap.document.PurchaseOrderAmendmentDocument; import org.kuali.kfs.module.purap.document.PurchaseOrderDocument; import org.kuali.kfs.module.purap.document.ReceivingDocument; import org.kuali.kfs.module.purap.document.dataaccess.ReceivingDao; import org.kuali.kfs.module.purap.document.service.LogicContainer; import org.kuali.kfs.module.purap.document.service.PurapService; import org.kuali.kfs.module.purap.document.service.PurchaseOrderService; import org.kuali.kfs.module.purap.document.service.ReceivingService; import org.kuali.kfs.module.purap.document.validation.event.AttributedContinuePurapEvent; import org.kuali.kfs.sys.KFSConstants; import org.kuali.kfs.sys.context.SpringContext; import org.kuali.kfs.sys.document.service.FinancialSystemDocumentService; import org.kuali.rice.core.api.config.property.ConfigurationService; import org.kuali.rice.core.api.util.type.KualiDecimal; import org.kuali.rice.coreservice.framework.parameter.ParameterService; import org.kuali.rice.kew.api.WorkflowDocument; import org.kuali.rice.kew.api.exception.WorkflowException; import org.kuali.rice.krad.bo.AdHocRoutePerson; import org.kuali.rice.krad.bo.Note; import org.kuali.rice.krad.document.Document; import org.kuali.rice.krad.exception.InfrastructureException; import org.kuali.rice.krad.service.DocumentService; import org.kuali.rice.krad.service.NoteService; import org.kuali.rice.krad.util.GlobalVariables; import org.kuali.rice.krad.util.ObjectUtils; import org.kuali.rice.krad.workflow.service.WorkflowDocumentService; import org.springframework.transaction.annotation.Transactional; @Transactional public class ReceivingServiceImpl implements ReceivingService { private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ReceivingServiceImpl.class); protected PurchaseOrderService purchaseOrderService; protected ReceivingDao receivingDao; protected DocumentService documentService; protected WorkflowDocumentService workflowDocumentService; protected ConfigurationService configurationService; protected PurapService purapService; protected NoteService noteService; protected FinancialSystemDocumentService financialSystemDocumentService; public void setPurchaseOrderService(PurchaseOrderService purchaseOrderService) { this.purchaseOrderService = purchaseOrderService; } public void setReceivingDao(ReceivingDao receivingDao) { this.receivingDao = receivingDao; } public void setDocumentService(DocumentService documentService){ this.documentService = documentService; } public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService){ this.workflowDocumentService = workflowDocumentService; } public void setConfigurationService(ConfigurationService configurationService) { this.configurationService = configurationService; } public void setPurapService(PurapService purapService) { this.purapService = purapService; } public void setNoteService(NoteService noteService) { this.noteService = noteService; } public void setFinancialSystemDocumentService(FinancialSystemDocumentService financialSystemDocumentService) { this.financialSystemDocumentService = financialSystemDocumentService; } /** * * @see org.kuali.kfs.module.purap.document.service.ReceivingService#populateReceivingLineFromPurchaseOrder(org.kuali.kfs.module.purap.document.LineItemReceivingDocument) */ @Override public void populateReceivingLineFromPurchaseOrder(LineItemReceivingDocument rlDoc) { if(rlDoc == null){ rlDoc = new LineItemReceivingDocument(); } //retrieve po by doc id PurchaseOrderDocument poDoc = null; poDoc = purchaseOrderService.getCurrentPurchaseOrder(rlDoc.getPurchaseOrderIdentifier()); if(poDoc != null){ rlDoc.populateReceivingLineFromPurchaseOrder(poDoc); } } @Override public void populateCorrectionReceivingFromReceivingLine(CorrectionReceivingDocument rcDoc) { if(rcDoc == null){ rcDoc = new CorrectionReceivingDocument(); } //retrieve receiving line by doc id LineItemReceivingDocument rlDoc = rcDoc.getLineItemReceivingDocument(); if(rlDoc != null){ rcDoc.populateCorrectionReceivingFromReceivingLine(rlDoc); } } /** * * @see org.kuali.kfs.module.purap.document.service.ReceivingService#populateAndSaveLineItemReceivingDocument(org.kuali.kfs.module.purap.document.LineItemReceivingDocument) */ @Override public void populateAndSaveLineItemReceivingDocument(LineItemReceivingDocument rlDoc) throws WorkflowException { try { documentService.saveDocument(rlDoc, AttributedContinuePurapEvent.class); } catch (WorkflowException we) { String errorMsg = "Error saving document # " + rlDoc.getDocumentHeader().getDocumentNumber() + " " + we.getMessage(); //LOG.error(errorMsg, we); throw new RuntimeException(errorMsg, we); } } /** * @see org.kuali.kfs.module.purap.document.service.ReceivingService#populateCorrectionReceivingDocument(org.kuali.kfs.module.purap.document.CorrectionReceivingDocument) */ @Override public void populateCorrectionReceivingDocument(CorrectionReceivingDocument rcDoc) { populateCorrectionReceivingFromReceivingLine(rcDoc); } /** * * @see org.kuali.kfs.module.purap.document.service.ReceivingService#canCreateLineItemReceivingDocument(java.lang.Integer, java.lang.String) */ @Override public boolean canCreateLineItemReceivingDocument(Integer poId, String receivingDocumentNumber) throws RuntimeException { PurchaseOrderDocument po = purchaseOrderService.getCurrentPurchaseOrder(poId); return canCreateLineItemReceivingDocument(po, receivingDocumentNumber); } /** * * @see org.kuali.kfs.module.purap.document.service.ReceivingService#canCreateLineItemReceivingDocument(org.kuali.kfs.module.purap.document.PurchaseOrderDocument) */ @Override public boolean canCreateLineItemReceivingDocument(PurchaseOrderDocument po) throws RuntimeException { return canCreateLineItemReceivingDocument(po, null); } protected boolean canCreateLineItemReceivingDocument(PurchaseOrderDocument po, String receivingDocumentNumber) { boolean canCreate = false; if (isPurchaseOrderValidForLineItemReceivingDocumentCreation(po) && !isLineItemReceivingDocumentInProcessForPurchaseOrder(po.getPurapDocumentIdentifier(), receivingDocumentNumber) && !isCorrectionReceivingDocumentInProcessForPurchaseOrder(po.getPurapDocumentIdentifier(), null)) { canCreate = true; } return canCreate; } @Override public boolean isPurchaseOrderActiveForLineItemReceivingDocumentCreation(Integer poId){ PurchaseOrderDocument po = purchaseOrderService.getCurrentPurchaseOrder(poId); return isPurchaseOrderValidForLineItemReceivingDocumentCreation(po); } protected boolean isPurchaseOrderValidForLineItemReceivingDocumentCreation(PurchaseOrderDocument po){ return po != null && ObjectUtils.isNotNull(po.getPurapDocumentIdentifier()) && po.isPurchaseOrderCurrentIndicator() && (PurchaseOrderStatuses.APPDOC_OPEN.equals(po.getApplicationDocumentStatus()) || PurchaseOrderStatuses.APPDOC_CLOSED.equals(po.getApplicationDocumentStatus()) || PurchaseOrderStatuses.APPDOC_PAYMENT_HOLD.equals(po.getApplicationDocumentStatus())); } @Override public boolean canCreateCorrectionReceivingDocument(LineItemReceivingDocument rl) throws RuntimeException { return canCreateCorrectionReceivingDocument(rl, null); } @Override public boolean canCreateCorrectionReceivingDocument(LineItemReceivingDocument rl, String receivingCorrectionDocNumber) throws RuntimeException { boolean canCreate = false; WorkflowDocument workflowDocument = null; try{ workflowDocument = workflowDocumentService.loadWorkflowDocument(rl.getDocumentNumber(), GlobalVariables.getUserSession().getPerson()); }catch(WorkflowException we){ throw new RuntimeException(we); } if( workflowDocument.isFinal() && !isCorrectionReceivingDocumentInProcessForReceivingLine(rl.getDocumentNumber(), receivingCorrectionDocNumber)){ canCreate = true; } return canCreate; } protected boolean isLineItemReceivingDocumentInProcessForPurchaseOrder(Integer poId, String receivingDocumentNumber) throws RuntimeException{ return !getLineItemReceivingDocumentNumbersInProcessForPurchaseOrder(poId, receivingDocumentNumber).isEmpty(); } @Override public List<String> getLineItemReceivingDocumentNumbersInProcessForPurchaseOrder(Integer poId, String receivingDocumentNumber){ List<String> inProcessDocNumbers = new ArrayList<String>(); List<String> docNumbers = receivingDao.getDocumentNumbersByPurchaseOrderId(poId); WorkflowDocument workflowDocument = null; for (String docNumber : docNumbers) { try{ workflowDocument = workflowDocumentService.loadWorkflowDocument(docNumber, GlobalVariables.getUserSession().getPerson()); }catch(WorkflowException we){ throw new RuntimeException(we); } if(!(workflowDocument.isCanceled() || workflowDocument.isException() || workflowDocument.isFinal()) && docNumber.equals(receivingDocumentNumber) == false ){ inProcessDocNumbers.add(docNumber); } } return inProcessDocNumbers; } @Override public List<LineItemReceivingDocument> getLineItemReceivingDocumentsInFinalForPurchaseOrder(Integer poId) { List<String> finalDocNumbers = new ArrayList<String>(); List<String> docNumbers = receivingDao.getDocumentNumbersByPurchaseOrderId(poId); WorkflowDocument workflowDocument = null; for (String docNumber : docNumbers) { try { workflowDocument = workflowDocumentService.loadWorkflowDocument(docNumber, GlobalVariables.getUserSession().getPerson()); } catch (WorkflowException we) { throw new RuntimeException(we); } if (workflowDocument.isFinal()) { finalDocNumbers.add(docNumber); } } if (finalDocNumbers.size() > 0) { try { List<LineItemReceivingDocument> docs = new ArrayList<LineItemReceivingDocument>(); for ( Document doc : documentService.getDocumentsByListOfDocumentHeaderIds(LineItemReceivingDocument.class, finalDocNumbers) ) { docs.add( (LineItemReceivingDocument) doc ); } return docs; } catch (WorkflowException e) { throw new InfrastructureException("unable to retrieve LineItemReceivingDocuments", e); } } else { return null; } } protected boolean isCorrectionReceivingDocumentInProcessForPurchaseOrder(Integer poId, String receivingDocumentNumber) throws RuntimeException{ return !getCorrectionReceivingDocumentNumbersInProcessForPurchaseOrder(poId, receivingDocumentNumber).isEmpty(); } @Override public List<String> getCorrectionReceivingDocumentNumbersInProcessForPurchaseOrder(Integer poId, String receivingDocumentNumber){ boolean isInProcess = false; List<String> inProcessDocNumbers = new ArrayList<String>(); List<String> docNumbers = receivingDao.getCorrectionReceivingDocumentNumbersByPurchaseOrderId(poId); WorkflowDocument workflowDocument = null; for (String docNumber : docNumbers) { try{ workflowDocument = workflowDocumentService.loadWorkflowDocument(docNumber, GlobalVariables.getUserSession().getPerson()); }catch(WorkflowException we){ throw new RuntimeException(we); } if(!(workflowDocument.isCanceled() || workflowDocument.isException() || workflowDocument.isFinal()) && docNumber.equals(receivingDocumentNumber) == false ){ inProcessDocNumbers.add(docNumber); } } return inProcessDocNumbers; } protected boolean isCorrectionReceivingDocumentInProcessForReceivingLine(String receivingDocumentNumber, String receivingCorrectionDocNumber) throws RuntimeException{ boolean isInProcess = false; List<String> docNumbers = receivingDao.getCorrectionReceivingDocumentNumbersByReceivingLineNumber(receivingDocumentNumber); WorkflowDocument workflowDocument = null; for (String docNumber : docNumbers) { try{ workflowDocument = workflowDocumentService.loadWorkflowDocument(docNumber, GlobalVariables.getUserSession().getPerson()); }catch(WorkflowException we){ throw new RuntimeException(we); } if(!(workflowDocument.isCanceled() || workflowDocument.isException() || workflowDocument.isFinal()) && docNumber.equals(receivingCorrectionDocNumber) == false ){ isInProcess = true; break; } } return isInProcess; } /** * * @see org.kuali.kfs.module.purap.document.service.ReceivingService#receivingLineDuplicateMessages(org.kuali.kfs.module.purap.document.LineItemReceivingDocument) */ @Override public HashMap<String, String> receivingLineDuplicateMessages(LineItemReceivingDocument rlDoc) { HashMap<String, String> msgs; msgs = new HashMap<String, String>(); Integer poId = rlDoc.getPurchaseOrderIdentifier(); StringBuffer currentMessage = new StringBuffer(""); List<String> docNumbers = null; //check vendor date for duplicates if( rlDoc.getShipmentReceivedDate() != null ){ docNumbers = receivingDao.duplicateVendorDate(poId, rlDoc.getShipmentReceivedDate()); if( hasDuplicateEntry(docNumbers) ){ appendDuplicateMessage(currentMessage, PurapKeyConstants.MESSAGE_DUPLICATE_RECEIVING_LINE_VENDOR_DATE, rlDoc.getPurchaseOrderIdentifier()); } } //check packing slip number for duplicates if( !StringUtils.isEmpty(rlDoc.getShipmentPackingSlipNumber()) ){ docNumbers = receivingDao.duplicatePackingSlipNumber(poId, rlDoc.getShipmentPackingSlipNumber()); if( hasDuplicateEntry(docNumbers) ){ appendDuplicateMessage(currentMessage, PurapKeyConstants.MESSAGE_DUPLICATE_RECEIVING_LINE_PACKING_SLIP_NUMBER, rlDoc.getPurchaseOrderIdentifier()); } } //check bill of lading number for duplicates if( !StringUtils.isEmpty(rlDoc.getShipmentBillOfLadingNumber()) ){ docNumbers = receivingDao.duplicateBillOfLadingNumber(poId, rlDoc.getShipmentBillOfLadingNumber()); if( hasDuplicateEntry(docNumbers) ){ appendDuplicateMessage(currentMessage, PurapKeyConstants.MESSAGE_DUPLICATE_RECEIVING_LINE_BILL_OF_LADING_NUMBER, rlDoc.getPurchaseOrderIdentifier()); } } //add message if one exists if(currentMessage.length() > 0){ //add suffix appendDuplicateMessage(currentMessage, PurapKeyConstants.MESSAGE_DUPLICATE_RECEIVING_LINE_SUFFIX, rlDoc.getPurchaseOrderIdentifier() ); //add msg to map msgs.put(PurapConstants.LineItemReceivingDocumentStrings.DUPLICATE_RECEIVING_LINE_QUESTION, currentMessage.toString()); } return msgs; } /** * Looks at a list of doc numbers, but only considers an entry duplicate * if the document is in a Final status. * * @param docNumbers * @return */ protected boolean hasDuplicateEntry(List<String> docNumbers){ boolean isDuplicate = false; WorkflowDocument workflowDocument = null; for (String docNumber : docNumbers) { try{ workflowDocument = workflowDocumentService.loadWorkflowDocument(docNumber, GlobalVariables.getUserSession().getPerson()); }catch(WorkflowException we){ throw new RuntimeException(we); } //if the doc number exists, and is in final status, consider this a dupe and return if(workflowDocument.isFinal()){ isDuplicate = true; break; } } return isDuplicate; } protected void appendDuplicateMessage(StringBuffer currentMessage, String duplicateMessageKey, Integer poId){ //append prefix if this is first call if(currentMessage.length() == 0){ String messageText = configurationService.getPropertyValueAsString(PurapKeyConstants.MESSAGE_DUPLICATE_RECEIVING_LINE_PREFIX); String prefix = MessageFormat.format(messageText, poId.toString() ); currentMessage.append(prefix); } //append message currentMessage.append( configurationService.getPropertyValueAsString(duplicateMessageKey) ); } @Override public void completeCorrectionReceivingDocument(ReceivingDocument correctionDocument){ ReceivingDocument receivingDoc = ((CorrectionReceivingDocument)correctionDocument).getLineItemReceivingDocument(); for (CorrectionReceivingItem correctionItem : (List<CorrectionReceivingItem>)correctionDocument.getItems()) { if(!StringUtils.equalsIgnoreCase(correctionItem.getItemType().getItemTypeCode(),PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE)) { LineItemReceivingItem recItem = (LineItemReceivingItem) receivingDoc.getItem(correctionItem.getItemLineNumber().intValue() - 1); List<PurchaseOrderItem> purchaseOrderItems = receivingDoc.getPurchaseOrderDocument().getItems(); PurchaseOrderItem poItem = purchaseOrderItems.get(correctionItem.getItemLineNumber().intValue() - 1); if(ObjectUtils.isNotNull(recItem)) { recItem.setItemReceivedTotalQuantity(correctionItem.getItemReceivedTotalQuantity()); recItem.setItemReturnedTotalQuantity(correctionItem.getItemReturnedTotalQuantity()); recItem.setItemDamagedTotalQuantity(correctionItem.getItemDamagedTotalQuantity()); } } } } /** * * This method deletes unneeded items and updates the totals on the po and does any additional processing based on items i.e. FYI etc * @param receivingDocument receiving document */ @Override public void completeReceivingDocument(ReceivingDocument receivingDocument) { PurchaseOrderDocument poDoc = null; if (receivingDocument instanceof LineItemReceivingDocument){ // delete unentered items purapService.deleteUnenteredItems(receivingDocument); poDoc = receivingDocument.getPurchaseOrderDocument(); }else if (receivingDocument instanceof CorrectionReceivingDocument){ CorrectionReceivingDocument correctionDocument = (CorrectionReceivingDocument)receivingDocument; poDoc = purchaseOrderService.getCurrentPurchaseOrder(correctionDocument.getLineItemReceivingDocument().getPurchaseOrderIdentifier()); } updateReceivingTotalsOnPurchaseOrder(receivingDocument, poDoc); //TODO: custom doc specific service hook here for correction to do it's receiving doc update purapService.saveDocumentNoValidation(poDoc); sendFyiForItems(receivingDocument); spawnPoAmendmentForUnorderedItems(receivingDocument, poDoc); purapService.saveDocumentNoValidation(receivingDocument); } @Override public void createNoteForReturnedAndDamagedItems(ReceivingDocument recDoc){ for (ReceivingItem item : (List<ReceivingItem>)recDoc.getItems()){ if(!StringUtils.equalsIgnoreCase(item.getItemType().getItemTypeCode(),PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE)) { if (item.getItemReturnedTotalQuantity() != null && item.getItemReturnedTotalQuantity().isGreaterThan(KualiDecimal.ZERO)){ try{ String noteString = SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(PurapKeyConstants.MESSAGE_RECEIVING_LINEITEM_RETURN_NOTE_TEXT); noteString = item.getItemReturnedTotalQuantity().intValue() + " " + noteString + " " + item.getItemLineNumber(); addNoteToReceivingDocument(recDoc, noteString); }catch (Exception e){ String errorMsg = "Note Service Exception caught: " + e.getLocalizedMessage(); throw new RuntimeException(errorMsg, e); } } if (item.getItemDamagedTotalQuantity() != null && item.getItemDamagedTotalQuantity().isGreaterThan(KualiDecimal.ZERO)){ try{ String noteString = SpringContext.getBean(ConfigurationService.class).getPropertyValueAsString(PurapKeyConstants.MESSAGE_RECEIVING_LINEITEM_DAMAGE_NOTE_TEXT); noteString = item.getItemDamagedTotalQuantity().intValue() + " " + noteString + " " + item.getItemLineNumber(); addNoteToReceivingDocument(recDoc, noteString); }catch (Exception e){ String errorMsg = "Note Service Exception caught: " + e.getLocalizedMessage(); throw new RuntimeException(errorMsg, e); } } } } } protected void updateReceivingTotalsOnPurchaseOrder(ReceivingDocument receivingDocument, PurchaseOrderDocument poDoc) { for (ReceivingItem receivingItem : (List<ReceivingItem>)receivingDocument.getItems()) { ItemType itemType = receivingItem.getItemType(); if(!StringUtils.equalsIgnoreCase(itemType.getItemTypeCode(),PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE)) { //TODO: Chris - this method of getting the line out of po should be turned into a method that can get an item based on a combo or itemType and line PurchaseOrderItem poItem = (PurchaseOrderItem)poDoc.getItemByLineNumber(receivingItem.getItemLineNumber()); if(ObjectUtils.isNotNull(poItem)) { KualiDecimal poItemReceivedTotal = poItem.getItemReceivedTotalQuantity(); KualiDecimal receivingItemReceivedOriginal = receivingItem.getItemOriginalReceivedTotalQuantity(); /** * FIXME: It's coming as null although we set the default value in the LineItemReceivingItem constructor - mpv */ if (ObjectUtils.isNull(receivingItemReceivedOriginal)){ receivingItemReceivedOriginal = KualiDecimal.ZERO; } KualiDecimal receivingItemReceived = receivingItem.getItemReceivedTotalQuantity(); KualiDecimal receivingItemTotalReceivedAdjested = receivingItemReceived.subtract(receivingItemReceivedOriginal); if (ObjectUtils.isNull(poItemReceivedTotal)){ poItemReceivedTotal = KualiDecimal.ZERO; } KualiDecimal poItemReceivedTotalAdjusted = poItemReceivedTotal.add(receivingItemTotalReceivedAdjested); KualiDecimal receivingItemReturnedOriginal = receivingItem.getItemOriginalReturnedTotalQuantity(); if (ObjectUtils.isNull(receivingItemReturnedOriginal)){ receivingItemReturnedOriginal = KualiDecimal.ZERO; } KualiDecimal receivingItemReturned = receivingItem.getItemReturnedTotalQuantity(); if (ObjectUtils.isNull(receivingItemReturned)){ receivingItemReturned = KualiDecimal.ZERO; } KualiDecimal receivingItemTotalReturnedAdjusted = receivingItemReturned.subtract(receivingItemReturnedOriginal); poItemReceivedTotalAdjusted = poItemReceivedTotalAdjusted.subtract(receivingItemTotalReturnedAdjusted); poItem.setItemReceivedTotalQuantity(poItemReceivedTotalAdjusted); KualiDecimal poTotalDamaged = poItem.getItemDamagedTotalQuantity(); if (ObjectUtils.isNull(poTotalDamaged)){ poTotalDamaged = KualiDecimal.ZERO; } KualiDecimal receivingItemTotalDamagedOriginal = receivingItem.getItemOriginalDamagedTotalQuantity(); if (ObjectUtils.isNull(receivingItemTotalDamagedOriginal)){ receivingItemTotalDamagedOriginal = KualiDecimal.ZERO; } KualiDecimal receivingItemTotalDamaged = receivingItem.getItemDamagedTotalQuantity(); if (ObjectUtils.isNull(receivingItemTotalDamaged)){ receivingItemTotalDamaged = KualiDecimal.ZERO; } KualiDecimal receivingItemTotalDamagedAdjusted = receivingItemTotalDamaged.subtract(receivingItemTotalDamagedOriginal); poItem.setItemDamagedTotalQuantity(poTotalDamaged.add(receivingItemTotalDamagedAdjusted)); } } } } /** * Spawns PO amendments for new unordered items on a receiving document. * * @param receivingDocument * @param po */ protected void spawnPoAmendmentForUnorderedItems(ReceivingDocument receivingDocument, PurchaseOrderDocument po){ //if receiving line document if (receivingDocument instanceof LineItemReceivingDocument) { LineItemReceivingDocument rlDoc = (LineItemReceivingDocument)receivingDocument; //if a new item has been added spawn a purchase order amendment if( hasNewUnorderedItem((LineItemReceivingDocument)receivingDocument) ){ String newSessionUserId = KFSConstants.SYSTEM_USER; try { LogicContainer logicToRun = new LogicContainer() { @Override public Object runLogic(Object[] objects) throws Exception { LineItemReceivingDocument rlDoc = (LineItemReceivingDocument)objects[0]; String poDocNumber = (String)objects[1]; //create a PO amendment PurchaseOrderAmendmentDocument amendmentPo = (PurchaseOrderAmendmentDocument) purchaseOrderService.createAndSavePotentialChangeDocument(poDocNumber, PurchaseOrderDocTypes.PURCHASE_ORDER_AMENDMENT_DOCUMENT, PurchaseOrderStatuses.APPDOC_AMENDMENT); //add new lines to amendement addUnorderedItemsToAmendment(amendmentPo, rlDoc); //route amendment documentService.routeDocument(amendmentPo, null, null); //add note to amendment po document String note = "Purchase Order Amendment " + amendmentPo.getPurapDocumentIdentifier() + " (document id " + amendmentPo.getDocumentNumber() + ") created for new unordered line items due to Receiving (document id " + rlDoc.getDocumentNumber() + ")"; Note noteObj = documentService.createNoteFromDocument(amendmentPo, note); amendmentPo.addNote(noteObj); noteService.save(noteObj); return null; } }; purapService.performLogicWithFakedUserSession(newSessionUserId, logicToRun, new Object[] { rlDoc, po.getDocumentNumber() }); } catch (WorkflowException e) { String errorMsg = "Workflow Exception caught: " + e.getLocalizedMessage(); throw new RuntimeException(errorMsg, e); } catch (Exception e) { throw new RuntimeException(e); } } } } /** * Checks the item list for newly added items. * * @param rlDoc * @return */ @Override public boolean hasNewUnorderedItem(LineItemReceivingDocument rlDoc){ boolean itemAdded = false; for(LineItemReceivingItem rlItem: (List<LineItemReceivingItem>)rlDoc.getItems()){ if( PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE.equals(rlItem.getItemTypeCode()) && !StringUtils.isEmpty(rlItem.getItemReasonAddedCode()) ){ itemAdded = true; break; } } return itemAdded; } /** * Adds an unordered item to a po amendment document. * * @param amendment * @param rlDoc */ protected void addUnorderedItemsToAmendment(PurchaseOrderAmendmentDocument amendment, LineItemReceivingDocument rlDoc){ PurchaseOrderItem poi = null; for(LineItemReceivingItem rlItem: (List<LineItemReceivingItem>)rlDoc.getItems()){ if( PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE.equals(rlItem.getItemTypeCode()) && !StringUtils.isEmpty(rlItem.getItemReasonAddedCode()) ){ poi = createPoItemFromReceivingLine(rlItem); poi.setDocumentNumber( amendment.getDocumentNumber() ); // add default commodity code from parameter, if commodity code is required on PO and not specified in the unordered item // Note: if we don't add logic to populate commodity code in LineItemReceivingItem, then at this point this field is always empty for unordered item if (purchaseOrderService.isCommodityCodeRequiredOnPurchaseOrder() && StringUtils.isEmpty(poi.getPurchasingCommodityCode())) { String defaultCommodityCode = SpringContext.getBean(ParameterService.class).getParameterValueAsString(PurchaseOrderAmendmentDocument.class, PurapParameterConstants.UNORDERED_ITEM_DEFAULT_COMMODITY_CODE); poi.setPurchasingCommodityCode(defaultCommodityCode); } poi.refreshNonUpdateableReferences(); amendment.addItem(poi); } } } /** * Creates a PO item from a receiving line item. * * @param rlItem * @return */ protected PurchaseOrderItem createPoItemFromReceivingLine(LineItemReceivingItem rlItem){ PurchaseOrderItem poi = new PurchaseOrderItem(); poi.setItemActiveIndicator(true); poi.setItemTypeCode(rlItem.getItemTypeCode()); poi.setItemLineNumber(rlItem.getItemLineNumber()); poi.setItemCatalogNumber( rlItem.getItemCatalogNumber() ); poi.setItemDescription( rlItem.getItemDescription() ); if( rlItem.getItemReturnedTotalQuantity() == null){ poi.setItemQuantity( rlItem.getItemReceivedTotalQuantity()); }else{ poi.setItemQuantity( rlItem.getItemReceivedTotalQuantity().subtract(rlItem.getItemReturnedTotalQuantity()) ); } poi.setItemUnitOfMeasureCode( rlItem.getItemUnitOfMeasureCode() ); poi.setItemUnitPrice(new BigDecimal(0)); poi.setItemDamagedTotalQuantity( rlItem.getItemDamagedTotalQuantity() ); poi.setItemReceivedTotalQuantity( rlItem.getItemReceivedTotalQuantity() ); return poi; } /** * Creates a list of fiscal officers for new unordered items added to a purchase order. * * @param po * @return */ protected List<AdHocRoutePerson> createFyiFiscalOfficerList(ReceivingDocument recDoc){ PurchaseOrderDocument po = recDoc.getPurchaseOrderDocument(); List<AdHocRoutePerson> adHocRoutePersons = new ArrayList<AdHocRoutePerson>(); Map fiscalOfficers = new HashMap(); AdHocRoutePerson adHocRoutePerson = null; for(ReceivingItem recItem: (List<ReceivingItem>)recDoc.getItems()){ //if this item has an item line number then it is coming from the po if (ObjectUtils.isNotNull(recItem.getItemLineNumber())) { PurchaseOrderItem poItem = (PurchaseOrderItem)po.getItemByLineNumber(recItem.getItemLineNumber()); if(poItem.getItemQuantity().isLessThan(poItem.getItemReceivedTotalQuantity())|| recItem.getItemDamagedTotalQuantity().isGreaterThan(KualiDecimal.ZERO)) { // 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 new unordered items. * * @param po */ protected void sendFyiForItems(ReceivingDocument recDoc){ List<AdHocRoutePerson> fyiList = createFyiFiscalOfficerList(recDoc); String annotation = "Notification of Item exceeded Quantity or Damaged" + "(document id " + recDoc.getDocumentNumber() + ")"; String responsibilityNote = "Please Review"; for(AdHocRoutePerson adHocPerson: fyiList){ try{ recDoc.appSpecificRouteDocumentToUser( recDoc.getDocumentHeader().getWorkflowDocument(), adHocPerson.getPerson().getPrincipalId(), annotation, responsibilityNote); }catch (WorkflowException e) { throw new RuntimeException("Error routing fyi for document with id " + recDoc.getDocumentNumber(), e); } } } @Override public void addNoteToReceivingDocument(ReceivingDocument receivingDocument, String note) throws Exception{ Note noteObj = documentService.createNoteFromDocument(receivingDocument, note); receivingDocument.addNote(noteObj); noteService.save(noteObj); } @Override public String getReceivingDeliveryCampusCode(PurchaseOrderDocument po){ String deliveryCampusCode = ""; String latestDocumentNumber = ""; List<LineItemReceivingView> rViews = null; WorkflowDocument workflowDocument = null; DateTime latestCreateDate = null; //get related views if(ObjectUtils.isNotNull(po.getRelatedViews()) ){ rViews = po.getRelatedViews().getRelatedLineItemReceivingViews(); } //if not empty, then grab the latest receiving view if(ObjectUtils.isNotNull(rViews) && rViews.isEmpty() == false){ for(LineItemReceivingView rView : rViews){ try{ workflowDocument = workflowDocumentService.loadWorkflowDocument(rView.getDocumentNumber(), GlobalVariables.getUserSession().getPerson()); //if latest create date is null or the latest is before the current, current is newer if( ObjectUtils.isNull(latestCreateDate) || latestCreateDate.isBefore(workflowDocument.getDateCreated()) ){ latestCreateDate = workflowDocument.getDateCreated(); latestDocumentNumber = workflowDocument.getDocumentId().toString(); } }catch(WorkflowException we){ throw new RuntimeException(we); } } //if there is a create date, a latest workflow doc was found if( ObjectUtils.isNotNull(latestCreateDate)){ try{ LineItemReceivingDocument rlDoc = (LineItemReceivingDocument)documentService.getByDocumentHeaderId(latestDocumentNumber); deliveryCampusCode = rlDoc.getDeliveryCampusCode(); }catch(WorkflowException we){ throw new RuntimeException(we); } } } return deliveryCampusCode; } /** * @see org.kuali.kfs.module.purap.document.service.ReceivingService#isLineItemReceivingDocumentGeneratedForPurchaseOrder(java.lang.Integer) */ @Override public boolean isLineItemReceivingDocumentGeneratedForPurchaseOrder(Integer poId) throws RuntimeException{ boolean isGenerated = false; List<String> docNumbers = receivingDao.getDocumentNumbersByPurchaseOrderId(poId); WorkflowDocument workflowDocument = null; for (String docNumber : docNumbers) { try{ workflowDocument = workflowDocumentService.loadWorkflowDocument(docNumber, GlobalVariables.getUserSession().getPerson()); }catch(WorkflowException we){ throw new RuntimeException(we); } if(workflowDocument.isFinal()){ isGenerated = true; break; } } return isGenerated; } @Override public void approveReceivingDocsForPOAmendment() { List<LineItemReceivingDocument> docs = getDocumentsAwaitingPurchaseOrderOpenStatus(); if (docs != null) { for (LineItemReceivingDocument receivingDoc : docs) { Set<String> currentNodes = receivingDoc.getDocumentHeader().getWorkflowDocument().getCurrentNodeNames(); if (CollectionUtils.isNotEmpty(currentNodes) && currentNodes.contains(PurapConstants.LineItemReceivingDocumentStrings.AWAITING_PO_OPEN_STATUS)) { approveReceivingDoc(receivingDoc); } } } } /** * @see org.kuali.kfs.module.purap.document.service.PaymentRequestService#getPaymentRequestByDocumentNumber(java.lang.String) */ public LineItemReceivingDocument getLineItemReceivingByDocumentNumber(String documentNumber) { LOG.debug("getLineItemReceivingByDocumentNumber() started"); if (ObjectUtils.isNotNull(documentNumber)) { try { LineItemReceivingDocument doc = (LineItemReceivingDocument) documentService.getByDocumentHeaderId(documentNumber); return doc; } catch (WorkflowException e) { String errorMessage = "Error getting LineItemReceiving document from document service"; LOG.error("getLineItemReceivingByDocumentNumber() " + errorMessage, e); throw new RuntimeException(errorMessage, e); } } return null; } protected void approveReceivingDoc(LineItemReceivingDocument receivingDoc){ PurchaseOrderDocument poDoc = receivingDoc.getPurchaseOrderDocument(); if (purchaseOrderService.isPurchaseOrderOpenForProcessing(poDoc)){ try{ SpringContext.getBean(DocumentService.class).approveDocument(receivingDoc, "Approved by the batch job", null); } catch (WorkflowException e) { LOG.error("approveReceivingDoc() Error approving receiving document from awaiting PO open", e); throw new RuntimeException("Error approving receiving document from awaiting PO open", e); } } } /** * Gets a list of strings of receiving line item document numbers where applicationdocumentstatus = 'Awaiting Purchase Order Open Status' * If there are documents then the document number is added to the list * * @return list of documentNumbers to retrieve line item receiving documents. */ @Deprecated protected List<String> getDocumentsNumbersAwaitingPurchaseOrderOpenStatus() { List<String> receivingDocumentNumbers = new ArrayList<String>(); List<LineItemReceivingDocument> receivingDocuments= getDocumentsAwaitingPurchaseOrderOpenStatus(); for (LineItemReceivingDocument document : receivingDocuments) { receivingDocumentNumbers.add(document.getDocumentNumber()); } return receivingDocumentNumbers; } /** * Gets a list of receiving line item documents where applicationdocumentstatus = 'Awaiting Purchase Order Open Status' * @return list of line item receiving documents. */ protected List<LineItemReceivingDocument> getDocumentsAwaitingPurchaseOrderOpenStatus() { List<LineItemReceivingDocument> receivingDocuments; try { receivingDocuments = (List<LineItemReceivingDocument>) financialSystemDocumentService.findByApplicationDocumentStatus( LineItemReceivingDocument.class, PurapConstants.LineItemReceivingStatuses.APPDOC_AWAITING_PO_OPEN_STATUS); } catch (WorkflowException we) { String errorMsg = "Unable to retrieve LineItemReceivingDocuments with status " + PurapConstants.LineItemReceivingStatuses.APPDOC_AWAITING_PO_OPEN_STATUS + " " + we.getMessage(); throw new RuntimeException(errorMsg, we); } return receivingDocuments; } }