001    /*
002     * Copyright 2011 The Kuali Foundation.
003     * 
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     * 
008     * http://www.opensource.org/licenses/ecl2.php
009     * 
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.kfs.module.purap.document.service.impl;
017    
018    import java.math.BigDecimal;
019    import java.sql.Timestamp;
020    import java.text.MessageFormat;
021    import java.util.ArrayList;
022    import java.util.HashMap;
023    import java.util.List;
024    import java.util.Map;
025    
026    import org.apache.commons.lang.StringUtils;
027    import org.kuali.kfs.module.purap.PurapConstants;
028    import org.kuali.kfs.module.purap.PurapKeyConstants;
029    import org.kuali.kfs.module.purap.PurapConstants.PurchaseOrderDocTypes;
030    import org.kuali.kfs.module.purap.PurapConstants.PurchaseOrderStatuses;
031    import org.kuali.kfs.module.purap.businessobject.CorrectionReceivingItem;
032    import org.kuali.kfs.module.purap.businessobject.ItemType;
033    import org.kuali.kfs.module.purap.businessobject.LineItemReceivingItem;
034    import org.kuali.kfs.module.purap.businessobject.LineItemReceivingView;
035    import org.kuali.kfs.module.purap.businessobject.PurApAccountingLine;
036    import org.kuali.kfs.module.purap.businessobject.PurchaseOrderItem;
037    import org.kuali.kfs.module.purap.businessobject.ReceivingItem;
038    import org.kuali.kfs.module.purap.document.CorrectionReceivingDocument;
039    import org.kuali.kfs.module.purap.document.LineItemReceivingDocument;
040    import org.kuali.kfs.module.purap.document.PurchaseOrderAmendmentDocument;
041    import org.kuali.kfs.module.purap.document.PurchaseOrderDocument;
042    import org.kuali.kfs.module.purap.document.ReceivingDocument;
043    import org.kuali.kfs.module.purap.document.dataaccess.ReceivingDao;
044    import org.kuali.kfs.module.purap.document.service.LogicContainer;
045    import org.kuali.kfs.module.purap.document.service.PurapService;
046    import org.kuali.kfs.module.purap.document.service.PurchaseOrderService;
047    import org.kuali.kfs.module.purap.document.service.ReceivingService;
048    import org.kuali.kfs.module.purap.document.validation.event.AttributedContinuePurapEvent;
049    import org.kuali.kfs.sys.KFSConstants;
050    import org.kuali.kfs.sys.context.SpringContext;
051    import org.kuali.rice.kew.exception.WorkflowException;
052    import org.kuali.rice.kns.bo.AdHocRoutePerson;
053    import org.kuali.rice.kns.bo.Note;
054    import org.kuali.rice.kns.exception.InfrastructureException;
055    import org.kuali.rice.kns.service.DocumentService;
056    import org.kuali.rice.kns.service.KualiConfigurationService;
057    import org.kuali.rice.kns.service.NoteService;
058    import org.kuali.rice.kns.util.GlobalVariables;
059    import org.kuali.rice.kns.util.KualiDecimal;
060    import org.kuali.rice.kns.util.ObjectUtils;
061    import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
062    import org.kuali.rice.kns.workflow.service.WorkflowDocumentService;
063    import org.springframework.transaction.annotation.Transactional;
064    
065    @Transactional
066    public class ReceivingServiceImpl implements ReceivingService {
067        private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(ReceivingServiceImpl.class);
068        
069        private PurchaseOrderService purchaseOrderService;
070        private ReceivingDao receivingDao;
071        private DocumentService documentService;
072        private WorkflowDocumentService workflowDocumentService;
073        private KualiConfigurationService configurationService;    
074        private PurapService purapService;
075        private NoteService noteService;
076    
077        public void setPurchaseOrderService(PurchaseOrderService purchaseOrderService) {
078            this.purchaseOrderService = purchaseOrderService;
079        }
080    
081        public void setReceivingDao(ReceivingDao receivingDao) {
082            this.receivingDao = receivingDao;
083        }
084    
085        public void setDocumentService(DocumentService documentService){
086            this.documentService = documentService;
087        }
088    
089        public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService){
090            this.workflowDocumentService = workflowDocumentService;
091        }
092    
093        public void setConfigurationService(KualiConfigurationService configurationService) {
094            this.configurationService = configurationService;
095        }
096    
097        public void setPurapService(PurapService purapService) {
098            this.purapService = purapService;
099        }
100    
101        public void setNoteService(NoteService noteService) {
102            this.noteService = noteService;
103        }
104    
105        /**
106         * 
107         * @see org.kuali.kfs.module.purap.document.service.ReceivingService#populateReceivingLineFromPurchaseOrder(org.kuali.kfs.module.purap.document.LineItemReceivingDocument)
108         */
109        public void populateReceivingLineFromPurchaseOrder(LineItemReceivingDocument rlDoc) {
110            
111            if(rlDoc == null){
112                rlDoc = new LineItemReceivingDocument();
113            }
114                                 
115            //retrieve po by doc id
116            PurchaseOrderDocument poDoc = null;
117            poDoc = purchaseOrderService.getCurrentPurchaseOrder(rlDoc.getPurchaseOrderIdentifier());
118    
119            if(poDoc != null){
120                rlDoc.populateReceivingLineFromPurchaseOrder(poDoc);
121            }                
122            
123        }
124    
125        public void populateCorrectionReceivingFromReceivingLine(CorrectionReceivingDocument rcDoc) {
126            
127            if(rcDoc == null){
128                rcDoc = new CorrectionReceivingDocument();
129            }
130                                 
131            //retrieve receiving line by doc id
132            LineItemReceivingDocument rlDoc = rcDoc.getLineItemReceivingDocument();
133    
134            if(rlDoc != null){
135                rcDoc.populateCorrectionReceivingFromReceivingLine(rlDoc);
136            }                
137            
138        }
139    
140        /**
141         * 
142         * @see org.kuali.kfs.module.purap.document.service.ReceivingService#populateAndSaveLineItemReceivingDocument(org.kuali.kfs.module.purap.document.LineItemReceivingDocument)
143         */
144        public void populateAndSaveLineItemReceivingDocument(LineItemReceivingDocument rlDoc) throws WorkflowException {
145            try {            
146                documentService.saveDocument(rlDoc, AttributedContinuePurapEvent.class);
147            }
148            catch (WorkflowException we) {
149                String errorMsg = "Error saving document # " + rlDoc.getDocumentHeader().getDocumentNumber() + " " + we.getMessage();
150                //LOG.error(errorMsg, we);
151                throw new RuntimeException(errorMsg, we);
152            }
153        }
154    
155        /**
156         * @see org.kuali.kfs.module.purap.document.service.ReceivingService#populateCorrectionReceivingDocument(org.kuali.kfs.module.purap.document.CorrectionReceivingDocument)
157         */
158        public void populateCorrectionReceivingDocument(CorrectionReceivingDocument rcDoc)  {
159                populateCorrectionReceivingFromReceivingLine(rcDoc);
160        }
161    
162        /**
163         * 
164         * @see org.kuali.kfs.module.purap.document.service.ReceivingService#canCreateLineItemReceivingDocument(java.lang.Integer, java.lang.String)
165         */
166        public boolean canCreateLineItemReceivingDocument(Integer poId, String receivingDocumentNumber) throws RuntimeException {
167            
168            PurchaseOrderDocument po = purchaseOrderService.getCurrentPurchaseOrder(poId);
169            
170            return canCreateLineItemReceivingDocument(po, receivingDocumentNumber);            
171        }
172    
173        /**
174         * 
175         * @see org.kuali.kfs.module.purap.document.service.ReceivingService#canCreateLineItemReceivingDocument(org.kuali.kfs.module.purap.document.PurchaseOrderDocument)
176         */
177        public boolean canCreateLineItemReceivingDocument(PurchaseOrderDocument po) throws RuntimeException {
178            return canCreateLineItemReceivingDocument(po, null);
179        }
180        
181        protected boolean canCreateLineItemReceivingDocument(PurchaseOrderDocument po, String receivingDocumentNumber) {
182            boolean canCreate = false;
183    
184            if (isPurchaseOrderValidForLineItemReceivingDocumentCreation(po) && 
185                !isLineItemReceivingDocumentInProcessForPurchaseOrder(po.getPurapDocumentIdentifier(), receivingDocumentNumber) && 
186                !isCorrectionReceivingDocumentInProcessForPurchaseOrder(po.getPurapDocumentIdentifier(), null)) {
187                canCreate = true;
188            }
189    
190            return canCreate;
191        }
192    
193        public boolean isPurchaseOrderActiveForLineItemReceivingDocumentCreation(Integer poId){
194            PurchaseOrderDocument po = purchaseOrderService.getCurrentPurchaseOrder(poId);
195            return isPurchaseOrderValidForLineItemReceivingDocumentCreation(po);
196        }
197        
198        protected boolean isPurchaseOrderValidForLineItemReceivingDocumentCreation(PurchaseOrderDocument po){
199            return po != null &&
200                   ObjectUtils.isNotNull(po.getPurapDocumentIdentifier()) && 
201                   po.isPurchaseOrderCurrentIndicator() && 
202                       (PurchaseOrderStatuses.OPEN.equals(po.getStatusCode()) || 
203                        PurchaseOrderStatuses.CLOSED.equals(po.getStatusCode()) || 
204                        PurchaseOrderStatuses.PAYMENT_HOLD.equals(po.getStatusCode()));
205        }
206        
207        public boolean canCreateCorrectionReceivingDocument(LineItemReceivingDocument rl) throws RuntimeException {
208            return canCreateCorrectionReceivingDocument(rl, null);
209        }
210    
211        public boolean canCreateCorrectionReceivingDocument(LineItemReceivingDocument rl, String receivingCorrectionDocNumber) throws RuntimeException {
212    
213            boolean canCreate = false;
214            KualiWorkflowDocument workflowDocument = null;
215            
216            try{
217                workflowDocument = workflowDocumentService.createWorkflowDocument(Long.valueOf(rl.getDocumentNumber()), GlobalVariables.getUserSession().getPerson());
218            }catch(WorkflowException we){
219                throw new RuntimeException(we);
220            }
221    
222            if( workflowDocument.stateIsFinal() &&
223                !isCorrectionReceivingDocumentInProcessForReceivingLine(rl.getDocumentNumber(), receivingCorrectionDocNumber)){            
224                canCreate = true;
225            }
226            
227            return canCreate;
228        }
229    
230        protected boolean isLineItemReceivingDocumentInProcessForPurchaseOrder(Integer poId, String receivingDocumentNumber) throws RuntimeException{
231            return !getLineItemReceivingDocumentNumbersInProcessForPurchaseOrder(poId, receivingDocumentNumber).isEmpty();
232        }
233    
234        public List<String> getLineItemReceivingDocumentNumbersInProcessForPurchaseOrder(Integer poId, 
235                                                                                         String receivingDocumentNumber){
236            
237            List<String> inProcessDocNumbers = new ArrayList<String>();
238            List<String> docNumbers = receivingDao.getDocumentNumbersByPurchaseOrderId(poId);
239            KualiWorkflowDocument workflowDocument = null;
240                    
241            for (String docNumber : docNumbers) {
242            
243                try{
244                    workflowDocument = workflowDocumentService.createWorkflowDocument(Long.valueOf(docNumber), GlobalVariables.getUserSession().getPerson());
245                }catch(WorkflowException we){
246                    throw new RuntimeException(we);
247                }
248                
249                if(!(workflowDocument.stateIsCanceled() ||
250                     workflowDocument.stateIsException() ||
251                     workflowDocument.stateIsFinal()) &&
252                     docNumber.equals(receivingDocumentNumber) == false ){
253                    inProcessDocNumbers.add(docNumber);
254                }
255            }
256    
257            return inProcessDocNumbers;
258        }
259        
260        public List<LineItemReceivingDocument> getLineItemReceivingDocumentsInFinalForPurchaseOrder(Integer poId) {
261    
262            List<String> finalDocNumbers = new ArrayList<String>();
263            List<String> docNumbers = receivingDao.getDocumentNumbersByPurchaseOrderId(poId);
264            KualiWorkflowDocument workflowDocument = null;
265    
266            for (String docNumber : docNumbers) {
267    
268                try {
269                    workflowDocument = workflowDocumentService.createWorkflowDocument(Long.valueOf(docNumber), GlobalVariables.getUserSession().getPerson());
270                }
271                catch (WorkflowException we) {
272                    throw new RuntimeException(we);
273                }
274    
275                if (workflowDocument.stateIsFinal()) {
276                    finalDocNumbers.add(docNumber);
277                }
278            }
279    
280            if (finalDocNumbers.size() > 0) {
281                try {
282                    return SpringContext.getBean(DocumentService.class).getDocumentsByListOfDocumentHeaderIds(LineItemReceivingDocument.class, finalDocNumbers);
283                }
284                catch (WorkflowException e) {
285                    throw new InfrastructureException("unable to retrieve LineItemReceivingDocuments", e);
286                }
287            }
288            else {
289                return null;
290            }
291            
292        }
293        
294        protected boolean isCorrectionReceivingDocumentInProcessForPurchaseOrder(Integer poId, String receivingDocumentNumber) throws RuntimeException{
295            return !getCorrectionReceivingDocumentNumbersInProcessForPurchaseOrder(poId, receivingDocumentNumber).isEmpty();
296        }
297        
298        public List<String> getCorrectionReceivingDocumentNumbersInProcessForPurchaseOrder(Integer poId, 
299                                                                                           String receivingDocumentNumber){
300            
301            boolean isInProcess = false;
302            
303            List<String> inProcessDocNumbers = new ArrayList<String>();
304            List<String> docNumbers = receivingDao.getCorrectionReceivingDocumentNumbersByPurchaseOrderId(poId);
305            KualiWorkflowDocument workflowDocument = null;
306                    
307            for (String docNumber : docNumbers) {
308            
309                try{
310                    workflowDocument = workflowDocumentService.createWorkflowDocument(Long.valueOf(docNumber), GlobalVariables.getUserSession().getPerson());
311                }catch(WorkflowException we){
312                    throw new RuntimeException(we);
313                }
314                
315                if(!(workflowDocument.stateIsCanceled() ||
316                     workflowDocument.stateIsException() ||
317                     workflowDocument.stateIsFinal()) &&
318                     docNumber.equals(receivingDocumentNumber) == false ){
319                    inProcessDocNumbers.add(docNumber);
320                }
321            }
322    
323            return inProcessDocNumbers;
324        }
325        
326        
327        protected boolean isCorrectionReceivingDocumentInProcessForReceivingLine(String receivingDocumentNumber, String receivingCorrectionDocNumber) throws RuntimeException{
328            
329            boolean isInProcess = false;
330            
331            List<String> docNumbers = receivingDao.getCorrectionReceivingDocumentNumbersByReceivingLineNumber(receivingDocumentNumber);
332            KualiWorkflowDocument workflowDocument = null;
333                    
334            for (String docNumber : docNumbers) {
335            
336                try{
337                    workflowDocument = workflowDocumentService.createWorkflowDocument(Long.valueOf(docNumber), GlobalVariables.getUserSession().getPerson());
338                }catch(WorkflowException we){
339                    throw new RuntimeException(we);
340                }
341                
342                if(!(workflowDocument.stateIsCanceled() ||
343                     workflowDocument.stateIsException() ||
344                     workflowDocument.stateIsFinal()) &&
345                     docNumber.equals(receivingCorrectionDocNumber) == false ){
346                         
347                    isInProcess = true;
348                    break;
349                }
350            }
351    
352            return isInProcess;
353        }
354    
355        /**
356         * 
357         * @see org.kuali.kfs.module.purap.document.service.ReceivingService#receivingLineDuplicateMessages(org.kuali.kfs.module.purap.document.LineItemReceivingDocument)
358         */
359        public HashMap<String, String> receivingLineDuplicateMessages(LineItemReceivingDocument rlDoc) {
360            HashMap<String, String> msgs;
361            msgs = new HashMap<String, String>();
362            Integer poId = rlDoc.getPurchaseOrderIdentifier();
363            StringBuffer currentMessage = new StringBuffer("");
364            List<String> docNumbers = null;
365            
366            //check vendor date for duplicates
367            if( rlDoc.getShipmentReceivedDate() != null ){
368                docNumbers = receivingDao.duplicateVendorDate(poId, rlDoc.getShipmentReceivedDate());
369                if( hasDuplicateEntry(docNumbers) ){
370                    appendDuplicateMessage(currentMessage, PurapKeyConstants.MESSAGE_DUPLICATE_RECEIVING_LINE_VENDOR_DATE, rlDoc.getPurchaseOrderIdentifier());                                
371                }
372            }
373            
374            //check packing slip number for duplicates
375            if( !StringUtils.isEmpty(rlDoc.getShipmentPackingSlipNumber()) ){
376                docNumbers = receivingDao.duplicatePackingSlipNumber(poId, rlDoc.getShipmentPackingSlipNumber());
377                if( hasDuplicateEntry(docNumbers) ){
378                    appendDuplicateMessage(currentMessage, PurapKeyConstants.MESSAGE_DUPLICATE_RECEIVING_LINE_PACKING_SLIP_NUMBER, rlDoc.getPurchaseOrderIdentifier());                                
379                }
380            }
381            
382            //check bill of lading number for duplicates
383            if( !StringUtils.isEmpty(rlDoc.getShipmentBillOfLadingNumber()) ){
384                docNumbers = receivingDao.duplicateBillOfLadingNumber(poId, rlDoc.getShipmentBillOfLadingNumber());
385                if( hasDuplicateEntry(docNumbers) ){
386                    appendDuplicateMessage(currentMessage, PurapKeyConstants.MESSAGE_DUPLICATE_RECEIVING_LINE_BILL_OF_LADING_NUMBER, rlDoc.getPurchaseOrderIdentifier());                
387                }
388            }
389            
390           //add message if one exists
391           if(currentMessage.length() > 0){
392               //add suffix
393               appendDuplicateMessage(currentMessage, PurapKeyConstants.MESSAGE_DUPLICATE_RECEIVING_LINE_SUFFIX, rlDoc.getPurchaseOrderIdentifier() );
394               
395               //add msg to map
396               msgs.put(PurapConstants.LineItemReceivingDocumentStrings.DUPLICATE_RECEIVING_LINE_QUESTION, currentMessage.toString());
397           }
398           
399           return msgs;
400        }
401    
402        /**
403         * Looks at a list of doc numbers, but only considers an entry duplicate
404         * if the document is in a Final status.
405         * 
406         * @param docNumbers
407         * @return
408         */
409        protected boolean hasDuplicateEntry(List<String> docNumbers){
410            
411            boolean isDuplicate = false;
412            KualiWorkflowDocument workflowDocument = null;
413            
414            for (String docNumber : docNumbers) {
415            
416                try{
417                    workflowDocument = workflowDocumentService.createWorkflowDocument(Long.valueOf(docNumber), GlobalVariables.getUserSession().getPerson());
418                }catch(WorkflowException we){
419                    throw new RuntimeException(we);
420                }
421                
422                //if the doc number exists, and is in final status, consider this a dupe and return
423                if(workflowDocument.stateIsFinal()){
424                    isDuplicate = true;
425                    break;
426                }
427            }
428            
429            return isDuplicate;
430    
431        }
432        protected void appendDuplicateMessage(StringBuffer currentMessage, String duplicateMessageKey, Integer poId){
433            
434            //append prefix if this is first call
435            if(currentMessage.length() == 0){
436                String messageText = configurationService.getPropertyString(PurapKeyConstants.MESSAGE_DUPLICATE_RECEIVING_LINE_PREFIX);
437                String prefix = MessageFormat.format(messageText, poId.toString() );
438                
439                currentMessage.append(prefix);
440            }
441            
442            //append message
443            currentMessage.append( configurationService.getPropertyString(duplicateMessageKey) );                
444        }
445        
446        public void completeCorrectionReceivingDocument(ReceivingDocument correctionDocument){
447           
448            ReceivingDocument receivingDoc = ((CorrectionReceivingDocument)correctionDocument).getLineItemReceivingDocument();
449            
450            for (CorrectionReceivingItem correctionItem : (List<CorrectionReceivingItem>)correctionDocument.getItems()) {
451                if(!StringUtils.equalsIgnoreCase(correctionItem.getItemType().getItemTypeCode(),PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE)) {
452    
453                    LineItemReceivingItem recItem = (LineItemReceivingItem) receivingDoc.getItem(correctionItem.getItemLineNumber().intValue() - 1);
454                    PurchaseOrderItem poItem = (PurchaseOrderItem) receivingDoc.getPurchaseOrderDocument().getItem(correctionItem.getItemLineNumber().intValue() - 1);
455                    
456                    if(ObjectUtils.isNotNull(recItem)) {
457                        recItem.setItemReceivedTotalQuantity(correctionItem.getItemReceivedTotalQuantity());
458                        recItem.setItemReturnedTotalQuantity(correctionItem.getItemReturnedTotalQuantity());
459                        recItem.setItemDamagedTotalQuantity(correctionItem.getItemDamagedTotalQuantity());
460                    }
461                }
462            }
463            
464        }
465        
466        /**
467         * 
468         * This method deletes unneeded items and updates the totals on the po and does any additional processing based on items i.e. FYI etc
469         * @param receivingDocument receiving document
470         */
471        public void completeReceivingDocument(ReceivingDocument receivingDocument) {
472    
473            PurchaseOrderDocument poDoc = null;
474    
475            if (receivingDocument instanceof LineItemReceivingDocument){
476                // delete unentered items
477                purapService.deleteUnenteredItems(receivingDocument);
478                poDoc = receivingDocument.getPurchaseOrderDocument();
479            }else if (receivingDocument instanceof CorrectionReceivingDocument){
480                CorrectionReceivingDocument correctionDocument = (CorrectionReceivingDocument)receivingDocument;
481                poDoc = purchaseOrderService.getCurrentPurchaseOrder(correctionDocument.getLineItemReceivingDocument().getPurchaseOrderIdentifier());
482            }
483            
484            updateReceivingTotalsOnPurchaseOrder(receivingDocument, poDoc);
485    
486            //TODO: custom doc specific service hook here for correction to do it's receiving doc update
487            
488            purapService.saveDocumentNoValidation(poDoc);
489    
490            sendFyiForItems(receivingDocument);
491            
492            spawnPoAmendmentForUnorderedItems(receivingDocument, poDoc);
493    
494            purapService.saveDocumentNoValidation(receivingDocument);
495        }
496    
497        public void createNoteForReturnedAndDamagedItems(ReceivingDocument recDoc){
498            
499            for (ReceivingItem item : (List<ReceivingItem>)recDoc.getItems()){
500                if(!StringUtils.equalsIgnoreCase(item.getItemType().getItemTypeCode(),PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE)) {
501                    if (item.getItemReturnedTotalQuantity() != null && item.getItemReturnedTotalQuantity().isGreaterThan(KualiDecimal.ZERO)){
502                        try{
503                            String noteString = SpringContext.getBean(KualiConfigurationService.class).getPropertyString(PurapKeyConstants.MESSAGE_RECEIVING_LINEITEM_RETURN_NOTE_TEXT);
504                            noteString = item.getItemReturnedTotalQuantity().intValue() + " " + noteString + " " + item.getItemLineNumber();
505                            addNoteToReceivingDocument(recDoc, noteString);
506                        }catch (Exception e){
507                            String errorMsg = "Note Service Exception caught: " + e.getLocalizedMessage();
508                            throw new RuntimeException(errorMsg, e);                    
509                        }
510                    }
511                    
512                    if (item.getItemDamagedTotalQuantity() != null && item.getItemDamagedTotalQuantity().isGreaterThan(KualiDecimal.ZERO)){
513                        try{
514                            String noteString = SpringContext.getBean(KualiConfigurationService.class).getPropertyString(PurapKeyConstants.MESSAGE_RECEIVING_LINEITEM_DAMAGE_NOTE_TEXT);
515                            noteString = item.getItemDamagedTotalQuantity().intValue() + " " + noteString + " " + item.getItemLineNumber();
516                            addNoteToReceivingDocument(recDoc, noteString);
517                        }catch (Exception e){
518                            String errorMsg = "Note Service Exception caught: " + e.getLocalizedMessage();
519                            throw new RuntimeException(errorMsg, e);                    
520                        }
521                    }
522                }
523            }
524        }
525        
526        protected void updateReceivingTotalsOnPurchaseOrder(ReceivingDocument receivingDocument, PurchaseOrderDocument poDoc) {
527            for (ReceivingItem receivingItem : (List<ReceivingItem>)receivingDocument.getItems()) {
528                ItemType itemType = receivingItem.getItemType();
529                if(!StringUtils.equalsIgnoreCase(itemType.getItemTypeCode(),PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE)) {
530                    //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
531                    PurchaseOrderItem poItem = (PurchaseOrderItem)poDoc.getItemByLineNumber(receivingItem.getItemLineNumber());
532                    
533                    if(ObjectUtils.isNotNull(poItem)) {
534                        
535                        KualiDecimal poItemReceivedTotal = poItem.getItemReceivedTotalQuantity();
536                        
537                        KualiDecimal receivingItemReceivedOriginal = receivingItem.getItemOriginalReceivedTotalQuantity();
538                        /**
539                         * FIXME: It's coming as null although we set the default value in the LineItemReceivingItem constructor - mpv
540                         */
541                        if (ObjectUtils.isNull(receivingItemReceivedOriginal)){
542                            receivingItemReceivedOriginal = KualiDecimal.ZERO; 
543                        }
544                        KualiDecimal receivingItemReceived = receivingItem.getItemReceivedTotalQuantity(); 
545                        KualiDecimal receivingItemTotalReceivedAdjested = receivingItemReceived.subtract(receivingItemReceivedOriginal); 
546                        
547                        if (ObjectUtils.isNull(poItemReceivedTotal)){
548                            poItemReceivedTotal = KualiDecimal.ZERO;
549                        }
550                        KualiDecimal poItemReceivedTotalAdjusted = poItemReceivedTotal.add(receivingItemTotalReceivedAdjested); 
551                        
552                        KualiDecimal receivingItemReturnedOriginal = receivingItem.getItemOriginalReturnedTotalQuantity();
553                        if (ObjectUtils.isNull(receivingItemReturnedOriginal)){
554                            receivingItemReturnedOriginal = KualiDecimal.ZERO; 
555                        }
556                        
557                        KualiDecimal receivingItemReturned = receivingItem.getItemReturnedTotalQuantity();
558                        if (ObjectUtils.isNull(receivingItemReturned)){
559                            receivingItemReturned = KualiDecimal.ZERO; 
560                        }
561                        
562                        KualiDecimal receivingItemTotalReturnedAdjusted = receivingItemReturned.subtract(receivingItemReturnedOriginal); 
563                        
564                        poItemReceivedTotalAdjusted = poItemReceivedTotalAdjusted.subtract(receivingItemTotalReturnedAdjusted);
565                        
566                        poItem.setItemReceivedTotalQuantity(poItemReceivedTotalAdjusted);
567                        
568                        KualiDecimal poTotalDamaged = poItem.getItemDamagedTotalQuantity();
569                        if (ObjectUtils.isNull(poTotalDamaged)){
570                            poTotalDamaged = KualiDecimal.ZERO; 
571                        }
572    
573                        KualiDecimal receivingItemTotalDamagedOriginal = receivingItem.getItemOriginalDamagedTotalQuantity();
574                        if (ObjectUtils.isNull(receivingItemTotalDamagedOriginal)){
575                            receivingItemTotalDamagedOriginal = KualiDecimal.ZERO; 
576                        }
577                        
578                        KualiDecimal receivingItemTotalDamaged = receivingItem.getItemDamagedTotalQuantity();
579                        if (ObjectUtils.isNull(receivingItemTotalDamaged)){
580                            receivingItemTotalDamaged = KualiDecimal.ZERO; 
581                        }
582                        
583                        KualiDecimal receivingItemTotalDamagedAdjusted = receivingItemTotalDamaged.subtract(receivingItemTotalDamagedOriginal);
584                        
585                        poItem.setItemDamagedTotalQuantity(poTotalDamaged.add(receivingItemTotalDamagedAdjusted));
586                        
587                    }
588                }
589            }
590        }
591        
592        /**
593         * Spawns PO amendments for new unordered items on a receiving document.
594         * 
595         * @param receivingDocument
596         * @param po
597         */
598        protected void spawnPoAmendmentForUnorderedItems(ReceivingDocument receivingDocument, PurchaseOrderDocument po){
599    
600            //if receiving line document
601            if (receivingDocument instanceof LineItemReceivingDocument) {
602                LineItemReceivingDocument rlDoc = (LineItemReceivingDocument)receivingDocument;
603                
604                //if a new item has been added spawn a purchase order amendment
605                if( hasNewUnorderedItem((LineItemReceivingDocument)receivingDocument) ){
606                    String newSessionUserId = KFSConstants.SYSTEM_USER;
607                    try {                    
608                        
609                        LogicContainer logicToRun = new LogicContainer() {
610                            public Object runLogic(Object[] objects) throws Exception {
611                                LineItemReceivingDocument rlDoc = (LineItemReceivingDocument)objects[0];
612                                String poDocNumber = (String)objects[1];
613                                
614                                //create a PO amendment
615                                PurchaseOrderAmendmentDocument amendmentPo = (PurchaseOrderAmendmentDocument) purchaseOrderService.createAndSavePotentialChangeDocument(poDocNumber, PurchaseOrderDocTypes.PURCHASE_ORDER_AMENDMENT_DOCUMENT, PurchaseOrderStatuses.AMENDMENT);
616    
617                                //add new lines to amendement
618                                addUnorderedItemsToAmendment(amendmentPo, rlDoc);
619                                
620                                //route amendment
621                                documentService.routeDocument(amendmentPo, null, null);
622    
623                                //add note to amendment po document
624                                String note = "Purchase Order Amendment " + amendmentPo.getPurapDocumentIdentifier() + " (document id " + amendmentPo.getDocumentNumber() + ") created for new unordered line items due to Receiving (document id " + rlDoc.getDocumentNumber() + ")";
625                                
626                                Note noteObj = documentService.createNoteFromDocument(amendmentPo, note);
627                                documentService.addNoteToDocument(amendmentPo, noteObj);
628                                noteService.save(noteObj);
629    
630                                return null;
631                            }
632                        };
633                        
634                        purapService.performLogicWithFakedUserSession(newSessionUserId, logicToRun, new Object[] { rlDoc, po.getDocumentNumber() });
635                    }
636                    catch (WorkflowException e) {
637                        String errorMsg = "Workflow Exception caught: " + e.getLocalizedMessage();
638                        throw new RuntimeException(errorMsg, e);
639                    }
640                    catch (Exception e) {
641                        throw new RuntimeException(e);
642                    }
643                }           
644            }
645        }
646        
647        /**
648         * Checks the item list for newly added items.
649         * 
650         * @param rlDoc
651         * @return
652         */
653        protected boolean hasNewUnorderedItem(LineItemReceivingDocument rlDoc){
654            
655            boolean itemAdded = false;
656            
657            for(LineItemReceivingItem rlItem: (List<LineItemReceivingItem>)rlDoc.getItems()){
658                if( PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE.equals(rlItem.getItemTypeCode()) &&
659                    !StringUtils.isEmpty(rlItem.getItemReasonAddedCode()) ){
660                    itemAdded = true;
661                    break;
662                }
663            }
664            
665            return itemAdded;
666        }
667        
668        /**
669         * Adds an unordered item to a po amendment document.
670         * 
671         * @param amendment
672         * @param rlDoc
673         */
674        protected void addUnorderedItemsToAmendment(PurchaseOrderAmendmentDocument amendment, LineItemReceivingDocument rlDoc){
675    
676            PurchaseOrderItem poi = null;
677            
678            for(LineItemReceivingItem rlItem: (List<LineItemReceivingItem>)rlDoc.getItems()){
679                if( PurapConstants.ItemTypeCodes.ITEM_TYPE_UNORDERED_ITEM_CODE.equals(rlItem.getItemTypeCode()) &&
680                    !StringUtils.isEmpty(rlItem.getItemReasonAddedCode()) ){
681                    
682                    poi = createPoItemFromReceivingLine(rlItem);
683                    poi.setDocumentNumber( amendment.getDocumentNumber() );
684                    poi.refreshNonUpdateableReferences();
685                    amendment.addItem(poi);
686                }
687            }
688    
689        }
690        
691        /**
692         * Creates a PO item from a receiving line item.
693         * 
694         * @param rlItem
695         * @return
696         */
697        protected PurchaseOrderItem createPoItemFromReceivingLine(LineItemReceivingItem rlItem){
698            
699            PurchaseOrderItem poi = new PurchaseOrderItem();
700                                 
701            poi.setItemActiveIndicator(true);
702            poi.setItemTypeCode(rlItem.getItemTypeCode());                
703            poi.setItemLineNumber(rlItem.getItemLineNumber());        
704            poi.setItemCatalogNumber( rlItem.getItemCatalogNumber() );
705            poi.setItemDescription( rlItem.getItemDescription() );
706    
707            if( rlItem.getItemReturnedTotalQuantity() == null){
708                poi.setItemQuantity( rlItem.getItemReceivedTotalQuantity());
709            }else{
710                poi.setItemQuantity( rlItem.getItemReceivedTotalQuantity().subtract(rlItem.getItemReturnedTotalQuantity()) );
711            }
712            
713            poi.setItemUnitOfMeasureCode( rlItem.getItemUnitOfMeasureCode() );
714            poi.setItemUnitPrice(new BigDecimal(0));
715            
716            poi.setItemDamagedTotalQuantity( rlItem.getItemDamagedTotalQuantity() );
717            poi.setItemReceivedTotalQuantity( rlItem.getItemReceivedTotalQuantity() );
718            
719            return poi;
720        }
721        
722        /**
723         * Creates a list of fiscal officers for new unordered items added to a purchase order.
724         * 
725         * @param po
726         * @return
727         */
728        protected List<AdHocRoutePerson> createFyiFiscalOfficerList(ReceivingDocument recDoc){
729    
730            PurchaseOrderDocument po = recDoc.getPurchaseOrderDocument();
731            List<AdHocRoutePerson> adHocRoutePersons = new ArrayList<AdHocRoutePerson>();
732            Map fiscalOfficers = new HashMap();
733            AdHocRoutePerson adHocRoutePerson = null;
734    
735            for(ReceivingItem recItem: (List<ReceivingItem>)recDoc.getItems()){
736                //if this item has an item line number then it is coming from the po
737                if (ObjectUtils.isNotNull(recItem.getItemLineNumber())) {
738                    PurchaseOrderItem poItem = (PurchaseOrderItem)po.getItemByLineNumber(recItem.getItemLineNumber());
739    
740                    if(poItem.getItemQuantity().isLessThan(poItem.getItemReceivedTotalQuantity())||
741                            recItem.getItemDamagedTotalQuantity().isGreaterThan(KualiDecimal.ZERO)) {
742    
743                        // loop through accounts and pull off fiscal officer
744                        for(PurApAccountingLine account : poItem.getSourceAccountingLines()){
745    
746                            //check for dupes of fiscal officer
747                            if( fiscalOfficers.containsKey(account.getAccount().getAccountFiscalOfficerUser().getPrincipalName()) == false ){
748    
749                                //add fiscal officer to list
750                                fiscalOfficers.put(account.getAccount().getAccountFiscalOfficerUser().getPrincipalName(), account.getAccount().getAccountFiscalOfficerUser().getPrincipalName());
751    
752                                //create AdHocRoutePerson object and add to list
753                                adHocRoutePerson = new AdHocRoutePerson();
754                                adHocRoutePerson.setId(account.getAccount().getAccountFiscalOfficerUser().getPrincipalName());
755                                adHocRoutePerson.setActionRequested(KFSConstants.WORKFLOW_FYI_REQUEST);
756                                adHocRoutePersons.add(adHocRoutePerson);
757                            }
758                        }
759    
760                    }
761    
762                }
763            }
764    
765            return adHocRoutePersons;
766        }
767        /**
768         * Sends an FYI to fiscal officers for new unordered items.
769         * 
770         * @param po
771         */
772        protected void sendFyiForItems(ReceivingDocument recDoc){
773    
774            List<AdHocRoutePerson> fyiList = createFyiFiscalOfficerList(recDoc);
775            String annotation = "Notification of Item exceeded Quantity or Damaged" + "(document id " + recDoc.getDocumentNumber() + ")";
776            String responsibilityNote = "Please Review";
777            
778            for(AdHocRoutePerson adHocPerson: fyiList){
779                try{
780                    recDoc.appSpecificRouteDocumentToUser(
781                            recDoc.getDocumentHeader().getWorkflowDocument(),
782                            adHocPerson.getId(),
783                            annotation,
784                            responsibilityNote);
785                }catch (WorkflowException e) {
786                    throw new RuntimeException("Error routing fyi for document with id " + recDoc.getDocumentNumber(), e);
787                }
788    
789            }
790        }    
791        
792        public void addNoteToReceivingDocument(ReceivingDocument receivingDocument, String note) throws Exception{
793            Note noteObj = documentService.createNoteFromDocument(receivingDocument, note);
794            documentService.addNoteToDocument(receivingDocument, noteObj);
795            noteService.save(noteObj);        
796        }
797        
798        public String getReceivingDeliveryCampusCode(PurchaseOrderDocument po){
799            String deliveryCampusCode = "";
800            String latestDocumentNumber = "";
801                            
802            List<LineItemReceivingView> rViews = null;
803            KualiWorkflowDocument workflowDocument = null;
804            Timestamp latestCreateDate = null;
805            
806            //get related views
807            if(ObjectUtils.isNotNull(po.getRelatedViews()) ){       
808                rViews = po.getRelatedViews().getRelatedLineItemReceivingViews();
809            }
810            
811            //if not empty, then grab the latest receiving view
812            if(ObjectUtils.isNotNull(rViews) && rViews.isEmpty() == false){                                    
813                
814                for(LineItemReceivingView rView : rViews){
815                    try{
816                        workflowDocument = workflowDocumentService.createWorkflowDocument(Long.valueOf(rView.getDocumentNumber()), GlobalVariables.getUserSession().getPerson());
817                        
818                        //if latest create date is null or the latest is before the current, current is newer
819                        if( ObjectUtils.isNull(latestCreateDate) || latestCreateDate.before(workflowDocument.getCreateDate()) ){
820                            latestCreateDate = workflowDocument.getCreateDate();
821                            latestDocumentNumber = workflowDocument.getRouteHeaderId().toString();
822                        }
823                    }catch(WorkflowException we){
824                        throw new RuntimeException(we);
825                    }                
826                }
827                
828                //if there is a create date, a latest workflow doc was found
829                if( ObjectUtils.isNotNull(latestCreateDate)){
830                    try{                                    
831                        LineItemReceivingDocument rlDoc = (LineItemReceivingDocument)documentService.getByDocumentHeaderId(latestDocumentNumber);                    
832                        deliveryCampusCode = rlDoc.getDeliveryCampusCode();
833                    }catch(WorkflowException we){
834                        throw new RuntimeException(we);
835                    }
836                }
837            }
838                    
839            return deliveryCampusCode;
840        }
841    
842        /**
843         * @see org.kuali.kfs.module.purap.document.service.ReceivingService#isLineItemReceivingDocumentGeneratedForPurchaseOrder(java.lang.Integer)
844         */
845        public boolean isLineItemReceivingDocumentGeneratedForPurchaseOrder(Integer poId) throws RuntimeException{
846            
847            boolean isGenerated = false;
848            
849            List<String> docNumbers = receivingDao.getDocumentNumbersByPurchaseOrderId(poId);
850            KualiWorkflowDocument workflowDocument = null;
851                    
852            for (String docNumber : docNumbers) {
853            
854                try{
855                    workflowDocument = workflowDocumentService.createWorkflowDocument(Long.valueOf(docNumber), GlobalVariables.getUserSession().getPerson());
856                }catch(WorkflowException we){
857                    throw new RuntimeException(we);
858                }
859                
860                if(workflowDocument.stateIsFinal()){                     
861                    isGenerated = true;
862                    break;
863                }
864            }
865    
866            return isGenerated;
867        }
868    
869        public void approveReceivingDocsForPOAmendment(){
870            List<LineItemReceivingDocument> docs = receivingDao.getReceivingDocumentsForPOAmendment();
871            if (docs != null){
872                for (LineItemReceivingDocument receivingDoc: docs) {
873                    if (StringUtils.equals(receivingDoc.getDocumentHeader().getWorkflowDocument().getRouteHeader().getCurrentRouteNodeNames(),
874                        PurapConstants.LineItemReceivingDocumentStrings.AWAITING_PO_OPEN_STATUS)){
875                            approveReceivingDoc(receivingDoc);
876                        }
877                }
878            }
879            
880        }
881        
882        protected void approveReceivingDoc(LineItemReceivingDocument receivingDoc){
883            PurchaseOrderDocument poDoc = receivingDoc.getPurchaseOrderDocument();
884            if (purchaseOrderService.canAmendPurchaseOrder(poDoc)){
885                try{
886                    SpringContext.getBean(DocumentService.class).approveDocument(receivingDoc, "Approved by the batch job", null);
887                }
888                catch (WorkflowException e) {
889                    LOG.error("approveReceivingDoc() Error approving receiving document from awaiting PO open", e);
890                    throw new RuntimeException("Error approving receiving document from awaiting PO open", e);
891                }
892            }
893        }
894    }
895